194 lines
5.4 KiB
TypeScript
194 lines
5.4 KiB
TypeScript
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";
|
||
|
||
type OwnImageProps = {
|
||
imageUrl?: string;
|
||
questionId: string;
|
||
variantId: string;
|
||
onValidationError: (error: "size" | "type") => void;
|
||
};
|
||
|
||
export const OwnImage = ({ imageUrl, questionId, variantId, onValidationError }: OwnImageProps) => {
|
||
const theme = useTheme();
|
||
const { t } = useTranslation();
|
||
const { quizId, preview } = useQuizStore();
|
||
const { answers, updateAnswer, ownVariants, updateOwnVariant } = useQuizViewStore((state) => state);
|
||
const { enqueueSnackbar } = useSnackbar();
|
||
|
||
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||
const [isUploading, setIsUploading] = useState(false);
|
||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||
|
||
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: file,
|
||
name: file.name,
|
||
preview,
|
||
},
|
||
qid: quizId,
|
||
});
|
||
|
||
const fileId = data!.data.fileIDMap[questionId];
|
||
|
||
// Сохраняем fileId в originalImageUrl
|
||
updateOwnVariant(variantId, "", "", fileId);
|
||
|
||
// Для UI — локальный preview
|
||
const localImageUrl = URL.createObjectURL(file);
|
||
updateAnswer(questionId, `${file.name}|${localImageUrl}`, 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);
|
||
updateAnswer(questionId, "", 0);
|
||
updateOwnVariant(variantId, "", "", "");
|
||
};
|
||
|
||
const imageToDisplay = selectedFile
|
||
? URL.createObjectURL(selectedFile)
|
||
: ownVariantData?.variant.originalImageUrl || 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,
|
||
}}
|
||
>
|
||
<input
|
||
type="file"
|
||
ref={fileInputRef}
|
||
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>
|
||
{(selectedFile || ownVariantData?.variant.originalImageUrl) && (
|
||
<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>
|
||
);
|
||
};
|