diff --git a/src/api/statistic.ts b/src/api/statistic.ts index 47ac40c0..9e240830 100644 --- a/src/api/statistic.ts +++ b/src/api/statistic.ts @@ -23,13 +23,18 @@ export type QuestionsResponse = { questions: Record>; }; +type TRequest = { + to: number; + from: number; +}; + export const getDevices = async ( quizId: string, to: number, from: number, ): Promise<[DevicesResponse | null, string?]> => { try { - const devicesResponse = await makeRequest({ + const devicesResponse = await makeRequest({ method: "POST", url: `${apiUrl}/${quizId}/devices`, withCredentials: true, @@ -50,7 +55,7 @@ export const getGeneral = async ( from: number, ): Promise<[GeneralResponse | null, string?]> => { try { - const generalResponse = await makeRequest({ + const generalResponse = await makeRequest({ method: "POST", url: `${apiUrl}/${quizId}/general`, withCredentials: true, @@ -71,7 +76,7 @@ export const getQuestions = async ( from: number, ): Promise<[QuestionsResponse | null, string?]> => { try { - const questionsResponse = await makeRequest({ + const questionsResponse = await makeRequest({ method: "POST", url: `${apiUrl}/${quizId}/questions`, withCredentials: true, diff --git a/src/pages/Analytics/Analytics.tsx b/src/pages/Analytics/Analytics.tsx index e06841ee..fb3cd113 100644 --- a/src/pages/Analytics/Analytics.tsx +++ b/src/pages/Analytics/Analytics.tsx @@ -1,15 +1,14 @@ -import { useLayoutEffect, useState } from "react"; +import * as React from "react"; +import { ReactNode, useLayoutEffect, useState } from "react"; import { Box, Button, IconButton, - Paper, Typography, useMediaQuery, useTheme, } from "@mui/material"; import { DatePicker } from "@mui/x-date-pickers"; -import { LineChart } from "@mui/x-charts"; import moment from "moment"; import { useQuizStore } from "@root/quizes/store"; import { useAnalytics } from "@utils/hooks/useAnalytics"; @@ -18,7 +17,7 @@ import HeaderFull from "@ui_kit/Header/HeaderFull"; import SectionWrapper from "@ui_kit/SectionWrapper"; import { General } from "./General"; -import { AnswersStatistics } from "./Answers"; +import { AnswersStatistics } from "./Answers/AnswersStatistics"; import { Devices } from "./Devices"; import CalendarIcon from "@icons/CalendarIcon"; @@ -26,11 +25,10 @@ import { redirect } from "react-router-dom"; export default function Analytics() { const { editQuizId } = useQuizStore(); - - const [isOpen, setOpen] = useState(false); - const [isOpenEnd, setOpenEnd] = useState(false); - const [to, setTo] = useState(null); - const [from, setFrom] = useState(null); + const [isOpen, setOpen] = useState(false); + const [isOpenEnd, setOpenEnd] = useState(false); + const [to, setTo] = useState(null); + const [from, setFrom] = useState(null); const { devices, general, questions } = useAnalytics({ quizId: editQuizId?.toString(), @@ -42,6 +40,7 @@ export default function Analytics() { setTo(null); setFrom(null); }; + useLayoutEffect(() => { if (editQuizId === undefined) redirect("/list"); }, [editQuizId]); @@ -52,12 +51,14 @@ export default function Analytics() { const handleClose = () => { setOpen(false); }; + const handleOpen = () => { setOpen(true); }; + const onAdornmentClick = () => { setOpen((old) => !old); - if (isOpenEnd === true) { + if (isOpenEnd) { handleCloseEnd(); } }; @@ -65,18 +66,17 @@ export default function Analytics() { const handleCloseEnd = () => { setOpenEnd(false); }; + const handleOpenEnd = () => { setOpenEnd(true); }; + const onAdornmentClickEnd = () => { setOpenEnd((old) => !old); - if (isOpen === true) { + if (isOpen) { handleClose(); } }; - console.log("questions", questions); - console.log("general", general); - console.log("devices", devices); const now = moment(); return ( @@ -113,7 +113,6 @@ export default function Analytics() { // defaultValue={now} sx={{ width: isMobile ? "146px" : "169px", - "& .MuiOutlinedInput-root": { borderRadius: "10px", fontSize: "16px", @@ -123,13 +122,15 @@ export default function Analytics() { }, }} slotProps={{ + //@ts-ignore + //TODO: fix types in @mui/x-date-pickers textField: { InputProps: { endAdornment: ( - ), + ) as ReactNode, }, }, }} @@ -146,8 +147,6 @@ export default function Analytics() { color: "4D4D4D", }} > - value={from} - onChange={(newValue) => setValue(setFrom)} Дата окончания setFrom(newValue)} /> diff --git a/src/pages/Analytics/Answers/Answers.tsx b/src/pages/Analytics/Answers/Answers.tsx index acbcad48..6fa9803f 100644 --- a/src/pages/Analytics/Answers/Answers.tsx +++ b/src/pages/Analytics/Answers/Answers.tsx @@ -1,15 +1,16 @@ -import { useState } from "react"; +import { FC, useState } from "react"; +import type { PaginationRenderItemParams } from "@mui/material"; import { Box, - Paper, - Typography, + ButtonBase, + Input, LinearProgress, Pagination as MuiPagination, PaginationItem, - Input, - ButtonBase, - useTheme, + Paper, + Typography, useMediaQuery, + useTheme, } from "@mui/material"; import { ReactComponent as DoubleCheckIcon } from "@icons/Analytics/doubleCheck.svg"; @@ -17,14 +18,16 @@ import { ReactComponent as NextIcon } from "@icons/Analytics/next.svg"; import { ReactComponent as LeftArrowIcon } from "@icons/Analytics/leftArrow.svg"; import { ReactComponent as RightArrowIcon } from "@icons/Analytics/rightArrow.svg"; -import type { PaginationRenderItemParams } from "@mui/material"; - type AnswerProps = { title: string; percent: number; highlight?: boolean; }; +type AnswersProps = { + data: Record> | null; +}; + const ANSWERS_MOCK: Record = { "Добавьте ответ": 67, "Вопрос пропущен": 7, @@ -186,16 +189,16 @@ const Pagination = () => { ); }; -export const Answers = (props) => { +export const Answers: FC = ({ data }) => { const theme = useTheme(); - console.log(props.data); - - if (Object.keys(props.data).length === 0) + if (!data) { return ( нет данных об ответах ); + } + return ( { - {Object.entries(props.data).map(([title, percent], index) => ( - - ))} + {/*{Object.entries(data).map(([title, percent], index) => (*/} + {/* */} + {/*))}*/} + {Object.entries(data).map(([title, values], index) => + Object.entries(values).map(([subTitle, percent]) => ( + + )), + )} diff --git a/src/pages/Analytics/Answers/index.tsx b/src/pages/Analytics/Answers/AnswersStatistics.tsx similarity index 75% rename from src/pages/Analytics/Answers/index.tsx rename to src/pages/Analytics/Answers/AnswersStatistics.tsx index 72306a26..3898caac 100644 --- a/src/pages/Analytics/Answers/index.tsx +++ b/src/pages/Analytics/Answers/AnswersStatistics.tsx @@ -1,24 +1,20 @@ -import { - Box, - ButtonBase, - Typography, - useMediaQuery, - useTheme, -} from "@mui/material"; +import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; import { Answers } from "./Answers"; +import { QuestionsResponse } from "@api/statistic"; +import { FC } from "react"; import { Funnel } from "./Funnel"; import { Results } from "./Results"; -import { ReactComponent as OpenIcon } from "@icons/Analytics/open.svg"; +type AnswersStatisticsProps = { + data: QuestionsResponse | null; +}; -export const AnswersStatistics = (props) => { +export const AnswersStatistics: FC = ({ data }) => { const theme = useTheme(); const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1150)); const isMobile = useMediaQuery(theme.breakpoints.down(850)); - console.log(props); - return ( { gap: "40px", }} > - - + + - + ); }; diff --git a/src/pages/Analytics/Answers/Funnel.tsx b/src/pages/Analytics/Answers/Funnel.tsx index 48f7e9ec..6b9df5b3 100644 --- a/src/pages/Analytics/Answers/Funnel.tsx +++ b/src/pages/Analytics/Answers/Funnel.tsx @@ -1,19 +1,22 @@ -import { useEffect, useState } from "react"; +import { FC, useEffect } from "react"; import { Box, + LinearProgress, Paper, Typography, - LinearProgress, - useTheme, useMediaQuery, + useTheme, } from "@mui/material"; -import { enqueueSnackbar } from "notistack"; type FunnelItemProps = { title: string; percent: number; }; +type FunnelProps = { + data: number[] | null; +}; + const FUNNEL_MOCK: Record = { "Стартовая страница": 100, "Воронка квиза": 0, @@ -100,11 +103,10 @@ const FunnelItem = ({ title, percent }: FunnelItemProps) => { ); }; -export const Funnel = (props) => { +export const Funnel: FC = ({ data }) => { const theme = useTheme(); const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1150)); const isMobile = useMediaQuery(theme.breakpoints.down(850)); - console.log(props); useEffect(() => { // const requestFunnel = async () => { // const [funnelResponse, funnelError] = await getGeneral("14761"); @@ -121,7 +123,7 @@ export const Funnel = (props) => { // requestFunnel(); }, []); - if (Object.keys(props.data).length === 0) + if (!data) return ( нет данных о разделах @@ -141,7 +143,7 @@ export const Funnel = (props) => { 0 ? props.data[index - 1] : percent} + percent={index > 0 ? data[index - 1] : percent} /> ))} diff --git a/src/pages/Analytics/Answers/Results.tsx b/src/pages/Analytics/Answers/Results.tsx index b9d3afe1..9d192457 100644 --- a/src/pages/Analytics/Answers/Results.tsx +++ b/src/pages/Analytics/Answers/Results.tsx @@ -1,11 +1,11 @@ -import { useState } from "react"; import { Box, + LinearProgress, Paper, Typography, - LinearProgress, useTheme, } from "@mui/material"; +import { FC } from "react"; type ResultProps = { title: string; @@ -13,6 +13,10 @@ type ResultProps = { highlight?: boolean; }; +type ResultsProps = { + data: Record | null; +}; + const RESULTS_MOCK: Record = { "Заголовок результата": 100, "Результат пропущен": 7, @@ -69,10 +73,10 @@ const Result = ({ title, percent, highlight }: ResultProps) => { ); }; -export const Results = (props) => { +export const Results: FC = ({ data }) => { const theme = useTheme(); - if (Object.keys(props.data).length === 0) + if (!data) return ( нет данных о результатах @@ -98,7 +102,7 @@ export const Results = (props) => { marginTop: "30px", }} > - {Object.entries(props.data).map(([title, percent], index) => ( + {Object.entries(data).map(([title, percent], index) => ( ; + devices: Record | null; +}; + +type DevicesProps = { + data: DevicesResponse | null; }; const COLORS: Record = { @@ -27,9 +28,10 @@ const DEVICES_MOCK: DevicesResponse = { const Device = ({ title, devices }: DeviceProps) => { const theme = useTheme(); - console.log("devices ", devices); - if (devices === undefined || Object.keys(devices).length === 0) + if (!devices) { return {title} - нет данных; + } + const data = Object.entries(devices).map(([id, value], index) => ({ id, value, @@ -98,8 +100,7 @@ const Device = ({ title, devices }: DeviceProps) => { ); }; -export const Devices = ({ data = {} }) => { - const [devices, setDevices] = useState(data); +export const Devices: FC = ({ data }) => { const theme = useTheme(); const isTablet = useMediaQuery(theme.breakpoints.down(1000)); const isMobile = useMediaQuery(theme.breakpoints.down(700)); @@ -150,9 +151,9 @@ export const Devices = ({ data = {} }) => { marginTop: "30px", }} > - - - + + + ); diff --git a/src/pages/Analytics/General.tsx b/src/pages/Analytics/General.tsx index 23aa55ea..102b0382 100644 --- a/src/pages/Analytics/General.tsx +++ b/src/pages/Analytics/General.tsx @@ -1,19 +1,20 @@ -import { useEffect, useState } from "react"; import { Box, Paper, Typography, useMediaQuery, useTheme } from "@mui/material"; import { LineChart } from "@mui/x-charts"; -import { enqueueSnackbar } from "notistack"; - -import { getGeneral } from "@api/statistic"; import type { GeneralResponse } from "@api/statistic"; +import { FC } from "react"; -type GeneralProps = { +type GeneralItemsProps = { title: string; general: Record; color: string; numberType: "sum" | "percent"; }; +type GeneralProps = { + data: GeneralResponse | null; +}; + const COLORS: Record = { 0: "#61BB1A", 1: "#7E2AEA", @@ -28,7 +29,12 @@ const GENERAL_MOCK: GeneralResponse = { conversation: { 100: 50, 1000: 50, 10000: 50 }, }; -const GeneralItem = ({ title, general, color, numberType }: GeneralProps) => { +const GeneralItem = ({ + title, + general, + color, + numberType, +}: GeneralItemsProps) => { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(700)); @@ -65,17 +71,18 @@ const GeneralItem = ({ title, general, color, numberType }: GeneralProps) => { ); }; -export const General = (props: any) => { +export const General: FC = ({ data }) => { const theme = useTheme(); const isTablet = useMediaQuery(theme.breakpoints.down(1000)); const isMobile = useMediaQuery(theme.breakpoints.down(700)); - if (Object.keys(props.data).length === 0) + if (!data) { return ( нет данных о ключевых метриках ); + } return ( { diff --git a/src/utils/hooks/useAnalytics.ts b/src/utils/hooks/useAnalytics.ts index 68b0cc9b..68d050fc 100644 --- a/src/utils/hooks/useAnalytics.ts +++ b/src/utils/hooks/useAnalytics.ts @@ -1,32 +1,44 @@ -import { getGeneral, getDevices, getQuestions } from "@api/statistic"; import { useEffect, useState } from "react"; import moment from "moment"; +import { + DevicesResponse, + GeneralResponse, + getDevices, + getGeneral, + getQuestions, + QuestionsResponse, +} from "@api/statistic"; -interface Props { - quizId: string; - to: number; - from: number; +interface useAnalyticsProps { + quizId: string | undefined; + to: moment.Moment | null; + from: moment.Moment | null; } -export function useAnalytics({ quizId, to, from }: Props) { - const formatTo = to === null ? 0 : moment(to).unix(); - const formatFrom = from === null ? 0 : moment(from).unix(); - console.log(to, from); - if (quizId === undefined) return {}; - const [devices, setDevices] = useState({}); - const [general, setGeneral] = useState({}); - const [questions, setQuestions] = useState({}); +export function useAnalytics({ quizId, to, from }: useAnalyticsProps) { + const formatTo = to === null ? 0 : to.unix(); + const formatFrom = from === null ? 0 : from.unix(); + + const [devices, setDevices] = useState(null); + const [general, setGeneral] = useState(null); + const [questions, setQuestions] = useState(null); useEffect(() => { + if (!quizId) return; (async () => { - const gottenGeneral = await getGeneral(quizId, formatTo, formatFrom); - const gottenDevices = await getDevices(quizId, formatTo, formatFrom); - const gottenQuestions = await getQuestions(quizId, formatTo, formatFrom); - setDevices(gottenGeneral[0]); - setGeneral(gottenDevices[0]); - setQuestions(gottenQuestions[0]); + const [gottenGeneral] = await getGeneral(quizId, formatTo, formatFrom); + const [gottenDevices] = await getDevices(quizId, formatTo, formatFrom); + const [gottenQuestions] = await getQuestions( + quizId, + formatTo, + formatFrom, + ); + + setGeneral(gottenGeneral); + setDevices(gottenDevices); + setQuestions(gottenQuestions); })(); - }, [to, from]); + }, [quizId, to, from]); return { devices, general, questions }; }