Merge branch 'dev' into 'staging'
realized videofile component, added logic to delete uploaded video, refactored... See merge request frontend/squiz!224
This commit is contained in:
commit
18272be5ad
@ -25,13 +25,15 @@ export type QuestionsResponse = {
|
|||||||
|
|
||||||
export const getDevices = async (
|
export const getDevices = async (
|
||||||
quizId: string,
|
quizId: string,
|
||||||
|
to: number,
|
||||||
|
from: number,
|
||||||
): Promise<[DevicesResponse | null, string?]> => {
|
): Promise<[DevicesResponse | null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const devicesResponse = await makeRequest<unknown, DevicesResponse>({
|
const devicesResponse = await makeRequest<unknown, DevicesResponse>({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: `${apiUrl}/${quizId}/devices`,
|
url: `${apiUrl}/${quizId}/devices`,
|
||||||
useToken: false,
|
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
|
body: { to, from },
|
||||||
});
|
});
|
||||||
|
|
||||||
return [devicesResponse];
|
return [devicesResponse];
|
||||||
@ -44,13 +46,15 @@ export const getDevices = async (
|
|||||||
|
|
||||||
export const getGeneral = async (
|
export const getGeneral = async (
|
||||||
quizId: string,
|
quizId: string,
|
||||||
|
to: number,
|
||||||
|
from: number,
|
||||||
): Promise<[GeneralResponse | null, string?]> => {
|
): Promise<[GeneralResponse | null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const generalResponse = await makeRequest<unknown, GeneralResponse>({
|
const generalResponse = await makeRequest<unknown, GeneralResponse>({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: `${apiUrl}/${quizId}/general`,
|
url: `${apiUrl}/${quizId}/general`,
|
||||||
useToken: false,
|
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
|
body: { to, from },
|
||||||
});
|
});
|
||||||
|
|
||||||
return [generalResponse];
|
return [generalResponse];
|
||||||
@ -63,13 +67,15 @@ export const getGeneral = async (
|
|||||||
|
|
||||||
export const getQuestions = async (
|
export const getQuestions = async (
|
||||||
quizId: string,
|
quizId: string,
|
||||||
|
to: number,
|
||||||
|
from: number,
|
||||||
): Promise<[QuestionsResponse | null, string?]> => {
|
): Promise<[QuestionsResponse | null, string?]> => {
|
||||||
try {
|
try {
|
||||||
const questionsResponse = await makeRequest<unknown, QuestionsResponse>({
|
const questionsResponse = await makeRequest<unknown, QuestionsResponse>({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: `${apiUrl}/${quizId}/questions`,
|
url: `${apiUrl}/${quizId}/questions`,
|
||||||
useToken: false,
|
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
|
body: { to, from },
|
||||||
});
|
});
|
||||||
|
|
||||||
return [questionsResponse];
|
return [questionsResponse];
|
||||||
|
@ -1,43 +1,35 @@
|
|||||||
import { Box, useTheme } from "@mui/material";
|
import { Box, SxProps, Theme } from "@mui/material";
|
||||||
|
|
||||||
export default function ChartIcon() {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
|
export default function ChartLineUp(sx: SxProps<Theme>) {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box sx={sx}>
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
alignItems: "center",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="24"
|
width="24"
|
||||||
height="24"
|
height="24"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M21 19.5H3V4.5"
|
d="M21 19.5H3V4.5"
|
||||||
stroke={theme.palette.brightPurple.main}
|
stroke="#7E2AEA"
|
||||||
strokeWidth="1.5"
|
stroke-width="1.5"
|
||||||
strokeLinecap="round"
|
stroke-linecap="round"
|
||||||
strokeLinejoin="round"
|
stroke-linejoin="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M19.5 6L12 13.5L9 10.5L3 16.5"
|
d="M19.5 6L12 13.5L9 10.5L3 16.5"
|
||||||
stroke={theme.palette.brightPurple.main}
|
stroke="#7E2AEA"
|
||||||
strokeWidth="1.5"
|
stroke-width="1.5"
|
||||||
strokeLinecap="round"
|
stroke-linecap="round"
|
||||||
strokeLinejoin="round"
|
stroke-linejoin="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M19.5 9.75V6H15.75"
|
d="M19.5 9.75V6H15.75"
|
||||||
stroke={theme.palette.brightPurple.main}
|
stroke="#7E2AEA"
|
||||||
strokeWidth="1.5"
|
stroke-width="1.5"
|
||||||
strokeLinecap="round"
|
stroke-linecap="round"
|
||||||
strokeLinejoin="round"
|
stroke-linejoin="round"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState } from "react";
|
import { useLayoutEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
@ -11,6 +11,8 @@ import {
|
|||||||
import { DatePicker } from "@mui/x-date-pickers";
|
import { DatePicker } from "@mui/x-date-pickers";
|
||||||
import { LineChart } from "@mui/x-charts";
|
import { LineChart } from "@mui/x-charts";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
import { useQuizStore } from "@root/quizes/store";
|
||||||
|
import { useAnalytics } from "@utils/hooks/useAnalytics";
|
||||||
|
|
||||||
import HeaderFull from "@ui_kit/Header/HeaderFull";
|
import HeaderFull from "@ui_kit/Header/HeaderFull";
|
||||||
import SectionWrapper from "@ui_kit/SectionWrapper";
|
import SectionWrapper from "@ui_kit/SectionWrapper";
|
||||||
@ -20,11 +22,29 @@ import { AnswersStatistics } from "./Answers";
|
|||||||
import { Devices } from "./Devices";
|
import { Devices } from "./Devices";
|
||||||
|
|
||||||
import CalendarIcon from "@icons/CalendarIcon";
|
import CalendarIcon from "@icons/CalendarIcon";
|
||||||
|
import { redirect } from "react-router-dom";
|
||||||
|
|
||||||
export default function Analytics() {
|
export default function Analytics() {
|
||||||
|
const { editQuizId } = useQuizStore();
|
||||||
|
|
||||||
const [isOpen, setOpen] = useState(false);
|
const [isOpen, setOpen] = useState(false);
|
||||||
const [isOpenEnd, setOpenEnd] = useState(false);
|
const [isOpenEnd, setOpenEnd] = useState(false);
|
||||||
|
const [to, setTo] = useState(null);
|
||||||
|
const [from, setFrom] = useState(null);
|
||||||
|
|
||||||
|
const { devices, general, questions } = useAnalytics({
|
||||||
|
quizId: editQuizId?.toString(),
|
||||||
|
to,
|
||||||
|
from,
|
||||||
|
});
|
||||||
|
|
||||||
|
const resetTime = () => {
|
||||||
|
setTo(null);
|
||||||
|
setFrom(null);
|
||||||
|
};
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (editQuizId === undefined) redirect("/list");
|
||||||
|
}, [editQuizId]);
|
||||||
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(600));
|
const isMobile = useMediaQuery(theme.breakpoints.down(600));
|
||||||
@ -54,6 +74,9 @@ export default function Analytics() {
|
|||||||
handleClose();
|
handleClose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
console.log("questions", questions);
|
||||||
|
console.log("general", general);
|
||||||
|
console.log("devices", devices);
|
||||||
|
|
||||||
const now = moment();
|
const now = moment();
|
||||||
return (
|
return (
|
||||||
@ -110,6 +133,8 @@ export default function Analytics() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
value={to}
|
||||||
|
onChange={(newValue) => setTo(newValue)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
@ -121,6 +146,8 @@ export default function Analytics() {
|
|||||||
color: "4D4D4D",
|
color: "4D4D4D",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
value={from}
|
||||||
|
onChange={(newValue) => setValue(setFrom)}
|
||||||
Дата окончания
|
Дата окончания
|
||||||
</Typography>
|
</Typography>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
@ -154,6 +181,7 @@ export default function Analytics() {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
|
onClick={resetTime}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
sx={{
|
sx={{
|
||||||
minWidth: isMobile ? "144px" : "180px",
|
minWidth: isMobile ? "144px" : "180px",
|
||||||
@ -172,9 +200,9 @@ export default function Analytics() {
|
|||||||
Сбросить
|
Сбросить
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
<General />
|
<General data={general} />
|
||||||
<AnswersStatistics />
|
<AnswersStatistics data={questions} />
|
||||||
<Devices />
|
<Devices data={devices} />
|
||||||
</SectionWrapper>
|
</SectionWrapper>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -186,10 +186,16 @@ const Pagination = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Answers = () => {
|
export const Answers = (props) => {
|
||||||
const [answers, setAnswers] = useState<Record<string, number>>(ANSWERS_MOCK);
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
console.log(props.data);
|
||||||
|
|
||||||
|
if (Object.keys(props.data).length === 0)
|
||||||
|
return (
|
||||||
|
<Typography textAlign="center" m="10px 0">
|
||||||
|
нет данных об ответах
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
<Box sx={{ flexGrow: 1 }}>
|
||||||
<Paper
|
<Paper
|
||||||
@ -244,7 +250,7 @@ export const Answers = () => {
|
|||||||
<NextIcon />
|
<NextIcon />
|
||||||
</ButtonBase>
|
</ButtonBase>
|
||||||
</Box>
|
</Box>
|
||||||
{Object.entries(answers).map(([title, percent], index) => (
|
{Object.entries(props.data).map(([title, percent], index) => (
|
||||||
<Answer
|
<Answer
|
||||||
key={title}
|
key={title}
|
||||||
title={title}
|
title={title}
|
||||||
|
@ -16,9 +16,9 @@ type FunnelItemProps = {
|
|||||||
|
|
||||||
const FUNNEL_MOCK: Record<string, number> = {
|
const FUNNEL_MOCK: Record<string, number> = {
|
||||||
"Стартовая страница": 100,
|
"Стартовая страница": 100,
|
||||||
"Воронка квиза": 69,
|
"Воронка квиза": 0,
|
||||||
Заявки: 56,
|
Заявки: 0,
|
||||||
Результаты: 56,
|
Результаты: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const FunnelItem = ({ title, percent }: FunnelItemProps) => {
|
const FunnelItem = ({ title, percent }: FunnelItemProps) => {
|
||||||
@ -100,12 +100,11 @@ const FunnelItem = ({ title, percent }: FunnelItemProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Funnel = () => {
|
export const Funnel = (props) => {
|
||||||
const [funnel, setFunnel] = useState<Record<string, number>>(FUNNEL_MOCK);
|
|
||||||
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");
|
||||||
@ -122,6 +121,12 @@ export const Funnel = () => {
|
|||||||
// requestFunnel();
|
// requestFunnel();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
if (Object.keys(props.data).length === 0)
|
||||||
|
return (
|
||||||
|
<Typography textAlign="center" m="10px 0">
|
||||||
|
нет данных о разделах
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<Paper
|
<Paper
|
||||||
sx={{
|
sx={{
|
||||||
@ -132,8 +137,12 @@ export const Funnel = () => {
|
|||||||
maxWidth: isSmallMonitor && !isMobile ? "366px" : "none",
|
maxWidth: isSmallMonitor && !isMobile ? "366px" : "none",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Object.entries(funnel).map(([title, percent]) => (
|
{Object.entries(FUNNEL_MOCK).map(([title, percent], index) => (
|
||||||
<FunnelItem key={title} title={title} percent={percent} />
|
<FunnelItem
|
||||||
|
key={title}
|
||||||
|
title={title}
|
||||||
|
percent={index > 0 ? props.data[index - 1] : percent}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
|
@ -69,10 +69,15 @@ const Result = ({ title, percent, highlight }: ResultProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Results = () => {
|
export const Results = (props) => {
|
||||||
const [results, setResults] = useState<Record<string, number>>(RESULTS_MOCK);
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
|
if (Object.keys(props.data).length === 0)
|
||||||
|
return (
|
||||||
|
<Typography margin="20px 0 0 0" textAlign="center" m="10px 0">
|
||||||
|
нет данных о результатах
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography
|
<Typography
|
||||||
@ -93,7 +98,7 @@ export const Results = () => {
|
|||||||
marginTop: "30px",
|
marginTop: "30px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{Object.entries(results).map(([title, percent], index) => (
|
{Object.entries(props.data).map(([title, percent], index) => (
|
||||||
<Result
|
<Result
|
||||||
key={title}
|
key={title}
|
||||||
title={title}
|
title={title}
|
||||||
|
@ -12,11 +12,13 @@ import { Results } from "./Results";
|
|||||||
|
|
||||||
import { ReactComponent as OpenIcon } from "@icons/Analytics/open.svg";
|
import { ReactComponent as OpenIcon } from "@icons/Analytics/open.svg";
|
||||||
|
|
||||||
export const AnswersStatistics = () => {
|
export const AnswersStatistics = (props) => {
|
||||||
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
|
||||||
@ -29,7 +31,7 @@ export const AnswersStatistics = () => {
|
|||||||
>
|
>
|
||||||
Статистика по ответам
|
Статистика по ответам
|
||||||
</Typography>
|
</Typography>
|
||||||
<ButtonBase
|
{/* <ButtonBase
|
||||||
sx={{
|
sx={{
|
||||||
marginTop: "35px",
|
marginTop: "35px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@ -50,17 +52,17 @@ export const AnswersStatistics = () => {
|
|||||||
<Box>
|
<Box>
|
||||||
<OpenIcon />
|
<OpenIcon />
|
||||||
</Box>
|
</Box>
|
||||||
</ButtonBase>
|
</ButtonBase> */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: isSmallMonitor && !isMobile ? "flex" : "block",
|
display: isSmallMonitor && !isMobile ? "flex" : "block",
|
||||||
gap: "40px",
|
gap: "40px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Answers />
|
<Answers data={props.data?.Questions || {}} />
|
||||||
<Funnel />
|
<Funnel data={props.data?.Funnel || {}} />
|
||||||
</Box>
|
</Box>
|
||||||
<Results />
|
<Results data={props.data?.Results || {}} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -27,6 +27,9 @@ 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 === undefined || Object.keys(devices).length === 0)
|
||||||
|
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,
|
||||||
@ -95,33 +98,33 @@ const Device = ({ title, devices }: DeviceProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Devices = () => {
|
export const Devices = ({ data = {} }) => {
|
||||||
const [devices, setDevices] = useState<DevicesResponse>(DEVICES_MOCK);
|
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));
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
const requestDevices = async () => {
|
// const requestDevices = async () => {
|
||||||
const [devicesResponse, devicesError] = await getDevices("14761");
|
// const [devicesResponse, devicesError] = await getDevices("14761");
|
||||||
|
|
||||||
if (devicesError) {
|
// if (devicesError) {
|
||||||
enqueueSnackbar(devicesError);
|
// enqueueSnackbar(devicesError);
|
||||||
|
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (!devicesResponse) {
|
// if (!devicesResponse) {
|
||||||
enqueueSnackbar("Список девайсов пуст.");
|
// enqueueSnackbar("Список девайсов пуст.");
|
||||||
|
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
setDevices(devicesResponse);
|
// setDevices(devicesResponse);
|
||||||
};
|
// };
|
||||||
|
|
||||||
// requestDevices();
|
// // requestDevices();
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ marginTop: "120px" }}>
|
<Box sx={{ marginTop: "120px" }}>
|
||||||
|
@ -38,8 +38,7 @@ const GeneralItem = ({ title, general, color, numberType }: GeneralProps) => {
|
|||||||
: Object.entries(general).reduce(
|
: Object.entries(general).reduce(
|
||||||
(total, [key, value]) => total + (value / Number(key)) * 100,
|
(total, [key, value]) => total + (value / Number(key)) * 100,
|
||||||
0,
|
0,
|
||||||
) / Object.keys(general).length;
|
) / Object.keys(general).length || Number(0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper
|
<Paper
|
||||||
sx={{
|
sx={{
|
||||||
@ -66,34 +65,17 @@ const GeneralItem = ({ title, general, color, numberType }: GeneralProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const General = () => {
|
export const General = (props: any) => {
|
||||||
const [general, setGeneral] = useState<GeneralResponse>(GENERAL_MOCK);
|
|
||||||
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));
|
||||||
|
|
||||||
useEffect(() => {
|
if (Object.keys(props.data).length === 0)
|
||||||
const requestGeneral = async () => {
|
return (
|
||||||
const [generalResponse, generalError] = await getGeneral("14761");
|
<Typography textAlign="center" m="10px 0">
|
||||||
|
нет данных о ключевых метриках
|
||||||
if (generalError) {
|
</Typography>
|
||||||
enqueueSnackbar(generalError);
|
);
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!generalResponse) {
|
|
||||||
enqueueSnackbar("Список девайсов пуст.");
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setGeneral(generalResponse);
|
|
||||||
};
|
|
||||||
|
|
||||||
// requestGeneral();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ marginTop: "45px" }}>
|
<Box sx={{ marginTop: "45px" }}>
|
||||||
<Typography
|
<Typography
|
||||||
@ -121,25 +103,25 @@ export const General = () => {
|
|||||||
<GeneralItem
|
<GeneralItem
|
||||||
title="Открыли квиз"
|
title="Открыли квиз"
|
||||||
numberType="sum"
|
numberType="sum"
|
||||||
general={general.open}
|
general={props.data.open || { 0: 0 }}
|
||||||
color={COLORS[0]}
|
color={COLORS[0]}
|
||||||
/>
|
/>
|
||||||
<GeneralItem
|
<GeneralItem
|
||||||
title="Получено заявок"
|
title="Получено заявок"
|
||||||
numberType="sum"
|
numberType="sum"
|
||||||
general={general.result}
|
general={props.data.result || { 0: 0 }}
|
||||||
color={COLORS[1]}
|
color={COLORS[1]}
|
||||||
/>
|
/>
|
||||||
<GeneralItem
|
<GeneralItem
|
||||||
title="Конверсия"
|
title="Конверсия"
|
||||||
numberType="percent"
|
numberType="percent"
|
||||||
general={general.conversation}
|
general={props.data.conversation || { 0: 0 }}
|
||||||
color={COLORS[2]}
|
color={COLORS[2]}
|
||||||
/>
|
/>
|
||||||
<GeneralItem
|
<GeneralItem
|
||||||
title="Среднее время прохождения квиза"
|
title="Среднее время прохождения квиза"
|
||||||
numberType="percent"
|
numberType="percent"
|
||||||
general={general.avtime}
|
general={props.data.avtime || { 0: 0 }}
|
||||||
color={COLORS[3]}
|
color={COLORS[3]}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -28,8 +28,8 @@ export const DraggableList = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isLoading && quiz && !filteredQuestions.length) {
|
if (!isLoading && quiz && !filteredQuestions.length) {
|
||||||
console.log("useEffect", quiz)
|
console.log("useEffect", quiz);
|
||||||
console.log(Number(quiz.backendId))
|
console.log(Number(quiz.backendId));
|
||||||
createUntypedQuestion(Number(quiz.backendId));
|
createUntypedQuestion(Number(quiz.backendId));
|
||||||
}
|
}
|
||||||
}, [quiz, filteredQuestions]);
|
}, [quiz, filteredQuestions]);
|
||||||
|
@ -36,7 +36,7 @@ export default function QuestionsPage({
|
|||||||
updateEditSomeQuestion();
|
updateEditSomeQuestion();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
console.log("quiz", quiz)
|
console.log("quiz", quiz);
|
||||||
if (!quiz) return null;
|
if (!quiz) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -4,8 +4,8 @@ import { CustomTab } from "./CustomTab";
|
|||||||
type TabsProps = {
|
type TabsProps = {
|
||||||
names: string[];
|
names: string[];
|
||||||
items: string[];
|
items: string[];
|
||||||
selectedItem: "count" | "day";
|
selectedItem: "count" | "day" | "dop";
|
||||||
setSelectedItem: (num: "count" | "day") => void;
|
setSelectedItem: (num: "count" | "day" | "dop") => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Tabs = ({
|
export const Tabs = ({
|
||||||
@ -18,7 +18,7 @@ export const Tabs = ({
|
|||||||
sx={{ m: "25px" }}
|
sx={{ m: "25px" }}
|
||||||
TabIndicatorProps={{ sx: { display: "none" } }}
|
TabIndicatorProps={{ sx: { display: "none" } }}
|
||||||
value={selectedItem}
|
value={selectedItem}
|
||||||
onChange={(event, newValue: "count" | "day") => {
|
onChange={(event, newValue: "count" | "day" | "dop") => {
|
||||||
setSelectedItem(newValue);
|
setSelectedItem(newValue);
|
||||||
}}
|
}}
|
||||||
variant="scrollable"
|
variant="scrollable"
|
||||||
|
@ -38,6 +38,7 @@ import { activatePromocode } from "@api/promocode";
|
|||||||
const StepperText: Record<string, string> = {
|
const StepperText: Record<string, string> = {
|
||||||
count: "Тарифы на объём",
|
count: "Тарифы на объём",
|
||||||
day: "Тарифы на время",
|
day: "Тарифы на время",
|
||||||
|
dop: "Доп. услуги",
|
||||||
};
|
};
|
||||||
|
|
||||||
function TariffPage() {
|
function TariffPage() {
|
||||||
@ -156,6 +157,19 @@ function TariffPage() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const filteredBadgeTariffs = tariffs.filter((tariff) => {
|
||||||
|
return (
|
||||||
|
tariff.privileges[0].serviceKey === "squiz" &&
|
||||||
|
!tariff.isDeleted &&
|
||||||
|
!tariff.isCustom &&
|
||||||
|
tariff.privileges[0].privilegeId === "squizHideBadge" &&
|
||||||
|
tariff.privileges[0]?.type === "day"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
const filteredBaseTariffs = filteredTariffs.filter((tariff) => {
|
||||||
|
return tariff.privileges[0].privilegeId !== "squizHideBadge";
|
||||||
|
});
|
||||||
|
|
||||||
async function handleLogoutClick() {
|
async function handleLogoutClick() {
|
||||||
const [, logoutError] = await logout();
|
const [, logoutError] = await logout();
|
||||||
|
|
||||||
@ -254,20 +268,54 @@ function TariffPage() {
|
|||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
justifyContent: "left",
|
justifyContent: "left",
|
||||||
display: "grid",
|
display: selectedItem === "dop" ? "flex" : "grid",
|
||||||
gap: "40px",
|
gap: "40px",
|
||||||
p: "20px",
|
p: "20px",
|
||||||
gridTemplateColumns: `repeat(auto-fit, minmax(300px, ${
|
gridTemplateColumns: `repeat(auto-fit, minmax(300px, ${
|
||||||
isTablet ? "436px" : "360px"
|
isTablet ? "436px" : "360px"
|
||||||
}))`,
|
}))`,
|
||||||
|
flexDirection: selectedItem === "dop" ? "column" : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{createTariffElements(
|
{selectedItem === "day" &&
|
||||||
filteredTariffs,
|
createTariffElements(
|
||||||
true,
|
filteredBaseTariffs,
|
||||||
user,
|
true,
|
||||||
discounts,
|
user,
|
||||||
openModalHC,
|
discounts,
|
||||||
|
openModalHC,
|
||||||
|
)}
|
||||||
|
{selectedItem === "count" &&
|
||||||
|
createTariffElements(
|
||||||
|
filteredTariffs,
|
||||||
|
true,
|
||||||
|
user,
|
||||||
|
discounts,
|
||||||
|
openModalHC,
|
||||||
|
)}
|
||||||
|
{selectedItem === "dop" && (
|
||||||
|
<>
|
||||||
|
<Typography fontWeight={500}>Убрать логотип "PenaQuiz"</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
justifyContent: "left",
|
||||||
|
display: "grid",
|
||||||
|
gap: "40px",
|
||||||
|
p: "20px",
|
||||||
|
gridTemplateColumns: `repeat(auto-fit, minmax(300px, ${
|
||||||
|
isTablet ? "436px" : "360px"
|
||||||
|
}))`,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{createTariffElements(
|
||||||
|
filteredBadgeTariffs,
|
||||||
|
false,
|
||||||
|
user,
|
||||||
|
discounts,
|
||||||
|
openModalHC,
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Modal
|
<Modal
|
||||||
|
@ -19,6 +19,7 @@ import { makeRequest } from "@frontend/kitui";
|
|||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
import { useDomainDefine } from "@utils/hooks/useDomainDefine";
|
import { useDomainDefine } from "@utils/hooks/useDomainDefine";
|
||||||
import CopyIcon from "@icons/CopyIcon";
|
import CopyIcon from "@icons/CopyIcon";
|
||||||
|
import ChartIcon from "@icons/ChartIcon";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
quiz: Quiz;
|
quiz: Quiz;
|
||||||
@ -45,6 +46,10 @@ export default function QuizCard({
|
|||||||
setEditQuizId(quiz.backendId);
|
setEditQuizId(quiz.backendId);
|
||||||
navigate("/edit");
|
navigate("/edit");
|
||||||
}
|
}
|
||||||
|
function handleStatisticClick() {
|
||||||
|
setEditQuizId(quiz.backendId);
|
||||||
|
navigate(`/analytics`);
|
||||||
|
}
|
||||||
|
|
||||||
const questionCount = useRef(quiz.questions_count.toString() || "");
|
const questionCount = useRef(quiz.questions_count.toString() || "");
|
||||||
|
|
||||||
@ -186,21 +191,24 @@ export default function QuizCard({
|
|||||||
>
|
>
|
||||||
{isMobile ? "" : "Редактировать"}
|
{isMobile ? "" : "Редактировать"}
|
||||||
</Button>
|
</Button>
|
||||||
{/* <Button
|
<IconButton
|
||||||
variant="outlined"
|
onClick={handleStatisticClick}
|
||||||
startIcon={<ChartIcon />}
|
|
||||||
sx={{
|
sx={{
|
||||||
minWidth: "46px",
|
height: "44px",
|
||||||
padding: "10px 10px",
|
width: "44px",
|
||||||
"& .MuiButton-startIcon": {
|
border: `${theme.palette.brightPurple.main} 1px solid`,
|
||||||
mr: 0,
|
borderRadius: "6px",
|
||||||
ml: 0,
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
/> */}
|
>
|
||||||
|
<ChartIcon />
|
||||||
|
</IconButton>
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => onClickCopy(quiz.id)}
|
onClick={() => onClickCopy(quiz.id)}
|
||||||
sx={{ borderRadius: "6px", padding: "0 4px" }}
|
sx={{
|
||||||
|
height: "44px",
|
||||||
|
width: "44px",
|
||||||
|
borderRadius: "6px",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<CopyIcon
|
<CopyIcon
|
||||||
color={theme.palette.brightPurple.main}
|
color={theme.palette.brightPurple.main}
|
||||||
|
@ -20,9 +20,9 @@ import {
|
|||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
MenuItem,
|
MenuItem,
|
||||||
Select,
|
Select,
|
||||||
|
Skeleton,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Typography,
|
Typography,
|
||||||
Skeleton,
|
|
||||||
useMediaQuery,
|
useMediaQuery,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
@ -45,6 +45,7 @@ import SelectableIconButton from "./SelectableIconButton";
|
|||||||
import { DropZone } from "./dropZone";
|
import { DropZone } from "./dropZone";
|
||||||
import Extra from "./extra";
|
import Extra from "./extra";
|
||||||
import TooltipClickInfo from "@ui_kit/Toolbars/TooltipClickInfo";
|
import TooltipClickInfo from "@ui_kit/Toolbars/TooltipClickInfo";
|
||||||
|
import { VideoElement } from "./VideoElement";
|
||||||
|
|
||||||
const designTypes = [
|
const designTypes = [
|
||||||
[
|
[
|
||||||
@ -386,91 +387,103 @@ export default function StartPageSettings() {
|
|||||||
|
|
||||||
{quiz.config.startpage.background.type === "video" && (
|
{quiz.config.startpage.background.type === "video" && (
|
||||||
<>
|
<>
|
||||||
<Box
|
{!quiz.config.startpage.background.video ? (
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
gap: "7px",
|
|
||||||
mt: "20px",
|
|
||||||
mb: "14px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
sx={{ fontWeight: 500, color: theme.palette.grey3.main }}
|
|
||||||
>
|
|
||||||
Добавить видео
|
|
||||||
</Typography>
|
|
||||||
{isMobile ? (
|
|
||||||
<TooltipClickInfo title={"Можно загрузить видео."} />
|
|
||||||
) : (
|
|
||||||
<Tooltip title="Можно загрузить видео." placement="top">
|
|
||||||
<Box>
|
|
||||||
<InfoIcon />
|
|
||||||
</Box>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
{backgroundUploding ? (
|
|
||||||
<Skeleton
|
|
||||||
sx={{
|
|
||||||
width: "48px",
|
|
||||||
height: "48px",
|
|
||||||
transform: "none",
|
|
||||||
margin: "20px 0",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<>
|
<>
|
||||||
<ButtonBase
|
<Box
|
||||||
component="label"
|
|
||||||
sx={{
|
sx={{
|
||||||
justifyContent: "center",
|
|
||||||
height: "48px",
|
|
||||||
width: "48px",
|
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
my: "20px",
|
gap: "7px",
|
||||||
|
mt: "20px",
|
||||||
|
mb: "14px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<input
|
<Typography
|
||||||
onChange={async (event) => {
|
sx={{
|
||||||
setBackgroundUploading(true);
|
fontWeight: 500,
|
||||||
const file = event.target.files?.[0];
|
color: theme.palette.grey3.main,
|
||||||
|
}}
|
||||||
if (file) {
|
>
|
||||||
await uploadQuizImage(
|
Добавить видео
|
||||||
quiz.id,
|
</Typography>
|
||||||
file,
|
{isMobile ? (
|
||||||
(quiz, url) => {
|
<TooltipClickInfo title={"Можно загрузить видео."} />
|
||||||
quiz.config.startpage.background.video = url;
|
) : (
|
||||||
},
|
<Tooltip title="Можно загрузить видео." placement="top">
|
||||||
);
|
<Box>
|
||||||
// setVideo(URL.createObjectURL(file));
|
<InfoIcon />
|
||||||
}
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
setBackgroundUploading(false);
|
)}
|
||||||
}}
|
</Box>
|
||||||
hidden
|
{backgroundUploding ? (
|
||||||
accept=".mp4"
|
<Skeleton
|
||||||
multiple
|
|
||||||
type="file"
|
|
||||||
/>
|
|
||||||
<UploadBox
|
|
||||||
icon={<UploadIcon />}
|
|
||||||
sx={{
|
sx={{
|
||||||
height: "48px",
|
|
||||||
width: "48px",
|
width: "48px",
|
||||||
|
height: "48px",
|
||||||
|
transform: "none",
|
||||||
|
margin: "20px 0",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</ButtonBase>
|
) : (
|
||||||
{quiz.config.startpage.background.video && (
|
<>
|
||||||
<video
|
<ButtonBase
|
||||||
src={quiz.config.startpage.background.video}
|
component="label"
|
||||||
width="400"
|
sx={{
|
||||||
controls
|
justifyContent: "center",
|
||||||
/>
|
height: "48px",
|
||||||
|
width: "48px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
my: "20px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
onChange={async (event) => {
|
||||||
|
setBackgroundUploading(true);
|
||||||
|
const file = event.target.files?.[0];
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
await uploadQuizImage(
|
||||||
|
quiz.id,
|
||||||
|
file,
|
||||||
|
(quiz, url) => {
|
||||||
|
quiz.config.startpage.background.video =
|
||||||
|
url;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setBackgroundUploading(false);
|
||||||
|
}}
|
||||||
|
hidden
|
||||||
|
accept=".mp4"
|
||||||
|
multiple
|
||||||
|
type="file"
|
||||||
|
/>
|
||||||
|
<UploadBox
|
||||||
|
icon={<UploadIcon />}
|
||||||
|
sx={{
|
||||||
|
height: "48px",
|
||||||
|
width: "48px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ButtonBase>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
) : (
|
||||||
|
<Box sx={{ marginTop: "20px" }}>
|
||||||
|
<VideoElement
|
||||||
|
videoSrc={quiz.config.startpage.background.video}
|
||||||
|
theme={theme}
|
||||||
|
onDeleteClick={() => {
|
||||||
|
updateQuiz(quiz.id, (quiz) => {
|
||||||
|
quiz.config.startpage.background.video = null;
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
46
src/pages/startPage/VideoElement.tsx
Normal file
46
src/pages/startPage/VideoElement.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import Box from "@mui/material/Box";
|
||||||
|
import { FC } from "react";
|
||||||
|
import DeleteIcon from "@mui/icons-material/Delete";
|
||||||
|
import { IconButton, SxProps, Theme } from "@mui/material";
|
||||||
|
|
||||||
|
type VideoElementProps = {
|
||||||
|
videoSrc: string;
|
||||||
|
width?: string;
|
||||||
|
theme: Theme;
|
||||||
|
onDeleteClick: () => void;
|
||||||
|
deleteIconSx?: SxProps<Theme>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const VideoElement: FC<VideoElementProps> = ({
|
||||||
|
videoSrc,
|
||||||
|
width = "300",
|
||||||
|
theme,
|
||||||
|
onDeleteClick,
|
||||||
|
deleteIconSx,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Box sx={{ position: "relative", width: `${width}px` }}>
|
||||||
|
<video
|
||||||
|
style={{ borderRadius: "8px" }}
|
||||||
|
src={videoSrc}
|
||||||
|
width={width}
|
||||||
|
controls
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
onClick={onDeleteClick}
|
||||||
|
sx={{
|
||||||
|
position: "absolute",
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
color: theme.palette.orange.main,
|
||||||
|
borderRadius: "8px",
|
||||||
|
borderBottomRightRadius: 0,
|
||||||
|
borderTopLeftRadius: 0,
|
||||||
|
...deleteIconSx,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DeleteIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -44,7 +44,7 @@ export const createUntypedQuestion = (
|
|||||||
) =>
|
) =>
|
||||||
setProducedState(
|
setProducedState(
|
||||||
(state) => {
|
(state) => {
|
||||||
console.log("createUntypedQuestion", quizId)
|
console.log("createUntypedQuestion", quizId);
|
||||||
const newUntypedQuestion = {
|
const newUntypedQuestion = {
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
quizId,
|
quizId,
|
||||||
@ -278,7 +278,7 @@ export const updateQuestion = async <T = AnyTypedQuizQuestion>(
|
|||||||
if (!q) return;
|
if (!q) return;
|
||||||
if (q.type === null)
|
if (q.type === null)
|
||||||
throw new Error("Cannot send update request for untyped question");
|
throw new Error("Cannot send update request for untyped question");
|
||||||
console.log("отправляемый квешен", q)
|
console.log("отправляемый квешен", q);
|
||||||
try {
|
try {
|
||||||
const response = await questionApi.edit(
|
const response = await questionApi.edit(
|
||||||
questionToEditQuestionRequest(replaceEmptyLinesToSpace(q)),
|
questionToEditQuestionRequest(replaceEmptyLinesToSpace(q)),
|
||||||
@ -449,8 +449,8 @@ export const createTypedQuestion = async (
|
|||||||
requestQueue.enqueue(`createTypedQuestion-${questionId}`, async () => {
|
requestQueue.enqueue(`createTypedQuestion-${questionId}`, async () => {
|
||||||
const questions = useQuestionsStore.getState().questions;
|
const questions = useQuestionsStore.getState().questions;
|
||||||
const question = questions.find((q) => q.id === questionId);
|
const question = questions.find((q) => q.id === questionId);
|
||||||
console.log("createTypedQuestion", question)
|
console.log("createTypedQuestion", question);
|
||||||
console.log("createTypedQuestion", question?.quizId)
|
console.log("createTypedQuestion", question?.quizId);
|
||||||
if (!question) return;
|
if (!question) return;
|
||||||
if (question.type !== null)
|
if (question.type !== null)
|
||||||
throw new Error("Cannot upgrade already typed question");
|
throw new Error("Cannot upgrade already typed question");
|
||||||
|
@ -21,6 +21,28 @@ export const EmojiPicker = ({ onEmojiSelect }: EmojiPickerProps) => (
|
|||||||
onEmojiSelect={onEmojiSelect}
|
onEmojiSelect={onEmojiSelect}
|
||||||
theme="light"
|
theme="light"
|
||||||
locale="ru"
|
locale="ru"
|
||||||
|
exceptEmojis={ignoreEmojis}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const ignoreEmojis = [
|
||||||
|
"two_men_holding_hands",
|
||||||
|
"two_women_holding_hands",
|
||||||
|
"man-kiss-man",
|
||||||
|
"woman-kiss-woman",
|
||||||
|
"man-heart-man",
|
||||||
|
"woman-heart-woman",
|
||||||
|
"man-man-boy",
|
||||||
|
"man-man-girl",
|
||||||
|
"man-man-girl-boy",
|
||||||
|
"man-man-girl-girl",
|
||||||
|
"man-man-boy-boy",
|
||||||
|
"woman-woman-boy",
|
||||||
|
"woman-woman-girl",
|
||||||
|
"woman-woman-girl-boy",
|
||||||
|
"woman-woman-girl-girl",
|
||||||
|
"woman-woman-boy-boy",
|
||||||
|
"rainbow-flag",
|
||||||
|
"transgender_flag",
|
||||||
|
];
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, FC } from "react";
|
import { FC, useState } from "react";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Button,
|
Button,
|
||||||
@ -7,7 +7,6 @@ import {
|
|||||||
Typography,
|
Typography,
|
||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import CustomTextField from "./CustomTextField";
|
|
||||||
import { updateQuestion, uploadQuestionImage } from "@root/questions/actions";
|
import { updateQuestion, uploadQuestionImage } from "@root/questions/actions";
|
||||||
import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal";
|
import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal";
|
||||||
|
|
||||||
@ -19,6 +18,7 @@ import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
|
|||||||
import UploadBox from "@ui_kit/UploadBox";
|
import UploadBox from "@ui_kit/UploadBox";
|
||||||
import UploadIcon from "@icons/UploadIcon";
|
import UploadIcon from "@icons/UploadIcon";
|
||||||
import InfoIcon from "@icons/InfoIcon";
|
import InfoIcon from "@icons/InfoIcon";
|
||||||
|
import { VideoElement } from "../pages/startPage/VideoElement";
|
||||||
|
|
||||||
interface Iprops {
|
interface Iprops {
|
||||||
resultData: AnyTypedQuizQuestion;
|
resultData: AnyTypedQuizQuestion;
|
||||||
@ -167,67 +167,78 @@ export const MediaSelectionAndDisplay: FC<Iprops> = ({ resultData }) => {
|
|||||||
)}
|
)}
|
||||||
{!resultData.content.useImage && (
|
{!resultData.content.useImage && (
|
||||||
<>
|
<>
|
||||||
<Box
|
{!resultData.content.video ? (
|
||||||
sx={{
|
<>
|
||||||
display: "flex",
|
<Box
|
||||||
alignItems: "center",
|
sx={{
|
||||||
gap: "7px",
|
display: "flex",
|
||||||
mt: "20px",
|
alignItems: "center",
|
||||||
mb: "14px",
|
gap: "7px",
|
||||||
}}
|
mt: "20px",
|
||||||
>
|
mb: "14px",
|
||||||
<Typography
|
}}
|
||||||
sx={{ fontWeight: 500, color: theme.palette.grey3.main }}
|
>
|
||||||
>
|
<Typography
|
||||||
Добавить видео
|
sx={{ fontWeight: 500, color: theme.palette.grey3.main }}
|
||||||
</Typography>
|
>
|
||||||
<Tooltip title="Можно загрузить видео." placement="top">
|
Добавить видео
|
||||||
<Box>
|
</Typography>
|
||||||
<InfoIcon />
|
<Tooltip title="Можно загрузить видео." placement="top">
|
||||||
|
<Box>
|
||||||
|
<InfoIcon />
|
||||||
|
</Box>
|
||||||
|
</Tooltip>
|
||||||
</Box>
|
</Box>
|
||||||
</Tooltip>
|
<ButtonBase
|
||||||
</Box>
|
component="label"
|
||||||
<ButtonBase
|
sx={{
|
||||||
component="label"
|
justifyContent: "center",
|
||||||
sx={{
|
height: "48px",
|
||||||
justifyContent: "center",
|
width: "48px",
|
||||||
height: "48px",
|
display: "flex",
|
||||||
width: "48px",
|
alignItems: "center",
|
||||||
display: "flex",
|
my: "20px",
|
||||||
alignItems: "center",
|
}}
|
||||||
my: "20px",
|
>
|
||||||
}}
|
<input
|
||||||
>
|
onChange={(event) => {
|
||||||
<input
|
const file = event.target.files?.[0];
|
||||||
onChange={(event) => {
|
if (file) {
|
||||||
const file = event.target.files?.[0];
|
uploadQuestionImage(
|
||||||
if (file) {
|
resultData.id,
|
||||||
uploadQuestionImage(
|
quizQid,
|
||||||
resultData.id,
|
file,
|
||||||
quizQid,
|
(question, url) => {
|
||||||
file,
|
question.content.video = url;
|
||||||
(question, url) => {
|
},
|
||||||
question.content.video = url;
|
);
|
||||||
},
|
}
|
||||||
);
|
}}
|
||||||
}
|
hidden
|
||||||
}}
|
accept=".mp4"
|
||||||
hidden
|
multiple
|
||||||
accept=".mp4"
|
type="file"
|
||||||
multiple
|
/>
|
||||||
type="file"
|
<UploadBox
|
||||||
/>
|
icon={<UploadIcon />}
|
||||||
<UploadBox
|
sx={{
|
||||||
icon={<UploadIcon />}
|
height: "48px",
|
||||||
sx={{
|
width: "48px",
|
||||||
height: "48px",
|
}}
|
||||||
width: "48px",
|
/>
|
||||||
|
</ButtonBase>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<VideoElement
|
||||||
|
videoSrc={resultData.content.video}
|
||||||
|
theme={theme}
|
||||||
|
onDeleteClick={() => {
|
||||||
|
updateQuestion(resultData.id, (question) => {
|
||||||
|
question.content.video = null;
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</ButtonBase>
|
)}
|
||||||
{resultData.content.video ? (
|
|
||||||
<video src={resultData.content.video} width="300" controls />
|
|
||||||
) : null}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -67,7 +67,7 @@ export const CropModal: FC<Props> = ({
|
|||||||
setCropModalImageBlob,
|
setCropModalImageBlob,
|
||||||
onSaveImageClick,
|
onSaveImageClick,
|
||||||
onClose,
|
onClose,
|
||||||
questionId
|
questionId,
|
||||||
}) => {
|
}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [percentCrop, setPercentCrop] = useState<PercentCrop>();
|
const [percentCrop, setPercentCrop] = useState<PercentCrop>();
|
||||||
@ -297,13 +297,13 @@ export const CropModal: FC<Props> = ({
|
|||||||
onChange={(_, newValue) => setDarken(newValue as number)}
|
onChange={(_, newValue) => setDarken(newValue as number)}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
{questionId !== undefined &&
|
{questionId !== undefined && (
|
||||||
<IconButton
|
<IconButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
updateQuestion(questionId, (question) => {
|
updateQuestion(questionId, (question) => {
|
||||||
question.content.back = null;
|
question.content.back = null;
|
||||||
question.content.originalBack = null;
|
question.content.originalBack = null;
|
||||||
})
|
});
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
@ -316,7 +316,7 @@ export const CropModal: FC<Props> = ({
|
|||||||
>
|
>
|
||||||
<DeleteIcon />
|
<DeleteIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -351,8 +351,9 @@ export const CropModal: FC<Props> = ({
|
|||||||
background: theme.palette.brightPurple.main,
|
background: theme.palette.brightPurple.main,
|
||||||
fontSize: "18px",
|
fontSize: "18px",
|
||||||
color: "#7E2AEA",
|
color: "#7E2AEA",
|
||||||
border: `1px solid ${!completedCrop ? "rgba(0, 0, 0, 0.26)" : "#7E2AEA"
|
border: `1px solid ${
|
||||||
}`,
|
!completedCrop ? "rgba(0, 0, 0, 0.26)" : "#7E2AEA"
|
||||||
|
}`,
|
||||||
backgroundColor: "transparent",
|
backgroundColor: "transparent",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
32
src/utils/hooks/useAnalytics.ts
Normal file
32
src/utils/hooks/useAnalytics.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { getGeneral, getDevices, getQuestions } from "@api/statistic";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import moment from "moment";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
quizId: string;
|
||||||
|
to: number;
|
||||||
|
from: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAnalytics({ quizId, to, from }: Props) {
|
||||||
|
const formatTo = to === null ? 0 : moment(to).unix();
|
||||||
|
const formatFrom = from === null ? 0 : moment(from).unix();
|
||||||
|
console.log(to, from);
|
||||||
|
if (quizId === undefined) return {};
|
||||||
|
const [devices, setDevices] = useState({});
|
||||||
|
const [general, setGeneral] = useState({});
|
||||||
|
const [questions, setQuestions] = useState({});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
const gottenGeneral = await getGeneral(quizId, formatTo, formatFrom);
|
||||||
|
const gottenDevices = await getDevices(quizId, formatTo, formatFrom);
|
||||||
|
const gottenQuestions = await getQuestions(quizId, formatTo, formatFrom);
|
||||||
|
setDevices(gottenGeneral[0]);
|
||||||
|
setGeneral(gottenDevices[0]);
|
||||||
|
setQuestions(gottenQuestions[0]);
|
||||||
|
})();
|
||||||
|
}, [to, from]);
|
||||||
|
|
||||||
|
return { devices, general, questions };
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user