261 lines
7.5 KiB
TypeScript
261 lines
7.5 KiB
TypeScript
import moment from "moment";
|
||
import { useEffect, useLayoutEffect, useState } from "react";
|
||
import {
|
||
Box,
|
||
Button,
|
||
IconButton,
|
||
Typography,
|
||
useMediaQuery,
|
||
useTheme,
|
||
} from "@mui/material";
|
||
import { redirect } from "react-router-dom";
|
||
import { LocalizationProvider, DatePicker } from "@mui/x-date-pickers";
|
||
import { AdapterMoment } from "@mui/x-date-pickers/AdapterMoment";
|
||
|
||
import HeaderFull from "@ui_kit/Header/HeaderFull";
|
||
import SectionWrapper from "@ui_kit/SectionWrapper";
|
||
|
||
import { General } from "./General";
|
||
import { AnswersStatistics } from "./Answers/AnswersStatistics";
|
||
import { Devices } from "./Devices";
|
||
|
||
import { setQuizes } from "@root/quizes/actions";
|
||
import { useQuizStore } from "@root/quizes/store";
|
||
|
||
import { useAnalytics } from "@utils/hooks/useAnalytics";
|
||
|
||
import { quizApi } from "@api/quiz";
|
||
|
||
import CalendarIcon from "@icons/CalendarIcon";
|
||
import { ReactComponent as ResetIcon } from "@icons/Analytics/reset.svg";
|
||
|
||
import type { Moment } from "moment";
|
||
import type { ReactNode } from "react";
|
||
import type { Quiz } from "@model/quiz/quiz";
|
||
|
||
export default function Analytics() {
|
||
const { quizes, editQuizId } = useQuizStore();
|
||
|
||
const [quiz, setQuiz] = useState<Quiz>({} as Quiz);
|
||
const [isOpen, setOpen] = useState<boolean>(false);
|
||
const [isOpenEnd, setOpenEnd] = useState<boolean>(false);
|
||
const [from, setFrom] = useState<Moment | null>(null);
|
||
const [to, setTo] = useState<Moment | null>(moment().add(1, "days"));
|
||
|
||
const theme = useTheme();
|
||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||
|
||
const { devices, general, questions } = useAnalytics({
|
||
ready: Boolean(Object.keys(quiz).length),
|
||
quizId: editQuizId?.toString() || "",
|
||
from,
|
||
to,
|
||
});
|
||
|
||
const resetTime = () => {
|
||
setFrom(moment(0));
|
||
setTo(moment(Date.now()));
|
||
};
|
||
|
||
useEffect(() => {
|
||
if (quizes.length > 0) {
|
||
const quiz = quizes.find((q) => q.backendId === editQuizId);
|
||
if (quiz === undefined) throw new Error("Не удалось получить квиз");
|
||
setQuiz(quiz);
|
||
setFrom(moment(new Date(quiz.created_at)));
|
||
}
|
||
}, [quizes]);
|
||
|
||
useEffect(() => {
|
||
const getData = async (): Promise<void> => {
|
||
try {
|
||
if (editQuizId !== null) {
|
||
const gottenQuizes = await quizApi.getList();
|
||
setQuizes(gottenQuizes);
|
||
}
|
||
} catch (error) {
|
||
console.error("Не удалось получить квизы", error);
|
||
}
|
||
};
|
||
|
||
getData();
|
||
}, []);
|
||
|
||
useLayoutEffect(() => {
|
||
if (editQuizId === undefined) redirect("/list");
|
||
}, [editQuizId]);
|
||
|
||
const handleClose = () => {
|
||
setOpen(false);
|
||
};
|
||
|
||
const handleOpen = () => {
|
||
setOpen(true);
|
||
};
|
||
|
||
const onAdornmentClick = () => {
|
||
setOpen((old) => !old);
|
||
if (isOpenEnd) {
|
||
handleCloseEnd();
|
||
}
|
||
};
|
||
|
||
const handleCloseEnd = () => {
|
||
setOpenEnd(false);
|
||
};
|
||
|
||
const handleOpenEnd = () => {
|
||
setOpenEnd(true);
|
||
};
|
||
|
||
const onAdornmentClickEnd = () => {
|
||
setOpenEnd((old) => !old);
|
||
if (isOpen) {
|
||
handleClose();
|
||
}
|
||
};
|
||
|
||
return (
|
||
<LocalizationProvider dateAdapter={AdapterMoment}>
|
||
<HeaderFull isRequest />
|
||
<SectionWrapper component={"section"} sx={{ padding: "60px 20px" }}>
|
||
<Typography variant={"h4"}>Аналитика</Typography>
|
||
<Box
|
||
sx={{
|
||
display: "flex",
|
||
gap: isMobile ? "15px" : "20px",
|
||
alignItems: isMobile ? "center" : "end",
|
||
justifyContent: "space-between",
|
||
padding: "40px 0 20px",
|
||
borderBottom: `1px solid ${theme.palette.grey2.main}`,
|
||
}}
|
||
>
|
||
<Box
|
||
sx={{
|
||
display: "flex",
|
||
flexWrap: "wrap",
|
||
gap: isMobile ? "15px" : "20px",
|
||
}}
|
||
>
|
||
<Box>
|
||
<Typography
|
||
sx={{
|
||
fontSize: "16px",
|
||
marginBottom: "5px",
|
||
fontWeight: 500,
|
||
color: "4D4D4D",
|
||
}}
|
||
>
|
||
Дата начала
|
||
</Typography>
|
||
<DatePicker
|
||
format="DD/MM/YYYY"
|
||
open={isOpen}
|
||
onClose={handleClose}
|
||
onOpen={handleOpen}
|
||
minDate={moment(quiz?.created_at)}
|
||
sx={{
|
||
width: isMobile ? "285px" : "170px",
|
||
"& .MuiOutlinedInput-root": {
|
||
background: theme.palette.background.paper,
|
||
borderRadius: "10px",
|
||
fontSize: "16px",
|
||
},
|
||
"& .MuiInputBase-input": {
|
||
padding: "12.5px 14px",
|
||
},
|
||
}}
|
||
slotProps={{
|
||
//@ts-ignore
|
||
//TODO: fix types in @mui/x-date-pickers
|
||
textField: {
|
||
InputProps: {
|
||
endAdornment: (
|
||
<IconButton onClick={onAdornmentClick}>
|
||
<CalendarIcon />
|
||
</IconButton>
|
||
) as ReactNode,
|
||
},
|
||
},
|
||
}}
|
||
value={from}
|
||
onChange={setFrom}
|
||
/>
|
||
</Box>
|
||
<Box>
|
||
<Typography
|
||
sx={{
|
||
fontSize: "16px",
|
||
marginBottom: "5px",
|
||
fontWeight: 500,
|
||
color: "4D4D4D",
|
||
}}
|
||
>
|
||
Дата окончания
|
||
</Typography>
|
||
<DatePicker
|
||
format="DD/MM/YYYY"
|
||
open={isOpenEnd}
|
||
onClose={handleCloseEnd}
|
||
onOpen={handleOpenEnd}
|
||
minDate={moment(quiz?.created_at)}
|
||
sx={{
|
||
width: isMobile ? "285px" : "170px",
|
||
"& .MuiOutlinedInput-root": {
|
||
background: theme.palette.background.paper,
|
||
borderRadius: "10px",
|
||
fontSize: "16px",
|
||
},
|
||
"& .MuiInputBase-input": {
|
||
padding: "12.5px 14px",
|
||
},
|
||
}}
|
||
slotProps={{
|
||
textField: {
|
||
InputProps: {
|
||
endAdornment: (
|
||
<IconButton onClick={onAdornmentClickEnd}>
|
||
<CalendarIcon />
|
||
</IconButton>
|
||
),
|
||
},
|
||
},
|
||
}}
|
||
value={to}
|
||
onChange={setTo}
|
||
/>
|
||
</Box>
|
||
</Box>
|
||
|
||
<Button
|
||
onClick={resetTime}
|
||
variant="outlined"
|
||
sx={{
|
||
padding: isMobile ? "8px" : "9px 48px",
|
||
minWidth: "auto",
|
||
marginTop: isMobile ? "25px" : null,
|
||
color: theme.palette.brightPurple.main,
|
||
"&:hover": {
|
||
backgroundColor: "#581CA7",
|
||
color: "#FFFFFF",
|
||
},
|
||
"&:active": {
|
||
backgroundColor: "#000000",
|
||
color: "#FFFFFF",
|
||
},
|
||
}}
|
||
>
|
||
{isMobile ? <ResetIcon /> : "Сбросить"}
|
||
</Button>
|
||
</Box>
|
||
<General
|
||
data={general}
|
||
day={86400 - moment(to).unix() - moment(from).unix() > 0}
|
||
/>
|
||
<AnswersStatistics data={questions} />
|
||
<Devices data={devices} />
|
||
</SectionWrapper>
|
||
</LocalizationProvider>
|
||
);
|
||
}
|