Merge branch 'dev' into 'staging'

Dev

See merge request frontend/squiz!236
This commit is contained in:
Nastya 2024-04-05 18:24:26 +00:00
commit 3149250dae
7 changed files with 95 additions and 61 deletions

@ -58,7 +58,7 @@ export default function Analytics() {
useEffect(() => { useEffect(() => {
if (quizes.length > 0) { if (quizes.length > 0) {
const quiz = quizes.find((q) => q.backendId === editQuizId); const quiz = quizes.find((q) => q.backendId === editQuizId);
if (quiz === undefined) throw new Error("Не удалось получить квиз") if (quiz === undefined) throw new Error("Не удалось получить квиз");
setQuiz(quiz); setQuiz(quiz);
setFrom(moment(new Date(quiz.created_at))); setFrom(moment(new Date(quiz.created_at)));
} }
@ -68,10 +68,8 @@ export default function Analytics() {
const getData = async (): Promise<void> => { const getData = async (): Promise<void> => {
try { try {
if (editQuizId !== null) { if (editQuizId !== null) {
const gottenQuizes = await quizApi.getList(); const gottenQuizes = await quizApi.getList();
setQuizes(gottenQuizes) setQuizes(gottenQuizes);
} }
} catch (error) { } catch (error) {
console.error("Не удалось получить квизы", error); console.error("Не удалось получить квизы", error);

@ -1,5 +1,4 @@
import { FC, useState } from "react"; import { FC, useEffect, useMemo, useState } from "react";
import type { PaginationRenderItemParams } from "@mui/material";
import { import {
Box, Box,
ButtonBase, ButtonBase,
@ -18,6 +17,8 @@ import { ReactComponent as NextIcon } from "@icons/Analytics/next.svg";
import { ReactComponent as LeftArrowIcon } from "@icons/Analytics/leftArrow.svg"; import { ReactComponent as LeftArrowIcon } from "@icons/Analytics/leftArrow.svg";
import { ReactComponent as RightArrowIcon } from "@icons/Analytics/rightArrow.svg"; import { ReactComponent as RightArrowIcon } from "@icons/Analytics/rightArrow.svg";
import type { PaginationRenderItemParams } from "@mui/material";
type AnswerProps = { type AnswerProps = {
title: string; title: string;
percent: number; percent: number;
@ -28,12 +29,14 @@ type AnswersProps = {
data: Record<string, Record<string, number>> | null; data: Record<string, Record<string, number>> | null;
}; };
const ANSWERS_MOCK: Record<string, number> = { type PaginationProps = {
"Добавьте ответ": 67, page: number;
"Вопрос пропущен": 7, pagesAmount: number;
Другое: 27, setPage: (page: number) => void;
}; };
const ANSWERS_PER_PAGE = 5;
const Answer = ({ title, percent, highlight }: AnswerProps) => { const Answer = ({ title, percent, highlight }: AnswerProps) => {
const theme = useTheme(); const theme = useTheme();
@ -60,7 +63,7 @@ const Answer = ({ title, percent, highlight }: AnswerProps) => {
border: `1px solid ${highlight ? theme.palette.brightPurple.main : theme.palette.grey2.main}`, border: `1px solid ${highlight ? theme.palette.brightPurple.main : theme.palette.grey2.main}`,
"& > span": { background: highlight ? "#D9C0F9" : "#9A9AAF1A" }, "& > span": { background: highlight ? "#D9C0F9" : "#9A9AAF1A" },
"&::before": { "&::before": {
content: `"${title}"`, content: title ? `"${title}"` : `"Без имени"`,
position: "absolute", position: "absolute",
zIndex: 1, zIndex: 1,
left: "20px", left: "20px",
@ -88,12 +91,15 @@ const Answer = ({ title, percent, highlight }: AnswerProps) => {
); );
}; };
const Pagination = () => { const Pagination = ({ page, setPage, pagesAmount }: PaginationProps) => {
const [count, setCount] = useState<number>(50); const [count, setCount] = useState<number>(0);
const [page, setPage] = useState<number>(1);
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(855)); const isMobile = useMediaQuery(theme.breakpoints.down(855));
useEffect(() => {
setCount(pagesAmount);
}, [pagesAmount]);
const getPaginationItem = (props: PaginationRenderItemParams) => { const getPaginationItem = (props: PaginationRenderItemParams) => {
if (props.type === "start-ellipsis" || props.type === "end-ellipsis") { if (props.type === "start-ellipsis" || props.type === "end-ellipsis") {
return; return;
@ -147,9 +153,13 @@ const Pagination = () => {
disableUnderline disableUnderline
placeholder="1" placeholder="1"
value={page} value={page}
onChange={({ target }) => onChange={({ target }) => {
setPage(Number(target.value.replace(/\D/, ""))) const newPage = Number(target.value.replace(/\D/, ""));
}
if (newPage <= count) {
setPage(newPage);
}
}}
sx={{ sx={{
height: "30px", height: "30px",
width: "65px", width: "65px",
@ -190,7 +200,27 @@ const Pagination = () => {
}; };
export const Answers: FC<AnswersProps> = ({ data }) => { export const Answers: FC<AnswersProps> = ({ data }) => {
const [page, setPage] = useState<number>(1);
const theme = useTheme(); const theme = useTheme();
const answers = useMemo(
() =>
data === null ?
[]
:
Object.keys(data ?? {})
.map((key) =>
Object.entries(data[key]).map(([title, percent]) => ({
title,
percent,
key,
})),
)
.flat(),
[data],
);
console.log(answers)
console.log(page)
if (!data) { if (!data) {
return ( return (
<Typography textAlign="center" m="10px 0"> <Typography textAlign="center" m="10px 0">
@ -244,7 +274,7 @@ export const Answers: FC<AnswersProps> = ({ data }) => {
}, },
}} }}
> >
Заголовок вопроса. Варианты ответов Заголовок вопроса. {answers.slice(ANSWERS_PER_PAGE * (page - 1), ANSWERS_PER_PAGE * page)[0]?.key}
</Typography> </Typography>
<ButtonBase> <ButtonBase>
<DoubleCheckIcon /> <DoubleCheckIcon />
@ -253,26 +283,22 @@ export const Answers: FC<AnswersProps> = ({ data }) => {
<NextIcon /> <NextIcon />
</ButtonBase> </ButtonBase>
</Box> </Box>
{/*{Object.entries(data).map(([title, percent], index) => (*/} {answers
{/* <Answer*/} .slice(ANSWERS_PER_PAGE * (page - 1), ANSWERS_PER_PAGE * page)
{/* key={title}*/} .map(({ title, percent }, index) => (
{/* title={title}*/}
{/* percent={percent}*/}
{/* highlight={!index}*/}
{/* />*/}
{/*))}*/}
{Object.entries(data).map(([title, values], index) =>
Object.entries(values).map(([subTitle, percent]) => (
<Answer <Answer
key={subTitle} key={index}
title={subTitle} title={title}
percent={percent} percent={percent}
highlight={!index} highlight={!index}
/> />
)), ))}
)}
</Paper> </Paper>
<Pagination /> <Pagination
page={page}
setPage={setPage}
pagesAmount={Math.ceil(answers.length / ANSWERS_PER_PAGE)}
/>
</Box> </Box>
); );
}; };

@ -5,7 +5,8 @@ import {
Typography, Typography,
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import { FC } from "react";
import type { FC } from "react";
type ResultProps = { type ResultProps = {
title: string; title: string;
@ -17,12 +18,6 @@ type ResultsProps = {
data: Record<string, number> | null; data: Record<string, number> | null;
}; };
const RESULTS_MOCK: Record<string, number> = {
"Заголовок результата": 100,
"Результат пропущен": 7,
Другое: 27,
};
const Result = ({ title, percent, highlight }: ResultProps) => { const Result = ({ title, percent, highlight }: ResultProps) => {
const theme = useTheme(); const theme = useTheme();
@ -76,12 +71,13 @@ const Result = ({ title, percent, highlight }: ResultProps) => {
export const Results: FC<ResultsProps> = ({ data }) => { export const Results: FC<ResultsProps> = ({ data }) => {
const theme = useTheme(); const theme = useTheme();
if (!data) if (!data || !Object.keys(data).length)
return ( return (
<Typography margin="20px 0 0 0" textAlign="center" m="10px 0"> <Typography margin="20px 0 0 0" textAlign="center" m="10px 0">
нет данных о результатах нет данных о результатах
</Typography> </Typography>
); );
return ( return (
<Box> <Box>
<Typography <Typography

@ -22,7 +22,7 @@ const COLORS: Record<number, string> = {
const Device = ({ title, devices }: DeviceProps) => { const Device = ({ title, devices }: DeviceProps) => {
const theme = useTheme(); const theme = useTheme();
if (!devices) { if (!devices || !Object.keys(devices ?? {}).length) {
return <Typography>{title} - нет данных</Typography>; return <Typography>{title} - нет данных</Typography>;
} }
@ -84,7 +84,7 @@ const Device = ({ title, devices }: DeviceProps) => {
}, },
}} }}
> >
{id} {id ? id : "Неопределено"}
</Typography> </Typography>
<Typography>{value.toFixed(1)} %</Typography> <Typography>{value.toFixed(1)} %</Typography>
</Box> </Box>

@ -9,7 +9,7 @@ type GeneralItemsProps = {
title: string; title: string;
general: Record<string, number>; general: Record<string, number>;
color: string; color: string;
numberType: "sum" | "percent"; numberType: "sum" | "percent" | "time";
}; };
type GeneralProps = { type GeneralProps = {
@ -24,13 +24,13 @@ const COLORS: Record<number, string> = {
}; };
const dateParser = (object: Record<string, number>): Record<string, number> => { const dateParser = (object: Record<string, number>): Record<string, number> => {
const result = {} as Record<string, number> const result = {} as Record<string, number>;
for (var key in object) { for (var key in object) {
result[moment.utc(Number(key)*1000).format('MM/DD/YYYY')] = object[key] result[moment.utc(Number(key) * 1000).format("DD/MM/YYYY")] = object[key];
console.log(result) console.log(result);
} }
return result; return result;
} };
const GeneralItem = ({ const GeneralItem = ({
title, title,
@ -44,10 +44,15 @@ const GeneralItem = ({
const numberValue = const numberValue =
numberType === "sum" numberType === "sum"
? Object.values(general).reduce((total, item) => total + item, 0) ? Object.values(general).reduce((total, item) => total + item, 0)
: Object.entries(general).reduce( : 0
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 || Number(0); ) / Object.keys(general).length || Number(0);
console.log("general" , general)
console.log(Object.keys(general).length === 0)
if (Object.keys(general).length === 0) return <Typography textAlign="center">{`${title} - нет данных`}</Typography>
return ( return (
<Paper <Paper
sx={{ sx={{
@ -61,8 +66,16 @@ const GeneralItem = ({
{numberType === "sum" ? numberValue : `${numberValue.toFixed()}%`} {numberType === "sum" ? numberValue : `${numberValue.toFixed()}%`}
</Typography> </Typography>
<LineChart <LineChart
xAxis={[{ data: Object.keys(general), valueFormatter: (value) => moment.utc(Number(value)*1000).format('MM/DD/YYYY') }]}
xAxis={[
{
data: Object.keys(general),
valueFormatter: (value) =>
moment.utc(Number(value) * 1000).format("DD/MM/YYYY"),
},
]}
series={[{ data: Object.values(general) }]} series={[{ data: Object.values(general) }]}
// dataset={Object.entries(general).map(([, v]) => moment.unix(v).format("ss/mm/HH")).reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {})}
height={220} height={220}
colors={[color]} colors={[color]}
sx={{ sx={{
@ -113,25 +126,25 @@ export const General: FC<GeneralProps> = ({ data }) => {
<GeneralItem <GeneralItem
title="Открыли квиз" title="Открыли квиз"
numberType="sum" numberType="sum"
general={data.Open || { 0: 0 }} general={ Object.entries(data.Open).filter(([, v]) => v > 0).reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {}) || { 0: 0 }}
color={COLORS[0]} color={COLORS[0]}
/> />
<GeneralItem <GeneralItem
title="Получено заявок" title="Получено заявок"
numberType="sum" numberType="sum"
general={data.Result || { 0: 0 }} general={ Object.entries(data.Result).filter(([, v]) => v > 0).reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {}) || { 0: 0 }}
color={COLORS[1]} color={COLORS[1]}
/> />
<GeneralItem <GeneralItem
title="Конверсия" title="Конверсия"
numberType="percent" numberType="percent"
general={data.Conversion || { 0: 0 }} general={ Object.entries(data.Conversion).filter(([, v]) => v > 0).reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {}) || { 0: 0 }}
color={COLORS[2]} color={COLORS[2]}
/> />
<GeneralItem <GeneralItem
title="Среднее время прохождения квиза" title="Среднее время прохождения квиза"
numberType="percent" numberType="time"
general={data.AvTime || { 0: 0 }} general={Object.entries(data.AvTime).filter(([, v]) => v > 0).reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {}) || { 0: 0 }}
color={COLORS[3]} color={COLORS[3]}
/> />
</Box> </Box>

@ -4,8 +4,8 @@ import { CustomTab } from "./CustomTab";
type TabsProps = { type TabsProps = {
names: string[]; names: string[];
items: string[]; items: string[];
selectedItem: "count" | "day" | "dop"; selectedItem: "day" | "count" | "dop";
setSelectedItem: (num: "count" | "day" | "dop") => void; setSelectedItem: (num: "day" | "count" | "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" | "dop") => { onChange={(event, newValue: "day" | "count" | "dop") => {
setSelectedItem(newValue); setSelectedItem(newValue);
}} }}
variant="scrollable" variant="scrollable"

@ -32,8 +32,8 @@ import { createTariffElements } from "./tariffsUtils/createTariffElements";
import { currencyFormatter } from "./tariffsUtils/currencyFormatter"; import { currencyFormatter } from "./tariffsUtils/currencyFormatter";
const StepperText: Record<string, string> = { const StepperText: Record<string, string> = {
count: "Тарифы на объём",
day: "Тарифы на время", day: "Тарифы на время",
count: "Тарифы на объём",
dop: "Доп. услуги", dop: "Доп. услуги",
}; };
@ -50,7 +50,9 @@ function TariffPage() {
const [discounts, setDiscounts] = useState(); const [discounts, setDiscounts] = useState();
const [openModal, setOpenModal] = useState({}); const [openModal, setOpenModal] = useState({});
const [cash, setCash] = useState("0"); const [cash, setCash] = useState("0");
const [selectedItem, setSelectedItem] = useState<"count" | "day">("count"); const [selectedItem, setSelectedItem] = useState<"count" | "day" | "dop">(
"day",
);
const { isTestServer } = useDomainDefine(); const { isTestServer } = useDomainDefine();
const [promocodeField, setPromocodeField] = useState<string>(""); const [promocodeField, setPromocodeField] = useState<string>("");
@ -297,7 +299,6 @@ function TariffPage() {
justifyContent: "left", justifyContent: "left",
display: "grid", display: "grid",
gap: "40px", gap: "40px",
p: "20px",
gridTemplateColumns: `repeat(auto-fit, minmax(300px, ${ gridTemplateColumns: `repeat(auto-fit, minmax(300px, ${
isTablet ? "436px" : "360px" isTablet ? "436px" : "360px"
}))`, }))`,