From 311cdedce621b4d5f7f047519ec763cd1d9595c1 Mon Sep 17 00:00:00 2001 From: Nastya Date: Fri, 20 Jun 2025 21:52:36 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BA=D0=BB=D0=B8=D0=BA=D0=B0=D0=B1=D0=B5?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D0=BE=20varimg?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../questions/Varimg/OwnVarimgImage.tsx | 197 ++++++++++++++++++ .../questions/Varimg/index.tsx | 89 +++++--- 2 files changed, 258 insertions(+), 28 deletions(-) create mode 100644 lib/components/ViewPublicationPage/questions/Varimg/OwnVarimgImage.tsx diff --git a/lib/components/ViewPublicationPage/questions/Varimg/OwnVarimgImage.tsx b/lib/components/ViewPublicationPage/questions/Varimg/OwnVarimgImage.tsx new file mode 100644 index 0000000..5c734d8 --- /dev/null +++ b/lib/components/ViewPublicationPage/questions/Varimg/OwnVarimgImage.tsx @@ -0,0 +1,197 @@ +import { Box, ButtonBase, IconButton, Typography, useTheme } from "@mui/material"; +import { useState, useRef } from "react"; +import CloseIcon from "@mui/icons-material/Close"; +import { useTranslation } from "react-i18next"; +import { useQuizStore } from "@/stores/useQuizStore"; +import { useQuizViewStore } from "@/stores/quizView"; +import { useSnackbar } from "notistack"; +import { Skeleton } from "@mui/material"; +import UploadIcon from "@/assets/icons/UploadIcon"; +import { sendFile } from "@/api/quizRelase"; +import { ACCEPT_SEND_FILE_TYPES_MAP, MAX_FILE_SIZE } from "../../tools/fileUpload"; + +// Пропсы компонента +export type OwnVarimgImageProps = { + imageUrl?: string; + questionId: string; + variantId: string; + onValidationError: (error: "size" | "type") => void; +}; + +export const OwnVarimgImage = ({ imageUrl, questionId, variantId, onValidationError }: OwnVarimgImageProps) => { + const theme = useTheme(); + const { t } = useTranslation(); + const { quizId, preview } = useQuizStore(); + const { ownVariants, updateOwnVariant, updateAnswer } = useQuizViewStore((state) => state); + const { enqueueSnackbar } = useSnackbar(); + + const [selectedFile, setSelectedFile] = useState(null); + const [isUploading, setIsUploading] = useState(false); + const fileInputRef = useRef(null); + + // Получаем ownVariant для этого варианта + const ownVariantData = ownVariants.find((v) => v.id === variantId); + + // Загрузка файла + const uploadImage = async (file: File) => { + if (isUploading) return; + if (!file) return; + if (file.size > MAX_FILE_SIZE) { + onValidationError("size"); + return; + } + const isFileTypeAccepted = ACCEPT_SEND_FILE_TYPES_MAP.picture.some((fileType) => + file.name.toLowerCase().endsWith(fileType) + ); + if (!isFileTypeAccepted) { + onValidationError("type"); + return; + } + setIsUploading(true); + try { + const data = await sendFile({ + questionId, + body: { file, name: file.name, preview }, + qid: quizId, + }); + const fileId = data?.data.fileIDMap[questionId]; + const localImageUrl = URL.createObjectURL(file); + // @ts-ignore + updateOwnVariant(variantId, "", "", fileId, localImageUrl, file); + updateAnswer(questionId, variantId, 0); + setSelectedFile(file); + } catch (error) { + console.error("Error uploading image:", error); + enqueueSnackbar(t("The answer was not counted")); + } finally { + setIsUploading(false); + } + }; + + // Обработчик выбора файла + const handleFileChange = (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (file) { + uploadImage(file); + } + }; + + // Открытие диалога выбора файла + const handleClick = (e: React.MouseEvent) => { + e.stopPropagation(); + if (fileInputRef.current) fileInputRef.current.value = ""; + fileInputRef.current?.click(); + }; + + // Удаление изображения + const handleRemoveImage = (e: React.MouseEvent) => { + e.stopPropagation(); + setSelectedFile(null); + updateOwnVariant(variantId, "", "", "", ""); + }; + + // Определяем, что показывать + let imageToDisplay: string | null = null; + if (selectedFile) { + imageToDisplay = URL.createObjectURL(selectedFile); + } else if (ownVariantData?.variant.localImageUrl) { + // @ts-ignore + if (ownVariantData.variant.file) { + // @ts-ignore + imageToDisplay = URL.createObjectURL(ownVariantData.variant.file); + } else { + imageToDisplay = ownVariantData.variant.localImageUrl; + } + } else if (imageUrl) { + imageToDisplay = imageUrl; + } + + if (isUploading) { + return ( + + ); + } + + return ( + + + {imageToDisplay ? ( + <> + + Preview + + + + + + ) : ( + + + + добавьте свою картинку + + + )} + + ); +}; diff --git a/lib/components/ViewPublicationPage/questions/Varimg/index.tsx b/lib/components/ViewPublicationPage/questions/Varimg/index.tsx index ac8ef83..4f72535 100644 --- a/lib/components/ViewPublicationPage/questions/Varimg/index.tsx +++ b/lib/components/ViewPublicationPage/questions/Varimg/index.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useState } from "react"; -import { Box, RadioGroup, Typography, useTheme } from "@mui/material"; +import { Box, ButtonBase, RadioGroup, Typography, useTheme } from "@mui/material"; import { VarimgVariant } from "./VarimgVariant"; @@ -30,6 +30,14 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => { const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {}; const ownVariant = ownVariants.find((variant) => variant.id === currentQuestion.id); const variant = currentQuestion.content.variants.find(({ id }) => answer === id); + const ownVariantInQuestion = useMemo( + () => currentQuestion.content.variants.find((v) => v.isOwn), + [currentQuestion.content.variants] + ); + const ownVariantData = ownVariants.find((v) => v.id === answer); + const ownImageUrl = ownVariantData?.variant.file + ? URL.createObjectURL(ownVariantData.variant.file) + : ownVariantData?.variant.localImageUrl; useEffect(() => { if (!ownVariant) { @@ -58,6 +66,19 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => { return currentQuestion.content.back; } }, [variant]); + + const handlePlaceholderClick = () => { + if (ownVariantInQuestion) { + document.getElementById(`own-image-input-${ownVariantInQuestion.id}`)?.click(); + } + }; + + const handlePreviewAreaClick = () => { + if (ownVariantInQuestion) { + document.getElementById(`own-image-input-${ownVariantInQuestion.id}`)?.click(); + } + }; + if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question"); return ( @@ -121,7 +142,9 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => { ))} - { backgroundColor: "#9A9AAF30", color: theme.palette.text.primary, textAlign: "center", + "&:hover": { + backgroundColor: ownVariantInQuestion ? "rgba(0,0,0,0.04)" : "transparent", + }, }} - onClick={(event) => event.preventDefault()} > - {answer ? ( - choiceImgUrlAnswer ? ( - - ) : ( - - ) - ) : choiceImgUrlQuestion !== " " && choiceImgUrlQuestion !== null && choiceImgUrlQuestion.length > 0 ? ( - - ) : currentQuestion.content.replText !== " " && currentQuestion.content.replText.length > 0 ? ( - currentQuestion.content.replText - ) : variant?.extendedText || isMobile ? ( - t("Select an answer option below") - ) : ( - t("Select an answer option on the left") - )} - + {(() => { + if (answer) { + const imageUrl = variant?.isOwn && ownImageUrl ? ownImageUrl : choiceImgUrlAnswer; + if (imageUrl) { + return ( + + ); + } + return ; + } + + if (choiceImgUrlQuestion && choiceImgUrlQuestion.trim().length > 0) { + return ( + + ); + } + + if (currentQuestion.content.replText && currentQuestion.content.replText.trim().length > 0) { + return currentQuestion.content.replText; + } + + return isMobile ? t("Select an answer option below") : t("Select an answer option on the left"); + })()} + );