From f4e8898400eed20f9fd14dbe0bf1c09600a2dacb Mon Sep 17 00:00:00 2001 From: Nastya Date: Sun, 14 Sep 2025 14:37:08 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BE=D1=81=D0=BE=D0=B1=D1=8B=D0=B5=20=D1=83?= =?UTF-8?q?=D1=81=D0=BB=D0=BE=D0=B2=D0=B8=D1=8F=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=B2=D1=8B=D0=B2=D0=BE=D0=B4=D0=B0=20=D0=BA=D0=B0=D1=80=D1=82?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + src/pages/Analytics/Analytics.tsx | 33 ++--- src/pages/Analytics/Answers/Answers.tsx | 133 ++++++++++++++++-- .../Analytics/Answers/AnswersStatistics.tsx | 6 +- src/stores/questions/hooks.ts | 25 +++- 5 files changed, 159 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 299f40d8..65c326e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ +1.0.4 _ 2025-09-14 _ особые условия для вывода картинок 1.0.3 _ 2025-09-12 _ среднее время не учитывает нули 1.0.2 _ 2025-09-07 _ добавлена автозапись в стейджинг 1.0.1 Страница заявок корректно отображает мультиответ diff --git a/src/pages/Analytics/Analytics.tsx b/src/pages/Analytics/Analytics.tsx index 6a5c2751..04feffa9 100644 --- a/src/pages/Analytics/Analytics.tsx +++ b/src/pages/Analytics/Analytics.tsx @@ -33,11 +33,17 @@ import { ReactComponent as ResetIcon } from "@icons/Analytics/reset.svg"; import type { Moment } from "moment"; import type { ReactNode } from "react"; import type { Quiz } from "@model/quiz/quiz"; +import { useCurrentQuiz } from "@/stores/quizes/hooks"; +import { useQuestions } from "@/stores/questions/hooks"; export default function Analytics() { - const { quizes, editQuizId } = useQuizStore(); - const [quiz, setQuiz] = useState({} as Quiz); + + const quiz = useCurrentQuiz(); + const globalQuestions = useQuestions({quizId: quiz?.backendId}).questions; + + + const [isOpen, setOpen] = useState(false); const [isOpenEnd, setOpenEnd] = useState(false); const [from, setFrom] = useState(null); @@ -47,29 +53,24 @@ export default function Analytics() { const isMobile = useMediaQuery(theme.breakpoints.down(600)); const { devices, general, questions, isLoading } = useAnalytics({ - ready: Boolean(Object.keys(quiz).length), - quizId: editQuizId?.toString() || "", + ready: quiz ? Boolean(Object.keys(quiz).length) : false, + quizId: quiz?.backendId?.toString() || "", from, to, }); const resetTime = () => { - setFrom(moment(new Date(quiz.created_at))); + setFrom(moment(new Date(quiz?.created_at))); setTo(moment().add(1, "days")); }; useEffect(() => { - if (quizes.length > 0) { - const quiz = quizes.find((q) => q.backendId === editQuizId); - if (quiz === undefined) throw new Error("Не удалось получить квиз"); - setQuiz(quiz); - setFrom(moment(new Date(quiz.created_at))); - } - }, [quizes]); + if (quiz) setFrom(moment(new Date(quiz?.created_at))); + }, [quiz]); useEffect(() => { const getData = async (): Promise => { - if (editQuizId !== null) { + if (quiz?.backendId !== null) { const [gottenQuizes, gottenQuizesError] = await quizApi.getList(); if (gottenQuizesError) { @@ -86,8 +87,8 @@ export default function Analytics() { }, []); useLayoutEffect(() => { - if (editQuizId === undefined) redirect("/list"); - }, [editQuizId]); + if (quiz?.backendId === undefined) redirect("/list"); + }, [quiz?.backendId]); const handleClose = () => { setOpen(false); @@ -264,7 +265,7 @@ export default function Analytics() { data={general} day={86400 - moment(to).unix() - moment(from).unix() > 0} /> - + )} diff --git a/src/pages/Analytics/Answers/Answers.tsx b/src/pages/Analytics/Answers/Answers.tsx index 270d83b4..d1b88cf7 100644 --- a/src/pages/Analytics/Answers/Answers.tsx +++ b/src/pages/Analytics/Answers/Answers.tsx @@ -3,6 +3,7 @@ import type { PaginationRenderItemParams } from "@mui/material"; import { Box, ButtonBase, + IconButton, Input, LinearProgress, Pagination as MuiPagination, @@ -19,6 +20,10 @@ import { ReactComponent as LeftArrowIcon } from "@icons/Analytics/leftArrow.svg" import { ReactComponent as RightArrowIcon } from "@icons/Analytics/rightArrow.svg"; import { extractOrder } from "@utils/extractOrder"; import { parseTitle } from "../utils/parseTitle"; +import { AnyTypedQuizQuestion } from "@frontend/squzanswerer"; +import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile'; +import { timewebContent, timewebContentFile } from "@/pages/QuizAnswersPage/cardAnswers/helper"; +import { useCurrentQuiz } from "@/stores/quizes/hooks"; type AnswerProps = { title: string; @@ -28,6 +33,7 @@ type AnswerProps = { type AnswersProps = { data: Record> | null; + globalQuestions: AnyTypedQuizQuestion[]; }; type PaginationProps = { @@ -40,8 +46,13 @@ const Answer = ({ title, percent, highlight }: AnswerProps) => { const theme = useTheme(); const parsedTitle = parseTitle(title); + console.log("Привет, я Answer. И вот что я о себе знаю:") + console.log("-------------------------------------------------------------------") + console.log("{ title, percent, highlight }") + console.log({ title, percent, highlight }) + return ( - + { ); }; -export const Answers: FC = ({ data }) => { +export const Answers: FC = ({ data, globalQuestions }) => { + const quiz = useCurrentQuiz(); const [page, setPage] = useState(1); const theme = useTheme(); const answers = useMemo(() => { @@ -209,17 +221,32 @@ export const Answers: FC = ({ data }) => { ); }, [data]); const currentAnswer = answers[page - 1]; + const globalQuestion = globalQuestions.find(e => { + console.log("---") + console.log("Привет, я ищу глоб вопро ", currentAnswer[0].slice(0, -4)) + console.log(" c ", e.title) + console.log("---") + console.log(e.title === currentAnswer[0].slice(0, -4)) + return e.title === currentAnswer[0].slice(0, -4) + }); + + console.log("globalQuestionglobalQuestionglobalQuestionglobalQuestionglobalQuestionglobalQuestion") + console.log(globalQuestion) + const percentsSum = Object.values(currentAnswer?.[1] ?? {}).reduce( (total, item) => (total += item), 0, ); + + console.log("currentAnswer") + console.log(currentAnswer) const currentAnswerExtended = percentsSum >= 100 ? Object.entries(currentAnswer?.[1] ?? {}) : [ - ...Object.entries(currentAnswer?.[1] ?? {}), - ["Другое", 100 - percentsSum] as [string, number], - ]; + ...Object.entries(currentAnswer?.[1] ?? {}), + ["Другое", 100 - percentsSum] as [string, number], + ]; if (!data) { return ( @@ -286,16 +313,92 @@ export const Answers: FC = ({ data }) => { */} - {currentAnswerExtended.map(([title, percent], index) => ( - - ))} - + {currentAnswerExtended.map(([title, percent], index) => { + console.log("kdgjhskjdfhkhsdgksfdhgjsdhfgkjsfdhgkj") + console.log("ewrqwrwr") + console.log("checkFileExtension") + console.log(checkFileExtension(title)) + console.log("quiz?.backendId") + console.log(quiz?.backendId) + console.log("globalQuestion?.backendId") + console.log(globalQuestion?.backendId) + console.log("data") + console.log(data) + if (checkFileExtension(title) && quiz?.backendId && globalQuestion?.backendId) { + return ( + + { + + + + console.log(timewebContentFile(quiz?.qid.toString(), title, globalQuestion?.backendId.toString())) + + const link = document.createElement('a'); + link.href = timewebContentFile(quiz?.qid.toString(), title, globalQuestion?.backendId.toString()); + link.download = title + link.style.display = 'none'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + }} + > + + + < Answer + key={index} + title={title} + percent={percent} + highlight={!index} + /> + + ) + } else { + return ( + < Answer + key={index} + title={title} + percent={percent} + highlight={!index} + /> + ) + } + })} + - + ); }; +function checkFileExtension(filename: string, maxLength = 6) { + if (typeof filename !== 'string') return false; + + // Ищем последнюю точку в строке + const lastDotIndex = filename.lastIndexOf('.'); + + // Если точки нет или она в конце строки - возвращаем false + if (lastDotIndex === -1 || lastDotIndex === filename.length - 1) { + return false; + } + + // Получаем расширение (часть после последней точки) + const extension = filename.slice(lastDotIndex + 1); + + // Проверяем что расширение состоит только из букв и не превышает максимальную длину + return /^[a-zA-Zа-яА-ЯёЁ]+$/.test(extension) && extension.length <= maxLength; +} \ No newline at end of file diff --git a/src/pages/Analytics/Answers/AnswersStatistics.tsx b/src/pages/Analytics/Answers/AnswersStatistics.tsx index 1a9ff469..3929fabd 100644 --- a/src/pages/Analytics/Answers/AnswersStatistics.tsx +++ b/src/pages/Analytics/Answers/AnswersStatistics.tsx @@ -5,12 +5,14 @@ import { QuestionsResponse } from "@api/statistic"; import { FC } from "react"; import { Funnel } from "./FunnelAnswers/Funnel"; import { Results } from "./Results"; +import { AnyTypedQuizQuestion } from "@frontend/squzanswerer"; type AnswersStatisticsProps = { data: QuestionsResponse | null; + globalQuestions: AnyTypedQuizQuestion[]; }; -export const AnswersStatistics: FC = ({ data }) => { +export const AnswersStatistics: FC = ({ data, globalQuestions }) => { const theme = useTheme(); const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1150)); const isMobile = useMediaQuery(theme.breakpoints.down(850)); @@ -33,7 +35,7 @@ export const AnswersStatistics: FC = ({ data }) => { gap: "40px", }} > - + diff --git a/src/stores/questions/hooks.ts b/src/stores/questions/hooks.ts index ed7a0f8b..90075e06 100644 --- a/src/stores/questions/hooks.ts +++ b/src/stores/questions/hooks.ts @@ -8,13 +8,20 @@ import { useQuestionsStore } from "./store"; import { useCurrentQuiz } from "@root/quizes/hooks"; import { useEffect } from "react"; -export function useQuestions() { - const quiz = useCurrentQuiz(); - const { isLoading, error, isValidating } = useSWR( - ["questions", quiz?.backendId], +export function useQuestions({ quizId }: { quizId?: number } = {}) { + const currentQuiz = useCurrentQuiz(); + const currentQuizId = quizId ?? currentQuiz?.backendId; + + const { data, isLoading, error, isValidating } = useSWR( + currentQuizId ? ["questions", currentQuizId] : null, ([, id]) => questionApi.getList({ quiz_id: id }), { - onSuccess: ([questions]) => setQuestions(questions), + onSuccess: (data) => { + // Добавляем проверку на существование данных + if (data && Array.isArray(data[0])) { + setQuestions(data[0]); + } + }, onError: (error) => { const message = isAxiosError(error) ? error.response?.data ?? "" @@ -25,7 +32,13 @@ export function useQuestions() { }, }, ); + const questions = useQuestionsStore((state) => state.questions); - return { questions, isLoading, error, isValidating }; + return { + questions: questions || [], // Гарантируем возврат массива + isLoading, + error, + isValidating + }; }