кликабельно varimg

This commit is contained in:
Nastya 2025-06-20 21:52:36 +03:00
parent bbe0be2798
commit 311cdedce6
2 changed files with 258 additions and 28 deletions

@ -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<File | null>(null);
const [isUploading, setIsUploading] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(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<HTMLInputElement>) => {
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 (
<Skeleton
variant="rounded"
sx={{ width: "100%", height: "100%", borderRadius: "12px" }}
/>
);
}
return (
<ButtonBase
component="div"
onClick={handleClick}
disabled={isUploading}
sx={{
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
borderRadius: "12px",
transition: "border-color 0.3s, background-color 0.3s",
overflow: "hidden",
position: "relative",
opacity: isUploading ? 0.7 : 1,
"&:hover": {
backgroundColor: "rgba(0, 0, 0, 0.1)",
},
}}
>
<input
type="file"
ref={fileInputRef}
id={`own-image-input-${variantId}`}
onChange={handleFileChange}
accept={ACCEPT_SEND_FILE_TYPES_MAP.picture.join(",")}
hidden
/>
{imageToDisplay ? (
<>
<Box sx={{ width: "100%", height: "100%", position: "relative" }}>
<img
src={imageToDisplay}
alt="Preview"
style={{ width: "100%", height: "100%", objectFit: "cover" }}
/>
</Box>
<IconButton
onClick={handleRemoveImage}
sx={{
position: "absolute",
top: 8,
right: 8,
zIndex: 1,
backgroundColor: "rgba(0, 0, 0, 0.5)",
color: "white",
height: "25px",
width: "25px",
"&:hover": {
backgroundColor: "rgba(0, 0, 0, 0.7)",
},
}}
>
<CloseIcon />
</IconButton>
</>
) : (
<Box
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
opacity: 0.5,
}}
>
<UploadIcon />
<Typography
variant="body2"
color="text.secondary"
sx={{ p: 2, textAlign: "center" }}
>
добавьте свою картинку
</Typography>
</Box>
)}
</ButtonBase>
);
};

@ -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) => {
))}
</Box>
</RadioGroup>
<Box
<ButtonBase
onClick={handlePreviewAreaClick}
disabled={!ownVariantInQuestion}
sx={{
maxWidth: "450px",
width: "100%",
@ -135,34 +158,44 @@ 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 ? (
{(() => {
if (answer) {
const imageUrl = variant?.isOwn && ownImageUrl ? ownImageUrl : choiceImgUrlAnswer;
if (imageUrl) {
return (
<img
key={choiceImgUrlAnswer}
src={choiceImgUrlAnswer}
key={imageUrl}
src={imageUrl}
style={{ width: "100%", height: "100%", objectFit: "cover" }}
alt=""
/>
) : (
<BlankImage />
)
) : choiceImgUrlQuestion !== " " && choiceImgUrlQuestion !== null && choiceImgUrlQuestion.length > 0 ? (
);
}
return <BlankImage />;
}
if (choiceImgUrlQuestion && choiceImgUrlQuestion.trim().length > 0) {
return (
<img
src={choiceImgUrlQuestion}
style={{ width: "100%", height: "100%", objectFit: "cover" }}
alt=""
/>
) : 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")
)}
</Box>
);
}
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");
})()}
</ButtonBase>
</Box>
</Box>
);