Merge branch 'analytics' into dev
This commit is contained in:
commit
a980fe5fe4
@ -10,6 +10,7 @@
|
||||
"@frontend/squzanswerer": "^1.0.6",
|
||||
"@mui/icons-material": "^5.10.14",
|
||||
"@mui/material": "^5.10.14",
|
||||
"@mui/x-charts": "^6.19.5",
|
||||
"@mui/x-date-pickers": "^6.16.1",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^13.0.0",
|
||||
@ -48,6 +49,7 @@
|
||||
"react-error-boundary": "^4.0.11",
|
||||
"react-image-crop": "^10.1.5",
|
||||
"react-image-file-resizer": "^0.4.8",
|
||||
"react-lazily": "^0.9.2",
|
||||
"react-rnd": "^10.4.1",
|
||||
"react-router-dom": "^6.6.2",
|
||||
"react-scripts": "5.0.1",
|
||||
|
112
src/App.tsx
112
src/App.tsx
@ -1,11 +1,10 @@
|
||||
import { Suspense, lazy } from "react";
|
||||
import { lazily } from "react-lazily";
|
||||
import ContactFormModal from "@ui_kit/ContactForm";
|
||||
import ImageCrop from "@ui_kit/Modal/ImageCrop";
|
||||
import dayjs from "dayjs";
|
||||
import "dayjs/locale/ru";
|
||||
import SigninDialog from "./pages/auth/Signin";
|
||||
import SignupDialog from "./pages/auth/Signup";
|
||||
import ViewPage from "./pages/ViewPublicationPage";
|
||||
import { DesignPage } from "./pages/DesignPage/DesignPage";
|
||||
import {
|
||||
Route,
|
||||
Routes,
|
||||
@ -14,16 +13,8 @@ import {
|
||||
Navigate,
|
||||
} from "react-router-dom";
|
||||
import "./index.css";
|
||||
import ContactFormPage from "./pages/ContactFormPage/ContactFormPage";
|
||||
import InstallQuiz from "./pages/InstallQuiz/InstallQuiz";
|
||||
import Landing from "./pages/Landing/Landing";
|
||||
import QuestionsPage from "./pages/Questions/QuestionsPage";
|
||||
import { Result } from "./pages/ResultPage/Result";
|
||||
import { ResultSettings } from "./pages/ResultPage/ResultSettings";
|
||||
import MyQuizzesFull from "./pages/createQuize/MyQuizzesFull";
|
||||
import Main from "./pages/main";
|
||||
import EditPage from "./pages/startPage/EditPage";
|
||||
import { Tariffs } from "./pages/Tariffs/Tariffs";
|
||||
import {
|
||||
clearAuthToken,
|
||||
getMessageFromFetchError,
|
||||
@ -50,12 +41,48 @@ import { isAxiosError } from "axios";
|
||||
import { useEffect, useLayoutEffect, useRef } from "react";
|
||||
import RecoverPassword from "./pages/auth/RecoverPassword";
|
||||
import OutdatedLink from "./pages/auth/OutdatedLink";
|
||||
import { QuizAnswersPage } from "./pages/QuizAnswersPage/QuizAnswersPage";
|
||||
|
||||
import type { OriginalUserAccount } from "@root/user";
|
||||
import ChatImageNewWindow from "@ui_kit/FloatingSupportChat/ChatImageNewWindow";
|
||||
|
||||
export function useUserAccountFetcher<T = UserAccount>({
|
||||
import type { SuspenseProps } from "react";
|
||||
|
||||
const MyQuizzesFull = lazy(() => import("./pages/createQuize/MyQuizzesFull"));
|
||||
const ViewPage = lazy(() => import("./pages/ViewPublicationPage"));
|
||||
const Analytics = lazy(() => import("./pages/Analytics/Analytics"));
|
||||
const EditPage = lazy(() => import("./pages/startPage/EditPage"));
|
||||
const { Tariffs } = lazily(() => import("./pages/Tariffs/Tariffs"));
|
||||
const { DesignPage } = lazily(() => import("./pages/DesignPage/DesignPage"));
|
||||
const { QuizAnswersPage } = lazily(
|
||||
() => import("./pages/QuizAnswersPage/QuizAnswersPage"),
|
||||
);
|
||||
const ChatImageNewWindow = lazy(
|
||||
() => import("@ui_kit/FloatingSupportChat/ChatImageNewWindow"),
|
||||
);
|
||||
|
||||
dayjs.locale("ru");
|
||||
|
||||
const routeslink = [
|
||||
{
|
||||
path: "/edit",
|
||||
page: EditPage,
|
||||
header: true,
|
||||
sidebar: true,
|
||||
footer: true,
|
||||
},
|
||||
{
|
||||
path: "/design",
|
||||
page: DesignPage,
|
||||
header: true,
|
||||
sidebar: true,
|
||||
footer: true,
|
||||
},
|
||||
] as const;
|
||||
|
||||
const LazyLoading = ({ children, fallback }: SuspenseProps) => (
|
||||
<Suspense fallback={fallback ?? <></>}>{children}</Suspense>
|
||||
);
|
||||
|
||||
function useUserAccountFetcher<T = UserAccount>({
|
||||
onError,
|
||||
onNewUserAccount,
|
||||
url,
|
||||
@ -107,25 +134,6 @@ export function useUserAccountFetcher<T = UserAccount>({
|
||||
}, [url, userId]);
|
||||
}
|
||||
|
||||
dayjs.locale("ru");
|
||||
|
||||
const routeslink = [
|
||||
{
|
||||
path: "/edit",
|
||||
page: EditPage,
|
||||
header: true,
|
||||
sidebar: true,
|
||||
footer: true,
|
||||
},
|
||||
{
|
||||
path: "/design",
|
||||
page: DesignPage,
|
||||
header: true,
|
||||
sidebar: true,
|
||||
footer: true,
|
||||
},
|
||||
] as const;
|
||||
|
||||
export default function App() {
|
||||
const userId = useUserStore((state) => state.userId);
|
||||
const location = useLocation();
|
||||
@ -239,10 +247,26 @@ export default function App() {
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Route path="/list" element={<MyQuizzesFull />} />
|
||||
<Route path={"/view/:quizId"} element={<ViewPage />} />
|
||||
<Route path={"/tariffs"} element={<Tariffs />} />
|
||||
<Route path={"/results/:quizId"} element={<QuizAnswersPage />} />
|
||||
<Route
|
||||
path="/list"
|
||||
element={<LazyLoading children={<MyQuizzesFull />} />}
|
||||
/>
|
||||
<Route
|
||||
path={"/view/:quizId"}
|
||||
element={<LazyLoading children={<ViewPage />} />}
|
||||
/>
|
||||
<Route
|
||||
path={"/tariffs"}
|
||||
element={<LazyLoading children={<Tariffs />} />}
|
||||
/>
|
||||
<Route
|
||||
path={"/analytics"}
|
||||
element={<LazyLoading children={<Analytics />} />}
|
||||
/>
|
||||
<Route
|
||||
path={"/results/:quizId"}
|
||||
element={<LazyLoading children={<QuizAnswersPage />} />}
|
||||
/>
|
||||
<Route element={<PrivateRoute />}>
|
||||
<Route path={"/image/:srcImage"} element={<ChatImageNewWindow />} />
|
||||
{routeslink.map((e, i) => (
|
||||
@ -250,11 +274,15 @@ export default function App() {
|
||||
key={i}
|
||||
path={e.path}
|
||||
element={
|
||||
<Main
|
||||
Page={e.page}
|
||||
header={e.header}
|
||||
sidebar={e.sidebar}
|
||||
footer={e.footer}
|
||||
<LazyLoading
|
||||
children={
|
||||
<Main
|
||||
Page={e.page}
|
||||
header={e.header}
|
||||
sidebar={e.sidebar}
|
||||
footer={e.footer}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
81
src/api/statistic.ts
Normal file
81
src/api/statistic.ts
Normal file
@ -0,0 +1,81 @@
|
||||
import { makeRequest } from "@frontend/kitui";
|
||||
|
||||
import { parseAxiosError } from "@utils/parse-error";
|
||||
|
||||
const apiUrl = process.env.REACT_APP_DOMAIN + "/squiz/statistic";
|
||||
|
||||
export type DevicesResponse = {
|
||||
device: Record<string, number>;
|
||||
os: Record<string, number>;
|
||||
browser: Record<string, number>;
|
||||
};
|
||||
|
||||
export type GeneralResponse = {
|
||||
open: Record<string, number>;
|
||||
result: Record<string, number>;
|
||||
avtime: Record<string, number>;
|
||||
conversation: Record<string, number>;
|
||||
};
|
||||
|
||||
export type QuestionsResponse = {
|
||||
funnel: number[];
|
||||
results: Record<string, number>;
|
||||
questions: Record<string, Record<string, number>>;
|
||||
};
|
||||
|
||||
export const getDevices = async (
|
||||
quizId: string,
|
||||
): Promise<[DevicesResponse | null, string?]> => {
|
||||
try {
|
||||
const devicesResponse = await makeRequest<unknown, DevicesResponse>({
|
||||
method: "POST",
|
||||
url: `${apiUrl}/${quizId}/devices`,
|
||||
useToken: false,
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
return [devicesResponse];
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
|
||||
return [null, `Не удалось получить статистику о девайсах. ${error}`];
|
||||
}
|
||||
};
|
||||
|
||||
export const getGeneral = async (
|
||||
quizId: string,
|
||||
): Promise<[GeneralResponse | null, string?]> => {
|
||||
try {
|
||||
const generalResponse = await makeRequest<unknown, GeneralResponse>({
|
||||
method: "POST",
|
||||
url: `${apiUrl}/${quizId}/general`,
|
||||
useToken: false,
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
return [generalResponse];
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
|
||||
return [null, `Не удалось получить ключевые метрики. ${error}`];
|
||||
}
|
||||
};
|
||||
|
||||
export const getQuestions = async (
|
||||
quizId: string,
|
||||
): Promise<[QuestionsResponse | null, string?]> => {
|
||||
try {
|
||||
const questionsResponse = await makeRequest<unknown, QuestionsResponse>({
|
||||
method: "POST",
|
||||
url: `${apiUrl}/${quizId}/questions`,
|
||||
useToken: false,
|
||||
withCredentials: true,
|
||||
});
|
||||
|
||||
return [questionsResponse];
|
||||
} catch (nativeError) {
|
||||
const [error] = parseAxiosError(nativeError);
|
||||
|
||||
return [null, `Не удалось получить статистику по результатам. ${error}`];
|
||||
}
|
||||
};
|
16
src/assets/icons/Analytics/doubleCheck.svg
Normal file
16
src/assets/icons/Analytics/doubleCheck.svg
Normal file
@ -0,0 +1,16 @@
|
||||
<svg
|
||||
width="30"
|
||||
height="30"
|
||||
viewBox="0 0 30 30"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="30" height="30" rx="6" fill="#FEDFD0" />
|
||||
<path
|
||||
d="M18.7891 11.2006L14.526 15.4943M10.3154 15.2084L14.526 19.2993L22.7891 10.7006M7.21053 15.4144L11 19.0962"
|
||||
stroke="#FC712F"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 395 B |
3
src/assets/icons/Analytics/leftArrow.svg
Normal file
3
src/assets/icons/Analytics/leftArrow.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6 1.5L1 6L6 10.5" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 212 B |
23
src/assets/icons/Analytics/next.svg
Normal file
23
src/assets/icons/Analytics/next.svg
Normal file
@ -0,0 +1,23 @@
|
||||
<svg
|
||||
width="30"
|
||||
height="30"
|
||||
viewBox="0 0 30 30"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect width="30" height="30" rx="6" fill="#FEDFD0" />
|
||||
<path
|
||||
d="M11.5 10.5L15.5 15L11.5 19.5"
|
||||
stroke="#FC712F"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M15.5 10.5L19.5 15L15.5 19.5"
|
||||
stroke="#FC712F"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
After Width: | Height: | Size: 468 B |
3
src/assets/icons/Analytics/open.svg
Normal file
3
src/assets/icons/Analytics/open.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="14" height="7" viewBox="0 0 14 7" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 1L7 6L13 1" stroke="#7E2AEA" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 210 B |
3
src/assets/icons/Analytics/rightArrow.svg
Normal file
3
src/assets/icons/Analytics/rightArrow.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="7" height="12" viewBox="0 0 7 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M1 1.5L6 6L1 10.5" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 212 B |
181
src/pages/Analytics/Analytics.tsx
Normal file
181
src/pages/Analytics/Analytics.tsx
Normal file
@ -0,0 +1,181 @@
|
||||
import { 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 HeaderFull from "@ui_kit/Header/HeaderFull";
|
||||
import SectionWrapper from "@ui_kit/SectionWrapper";
|
||||
|
||||
import { General } from "./General";
|
||||
import { AnswersStatistics } from "./Answers";
|
||||
import { Devices } from "./Devices";
|
||||
|
||||
import CalendarIcon from "@icons/CalendarIcon";
|
||||
|
||||
export default function Analytics() {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const [isOpenEnd, setOpenEnd] = useState(false);
|
||||
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
const handleOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
const onAdornmentClick = () => {
|
||||
setOpen((old) => !old);
|
||||
if (isOpenEnd === true) {
|
||||
handleCloseEnd();
|
||||
}
|
||||
};
|
||||
|
||||
const handleCloseEnd = () => {
|
||||
setOpenEnd(false);
|
||||
};
|
||||
const handleOpenEnd = () => {
|
||||
setOpenEnd(true);
|
||||
};
|
||||
const onAdornmentClickEnd = () => {
|
||||
setOpenEnd((old) => !old);
|
||||
if (isOpen === true) {
|
||||
handleClose();
|
||||
}
|
||||
};
|
||||
|
||||
const now = moment();
|
||||
return (
|
||||
<>
|
||||
<HeaderFull isRequest />
|
||||
<SectionWrapper component={"section"} sx={{ padding: "60px 20px" }}>
|
||||
<Typography variant={"h4"}>Аналитика</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: isMobile ? "15px" : "20px",
|
||||
alignItems: "end",
|
||||
justifyContent: "space-between",
|
||||
padding: "40px 0 20px",
|
||||
borderBottom: `1px solid ${theme.palette.grey2.main}`,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: "flex", gap: isMobile ? "15px" : "20px" }}>
|
||||
<Box>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
marginBottom: "5px",
|
||||
fontWeight: 500,
|
||||
color: "4D4D4D",
|
||||
}}
|
||||
>
|
||||
Дата начала
|
||||
</Typography>
|
||||
<DatePicker
|
||||
open={isOpen}
|
||||
onClose={handleClose}
|
||||
onOpen={handleOpen}
|
||||
// defaultValue={now}
|
||||
sx={{
|
||||
width: isMobile ? "146px" : "169px",
|
||||
|
||||
"& .MuiOutlinedInput-root": {
|
||||
borderRadius: "10px",
|
||||
fontSize: "16px",
|
||||
},
|
||||
"& .MuiInputBase-input": {
|
||||
padding: "12.5px 14px",
|
||||
},
|
||||
}}
|
||||
slotProps={{
|
||||
textField: {
|
||||
InputProps: {
|
||||
endAdornment: (
|
||||
<IconButton onClick={onAdornmentClick}>
|
||||
<CalendarIcon />
|
||||
</IconButton>
|
||||
),
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
marginBottom: "5px",
|
||||
fontWeight: 500,
|
||||
color: "4D4D4D",
|
||||
}}
|
||||
>
|
||||
Дата окончания
|
||||
</Typography>
|
||||
<DatePicker
|
||||
open={isOpenEnd}
|
||||
onClose={handleCloseEnd}
|
||||
onOpen={handleOpenEnd}
|
||||
// defaultValue={now}
|
||||
sx={{
|
||||
width: isMobile ? "146px" : "169px",
|
||||
"& .MuiOutlinedInput-root": {
|
||||
borderRadius: "10px",
|
||||
fontSize: "16px",
|
||||
},
|
||||
"& .MuiInputBase-input": {
|
||||
padding: "12.5px 14px",
|
||||
},
|
||||
}}
|
||||
slotProps={{
|
||||
textField: {
|
||||
InputProps: {
|
||||
endAdornment: (
|
||||
<IconButton onClick={onAdornmentClickEnd}>
|
||||
<CalendarIcon />
|
||||
</IconButton>
|
||||
),
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
sx={{
|
||||
minWidth: isMobile ? "144px" : "180px",
|
||||
px: isMobile ? "31px" : "43px",
|
||||
color: theme.palette.brightPurple.main,
|
||||
"&:hover": {
|
||||
backgroundColor: "#581CA7",
|
||||
color: "#FFFFFF",
|
||||
},
|
||||
"&:active": {
|
||||
backgroundColor: "#000000",
|
||||
color: "#FFFFFF",
|
||||
},
|
||||
}}
|
||||
>
|
||||
Сбросить
|
||||
</Button>
|
||||
</Box>
|
||||
<General />
|
||||
<AnswersStatistics />
|
||||
<Devices />
|
||||
</SectionWrapper>
|
||||
</>
|
||||
);
|
||||
}
|
259
src/pages/Analytics/Answers/Answers.tsx
Normal file
259
src/pages/Analytics/Answers/Answers.tsx
Normal file
@ -0,0 +1,259 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
Paper,
|
||||
Typography,
|
||||
LinearProgress,
|
||||
Pagination as MuiPagination,
|
||||
PaginationItem,
|
||||
Input,
|
||||
ButtonBase,
|
||||
useTheme,
|
||||
useMediaQuery,
|
||||
} from "@mui/material";
|
||||
|
||||
import { ReactComponent as DoubleCheckIcon } from "@icons/Analytics/doubleCheck.svg";
|
||||
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;
|
||||
};
|
||||
|
||||
const ANSWERS_MOCK: Record<string, number> = {
|
||||
"Добавьте ответ": 67,
|
||||
"Вопрос пропущен": 7,
|
||||
Другое: 27,
|
||||
};
|
||||
|
||||
const Answer = ({ title, percent, highlight }: AnswerProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box sx={{ padding: "15px 25px" }}>
|
||||
<Box
|
||||
sx={{
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
gap: "15px",
|
||||
alignItems: "center",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
title={title}
|
||||
value={percent}
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "44px",
|
||||
background: theme.palette.background.default,
|
||||
borderRadius: "10px",
|
||||
border: `1px solid ${highlight ? theme.palette.brightPurple.main : theme.palette.grey2.main}`,
|
||||
"& > span": { background: highlight ? "#D9C0F9" : "#9A9AAF1A" },
|
||||
"&::before": {
|
||||
content: `"${title}"`,
|
||||
position: "absolute",
|
||||
zIndex: 1,
|
||||
left: "20px",
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
color: highlight
|
||||
? theme.palette.brightPurple.main
|
||||
: theme.palette.grey3.main,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Box sx={{ minWidth: 35 }}>
|
||||
<Typography
|
||||
sx={{
|
||||
minWidth: "45px",
|
||||
fontWeight: highlight ? "bold" : "normal",
|
||||
color: highlight
|
||||
? theme.palette.brightPurple.main
|
||||
: theme.palette.text.primary,
|
||||
}}
|
||||
>{`${percent}%`}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const Pagination = () => {
|
||||
const [count, setCount] = useState<number>(50);
|
||||
const [page, setPage] = useState<number>(1);
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(855));
|
||||
|
||||
const getPaginationItem = (props: PaginationRenderItemParams) => {
|
||||
if (props.type === "start-ellipsis" || props.type === "end-ellipsis") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (props.type !== "previous" && props.type !== "next" && props.page) {
|
||||
if (isMobile) {
|
||||
const allowedPages = [
|
||||
page - 1 < 1 ? page + 2 : page - 1,
|
||||
page,
|
||||
page + 1 > count ? page - 2 : page + 1,
|
||||
];
|
||||
|
||||
if (!allowedPages.includes(props.page)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const allowedPages = [
|
||||
page - 2 < 1 ? page + 3 : page - 2,
|
||||
page - 1 < 1 ? page + 4 : page - 1,
|
||||
page,
|
||||
page + 1 > count ? page - 4 : page + 1,
|
||||
page + 2 > count ? page - 3 : page + 2,
|
||||
];
|
||||
|
||||
if (!allowedPages.includes(props.page)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<PaginationItem
|
||||
component="div"
|
||||
slots={{ previous: LeftArrowIcon, next: RightArrowIcon }}
|
||||
{...{ ...props, variant: undefined }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
marginTop: "30px",
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
disableUnderline
|
||||
placeholder="1"
|
||||
value={page}
|
||||
onChange={({ target }) =>
|
||||
setPage(Number(target.value.replace(/\D/, "")))
|
||||
}
|
||||
sx={{
|
||||
height: "30px",
|
||||
width: "65px",
|
||||
borderRadius: "5px",
|
||||
padding: "10px",
|
||||
marginRight: "5px",
|
||||
boxShadow: "0 0 20px rgba(0, 0, 0, 0.15)",
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
}}
|
||||
/>
|
||||
<MuiPagination
|
||||
page={page}
|
||||
count={count}
|
||||
siblingCount={isMobile ? 1 : 2}
|
||||
boundaryCount={isMobile ? 1 : 2}
|
||||
onChange={(_, page) => setPage(page)}
|
||||
renderItem={getPaginationItem}
|
||||
sx={{
|
||||
"& .MuiButtonBase-root": {
|
||||
borderRadius: "5px",
|
||||
margin: "0 5px",
|
||||
height: "30px",
|
||||
background: theme.palette.background.paper,
|
||||
boxShadow: "0 0 20px rgba(0, 0, 0, 0.15)",
|
||||
"&.Mui-selected": {
|
||||
background: theme.palette.background.paper,
|
||||
color: theme.palette.brightPurple.main,
|
||||
},
|
||||
"&.MuiPaginationItem-previousNext": {
|
||||
margin: "0 15px",
|
||||
background: theme.palette.brightPurple.main,
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const Answers = () => {
|
||||
const [answers, setAnswers] = useState<Record<string, number>>(ANSWERS_MOCK);
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<Paper
|
||||
sx={{
|
||||
borderRadius: "12px",
|
||||
boxShadow: "0 0 20px rgba(0, 0, 0, 0.15)",
|
||||
marginTop: "20px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
gap: "10px",
|
||||
alignItems: "center",
|
||||
padding: "25px",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
component="h3"
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
position: "relative",
|
||||
fontSize: "18px",
|
||||
fontWeight: "bold",
|
||||
paddingLeft: "40px",
|
||||
color: theme.palette.text.primary,
|
||||
"&::before": {
|
||||
content: "'1'",
|
||||
position: "absolute",
|
||||
top: "50%",
|
||||
left: "0",
|
||||
transform: "translateY(-50%)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: "30px",
|
||||
height: "30px",
|
||||
borderRadius: "50%",
|
||||
fontSize: "14px",
|
||||
fontWeight: "normal",
|
||||
background: "#EEE4FC",
|
||||
color: theme.palette.brightPurple.main,
|
||||
},
|
||||
}}
|
||||
>
|
||||
Заголовок вопроса. Варианты ответов
|
||||
</Typography>
|
||||
<ButtonBase>
|
||||
<DoubleCheckIcon />
|
||||
</ButtonBase>
|
||||
<ButtonBase>
|
||||
<NextIcon />
|
||||
</ButtonBase>
|
||||
</Box>
|
||||
{Object.entries(answers).map(([title, percent], index) => (
|
||||
<Answer
|
||||
key={title}
|
||||
title={title}
|
||||
percent={percent}
|
||||
highlight={!index}
|
||||
/>
|
||||
))}
|
||||
</Paper>
|
||||
<Pagination />
|
||||
</Box>
|
||||
);
|
||||
};
|
140
src/pages/Analytics/Answers/Funnel.tsx
Normal file
140
src/pages/Analytics/Answers/Funnel.tsx
Normal file
@ -0,0 +1,140 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
Paper,
|
||||
Typography,
|
||||
LinearProgress,
|
||||
useTheme,
|
||||
useMediaQuery,
|
||||
} from "@mui/material";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
|
||||
type FunnelItemProps = {
|
||||
title: string;
|
||||
percent: number;
|
||||
};
|
||||
|
||||
const FUNNEL_MOCK: Record<string, number> = {
|
||||
"Стартовая страница": 100,
|
||||
"Воронка квиза": 69,
|
||||
Заявки: 56,
|
||||
Результаты: 56,
|
||||
};
|
||||
|
||||
const FunnelItem = ({ title, percent }: FunnelItemProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
padding: "15px 25px",
|
||||
"&:last-child div::after": { display: "none" },
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ marginBottom: "10px", fontWeight: "bold" }}>
|
||||
{title}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
gap: "15px",
|
||||
"&::after": {
|
||||
content: "''",
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
bottom: "-15px",
|
||||
height: "1px",
|
||||
width: "100%",
|
||||
maxWidth: "300px",
|
||||
background: "#9A9AAF80",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: "30px",
|
||||
height: "30px",
|
||||
border: "1px solid black",
|
||||
borderRadius: "8px",
|
||||
fontSize: "14px",
|
||||
}}
|
||||
>
|
||||
1
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", alignItems: "center", flexGrow: 1 }}>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={percent}
|
||||
sx={{
|
||||
width: "100%",
|
||||
marginRight: "15px",
|
||||
|
||||
height: "12px",
|
||||
background: theme.palette.background.default,
|
||||
borderRadius: "6px",
|
||||
border:
|
||||
percent === 100
|
||||
? `1px solid ${theme.palette.brightPurple.main}`
|
||||
: null,
|
||||
"& > span": { background: "#D9C0F9" },
|
||||
}}
|
||||
/>
|
||||
<Box sx={{ minWidth: 35 }}>
|
||||
<Typography
|
||||
sx={{
|
||||
minWidth: "45px",
|
||||
color:
|
||||
percent === 100
|
||||
? theme.palette.text.secondary
|
||||
: theme.palette.text.primary,
|
||||
}}
|
||||
>{`${percent}%`}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const Funnel = () => {
|
||||
const [funnel, setFunnel] = useState<Record<string, number>>(FUNNEL_MOCK);
|
||||
const theme = useTheme();
|
||||
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1150));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(850));
|
||||
|
||||
useEffect(() => {
|
||||
// const requestFunnel = async () => {
|
||||
// const [funnelResponse, funnelError] = await getGeneral("14761");
|
||||
// if (funnelError) {
|
||||
// enqueueSnackbar(funnelError);
|
||||
// return;
|
||||
// }
|
||||
// if (!funnelResponse) {
|
||||
// enqueueSnackbar("Воронка пуста.");
|
||||
// return;
|
||||
// }
|
||||
// setFunnel(funnelResponse);
|
||||
// };
|
||||
// requestFunnel();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Paper
|
||||
sx={{
|
||||
borderRadius: "12px",
|
||||
boxShadow: "0 0 20px rgba(0, 0, 0, 0.15)",
|
||||
marginTop: isSmallMonitor && !isMobile ? 0 : "60px",
|
||||
width: "100%",
|
||||
maxWidth: isSmallMonitor && !isMobile ? "366px" : "none",
|
||||
}}
|
||||
>
|
||||
{Object.entries(funnel).map(([title, percent]) => (
|
||||
<FunnelItem key={title} title={title} percent={percent} />
|
||||
))}
|
||||
</Paper>
|
||||
);
|
||||
};
|
107
src/pages/Analytics/Answers/Results.tsx
Normal file
107
src/pages/Analytics/Answers/Results.tsx
Normal file
@ -0,0 +1,107 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Box,
|
||||
Paper,
|
||||
Typography,
|
||||
LinearProgress,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
|
||||
type ResultProps = {
|
||||
title: string;
|
||||
percent: number;
|
||||
highlight?: boolean;
|
||||
};
|
||||
|
||||
const RESULTS_MOCK: Record<string, number> = {
|
||||
"Заголовок результата": 100,
|
||||
"Результат пропущен": 7,
|
||||
Другое: 27,
|
||||
};
|
||||
|
||||
const Result = ({ title, percent, highlight }: ResultProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box sx={{ padding: "15px 25px" }}>
|
||||
<Box
|
||||
sx={{
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
gap: "15px",
|
||||
alignItems: "center",
|
||||
flexGrow: 1,
|
||||
}}
|
||||
>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
title={title}
|
||||
value={percent}
|
||||
sx={{
|
||||
width: "100%",
|
||||
height: "44px",
|
||||
background: theme.palette.background.default,
|
||||
borderRadius: "10px",
|
||||
border: `1px solid ${highlight ? "#FC7230" : theme.palette.grey2.main}`,
|
||||
"& > span": { background: highlight ? "#FDE9E0" : "#9A9AAF1A" },
|
||||
"&::before": {
|
||||
content: `"${title}"`,
|
||||
position: "absolute",
|
||||
zIndex: 1,
|
||||
left: "20px",
|
||||
top: "50%",
|
||||
transform: "translateY(-50%)",
|
||||
color: highlight ? "#FC7230" : theme.palette.grey3.main,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Box sx={{ minWidth: 35 }}>
|
||||
<Typography
|
||||
sx={{
|
||||
minWidth: "45px",
|
||||
fontWeight: highlight ? "bold" : "normal",
|
||||
color: highlight ? "#FC7230" : theme.palette.text.primary,
|
||||
}}
|
||||
>{`${percent}%`}</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const Results = () => {
|
||||
const [results, setResults] = useState<Record<string, number>>(RESULTS_MOCK);
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography
|
||||
component="h3"
|
||||
sx={{
|
||||
marginTop: "100px",
|
||||
fontSize: "24px",
|
||||
fontWeight: "bold",
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
>
|
||||
Статистика по результатам
|
||||
</Typography>
|
||||
<Paper
|
||||
sx={{
|
||||
borderRadius: "12px",
|
||||
boxShadow: "0 0 20px rgba(0, 0, 0, 0.15)",
|
||||
marginTop: "30px",
|
||||
}}
|
||||
>
|
||||
{Object.entries(results).map(([title, percent], index) => (
|
||||
<Result
|
||||
key={title}
|
||||
title={title}
|
||||
percent={percent}
|
||||
highlight={!index}
|
||||
/>
|
||||
))}
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
};
|
66
src/pages/Analytics/Answers/index.tsx
Normal file
66
src/pages/Analytics/Answers/index.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
import {
|
||||
Box,
|
||||
ButtonBase,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
useTheme,
|
||||
} from "@mui/material";
|
||||
|
||||
import { Answers } from "./Answers";
|
||||
import { Funnel } from "./Funnel";
|
||||
import { Results } from "./Results";
|
||||
|
||||
import { ReactComponent as OpenIcon } from "@icons/Analytics/open.svg";
|
||||
|
||||
export const AnswersStatistics = () => {
|
||||
const theme = useTheme();
|
||||
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1150));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(850));
|
||||
|
||||
return (
|
||||
<Box sx={{ marginTop: "120px" }}>
|
||||
<Typography
|
||||
component="h3"
|
||||
sx={{
|
||||
fontSize: "24px",
|
||||
fontWeight: "bold",
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
>
|
||||
Статистика по ответам
|
||||
</Typography>
|
||||
<ButtonBase
|
||||
sx={{
|
||||
marginTop: "35px",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: "5px",
|
||||
padding: "10px",
|
||||
width: "100%",
|
||||
maxWidth: "160px",
|
||||
background: "#FFFFFF",
|
||||
border: "1px solid #9A9AAF",
|
||||
borderRadius: "8px",
|
||||
color: theme.palette.brightPurple.main,
|
||||
boxShadow: "0 0 20px rgba(0, 0, 0, 0.15)",
|
||||
}}
|
||||
>
|
||||
<Typography>Фильтры</Typography>
|
||||
<Box>
|
||||
<OpenIcon />
|
||||
</Box>
|
||||
</ButtonBase>
|
||||
<Box
|
||||
sx={{
|
||||
display: isSmallMonitor && !isMobile ? "flex" : "block",
|
||||
gap: "40px",
|
||||
}}
|
||||
>
|
||||
<Answers />
|
||||
<Funnel />
|
||||
</Box>
|
||||
<Results />
|
||||
</Box>
|
||||
);
|
||||
};
|
156
src/pages/Analytics/Devices.tsx
Normal file
156
src/pages/Analytics/Devices.tsx
Normal file
@ -0,0 +1,156 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Box, Paper, Typography, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { PieChart } from "@mui/x-charts";
|
||||
import { enqueueSnackbar } from "notistack";
|
||||
|
||||
import { getDevices } from "@api/statistic";
|
||||
|
||||
import type { DevicesResponse } from "@api/statistic";
|
||||
|
||||
type DeviceProps = {
|
||||
title: string;
|
||||
devices: Record<string, number>;
|
||||
};
|
||||
|
||||
const COLORS: Record<number, string> = {
|
||||
0: "#7E2AEA",
|
||||
1: "#FA7738",
|
||||
2: "#62BB1C",
|
||||
3: "#0886FB",
|
||||
};
|
||||
|
||||
const DEVICES_MOCK: DevicesResponse = {
|
||||
device: { PC: 75, Mobile: 25 },
|
||||
os: { Windows: 44, AndroidOS: 25, "OS X": 19, Linux: 13 },
|
||||
browser: { Chrome: 75, Firefox: 25 },
|
||||
};
|
||||
|
||||
const Device = ({ title, devices }: DeviceProps) => {
|
||||
const theme = useTheme();
|
||||
const data = Object.entries(devices).map(([id, value], index) => ({
|
||||
id,
|
||||
value,
|
||||
color: COLORS[index],
|
||||
}));
|
||||
|
||||
return (
|
||||
<Paper
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
minHeight: "500px",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
gap: "30px",
|
||||
borderRadius: "12px",
|
||||
boxShadow: "0 0 20px rgba(0, 0, 0, 0.15)",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ margin: "20px" }}>{title}</Typography>
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<PieChart
|
||||
height={245}
|
||||
width={245}
|
||||
margin={{ right: 0 }}
|
||||
series={[{ data, innerRadius: 50 }]}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{ background: theme.palette.background.default, padding: "20px" }}
|
||||
>
|
||||
{data.map(({ id, value, color }) => (
|
||||
<Box
|
||||
key={id}
|
||||
sx={{
|
||||
display: "flex",
|
||||
marginBottom: "10px",
|
||||
"&:last-child": { margin: 0 },
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
flexGrow: 1,
|
||||
position: "relative",
|
||||
paddingLeft: "30px",
|
||||
"&::before": {
|
||||
content: "''",
|
||||
display: "block",
|
||||
position: "absolute",
|
||||
left: "0",
|
||||
background: color,
|
||||
height: "20px",
|
||||
width: "20px",
|
||||
borderRadius: "6px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{id}
|
||||
</Typography>
|
||||
<Typography>{value} %</Typography>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export const Devices = () => {
|
||||
const [devices, setDevices] = useState<DevicesResponse>(DEVICES_MOCK);
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(700));
|
||||
|
||||
useEffect(() => {
|
||||
const requestDevices = async () => {
|
||||
const [devicesResponse, devicesError] = await getDevices("14761");
|
||||
|
||||
if (devicesError) {
|
||||
enqueueSnackbar(devicesError);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!devicesResponse) {
|
||||
enqueueSnackbar("Список девайсов пуст.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setDevices(devicesResponse);
|
||||
};
|
||||
|
||||
// requestDevices();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box sx={{ marginTop: "120px" }}>
|
||||
<Typography
|
||||
component="h3"
|
||||
sx={{
|
||||
fontSize: "24px",
|
||||
fontWeight: "bold",
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
>
|
||||
Статистика пользователей
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: isTablet
|
||||
? isMobile
|
||||
? "1fr"
|
||||
: "1fr 1fr"
|
||||
: "1fr 1fr 1fr",
|
||||
gap: "20px",
|
||||
marginTop: "30px",
|
||||
}}
|
||||
>
|
||||
<Device title="Устройства" devices={devices.device} />
|
||||
<Device title="Операционные системы" devices={devices.os} />
|
||||
<Device title="Браузеры" devices={devices.browser} />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
148
src/pages/Analytics/General.tsx
Normal file
148
src/pages/Analytics/General.tsx
Normal file
@ -0,0 +1,148 @@
|
||||
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";
|
||||
|
||||
type GeneralProps = {
|
||||
title: string;
|
||||
general: Record<string, number>;
|
||||
color: string;
|
||||
numberType: "sum" | "percent";
|
||||
};
|
||||
|
||||
const COLORS: Record<number, string> = {
|
||||
0: "#61BB1A",
|
||||
1: "#7E2AEA",
|
||||
2: "#FB5607",
|
||||
3: "#0886FB",
|
||||
};
|
||||
|
||||
const GENERAL_MOCK: GeneralResponse = {
|
||||
open: { 100: 20, 50: 10, 60: 5 },
|
||||
result: { 100: 90, 10: 3, 50: 48 },
|
||||
avtime: { 100: 0, 2000: 550, 60: 0 },
|
||||
conversation: { 100: 50, 1000: 50, 10000: 50 },
|
||||
};
|
||||
|
||||
const GeneralItem = ({ title, general, color, numberType }: GeneralProps) => {
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(700));
|
||||
|
||||
const numberValue =
|
||||
numberType === "sum"
|
||||
? Object.values(general).reduce((total, item) => total + item, 0)
|
||||
: Object.entries(general).reduce(
|
||||
(total, [key, value]) => total + (value / Number(key)) * 100,
|
||||
0,
|
||||
) / Object.keys(general).length;
|
||||
|
||||
return (
|
||||
<Paper
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
borderRadius: "12px",
|
||||
boxShadow: "0 0 20px rgba(0, 0, 0, 0.15)",
|
||||
}}
|
||||
>
|
||||
<Typography sx={{ margin: "20px 20px 0" }}>{title}</Typography>
|
||||
<Typography sx={{ margin: "10px 20px 0", fontWeight: "bold" }}>
|
||||
{numberType === "sum" ? numberValue : `${numberValue.toFixed()}%`}
|
||||
</Typography>
|
||||
<LineChart
|
||||
xAxis={[{ data: Object.keys(general) }]}
|
||||
series={[{ data: Object.values(general) }]}
|
||||
height={220}
|
||||
colors={[color]}
|
||||
sx={{
|
||||
transform: isMobile ? "scale(1.1)" : "scale(1.2)",
|
||||
"& .MuiChartsAxis-tickContainer": { display: "none" },
|
||||
}}
|
||||
/>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
export const General = () => {
|
||||
const [general, setGeneral] = useState<GeneralResponse>(GENERAL_MOCK);
|
||||
const theme = useTheme();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down(700));
|
||||
|
||||
useEffect(() => {
|
||||
const requestGeneral = async () => {
|
||||
const [generalResponse, generalError] = await getGeneral("14761");
|
||||
|
||||
if (generalError) {
|
||||
enqueueSnackbar(generalError);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!generalResponse) {
|
||||
enqueueSnackbar("Список девайсов пуст.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setGeneral(generalResponse);
|
||||
};
|
||||
|
||||
// requestGeneral();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Box sx={{ marginTop: "45px" }}>
|
||||
<Typography
|
||||
component="h3"
|
||||
sx={{
|
||||
fontSize: "24px",
|
||||
fontWeight: "bold",
|
||||
color: theme.palette.text.primary,
|
||||
}}
|
||||
>
|
||||
Ключевые метрики
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: isTablet
|
||||
? isMobile
|
||||
? "1fr"
|
||||
: "1fr 1fr"
|
||||
: "1fr 1fr 1fr",
|
||||
gap: "20px",
|
||||
marginTop: "40px",
|
||||
}}
|
||||
>
|
||||
<GeneralItem
|
||||
title="Открыли квиз"
|
||||
numberType="sum"
|
||||
general={general.open}
|
||||
color={COLORS[0]}
|
||||
/>
|
||||
<GeneralItem
|
||||
title="Получено заявок"
|
||||
numberType="sum"
|
||||
general={general.result}
|
||||
color={COLORS[1]}
|
||||
/>
|
||||
<GeneralItem
|
||||
title="Конверсия"
|
||||
numberType="percent"
|
||||
general={general.conversation}
|
||||
color={COLORS[2]}
|
||||
/>
|
||||
<GeneralItem
|
||||
title="Среднее время прохождения квиза"
|
||||
numberType="percent"
|
||||
general={general.avtime}
|
||||
color={COLORS[3]}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
@ -206,7 +206,7 @@ function TariffPage() {
|
||||
<ArrowLeft color="black" />
|
||||
</IconButton>
|
||||
<Box sx={{ display: "flex", ml: "auto" }}>
|
||||
<Box sx={{ whiteSpace: "nowrap" }} onClick={() => console.log(cash)}>
|
||||
<Box sx={{ whiteSpace: "nowrap" }}>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: "12px",
|
||||
|
@ -23,12 +23,12 @@ import { ToTariffsButton } from "@ui_kit/Toolbars/ToTariffsButton";
|
||||
import ArrowLeft from "@icons/questionsPage/arrowLeft";
|
||||
import { cleanAuthTicketData } from "@root/ticket";
|
||||
|
||||
interface Props {
|
||||
interface HeaderFullProps {
|
||||
isRequest: boolean;
|
||||
sx?: SxProps<Theme>;
|
||||
}
|
||||
|
||||
export default function HeaderFull({ isRequest, sx }: Props) {
|
||||
export default function HeaderFull({ isRequest = false, sx }: HeaderFullProps) {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
|
||||
|
275
yarn.lock
275
yarn.lock
@ -1112,13 +1112,20 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
|
||||
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
|
||||
|
||||
"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.2", "@babel/runtime@^7.23.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
|
||||
"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
version "7.23.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7"
|
||||
integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/runtime@^7.20.1", "@babel/runtime@^7.23.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
|
||||
version "7.24.1"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.1.tgz#431f9a794d173b53720e69a6464abc6f0e2a5c57"
|
||||
integrity sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.14.0"
|
||||
|
||||
"@babel/template@^7.22.15", "@babel/template@^7.23.9", "@babel/template@^7.3.3":
|
||||
version "7.23.9"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.23.9.tgz#f881d0487cba2828d3259dcb9ef5005a9731011a"
|
||||
@ -1369,6 +1376,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43"
|
||||
integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ==
|
||||
|
||||
"@emotion/is-prop-valid@^1.2.0":
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz#d4175076679c6a26faa92b03bb786f9e52612337"
|
||||
integrity sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==
|
||||
dependencies:
|
||||
"@emotion/memoize" "^0.8.1"
|
||||
|
||||
"@emotion/is-prop-valid@^1.2.1":
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz#23116cf1ed18bfeac910ec6436561ecb1a3885cc"
|
||||
@ -1854,7 +1868,21 @@
|
||||
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b"
|
||||
integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==
|
||||
|
||||
"@mui/base@5.0.0-beta.36", "@mui/base@^5.0.0-beta.22":
|
||||
"@mui/base@5.0.0-alpha.106":
|
||||
version "5.0.0-alpha.106"
|
||||
resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-alpha.106.tgz#23e5f61639b5786318be873d7e6d26db412c5211"
|
||||
integrity sha512-xJQQtwPCPwr6hGWTBdvDwHYwExn3Bw7nPQkN8Fuz8kHpZqoMVWQvvaFS557AIkkI2AFLV3DxVIMjbCvrIntBWg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.20.1"
|
||||
"@emotion/is-prop-valid" "^1.2.0"
|
||||
"@mui/types" "^7.2.1"
|
||||
"@mui/utils" "^5.10.14"
|
||||
"@popperjs/core" "^2.11.6"
|
||||
clsx "^1.2.1"
|
||||
prop-types "^15.8.1"
|
||||
react-is "^18.2.0"
|
||||
|
||||
"@mui/base@^5.0.0-beta.22":
|
||||
version "5.0.0-beta.36"
|
||||
resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.36.tgz#29ca2de9d387f6d3943b6f18a84415c43e5f206c"
|
||||
integrity sha512-6A8fYiXgjqTO6pgj31Hc8wm1M3rFYCxDRh09dBVk0L0W4cb2lnurRJa3cAyic6hHY+we1S58OdGYRbKmOsDpGQ==
|
||||
@ -1867,10 +1895,10 @@
|
||||
clsx "^2.1.0"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
"@mui/core-downloads-tracker@^5.15.10":
|
||||
version "5.15.10"
|
||||
resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.10.tgz#616bfb54e3860268d56ff59cd187d47044d954f3"
|
||||
integrity sha512-qPv7B+LeMatYuzRjB3hlZUHqinHx/fX4YFBiaS19oC02A1e9JFuDKDvlyRQQ5oRSbJJt0QlaLTlr0IcauVcJRQ==
|
||||
"@mui/core-downloads-tracker@^5.10.14":
|
||||
version "5.15.14"
|
||||
resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.15.14.tgz#f7c57b261904831877220182303761c012d05046"
|
||||
integrity sha512-on75VMd0XqZfaQW+9pGjSNiqW+ghc5E2ZSLRBXwcXl/C4YzjfyjrLPhrEpKnR9Uym9KXBvxrhoHfPcczYHweyA==
|
||||
|
||||
"@mui/icons-material@^5.10.14":
|
||||
version "5.15.10"
|
||||
@ -1879,63 +1907,73 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.23.9"
|
||||
|
||||
"@mui/material@^5.10.14":
|
||||
version "5.15.10"
|
||||
resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.15.10.tgz#6533ba53edbd0790dbc5bb7e9e173f6069ffd7e6"
|
||||
integrity sha512-YJJGHjwDOucecjDEV5l9ISTCo+l9YeWrho623UajzoHRYxuKUmwrGVYOW4PKwGvCx9SU9oklZnbbi2Clc5XZHw==
|
||||
"@mui/material@5.10.14":
|
||||
version "5.10.14"
|
||||
resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.10.14.tgz#fcd687c92b22a71dc139376a5bc2d0bc578a172b"
|
||||
integrity sha512-HWzKVAykePMx54WtxVwZyL1W4k3xlHYIqwMw0CaXAvgB3UE9yjABZuuGr8vG5Z6CSNWamzd+s1x8u7pQPFl9og==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.23.9"
|
||||
"@mui/base" "5.0.0-beta.36"
|
||||
"@mui/core-downloads-tracker" "^5.15.10"
|
||||
"@mui/system" "^5.15.9"
|
||||
"@mui/types" "^7.2.13"
|
||||
"@mui/utils" "^5.15.9"
|
||||
"@types/react-transition-group" "^4.4.10"
|
||||
clsx "^2.1.0"
|
||||
csstype "^3.1.3"
|
||||
"@babel/runtime" "^7.20.1"
|
||||
"@mui/base" "5.0.0-alpha.106"
|
||||
"@mui/core-downloads-tracker" "^5.10.14"
|
||||
"@mui/system" "^5.10.14"
|
||||
"@mui/types" "^7.2.1"
|
||||
"@mui/utils" "^5.10.14"
|
||||
"@types/react-transition-group" "^4.4.5"
|
||||
clsx "^1.2.1"
|
||||
csstype "^3.1.1"
|
||||
prop-types "^15.8.1"
|
||||
react-is "^18.2.0"
|
||||
react-transition-group "^4.4.5"
|
||||
|
||||
"@mui/private-theming@^5.15.9":
|
||||
version "5.15.9"
|
||||
resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.15.9.tgz#3ea3514ed2f6bf68541dbe9206665a82cd89cb01"
|
||||
integrity sha512-/aMJlDOxOTAXyp4F2rIukW1O0anodAMCkv1DfBh/z9vaKHY3bd5fFf42wmP+0GRmwMinC5aWPpNfHXOED1fEtg==
|
||||
"@mui/private-theming@^5.15.14":
|
||||
version "5.15.14"
|
||||
resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.15.14.tgz#edd9a82948ed01586a01c842eb89f0e3f68970ee"
|
||||
integrity sha512-UH0EiZckOWcxiXLX3Jbb0K7rC8mxTr9L9l6QhOZxYc4r8FHUkefltV9VDGLrzCaWh30SQiJvAEd7djX3XXY6Xw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.23.9"
|
||||
"@mui/utils" "^5.15.9"
|
||||
"@mui/utils" "^5.15.14"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
"@mui/styled-engine@^5.15.9":
|
||||
version "5.15.9"
|
||||
resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.15.9.tgz#444605039ec3fe456bdd5d5cb94330183be62b91"
|
||||
integrity sha512-NRKtYkL5PZDH7dEmaLEIiipd3mxNnQSO+Yo8rFNBNptY8wzQnQ+VjayTq39qH7Sast5cwHKYFusUrQyD+SS4Og==
|
||||
"@mui/styled-engine@^5.15.14":
|
||||
version "5.15.14"
|
||||
resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.15.14.tgz#168b154c4327fa4ccc1933a498331d53f61c0de2"
|
||||
integrity sha512-RILkuVD8gY6PvjZjqnWhz8fu68dVkqhM5+jYWfB5yhlSQKg+2rHkmEwm75XIeAqI3qwOndK6zELK5H6Zxn4NHw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.23.9"
|
||||
"@emotion/cache" "^11.11.0"
|
||||
csstype "^3.1.3"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
"@mui/system@^5.15.9":
|
||||
version "5.15.9"
|
||||
resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.15.9.tgz#8a34ac0ab133af2550cc7ab980a35174142fd265"
|
||||
integrity sha512-SxkaaZ8jsnIJ77bBXttfG//LUf6nTfOcaOuIgItqfHv60ZCQy/Hu7moaob35kBb+guxVJnoSZ+7vQJrA/E7pKg==
|
||||
"@mui/system@^5.10.14":
|
||||
version "5.15.14"
|
||||
resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.15.14.tgz#8a0c6571077eeb6b5f1ff7aa7ff6a3dc4a14200d"
|
||||
integrity sha512-auXLXzUaCSSOLqJXmsAaq7P96VPRXg2Rrz6OHNV7lr+kB8lobUF+/N84Vd9C4G/wvCXYPs5TYuuGBRhcGbiBGg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.23.9"
|
||||
"@mui/private-theming" "^5.15.9"
|
||||
"@mui/styled-engine" "^5.15.9"
|
||||
"@mui/types" "^7.2.13"
|
||||
"@mui/utils" "^5.15.9"
|
||||
"@mui/private-theming" "^5.15.14"
|
||||
"@mui/styled-engine" "^5.15.14"
|
||||
"@mui/types" "^7.2.14"
|
||||
"@mui/utils" "^5.15.14"
|
||||
clsx "^2.1.0"
|
||||
csstype "^3.1.3"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
"@mui/types@^7.2.13":
|
||||
version "7.2.13"
|
||||
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.13.tgz#d1584912942f9dc042441ecc2d1452be39c666b8"
|
||||
integrity sha512-qP9OgacN62s+l8rdDhSFRe05HWtLLJ5TGclC9I1+tQngbssu0m2dmFZs+Px53AcOs9fD7TbYd4gc9AXzVqO/+g==
|
||||
"@mui/types@^7.2.1", "@mui/types@^7.2.13", "@mui/types@^7.2.14":
|
||||
version "7.2.14"
|
||||
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.14.tgz#8a02ac129b70f3d82f2f9b76ded2c8d48e3fc8c9"
|
||||
integrity sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==
|
||||
|
||||
"@mui/utils@^5.14.16", "@mui/utils@^5.15.9":
|
||||
"@mui/utils@^5.10.14", "@mui/utils@^5.15.14", "@mui/utils@^5.15.9":
|
||||
version "5.15.14"
|
||||
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.15.14.tgz#e414d7efd5db00bfdc875273a40c0a89112ade3a"
|
||||
integrity sha512-0lF/7Hh/ezDv5X7Pry6enMsbYyGKjADzvHyo3Qrc/SSlTsQ1VkbDMbH0m2t3OR5iIVLwMoxwM7yGd+6FCMtTFA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.23.9"
|
||||
"@types/prop-types" "^15.7.11"
|
||||
prop-types "^15.8.1"
|
||||
react-is "^18.2.0"
|
||||
|
||||
"@mui/utils@^5.14.16":
|
||||
version "5.15.9"
|
||||
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.15.9.tgz#2bdf925e274d87cbe90c14eb52d0835318205e86"
|
||||
integrity sha512-yDYfr61bCYUz1QtwvpqYy/3687Z8/nS4zv7lv/ih/6ZFGMl1iolEvxRmR84v2lOYxlds+kq1IVYbXxDKh8Z9sg==
|
||||
@ -1945,6 +1983,21 @@
|
||||
prop-types "^15.8.1"
|
||||
react-is "^18.2.0"
|
||||
|
||||
"@mui/x-charts@^6.19.5":
|
||||
version "6.19.5"
|
||||
resolved "https://registry.yarnpkg.com/@mui/x-charts/-/x-charts-6.19.5.tgz#1c427961b87ba12ea3407ce1623e235a090828f4"
|
||||
integrity sha512-BBRGLup5gpaLkhECv+J2ahFbDDgqK4BgLyLXLHKUASoWSU3YRCyDt9ifBREspEPfTZXgrcqNkybAl5b+l6baFQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.23.2"
|
||||
"@mui/base" "^5.0.0-beta.22"
|
||||
"@react-spring/rafz" "^9.7.3"
|
||||
"@react-spring/web" "^9.7.3"
|
||||
clsx "^2.0.0"
|
||||
d3-color "^3.1.0"
|
||||
d3-scale "^4.0.2"
|
||||
d3-shape "^3.2.0"
|
||||
prop-types "^15.8.1"
|
||||
|
||||
"@mui/x-date-pickers@^6.16.1":
|
||||
version "6.19.4"
|
||||
resolved "https://registry.yarnpkg.com/@mui/x-date-pickers/-/x-date-pickers-6.19.4.tgz#7df5ea081bdf9e2ce75c346bd95af340b1b56e9f"
|
||||
@ -2006,7 +2059,7 @@
|
||||
schema-utils "^3.0.0"
|
||||
source-map "^0.7.3"
|
||||
|
||||
"@popperjs/core@^2.0.0", "@popperjs/core@^2.11.8":
|
||||
"@popperjs/core@^2.0.0", "@popperjs/core@^2.11.6", "@popperjs/core@^2.11.8":
|
||||
version "2.11.8"
|
||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
|
||||
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
|
||||
@ -2026,6 +2079,50 @@
|
||||
resolved "https://registry.yarnpkg.com/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz#d1b4befa423f692fa4abf1c79209702e7d8ae4b4"
|
||||
integrity sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==
|
||||
|
||||
"@react-spring/animated@~9.7.3":
|
||||
version "9.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/animated/-/animated-9.7.3.tgz#4211b1a6d48da0ff474a125e93c0f460ff816e0f"
|
||||
integrity sha512-5CWeNJt9pNgyvuSzQH+uy2pvTg8Y4/OisoscZIR8/ZNLIOI+CatFBhGZpDGTF/OzdNFsAoGk3wiUYTwoJ0YIvw==
|
||||
dependencies:
|
||||
"@react-spring/shared" "~9.7.3"
|
||||
"@react-spring/types" "~9.7.3"
|
||||
|
||||
"@react-spring/core@~9.7.3":
|
||||
version "9.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/core/-/core-9.7.3.tgz#60056bcb397f2c4f371c6c9a5f882db77ae90095"
|
||||
integrity sha512-IqFdPVf3ZOC1Cx7+M0cXf4odNLxDC+n7IN3MDcVCTIOSBfqEcBebSv+vlY5AhM0zw05PDbjKrNmBpzv/AqpjnQ==
|
||||
dependencies:
|
||||
"@react-spring/animated" "~9.7.3"
|
||||
"@react-spring/shared" "~9.7.3"
|
||||
"@react-spring/types" "~9.7.3"
|
||||
|
||||
"@react-spring/rafz@^9.7.3":
|
||||
version "9.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/rafz/-/rafz-9.7.3.tgz#d6a9695c581f58a49e047ff7ed5646e0b681396c"
|
||||
integrity sha512-9vzW1zJPcC4nS3aCV+GgcsK/WLaB520Iyvm55ARHfM5AuyBqycjvh1wbmWmgCyJuX4VPoWigzemq1CaaeRSHhQ==
|
||||
|
||||
"@react-spring/shared@~9.7.3":
|
||||
version "9.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/shared/-/shared-9.7.3.tgz#4cf29797847c689912aec4e62e34c99a4d5d9e53"
|
||||
integrity sha512-NEopD+9S5xYyQ0pGtioacLhL2luflh6HACSSDUZOwLHoxA5eku1UPuqcJqjwSD6luKjjLfiLOspxo43FUHKKSA==
|
||||
dependencies:
|
||||
"@react-spring/types" "~9.7.3"
|
||||
|
||||
"@react-spring/types@~9.7.3":
|
||||
version "9.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/types/-/types-9.7.3.tgz#ea78fd447cbc2612c1f5d55852e3c331e8172a0b"
|
||||
integrity sha512-Kpx/fQ/ZFX31OtlqVEFfgaD1ACzul4NksrvIgYfIFq9JpDHFwQkMVZ10tbo0FU/grje4rcL4EIrjekl3kYwgWw==
|
||||
|
||||
"@react-spring/web@^9.7.3":
|
||||
version "9.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@react-spring/web/-/web-9.7.3.tgz#d9f4e17fec259f1d65495a19502ada4f5b57fa3d"
|
||||
integrity sha512-BXt6BpS9aJL/QdVqEIX9YoUy8CE6TJrU0mNCqSoxdXlIeNcEBWOfIyE6B14ENNsyQKS3wOWkiJfco0tCr/9tUg==
|
||||
dependencies:
|
||||
"@react-spring/animated" "~9.7.3"
|
||||
"@react-spring/core" "~9.7.3"
|
||||
"@react-spring/shared" "~9.7.3"
|
||||
"@react-spring/types" "~9.7.3"
|
||||
|
||||
"@remix-run/router@1.15.0":
|
||||
version "1.15.0"
|
||||
resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.15.0.tgz#461a952c2872dd82c8b2e9b74c4dfaff569123e2"
|
||||
@ -2534,9 +2631,9 @@
|
||||
integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==
|
||||
|
||||
"@types/prop-types@*", "@types/prop-types@^15.7.11":
|
||||
version "15.7.11"
|
||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563"
|
||||
integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==
|
||||
version "15.7.12"
|
||||
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6"
|
||||
integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==
|
||||
|
||||
"@types/q@^1.5.1":
|
||||
version "1.5.8"
|
||||
@ -2599,14 +2696,23 @@
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-transition-group@^4.4.10", "@types/react-transition-group@^4.4.8":
|
||||
"@types/react-transition-group@^4.4.5", "@types/react-transition-group@^4.4.8":
|
||||
version "4.4.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.10.tgz#6ee71127bdab1f18f11ad8fb3322c6da27c327ac"
|
||||
integrity sha512-hT/+s0VQs2ojCX823m60m5f0sL5idt9SO6Tj6Dg+rdphGPIeJbJ6CxvBYkgkGKrYeDjvIpKTR38UzmtHJOGW3Q==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*", "@types/react@^18.2.55":
|
||||
"@types/react@*":
|
||||
version "18.2.70"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.70.tgz#89a37f9e0a6a4931f4259c598f40fd44dd6abf71"
|
||||
integrity sha512-hjlM2hho2vqklPhopNkXkdkeq6Lv8WSZTpr7956zY+3WS5cfYUewtCzsJLsbW5dEv3lfSeQ4W14ZFeKC437JRQ==
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
"@types/scheduler" "*"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@types/react@^18.2.55":
|
||||
version "18.2.55"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.55.tgz#38141821b7084404b5013742bc4ae08e44da7a67"
|
||||
integrity sha512-Y2Tz5P4yz23brwm2d7jNon39qoAtMMmalOQv6+fEFt1mT+FcM3D841wDpoUvFXhaYenuROCy3FZYqdTjM7qVyA==
|
||||
@ -3914,7 +4020,7 @@ clone-deep@^4.0.1:
|
||||
kind-of "^6.0.2"
|
||||
shallow-clone "^3.0.0"
|
||||
|
||||
clsx@^1.1.0, clsx@^1.1.1:
|
||||
clsx@^1.1.0, clsx@^1.1.1, clsx@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12"
|
||||
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
|
||||
@ -4379,7 +4485,7 @@ cssstyle@^2.3.0:
|
||||
dependencies:
|
||||
cssom "~0.3.6"
|
||||
|
||||
csstype@^3.0.2, csstype@^3.1.3:
|
||||
csstype@^3.0.2, csstype@^3.1.1, csstype@^3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81"
|
||||
integrity sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==
|
||||
@ -4452,6 +4558,67 @@ cytoscape@^3.26.0:
|
||||
heap "^0.2.6"
|
||||
lodash "^4.17.21"
|
||||
|
||||
"d3-array@2 - 3", "d3-array@2.10.0 - 3":
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-3.2.4.tgz#15fec33b237f97ac5d7c986dc77da273a8ed0bb5"
|
||||
integrity sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==
|
||||
dependencies:
|
||||
internmap "1 - 2"
|
||||
|
||||
"d3-color@1 - 3", d3-color@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-3.1.0.tgz#395b2833dfac71507f12ac2f7af23bf819de24e2"
|
||||
integrity sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==
|
||||
|
||||
"d3-format@1 - 3":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-3.1.0.tgz#9260e23a28ea5cb109e93b21a06e24e2ebd55641"
|
||||
integrity sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==
|
||||
|
||||
"d3-interpolate@1.2.0 - 3":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-3.0.1.tgz#3c47aa5b32c5b3dfb56ef3fd4342078a632b400d"
|
||||
integrity sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==
|
||||
dependencies:
|
||||
d3-color "1 - 3"
|
||||
|
||||
d3-path@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-3.1.0.tgz#22df939032fb5a71ae8b1800d61ddb7851c42526"
|
||||
integrity sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==
|
||||
|
||||
d3-scale@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-4.0.2.tgz#82b38e8e8ff7080764f8dcec77bd4be393689396"
|
||||
integrity sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==
|
||||
dependencies:
|
||||
d3-array "2.10.0 - 3"
|
||||
d3-format "1 - 3"
|
||||
d3-interpolate "1.2.0 - 3"
|
||||
d3-time "2.1.1 - 3"
|
||||
d3-time-format "2 - 4"
|
||||
|
||||
d3-shape@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-3.2.0.tgz#a1a839cbd9ba45f28674c69d7f855bcf91dfc6a5"
|
||||
integrity sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==
|
||||
dependencies:
|
||||
d3-path "^3.1.0"
|
||||
|
||||
"d3-time-format@2 - 4":
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-4.1.0.tgz#7ab5257a5041d11ecb4fe70a5c7d16a195bb408a"
|
||||
integrity sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==
|
||||
dependencies:
|
||||
d3-time "1 - 3"
|
||||
|
||||
"d3-time@1 - 3", "d3-time@2.1.1 - 3":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-3.1.0.tgz#9310db56e992e3c0175e1ef385e545e48a9bb5c7"
|
||||
integrity sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==
|
||||
dependencies:
|
||||
d3-array "2 - 3"
|
||||
|
||||
damerau-levenshtein@^1.0.8:
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
|
||||
@ -6408,6 +6575,11 @@ internal-slot@^1.0.4, internal-slot@^1.0.5, internal-slot@^1.0.7:
|
||||
hasown "^2.0.0"
|
||||
side-channel "^1.0.4"
|
||||
|
||||
"internmap@1 - 2":
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009"
|
||||
integrity sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==
|
||||
|
||||
ipaddr.js@1.9.1:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
|
||||
@ -9400,6 +9572,11 @@ react-is@^18.0.0, react-is@^18.2.0:
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
|
||||
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
|
||||
|
||||
react-lazily@^0.9.2:
|
||||
version "0.9.2"
|
||||
resolved "https://registry.yarnpkg.com/react-lazily/-/react-lazily-0.9.2.tgz#74596dbde43c8e0f607445da5c4839cf6cc48ab5"
|
||||
integrity sha512-oBVRDQ+SuMPWenBO/0Kq+iZk34lOYJEmjiTto4bYRufndf8pux3E50BT3mJZbsq0vBsAVbX3fpQjlUvsXgDVag==
|
||||
|
||||
react-redux@^7.2.0:
|
||||
version "7.2.9"
|
||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.9.tgz#09488fbb9416a4efe3735b7235055442b042481d"
|
||||
|
Loading…
Reference in New Issue
Block a user