фильтрация тарифов шильдика от базовых

This commit is contained in:
Tamara 2024-03-30 21:54:28 +03:00
parent 93ddfb8570
commit af70d8b2c6
17 changed files with 200 additions and 111 deletions

@ -33,7 +33,7 @@ export const getDevices = async (
method: "POST",
url: `${apiUrl}/${quizId}/devices`,
withCredentials: true,
body: {to, from}
body: { to, from },
});
return [devicesResponse];
@ -54,7 +54,7 @@ export const getGeneral = async (
method: "POST",
url: `${apiUrl}/${quizId}/general`,
withCredentials: true,
body: {to, from}
body: { to, from },
});
return [generalResponse];
@ -75,7 +75,7 @@ export const getQuestions = async (
method: "POST",
url: `${apiUrl}/${quizId}/questions`,
withCredentials: true,
body: {to, from}
body: { to, from },
});
return [questionsResponse];

@ -1,15 +1,37 @@
import { Box, SxProps, Theme } from "@mui/material";
export default function ChartLineUp(sx: SxProps<Theme>) {
return (
<Box
sx={sx}
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21 19.5H3V4.5" stroke="#7E2AEA" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
<path d="M19.5 6L12 13.5L9 10.5L3 16.5" stroke="#7E2AEA" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
<path d="M19.5 9.75V6H15.75" stroke="#7E2AEA" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</Box>
);
}
return (
<Box sx={sx}>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M21 19.5H3V4.5"
stroke="#7E2AEA"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M19.5 6L12 13.5L9 10.5L3 16.5"
stroke="#7E2AEA"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M19.5 9.75V6H15.75"
stroke="#7E2AEA"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</Box>
);
}

