refactored analytics page, fixed ts
This commit is contained in:
parent
af70d8b2c6
commit
47d199ab0b
@ -23,13 +23,18 @@ export type QuestionsResponse = {
|
|||||||
questions: Record<string, Record<string, number>>;
|
questions: Record<string, Record<string, number>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type TRequest = {
|
||||||
|
to: number;
|
||||||
|
from: number;
|
||||||
|
};
|
||||||
|
|
||||||
export const getDevices = async (
|
export const getDevices = async (
|
||||||
quizId: string,
|
quizId: string,
|
||||||
to: number,
|
to: number,
|
||||||
from: number,
|
from: number,
|
||||||
): Promise<[DevicesResponse | null, string?]> => {
|
): Promise<[DevicesResponse | null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const devicesResponse = await makeRequest<unknown, DevicesResponse>({
|
const devicesResponse = await makeRequest<TRequest, DevicesResponse>({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: `${apiUrl}/${quizId}/devices`,
|
url: `${apiUrl}/${quizId}/devices`,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
@ -50,7 +55,7 @@ export const getGeneral = async (
|
|||||||
from: number,
|
from: number,
|
||||||
): Promise<[GeneralResponse | null, string?]> => {
|
): Promise<[GeneralResponse | null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const generalResponse = await makeRequest<unknown, GeneralResponse>({
|
const generalResponse = await makeRequest<TRequest, GeneralResponse>({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: `${apiUrl}/${quizId}/general`,
|
url: `${apiUrl}/${quizId}/general`,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
@ -71,7 +76,7 @@ export const getQuestions = async (
|
|||||||
from: number,
|
from: number,
|
||||||
): Promise<[QuestionsResponse | null, string?]> => {
|
): Promise<[QuestionsResponse | null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const questionsResponse = await makeRequest<unknown, QuestionsResponse>({
|
const questionsResponse = await makeRequest<TRequest, QuestionsResponse>({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: `${apiUrl}/${quizId}/questions`,
|
url: `${apiUrl}/${quizId}/questions`,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
|
@ -1,15 +1,14 @@
|
|||||||
import { useLayoutEffect, useState } from "react";
|
import * as React from "react";
|
||||||
|
import { ReactNode, useLayoutEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
IconButton,
|
IconButton,
|
||||||
Paper,
|
|
||||||
Typography,
|
Typography,
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { DatePicker } from "@mui/x-date-pickers";
|
import { DatePicker } from "@mui/x-date-pickers";
|
||||||
import { LineChart } from "@mui/x-charts";
|
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { useQuizStore } from "@root/quizes/store";
|
import { useQuizStore } from "@root/quizes/store";
|
||||||
import { useAnalytics } from "@utils/hooks/useAnalytics";
|
import { useAnalytics } from "@utils/hooks/useAnalytics";
|
||||||
@ -18,7 +17,7 @@ import HeaderFull from "@ui_kit/Header/HeaderFull";
|
|||||||
import SectionWrapper from "@ui_kit/SectionWrapper";
|
import SectionWrapper from "@ui_kit/SectionWrapper";
|
||||||
|
|
||||||
import { General } from "./General";
|
import { General } from "./General";
|
||||||
import { AnswersStatistics } from "./Answers";
|
import { AnswersStatistics } from "./Answers/AnswersStatistics";
|
||||||
import { Devices } from "./Devices";
|
import { Devices } from "./Devices";
|
||||||
|
|
||||||
import CalendarIcon from "@icons/CalendarIcon";
|
import CalendarIcon from "@icons/CalendarIcon";
|
||||||
@ -26,11 +25,10 @@ import { redirect } from "react-router-dom";
|
|||||||
|
|
||||||
export default function Analytics() {
|
export default function Analytics() {
|
||||||
const { editQuizId } = useQuizStore();
|
const { editQuizId } = useQuizStore();
|
||||||
|
const [isOpen, setOpen] = useState<boolean>(false);
|
||||||
const [isOpen, setOpen] = useState(false);
|
const [isOpenEnd, setOpenEnd] = useState<boolean>(false);
|
||||||
const [isOpenEnd, setOpenEnd] = useState(false);
|
const [to, setTo] = useState<moment.Moment | null>(null);
|
||||||
const [to, setTo] = useState(null);
|
const [from, setFrom] = useState<moment.Moment | null>(null);
|
||||||
const [from, setFrom] = useState(null);
|
|
||||||
|
|
||||||
const { devices, general, questions } = useAnalytics({
|
const { devices, general, questions } = useAnalytics({
|
||||||
quizId: editQuizId?.toString(),
|
quizId: editQuizId?.toString(),
|
||||||
@ -42,6 +40,7 @@ export default function Analytics() {
|
|||||||
setTo(null);
|
setTo(null);
|
||||||
setFrom(null);
|
setFrom(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (editQuizId === undefined) redirect("/list");
|
if (editQuizId === undefined) redirect("/list");
|
||||||
}, [editQuizId]);
|
}, [editQuizId]);
|
||||||
@ -52,12 +51,14 @@ export default function Analytics() {
|
|||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOpen = () => {
|
const handleOpen = () => {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onAdornmentClick = () => {
|
const onAdornmentClick = () => {
|
||||||
setOpen((old) => !old);
|
setOpen((old) => !old);
|
||||||
if (isOpenEnd === true) {
|
if (isOpenEnd) {
|
||||||
handleCloseEnd();
|
handleCloseEnd();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -65,18 +66,17 @@ export default function Analytics() {
|
|||||||
const handleCloseEnd = () => {
|
const handleCloseEnd = () => {
|
||||||
setOpenEnd(false);
|
setOpenEnd(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOpenEnd = () => {
|
const handleOpenEnd = () => {
|
||||||
setOpenEnd(true);
|
setOpenEnd(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onAdornmentClickEnd = () => {
|
const onAdornmentClickEnd = () => {
|
||||||
setOpenEnd((old) => !old);
|
setOpenEnd((old) => !old);
|
||||||
if (isOpen === true) {
|
if (isOpen) {
|
||||||
handleClose();
|
handleClose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
console.log("questions", questions);
|
|
||||||
console.log("general", general);
|
|
||||||
console.log("devices", devices);
|
|
||||||
|
|
||||||
const now = moment();
|
const now = moment();
|
||||||
return (
|
return (
|
||||||
@ -113,7 +113,6 @@ export default function Analytics() {
|
|||||||
// defaultValue={now}
|
// defaultValue={now}
|
||||||
sx={{
|
sx={{
|
||||||
width: isMobile ? "146px" : "169px",
|
width: isMobile ? "146px" : "169px",
|
||||||
|
|
||||||
"& .MuiOutlinedInput-root": {
|
"& .MuiOutlinedInput-root": {
|
||||||
borderRadius: "10px",
|
borderRadius: "10px",
|
||||||
fontSize: "16px",
|
fontSize: "16px",
|
||||||
@ -123,13 +122,15 @@ export default function Analytics() {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
|
//@ts-ignore
|
||||||
|
//TODO: fix types in @mui/x-date-pickers
|
||||||
textField: {
|
textField: {
|
||||||
InputProps: {
|
InputProps: {
|
||||||
endAdornment: (
|
endAdornment: (
|
||||||
<IconButton onClick={onAdornmentClick}>
|
<IconButton onClick={onAdornmentClick}>
|
||||||
<CalendarIcon />
|
<CalendarIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
),
|
) as ReactNode,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
@ -146,8 +147,6 @@ export default function Analytics() {
|
|||||||
color: "4D4D4D",
|
color: "4D4D4D",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
value={from}
|
|
||||||
onChange={(newValue) => setValue(setFrom)}
|
|
||||||
Дата окончания
|
Дата окончания
|
||||||
</Typography>
|
</Typography>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
@ -176,6 +175,8 @@ export default function Analytics() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
value={from}
|
||||||
|
onChange={(newValue) => setFrom(newValue)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import { useState } from "react";
|
import { FC, useState } from "react";
|
||||||
|
import type { PaginationRenderItemParams } from "@mui/material";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Paper,
|
ButtonBase,
|
||||||
Typography,
|
Input,
|
||||||
LinearProgress,
|
LinearProgress,
|
||||||
Pagination as MuiPagination,
|
Pagination as MuiPagination,
|
||||||
PaginationItem,
|
PaginationItem,
|
||||||
Input,
|
Paper,
|
||||||
ButtonBase,
|
Typography,
|
||||||
useTheme,
|
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
|
||||||
import { ReactComponent as DoubleCheckIcon } from "@icons/Analytics/doubleCheck.svg";
|
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 LeftArrowIcon } from "@icons/Analytics/leftArrow.svg";
|
||||||
import { ReactComponent as RightArrowIcon } from "@icons/Analytics/rightArrow.svg";
|
import { ReactComponent as RightArrowIcon } from "@icons/Analytics/rightArrow.svg";
|
||||||
|
|
||||||
import type { PaginationRenderItemParams } from "@mui/material";
|
|
||||||
|
|
||||||
type AnswerProps = {
|
type AnswerProps = {
|
||||||
title: string;
|
title: string;
|
||||||
percent: number;
|
percent: number;
|
||||||
highlight?: boolean;
|
highlight?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type AnswersProps = {
|
||||||
|
data: Record<string, Record<string, number>> | null;
|
||||||
|
};
|
||||||
|
|
||||||
const ANSWERS_MOCK: Record<string, number> = {
|
const ANSWERS_MOCK: Record<string, number> = {
|
||||||
"Добавьте ответ": 67,
|
"Добавьте ответ": 67,
|
||||||
"Вопрос пропущен": 7,
|
"Вопрос пропущен": 7,
|
||||||
@ -186,16 +189,16 @@ const Pagination = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Answers = (props) => {
|
export const Answers: FC<AnswersProps> = ({ data }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
console.log(props.data);
|
if (!data) {
|
||||||
|
|
||||||
if (Object.keys(props.data).length === 0)
|
|
||||||
return (
|
return (
|
||||||
<Typography textAlign="center" m="10px 0">
|
<Typography textAlign="center" m="10px 0">
|
||||||
нет данных об ответах
|
нет данных об ответах
|
||||||
</Typography>
|
</Typography>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
<Box sx={{ flexGrow: 1 }}>
|
||||||
<Paper
|
<Paper
|
||||||
@ -250,14 +253,24 @@ export const Answers = (props) => {
|
|||||||
<NextIcon />
|
<NextIcon />
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
</Box>
|
</Box>
|
||||||
{Object.entries(props.data).map(([title, percent], index) => (
|
{/*{Object.entries(data).map(([title, percent], index) => (*/}
|
||||||
<Answer
|
{/* <Answer*/}
|
||||||
key={title}
|
{/* key={title}*/}
|
||||||
title={title}
|
{/* title={title}*/}
|
||||||
percent={percent}
|
{/* percent={percent}*/}
|
||||||
highlight={!index}
|
{/* highlight={!index}*/}
|
||||||
/>
|
{/* />*/}
|
||||||
))}
|
{/*))}*/}
|
||||||
|
{Object.entries(data).map(([title, values], index) =>
|
||||||
|
Object.entries(values).map(([subTitle, percent]) => (
|
||||||
|
<Answer
|
||||||
|
key={subTitle}
|
||||||
|
title={subTitle}
|
||||||
|
percent={percent}
|
||||||
|
highlight={!index}
|
||||||
|
/>
|
||||||
|
)),
|
||||||
|
)}
|
||||||
</Paper>
|
</Paper>
|
||||||
<Pagination />
|
<Pagination />
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -1,24 +1,20 @@
|
|||||||
import {
|
import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
Box,
|
|
||||||
ButtonBase,
|
|
||||||
Typography,
|
|
||||||
useMediaQuery,
|
|
||||||
useTheme,
|
|
||||||
} from "@mui/material";
|
|
||||||
|
|
||||||
import { Answers } from "./Answers";
|
import { Answers } from "./Answers";
|
||||||
|
import { QuestionsResponse } from "@api/statistic";
|
||||||
|
import { FC } from "react";
|
||||||
import { Funnel } from "./Funnel";
|
import { Funnel } from "./Funnel";
|
||||||
import { Results } from "./Results";
|
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<AnswersStatisticsProps> = ({ data }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1150));
|
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1150));
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(850));
|
const isMobile = useMediaQuery(theme.breakpoints.down(850));
|
||||||
|
|
||||||
console.log(props);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ marginTop: "120px" }}>
|
<Box sx={{ marginTop: "120px" }}>
|
||||||
<Typography
|
<Typography
|
||||||
@ -59,10 +55,10 @@ export const AnswersStatistics = (props) => {
|
|||||||
gap: "40px",
|
gap: "40px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Answers data={props.data?.Questions || {}} />
|
<Answers data={data?.questions || null} />
|
||||||
<Funnel data={props.data?.Funnel || {}} />
|
<Funnel data={data?.funnel || null} />
|
||||||
</Box>
|
</Box>
|
||||||
<Results data={props.data?.Results || {}} />
|
<Results data={data?.results || null} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
@ -1,19 +1,22 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { FC, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
|
LinearProgress,
|
||||||
Paper,
|
Paper,
|
||||||
Typography,
|
Typography,
|
||||||
LinearProgress,
|
|
||||||
useTheme,
|
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { enqueueSnackbar } from "notistack";
|
|
||||||
|
|
||||||
type FunnelItemProps = {
|
type FunnelItemProps = {
|
||||||
title: string;
|
title: string;
|
||||||
percent: number;
|
percent: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type FunnelProps = {
|
||||||
|
data: number[] | null;
|
||||||
|
};
|
||||||
|
|
||||||
const FUNNEL_MOCK: Record<string, number> = {
|
const FUNNEL_MOCK: Record<string, number> = {
|
||||||
"Стартовая страница": 100,
|
"Стартовая страница": 100,
|
||||||
"Воронка квиза": 0,
|
"Воронка квиза": 0,
|
||||||
@ -100,11 +103,10 @@ const FunnelItem = ({ title, percent }: FunnelItemProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Funnel = (props) => {
|
export const Funnel: FC<FunnelProps> = ({ data }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1150));
|
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1150));
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(850));
|
const isMobile = useMediaQuery(theme.breakpoints.down(850));
|
||||||
console.log(props);
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// const requestFunnel = async () => {
|
// const requestFunnel = async () => {
|
||||||
// const [funnelResponse, funnelError] = await getGeneral("14761");
|
// const [funnelResponse, funnelError] = await getGeneral("14761");
|
||||||
@ -121,7 +123,7 @@ export const Funnel = (props) => {
|
|||||||
// requestFunnel();
|
// requestFunnel();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (Object.keys(props.data).length === 0)
|
if (!data)
|
||||||
return (
|
return (
|
||||||
<Typography textAlign="center" m="10px 0">
|
<Typography textAlign="center" m="10px 0">
|
||||||
нет данных о разделах
|
нет данных о разделах
|
||||||
@ -141,7 +143,7 @@ export const Funnel = (props) => {
|
|||||||
<FunnelItem
|
<FunnelItem
|
||||||
key={title}
|
key={title}
|
||||||
title={title}
|
title={title}
|
||||||
percent={index > 0 ? props.data[index - 1] : percent}
|
percent={index > 0 ? data[index - 1] : percent}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Paper>
|
</Paper>
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
|
LinearProgress,
|
||||||
Paper,
|
Paper,
|
||||||
Typography,
|
Typography,
|
||||||
LinearProgress,
|
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
import { FC } from "react";
|
||||||
|
|
||||||
type ResultProps = {
|
type ResultProps = {
|
||||||
title: string;
|
title: string;
|
||||||
@ -13,6 +13,10 @@ type ResultProps = {
|
|||||||
highlight?: boolean;
|
highlight?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ResultsProps = {
|
||||||
|
data: Record<string, number> | null;
|
||||||
|
};
|
||||||
|
|
||||||
const RESULTS_MOCK: Record<string, number> = {
|
const RESULTS_MOCK: Record<string, number> = {
|
||||||
"Заголовок результата": 100,
|
"Заголовок результата": 100,
|
||||||
"Результат пропущен": 7,
|
"Результат пропущен": 7,
|
||||||
@ -69,10 +73,10 @@ const Result = ({ title, percent, highlight }: ResultProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Results = (props) => {
|
export const Results: FC<ResultsProps> = ({ data }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
if (Object.keys(props.data).length === 0)
|
if (!data)
|
||||||
return (
|
return (
|
||||||
<Typography margin="20px 0 0 0" textAlign="center" m="10px 0">
|
<Typography margin="20px 0 0 0" textAlign="center" m="10px 0">
|
||||||
нет данных о результатах
|
нет данных о результатах
|
||||||
@ -98,7 +102,7 @@ export const Results = (props) => {
|
|||||||
marginTop: "30px",
|
marginTop: "30px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Object.entries(props.data).map(([title, percent], index) => (
|
{Object.entries(data).map(([title, percent], index) => (
|
||||||
<Result
|
<Result
|
||||||
key={title}
|
key={title}
|
||||||
title={title}
|
title={title}
|
||||||
|
@ -1,15 +1,16 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { FC } from "react";
|
||||||
import { Box, Paper, Typography, useMediaQuery, useTheme } from "@mui/material";
|
import { Box, Paper, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import { PieChart } from "@mui/x-charts";
|
import { PieChart } from "@mui/x-charts";
|
||||||
import { enqueueSnackbar } from "notistack";
|
|
||||||
|
|
||||||
import { getDevices } from "@api/statistic";
|
|
||||||
|
|
||||||
import type { DevicesResponse } from "@api/statistic";
|
import type { DevicesResponse } from "@api/statistic";
|
||||||
|
|
||||||
type DeviceProps = {
|
type DeviceProps = {
|
||||||
title: string;
|
title: string;
|
||||||
devices: Record<string, number>;
|
devices: Record<string, number> | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DevicesProps = {
|
||||||
|
data: DevicesResponse | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const COLORS: Record<number, string> = {
|
const COLORS: Record<number, string> = {
|
||||||
@ -27,9 +28,10 @@ const DEVICES_MOCK: DevicesResponse = {
|
|||||||
|
|
||||||
const Device = ({ title, devices }: DeviceProps) => {
|
const Device = ({ title, devices }: DeviceProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
console.log("devices ", devices);
|
if (!devices) {
|
||||||
if (devices === undefined || Object.keys(devices).length === 0)
|
|
||||||
return <Typography>{title} - нет данных</Typography>;
|
return <Typography>{title} - нет данных</Typography>;
|
||||||
|
}
|
||||||
|
|
||||||
const data = Object.entries(devices).map(([id, value], index) => ({
|
const data = Object.entries(devices).map(([id, value], index) => ({
|
||||||
id,
|
id,
|
||||||
value,
|
value,
|
||||||
@ -98,8 +100,7 @@ const Device = ({ title, devices }: DeviceProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Devices = ({ data = {} }) => {
|
export const Devices: FC<DevicesProps> = ({ data }) => {
|
||||||
const [devices, setDevices] = useState<DevicesResponse>(data);
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(700));
|
const isMobile = useMediaQuery(theme.breakpoints.down(700));
|
||||||
@ -150,9 +151,9 @@ export const Devices = ({ data = {} }) => {
|
|||||||
marginTop: "30px",
|
marginTop: "30px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Device title="Устройства" devices={devices.device} />
|
<Device title="Устройства" devices={data?.device || null} />
|
||||||
<Device title="Операционные системы" devices={devices.os} />
|
<Device title="Операционные системы" devices={data?.os || null} />
|
||||||
<Device title="Браузеры" devices={devices.browser} />
|
<Device title="Браузеры" devices={data?.browser || null} />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
@ -1,19 +1,20 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { Box, Paper, Typography, useMediaQuery, useTheme } from "@mui/material";
|
import { Box, Paper, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||||
import { LineChart } from "@mui/x-charts";
|
import { LineChart } from "@mui/x-charts";
|
||||||
import { enqueueSnackbar } from "notistack";
|
|
||||||
|
|
||||||
import { getGeneral } from "@api/statistic";
|
|
||||||
|
|
||||||
import type { GeneralResponse } from "@api/statistic";
|
import type { GeneralResponse } from "@api/statistic";
|
||||||
|
import { FC } from "react";
|
||||||
|
|
||||||
type GeneralProps = {
|
type GeneralItemsProps = {
|
||||||
title: string;
|
title: string;
|
||||||
general: Record<string, number>;
|
general: Record<string, number>;
|
||||||
color: string;
|
color: string;
|
||||||
numberType: "sum" | "percent";
|
numberType: "sum" | "percent";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type GeneralProps = {
|
||||||
|
data: GeneralResponse | null;
|
||||||
|
};
|
||||||
|
|
||||||
const COLORS: Record<number, string> = {
|
const COLORS: Record<number, string> = {
|
||||||
0: "#61BB1A",
|
0: "#61BB1A",
|
||||||
1: "#7E2AEA",
|
1: "#7E2AEA",
|
||||||
@ -28,7 +29,12 @@ const GENERAL_MOCK: GeneralResponse = {
|
|||||||
conversation: { 100: 50, 1000: 50, 10000: 50 },
|
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 theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(700));
|
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<GeneralProps> = ({ data }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down(700));
|
const isMobile = useMediaQuery(theme.breakpoints.down(700));
|
||||||
|
|
||||||
if (Object.keys(props.data).length === 0)
|
if (!data) {
|
||||||
return (
|
return (
|
||||||
<Typography textAlign="center" m="10px 0">
|
<Typography textAlign="center" m="10px 0">
|
||||||
нет данных о ключевых метриках
|
нет данных о ключевых метриках
|
||||||
</Typography>
|
</Typography>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<Box sx={{ marginTop: "45px" }}>
|
<Box sx={{ marginTop: "45px" }}>
|
||||||
<Typography
|
<Typography
|
||||||
@ -103,25 +110,25 @@ export const General = (props: any) => {
|
|||||||
<GeneralItem
|
<GeneralItem
|
||||||
title="Открыли квиз"
|
title="Открыли квиз"
|
||||||
numberType="sum"
|
numberType="sum"
|
||||||
general={props.data.open || { 0: 0 }}
|
general={data.open || { 0: 0 }}
|
||||||
color={COLORS[0]}
|
color={COLORS[0]}
|
||||||
/>
|
/>
|
||||||
<GeneralItem
|
<GeneralItem
|
||||||
title="Получено заявок"
|
title="Получено заявок"
|
||||||
numberType="sum"
|
numberType="sum"
|
||||||
general={props.data.result || { 0: 0 }}
|
general={data.result || { 0: 0 }}
|
||||||
color={COLORS[1]}
|
color={COLORS[1]}
|
||||||
/>
|
/>
|
||||||
<GeneralItem
|
<GeneralItem
|
||||||
title="Конверсия"
|
title="Конверсия"
|
||||||
numberType="percent"
|
numberType="percent"
|
||||||
general={props.data.conversation || { 0: 0 }}
|
general={data.conversation || { 0: 0 }}
|
||||||
color={COLORS[2]}
|
color={COLORS[2]}
|
||||||
/>
|
/>
|
||||||
<GeneralItem
|
<GeneralItem
|
||||||
title="Среднее время прохождения квиза"
|
title="Среднее время прохождения квиза"
|
||||||
numberType="percent"
|
numberType="percent"
|
||||||
general={props.data.avtime || { 0: 0 }}
|
general={data.avtime || { 0: 0 }}
|
||||||
color={COLORS[3]}
|
color={COLORS[3]}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -1,32 +1,44 @@
|
|||||||
import { getGeneral, getDevices, getQuestions } from "@api/statistic";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
import {
|
||||||
|
DevicesResponse,
|
||||||
|
GeneralResponse,
|
||||||
|
getDevices,
|
||||||
|
getGeneral,
|
||||||
|
getQuestions,
|
||||||
|
QuestionsResponse,
|
||||||
|
} from "@api/statistic";
|
||||||
|
|
||||||
interface Props {
|
interface useAnalyticsProps {
|
||||||
quizId: string;
|
quizId: string | undefined;
|
||||||
to: number;
|
to: moment.Moment | null;
|
||||||
from: number;
|
from: moment.Moment | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAnalytics({ quizId, to, from }: Props) {
|
export function useAnalytics({ quizId, to, from }: useAnalyticsProps) {
|
||||||
const formatTo = to === null ? 0 : moment(to).unix();
|
const formatTo = to === null ? 0 : to.unix();
|
||||||
const formatFrom = from === null ? 0 : moment(from).unix();
|
const formatFrom = from === null ? 0 : from.unix();
|
||||||
console.log(to, from);
|
|
||||||
if (quizId === undefined) return {};
|
const [devices, setDevices] = useState<DevicesResponse | null>(null);
|
||||||
const [devices, setDevices] = useState({});
|
const [general, setGeneral] = useState<GeneralResponse | null>(null);
|
||||||
const [general, setGeneral] = useState({});
|
const [questions, setQuestions] = useState<QuestionsResponse | null>(null);
|
||||||
const [questions, setQuestions] = useState({});
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!quizId) return;
|
||||||
(async () => {
|
(async () => {
|
||||||
const gottenGeneral = await getGeneral(quizId, formatTo, formatFrom);
|
const [gottenGeneral] = await getGeneral(quizId, formatTo, formatFrom);
|
||||||
const gottenDevices = await getDevices(quizId, formatTo, formatFrom);
|
const [gottenDevices] = await getDevices(quizId, formatTo, formatFrom);
|
||||||
const gottenQuestions = await getQuestions(quizId, formatTo, formatFrom);
|
const [gottenQuestions] = await getQuestions(
|
||||||
setDevices(gottenGeneral[0]);
|
quizId,
|
||||||
setGeneral(gottenDevices[0]);
|
formatTo,
|
||||||
setQuestions(gottenQuestions[0]);
|
formatFrom,
|
||||||
|
);
|
||||||
|
|
||||||
|
setGeneral(gottenGeneral);
|
||||||
|
setDevices(gottenDevices);
|
||||||
|
setQuestions(gottenQuestions);
|
||||||
})();
|
})();
|
||||||
}, [to, from]);
|
}, [quizId, to, from]);
|
||||||
|
|
||||||
return { devices, general, questions };
|
return { devices, general, questions };
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user