особые условия для вывода картинок

This commit is contained in:
Nastya 2025-09-14 14:37:08 +03:00
parent b0ff595f5d
commit f4e8898400
5 changed files with 159 additions and 39 deletions

@ -1,3 +1,4 @@
1.0.4 _ 2025-09-14 _ особые условия для вывода картинок
1.0.3 _ 2025-09-12 _ среднее время не учитывает нули 1.0.3 _ 2025-09-12 _ среднее время не учитывает нули
1.0.2 _ 2025-09-07 _ добавлена автозапись в стейджинг 1.0.2 _ 2025-09-07 _ добавлена автозапись в стейджинг
1.0.1 Страница заявок корректно отображает мультиответ 1.0.1 Страница заявок корректно отображает мультиответ

@ -33,11 +33,17 @@ import { ReactComponent as ResetIcon } from "@icons/Analytics/reset.svg";
import type { Moment } from "moment"; import type { Moment } from "moment";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import type { Quiz } from "@model/quiz/quiz"; import type { Quiz } from "@model/quiz/quiz";
import { useCurrentQuiz } from "@/stores/quizes/hooks";
import { useQuestions } from "@/stores/questions/hooks";
export default function Analytics() { export default function Analytics() {
const { quizes, editQuizId } = useQuizStore();
const [quiz, setQuiz] = useState<Quiz>({} as Quiz);
const quiz = useCurrentQuiz();
const globalQuestions = useQuestions({quizId: quiz?.backendId}).questions;
const [isOpen, setOpen] = useState<boolean>(false); const [isOpen, setOpen] = useState<boolean>(false);
const [isOpenEnd, setOpenEnd] = useState<boolean>(false); const [isOpenEnd, setOpenEnd] = useState<boolean>(false);
const [from, setFrom] = useState<Moment | null>(null); const [from, setFrom] = useState<Moment | null>(null);
@ -47,29 +53,24 @@ export default function Analytics() {
const isMobile = useMediaQuery(theme.breakpoints.down(600)); const isMobile = useMediaQuery(theme.breakpoints.down(600));
const { devices, general, questions, isLoading } = useAnalytics({ const { devices, general, questions, isLoading } = useAnalytics({
ready: Boolean(Object.keys(quiz).length), ready: quiz ? Boolean(Object.keys(quiz).length) : false,
quizId: editQuizId?.toString() || "", quizId: quiz?.backendId?.toString() || "",
from, from,
to, to,
}); });
const resetTime = () => { const resetTime = () => {
setFrom(moment(new Date(quiz.created_at))); setFrom(moment(new Date(quiz?.created_at)));
setTo(moment().add(1, "days")); setTo(moment().add(1, "days"));
}; };
useEffect(() => { useEffect(() => {
if (quizes.length > 0) { if (quiz) setFrom(moment(new Date(quiz?.created_at)));
const quiz = quizes.find((q) => q.backendId === editQuizId); }, [quiz]);
if (quiz === undefined) throw new Error("Не удалось получить квиз");
setQuiz(quiz);
setFrom(moment(new Date(quiz.created_at)));
}
}, [quizes]);
useEffect(() => { useEffect(() => {
const getData = async (): Promise<void> => { const getData = async (): Promise<void> => {
if (editQuizId !== null) { if (quiz?.backendId !== null) {
const [gottenQuizes, gottenQuizesError] = await quizApi.getList(); const [gottenQuizes, gottenQuizesError] = await quizApi.getList();
if (gottenQuizesError) { if (gottenQuizesError) {
@ -86,8 +87,8 @@ export default function Analytics() {
}, []); }, []);
useLayoutEffect(() => { useLayoutEffect(() => {
if (editQuizId === undefined) redirect("/list"); if (quiz?.backendId === undefined) redirect("/list");
}, [editQuizId]); }, [quiz?.backendId]);
const handleClose = () => { const handleClose = () => {
setOpen(false); setOpen(false);
@ -264,7 +265,7 @@ export default function Analytics() {
data={general} data={general}
day={86400 - moment(to).unix() - moment(from).unix() > 0} day={86400 - moment(to).unix() - moment(from).unix() > 0}
/> />
<AnswersStatistics data={questions} /> <AnswersStatistics data={questions} globalQuestions={globalQuestions}/>
<Devices data={devices} /> <Devices data={devices} />
</> </>
)} )}

@ -3,6 +3,7 @@ import type { PaginationRenderItemParams } from "@mui/material";
import { import {
Box, Box,
ButtonBase, ButtonBase,
IconButton,
Input, Input,
LinearProgress, LinearProgress,
Pagination as MuiPagination, 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 { ReactComponent as RightArrowIcon } from "@icons/Analytics/rightArrow.svg";
import { extractOrder } from "@utils/extractOrder"; import { extractOrder } from "@utils/extractOrder";
import { parseTitle } from "../utils/parseTitle"; 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 = { type AnswerProps = {
title: string; title: string;
@ -28,6 +33,7 @@ type AnswerProps = {
type AnswersProps = { type AnswersProps = {
data: Record<string, Record<string, number>> | null; data: Record<string, Record<string, number>> | null;
globalQuestions: AnyTypedQuizQuestion[];
}; };
type PaginationProps = { type PaginationProps = {
@ -40,8 +46,13 @@ const Answer = ({ title, percent, highlight }: AnswerProps) => {
const theme = useTheme(); const theme = useTheme();
const parsedTitle = parseTitle(title); const parsedTitle = parseTitle(title);
console.log("Привет, я Answer. И вот что я о себе знаю:")
console.log("-------------------------------------------------------------------")
console.log("{ title, percent, highlight }")
console.log({ title, percent, highlight })
return ( return (
<Box sx={{ padding: "15px 25px" }}> <Box sx={{ padding: "15px 25px", width: "100%" }}>
<Box <Box
sx={{ sx={{
position: "relative", position: "relative",
@ -199,7 +210,8 @@ const Pagination = ({ page, setPage, pagesAmount }: PaginationProps) => {
); );
}; };
export const Answers: FC<AnswersProps> = ({ data }) => { export const Answers: FC<AnswersProps> = ({ data, globalQuestions }) => {
const quiz = useCurrentQuiz();
const [page, setPage] = useState<number>(1); const [page, setPage] = useState<number>(1);
const theme = useTheme(); const theme = useTheme();
const answers = useMemo(() => { const answers = useMemo(() => {
@ -209,17 +221,32 @@ export const Answers: FC<AnswersProps> = ({ data }) => {
); );
}, [data]); }, [data]);
const currentAnswer = answers[page - 1]; 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( const percentsSum = Object.values(currentAnswer?.[1] ?? {}).reduce(
(total, item) => (total += item), (total, item) => (total += item),
0, 0,
); );
console.log("currentAnswer")
console.log(currentAnswer)
const currentAnswerExtended = const currentAnswerExtended =
percentsSum >= 100 percentsSum >= 100
? Object.entries(currentAnswer?.[1] ?? {}) ? Object.entries(currentAnswer?.[1] ?? {})
: [ : [
...Object.entries(currentAnswer?.[1] ?? {}), ...Object.entries(currentAnswer?.[1] ?? {}),
["Другое", 100 - percentsSum] as [string, number], ["Другое", 100 - percentsSum] as [string, number],
]; ];
if (!data) { if (!data) {
return ( return (
@ -286,16 +313,92 @@ export const Answers: FC<AnswersProps> = ({ data }) => {
<NextIcon /> <NextIcon />
</ButtonBase> */} </ButtonBase> */}
</Box> </Box>
{currentAnswerExtended.map(([title, percent], index) => ( {currentAnswerExtended.map(([title, percent], index) => {
<Answer console.log("kdgjhskjdfhkhsdgksfdhgjsdhfgkjsfdhgkj")
key={index} console.log("ewrqwrwr")
title={title} console.log("checkFileExtension")
percent={percent} console.log(checkFileExtension(title))
highlight={!index} console.log("quiz?.backendId")
/> console.log(quiz?.backendId)
))} console.log("globalQuestion?.backendId")
</Paper> console.log(globalQuestion?.backendId)
console.log("data")
console.log(data)
if (checkFileExtension(title) && quiz?.backendId && globalQuestion?.backendId) {
return (
<Box
sx={{
display: "inline - flex",
width: "100 %",
alignItems: "center",
padding: "0 48px",
paddingLeft: "26px"
}}
>
<IconButton
// <IconButton target="_blank" href={`/image/${title}`}
sx={{
width: "50px",
height: "40px",
bgcolor: "#e9eaf0",
borderRadius: "5px"
}}
onClick={() => {
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);
}}
>
<InsertDriveFileIcon />
</IconButton>
< Answer
key={index}
title={title}
percent={percent}
highlight={!index}
/>
</Box>
)
} else {
return (
< Answer
key={index}
title={title}
percent={percent}
highlight={!index}
/>
)
}
})}
</Paper >
<Pagination page={page} setPage={setPage} pagesAmount={answers.length} /> <Pagination page={page} setPage={setPage} pagesAmount={answers.length} />
</Box> </Box >
); );
}; };
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;
}

@ -5,12 +5,14 @@ import { QuestionsResponse } from "@api/statistic";
import { FC } from "react"; import { FC } from "react";
import { Funnel } from "./FunnelAnswers/Funnel"; import { Funnel } from "./FunnelAnswers/Funnel";
import { Results } from "./Results"; import { Results } from "./Results";
import { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
type AnswersStatisticsProps = { type AnswersStatisticsProps = {
data: QuestionsResponse | null; data: QuestionsResponse | null;
globalQuestions: AnyTypedQuizQuestion[];
}; };
export const AnswersStatistics: FC<AnswersStatisticsProps> = ({ data }) => { export const AnswersStatistics: FC<AnswersStatisticsProps> = ({ data, globalQuestions }) => {
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));
@ -33,7 +35,7 @@ export const AnswersStatistics: FC<AnswersStatisticsProps> = ({ data }) => {
gap: "40px", gap: "40px",
}} }}
> >
<Answers data={data?.Questions || null} /> <Answers data={data?.Questions || null} globalQuestions={globalQuestions} />
<Funnel data={data?.Funnel || []} funnelData={data?.FunnelData || []} /> <Funnel data={data?.Funnel || []} funnelData={data?.FunnelData || []} />
</Box> </Box>
<Results data={data?.Results || null} /> <Results data={data?.Results || null} />

@ -8,13 +8,20 @@ import { useQuestionsStore } from "./store";
import { useCurrentQuiz } from "@root/quizes/hooks"; import { useCurrentQuiz } from "@root/quizes/hooks";
import { useEffect } from "react"; import { useEffect } from "react";
export function useQuestions() { export function useQuestions({ quizId }: { quizId?: number } = {}) {
const quiz = useCurrentQuiz(); const currentQuiz = useCurrentQuiz();
const { isLoading, error, isValidating } = useSWR( const currentQuizId = quizId ?? currentQuiz?.backendId;
["questions", quiz?.backendId],
const { data, isLoading, error, isValidating } = useSWR(
currentQuizId ? ["questions", currentQuizId] : null,
([, id]) => questionApi.getList({ quiz_id: id }), ([, id]) => questionApi.getList({ quiz_id: id }),
{ {
onSuccess: ([questions]) => setQuestions(questions), onSuccess: (data) => {
// Добавляем проверку на существование данных
if (data && Array.isArray(data[0])) {
setQuestions(data[0]);
}
},
onError: (error) => { onError: (error) => {
const message = isAxiosError<string>(error) const message = isAxiosError<string>(error)
? error.response?.data ?? "" ? error.response?.data ?? ""
@ -25,7 +32,13 @@ export function useQuestions() {
}, },
}, },
); );
const questions = useQuestionsStore((state) => state.questions); const questions = useQuestionsStore((state) => state.questions);
return { questions, isLoading, error, isValidating }; return {
questions: questions || [], // Гарантируем возврат массива
isLoading,
error,
isValidating
};
} }