@ -32,15 +32,19 @@ export default function Analytics() {
const [to, setTo] = useState(null);
const [from, setFrom] = useState(null);
const { devices, general, questions } = useAnalytics({quizId: editQuizId?.toString(), to, from})
const { devices, general, questions } = useAnalytics({
quizId: editQuizId?.toString(),
to,
from,
});
const resetTime = () => {
setTo(null);
setFrom(null);
}
};
useLayoutEffect(() => {
if (editQuizId === undefined) redirect("/list")
}, [editQuizId])
if (editQuizId === undefined) redirect("/list");
}, [editQuizId]);
const theme = useTheme();
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const isMobile = useMediaQuery(theme.breakpoints.down(600));
@ -70,9 +74,9 @@ export default function Analytics() {
handleClose();
}
};
console.log("questions",questions)
console.log("general",general)
console.log("devices",devices)
console.log("questions", questions);
console.log("general", general);
console.log("devices", devices);
const now = moment();
return (
@ -142,8 +146,8 @@ export default function Analytics() {
color: "4D4D4D",
}}
>
value={from}
onChange={(newValue) => setValue(setFrom)}
value={from}
onChange={(newValue) => setValue(setFrom)}
Дата окончания
</Typography>
<DatePicker
@ -177,7 +181,7 @@ export default function Analytics() {
</Box>
<Button
onClick={resetTime}
onClick={resetTime}
variant="outlined"
sx={{
minWidth: isMobile ? "144px" : "180px",
@ -196,9 +200,9 @@ export default function Analytics() {
Сбросить
</Button>
</Box>
<General data={ general }/>
<AnswersStatistics data={ questions }/>
<Devices data={ devices }/>
<General data={general} />
<AnswersStatistics data={questions} />
<Devices data={devices} />
</SectionWrapper>
</>
);

@ -188,9 +188,14 @@ const Pagination = () => {
export const Answers = (props) => {
const theme = useTheme();
console.log(props.data)
console.log(props.data);
if (Object.keys(props.data).length === 0) return <Typography textAlign="center" m="10px 0">нет данных об ответах</Typography>
if (Object.keys(props.data).length === 0)
return (
<Typography textAlign="center" m="10px 0">
нет данных об ответах
</Typography>
);
return (
<Box sx={{ flexGrow: 1 }}>
<Paper

@ -104,7 +104,7 @@ export const Funnel = (props) => {
const theme = useTheme();
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1150));
const isMobile = useMediaQuery(theme.breakpoints.down(850));
console.log(props)
console.log(props);
useEffect(() => {
// const requestFunnel = async () => {
// const [funnelResponse, funnelError] = await getGeneral("14761");
@ -121,7 +121,12 @@ console.log(props)
// requestFunnel();
}, []);
if (Object.keys(props.data).length === 0) return <Typography textAlign="center" m="10px 0">нет данных о разделах</Typography>
if (Object.keys(props.data).length === 0)
return (
<Typography textAlign="center" m="10px 0">
нет данных о разделах
</Typography>
);
return (
<Paper
sx={{
@ -133,7 +138,11 @@ console.log(props)
}}
>
{Object.entries(FUNNEL_MOCK).map(([title, percent], index) => (
<FunnelItem key={title} title={title} percent={index > 0 ? props.data[index-1] : percent} />
<FunnelItem
key={title}
title={title}
percent={index > 0 ? props.data[index - 1] : percent}
/>
))}
</Paper>
);

@ -72,7 +72,12 @@ const Result = ({ title, percent, highlight }: ResultProps) => {
export const Results = (props) => {
const theme = useTheme();
if (Object.keys(props.data).length === 0) return <Typography margin="20px 0 0 0" textAlign="center" m="10px 0">нет данных о результатах</Typography>
if (Object.keys(props.data).length === 0)
return (
<Typography margin="20px 0 0 0" textAlign="center" m="10px 0">
нет данных о результатах
</Typography>
);
return (
<Box>
<Typography

@ -17,7 +17,7 @@ export const AnswersStatistics = (props) => {
const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1150));
const isMobile = useMediaQuery(theme.breakpoints.down(850));
console.log(props)
console.log(props);
return (
<Box sx={{ marginTop: "120px" }}>
@ -59,10 +59,10 @@ console.log(props)
gap: "40px",
}}
>
<Answers data={props.data?.Questions || {}}/>
<Funnel data={props.data?.Funnel || {}}/>
<Answers data={props.data?.Questions || {}} />
<Funnel data={props.data?.Funnel || {}} />
</Box>
<Results data={props.data?.Results || {}}/>
<Results data={props.data?.Results || {}} />
</Box>
);
};

@ -27,8 +27,9 @@ const DEVICES_MOCK: DevicesResponse = {
const Device = ({ title, devices }: DeviceProps) => {
const theme = useTheme();
console.log("devices ", devices)
if (devices === undefined || Object.keys(devices).length === 0) return <Typography>{title} - нет данных</Typography>
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) => ({
id,
value,
@ -97,7 +98,7 @@ const Device = ({ title, devices }: DeviceProps) => {
);
};
export const Devices = ({data = {}}) => {
export const Devices = ({ data = {} }) => {
const [devices, setDevices] = useState<DevicesResponse>(data);
const theme = useTheme();
const isTablet = useMediaQuery(theme.breakpoints.down(1000));

@ -38,11 +38,7 @@ const GeneralItem = ({ title, general, color, numberType }: GeneralProps) => {
: Object.entries(general).reduce(
(total, [key, value]) => total + (value / Number(key)) * 100,
0,
) / Object.keys(general).length
||
Number(0)
;
) / Object.keys(general).length || Number(0);
return (
<Paper
sx={{
@ -69,13 +65,17 @@ const GeneralItem = ({ title, general, color, numberType }: GeneralProps) => {
);
};
export const General = (props:any) => {
export const General = (props: any) => {
const theme = useTheme();
const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const isMobile = useMediaQuery(theme.breakpoints.down(700));
if (Object.keys(props.data).length === 0) return <Typography textAlign="center" m="10px 0">нет данных о ключевых метриках</Typography>
if (Object.keys(props.data).length === 0)
return (
<Typography textAlign="center" m="10px 0">
нет данных о ключевых метриках
</Typography>
);
return (
<Box sx={{ marginTop: "45px" }}>
<Typography
@ -103,25 +103,25 @@ export const General = (props:any) => {
<GeneralItem
title="Открыли квиз"
numberType="sum"
general={props.data.open || {0:0}}
general={props.data.open || { 0: 0 }}
color={COLORS[0]}
/>
<GeneralItem
title="Получено заявок"
numberType="sum"
general={props.data.result || {0:0}}
general={props.data.result || { 0: 0 }}
color={COLORS[1]}
/>
<GeneralItem
title="Конверсия"
numberType="percent"
general={props.data.conversation || {0:0}}
general={props.data.conversation || { 0: 0 }}
color={COLORS[2]}
/>
<GeneralItem
title="Среднее время прохождения квиза"
numberType="percent"
general={props.data.avtime || {0:0}}
general={props.data.avtime || { 0: 0 }}
color={COLORS[3]}
/>
</Box>

@ -28,8 +28,8 @@ export const DraggableList = ({
useEffect(() => {
if (!isLoading && quiz && !filteredQuestions.length) {
console.log("useEffect", quiz)
console.log(Number(quiz.backendId))
console.log("useEffect", quiz);
console.log(Number(quiz.backendId));
createUntypedQuestion(Number(quiz.backendId));
}
}, [quiz, filteredQuestions]);

@ -36,7 +36,7 @@ export default function QuestionsPage({
updateEditSomeQuestion();
}, []);
console.log("quiz", quiz)
console.log("quiz", quiz);
if (!quiz) return null;
return (

@ -4,8 +4,8 @@ import { CustomTab } from "./CustomTab";
type TabsProps = {
names: string[];
items: string[];
selectedItem: "count" | "day";
setSelectedItem: (num: "count" | "day") => void;
selectedItem: "count" | "day" | "dop";
setSelectedItem: (num: "count" | "day" | "dop") => void;
};
export const Tabs = ({
@ -18,7 +18,7 @@ export const Tabs = ({
sx={{ m: "25px" }}
TabIndicatorProps={{ sx: { display: "none" } }}
value={selectedItem}
onChange={(event, newValue: "count" | "day") => {
onChange={(event, newValue: "count" | "day" | "dop") => {
setSelectedItem(newValue);
}}
variant="scrollable"

@ -38,6 +38,7 @@ import { activatePromocode } from "@api/promocode";
const StepperText: Record<string, string> = {
count: "Тарифы на объём",
day: "Тарифы на время",
dop: "Доп. услуги",
};
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() {
const [, logoutError] = await logout();
@ -254,20 +268,54 @@ function TariffPage() {
<Box
sx={{
justifyContent: "left",
display: "grid",
display: selectedItem === "dop" ? "flex" : "grid",
gap: "40px",
p: "20px",
gridTemplateColumns: `repeat(auto-fit, minmax(300px, ${
isTablet ? "436px" : "360px"
}))`,
flexDirection: selectedItem === "dop" ? "column" : undefined,
}}
>
{createTariffElements(
filteredTariffs,
true,
user,
discounts,
openModalHC,
{selectedItem === "day" &&
createTariffElements(
filteredBaseTariffs,
true,
user,
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>
<Modal

@ -193,18 +193,18 @@ export default function QuizCard({
</Button>
<IconButton
onClick={handleStatisticClick}
sx={{
sx={{
height: "44px",
width: "44px",
border: `${theme.palette.brightPurple.main} 1px solid` ,
borderRadius: "6px"
}}
border: `${theme.palette.brightPurple.main} 1px solid`,
borderRadius: "6px",
}}
>
<ChartIcon/>
<ChartIcon />
</IconButton>
<IconButton
onClick={() => onClickCopy(quiz.id)}
sx={{
sx={{
height: "44px",
width: "44px",
borderRadius: "6px",

@ -44,7 +44,7 @@ export const createUntypedQuestion = (
) =>
setProducedState(
(state) => {
console.log("createUntypedQuestion", quizId)
console.log("createUntypedQuestion", quizId);
const newUntypedQuestion = {
id: nanoid(),
quizId,
@ -278,7 +278,7 @@ export const updateQuestion = async <T = AnyTypedQuizQuestion>(
if (!q) return;
if (q.type === null)
throw new Error("Cannot send update request for untyped question");
console.log("отправляемый квешен", q)
console.log("отправляемый квешен", q);
try {
const response = await questionApi.edit(
questionToEditQuestionRequest(replaceEmptyLinesToSpace(q)),
@ -449,8 +449,8 @@ export const createTypedQuestion = async (
requestQueue.enqueue(`createTypedQuestion-${questionId}`, async () => {
const questions = useQuestionsStore.getState().questions;
const question = questions.find((q) => q.id === questionId);
console.log("createTypedQuestion", question)
console.log("createTypedQuestion", question?.quizId)
console.log("createTypedQuestion", question);
console.log("createTypedQuestion", question?.quizId);
if (!question) return;
if (question.type !== null)
throw new Error("Cannot upgrade already typed question");

@ -67,7 +67,7 @@ export const CropModal: FC<Props> = ({
setCropModalImageBlob,
onSaveImageClick,
onClose,
questionId
questionId,
}) => {
const theme = useTheme();
const [percentCrop, setPercentCrop] = useState<PercentCrop>();
@ -297,13 +297,13 @@ export const CropModal: FC<Props> = ({
onChange={(_, newValue) => setDarken(newValue as number)}
/>
</Box>
{questionId !== undefined &&
{questionId !== undefined && (
<IconButton
onClick={() => {
updateQuestion(questionId, (question) => {
question.content.back = null;
question.content.originalBack = null;
})
});
onClose();
}}
sx={{
@ -316,7 +316,7 @@ export const CropModal: FC<Props> = ({
>
<DeleteIcon />
</IconButton>
}
)}
</Box>
<Box
sx={{
@ -351,8 +351,9 @@ export const CropModal: FC<Props> = ({
background: theme.palette.brightPurple.main,
fontSize: "18px",
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",
}}
>

@ -3,36 +3,30 @@ import { useEffect, useState } from "react";
import moment from "moment";
interface Props {
quizId: string;
to: number;
from: number;
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({});
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]);
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 };
}
return { devices, general, questions };
}