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

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.2 _ 2025-09-07 _ добавлена автозапись в стейджинг
1.0.1 Страница заявок корректно отображает мультиответ

@ -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<Quiz>({} as Quiz);
const quiz = useCurrentQuiz();
const globalQuestions = useQuestions({quizId: quiz?.backendId}).questions;
const [isOpen, setOpen] = useState<boolean>(false);
const [isOpenEnd, setOpenEnd] = useState<boolean>(false);
const [from, setFrom] = useState<Moment | null>(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<void> => {
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}
/>
<AnswersStatistics data={questions} />
<AnswersStatistics data={questions} globalQuestions={globalQuestions}/>
<Devices data={devices} />
</>
)}

@ -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<string, Record<string, number>> | 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 (
<Box sx={{ padding: "15px 25px" }}>
<Box sx={{ padding: "15px 25px", width: "100%" }}>
<Box
sx={{
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 theme = useTheme();
const answers = useMemo(() => {
@ -209,17 +221,32 @@ export const Answers: FC<AnswersProps> = ({ 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<AnswersProps> = ({ data }) => {
<NextIcon />
</ButtonBase> */}
</Box>
{currentAnswerExtended.map(([title, percent], index) => (
<Answer
key={index}
title={title}
percent={percent}
highlight={!index}
/>
))}
</Paper>
{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 (
<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} />
</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 { Funnel } from "./FunnelAnswers/Funnel";
import { Results } from "./Results";
import { AnyTypedQuizQuestion } from "@frontend/squzanswerer";
type AnswersStatisticsProps = {
data: QuestionsResponse | null;
globalQuestions: AnyTypedQuizQuestion[];
};
export const AnswersStatistics: FC<AnswersStatisticsProps> = ({ data }) => {
export const AnswersStatistics: FC<AnswersStatisticsProps> = ({ 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<AnswersStatisticsProps> = ({ data }) => {
gap: "40px",
}}
>
<Answers data={data?.Questions || null} />
<Answers data={data?.Questions || null} globalQuestions={globalQuestions} />
<Funnel data={data?.Funnel || []} funnelData={data?.FunnelData || []} />
</Box>
<Results data={data?.Results || null} />

@ -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<string>(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
};
}