Обработка ошибки, когда вопросы для аудитории ещё не созданы
This commit is contained in:
parent
c186a04fa5
commit
12a1aab506
@ -93,18 +93,21 @@ export async function getData({ quizId }: { quizId: string }): Promise<{
|
|||||||
if (paudParam) body.auditory = Number(paudParam);
|
if (paudParam) body.auditory = Number(paudParam);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data, headers } = await axios<GetQuizDataResponse>(domain + `/answer/v1.0.0/settings`, {
|
const { data, headers } = await axios<GetQuizDataResponse>(
|
||||||
method: "POST",
|
domain + `/answer/v1.0.0/settings${window.location.search}`,
|
||||||
headers: {
|
{
|
||||||
"X-Sessionkey": SESSIONS,
|
method: "POST",
|
||||||
"Content-Type": "application/json",
|
headers: {
|
||||||
DeviceType: DeviceType,
|
"X-Sessionkey": SESSIONS,
|
||||||
Device: Device,
|
"Content-Type": "application/json",
|
||||||
OS: OSDevice,
|
DeviceType: DeviceType,
|
||||||
Browser: userAgent,
|
Device: Device,
|
||||||
},
|
OS: OSDevice,
|
||||||
data: body,
|
Browser: userAgent,
|
||||||
});
|
},
|
||||||
|
data: body,
|
||||||
|
}
|
||||||
|
);
|
||||||
const sessions = JSON.parse(localStorage.getItem("sessions") || "{}");
|
const sessions = JSON.parse(localStorage.getItem("sessions") || "{}");
|
||||||
|
|
||||||
//Тут ещё проверка на антифрод без парса конфига. Нам не интересно время если не нужно запрещать проходить чаще чем в сутки
|
//Тут ещё проверка на антифрод без парса конфига. Нам не интересно время если не нужно запрещать проходить чаще чем в сутки
|
||||||
@ -133,23 +136,26 @@ export async function getDataSingle({ quizId, page }: { quizId: string; page?: n
|
|||||||
try {
|
try {
|
||||||
// Первый запрос: 1 вопрос + конфиг
|
// Первый запрос: 1 вопрос + конфиг
|
||||||
if (isFirstRequest) {
|
if (isFirstRequest) {
|
||||||
const { data, headers } = await axios<GetQuizDataResponse>(domain + `/answer/v1.0.0/settings`, {
|
const { data, headers } = await axios<GetQuizDataResponse>(
|
||||||
method: "POST",
|
domain + `/answer/v1.0.0/settings${window.location.search}`,
|
||||||
headers: {
|
{
|
||||||
"X-Sessionkey": SESSIONS,
|
method: "POST",
|
||||||
"Content-Type": "application/json",
|
headers: {
|
||||||
DeviceType: DeviceType,
|
"X-Sessionkey": SESSIONS,
|
||||||
Device: Device,
|
"Content-Type": "application/json",
|
||||||
OS: OSDevice,
|
DeviceType: DeviceType,
|
||||||
Browser: userAgent,
|
Device: Device,
|
||||||
},
|
OS: OSDevice,
|
||||||
data: {
|
Browser: userAgent,
|
||||||
quiz_id: quizId,
|
},
|
||||||
limit: 1,
|
data: {
|
||||||
page: 0,
|
quiz_id: quizId,
|
||||||
need_config: true,
|
limit: 1,
|
||||||
},
|
page: 0,
|
||||||
});
|
need_config: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
globalStatus = data.settings.status;
|
globalStatus = data.settings.status;
|
||||||
isFirstRequest = false;
|
isFirstRequest = false;
|
||||||
@ -165,23 +171,26 @@ export async function getDataSingle({ quizId, page }: { quizId: string; page?: n
|
|||||||
|
|
||||||
// Если статус не AI - сразу делаем запрос за всеми вопросами
|
// Если статус не AI - сразу делаем запрос за всеми вопросами
|
||||||
if (globalStatus !== "ai") {
|
if (globalStatus !== "ai") {
|
||||||
const secondResponse = await axios<GetQuizDataResponse>(domain + `/answer/v1.0.0/settings`, {
|
const secondResponse = await axios<GetQuizDataResponse>(
|
||||||
method: "POST",
|
domain + `/answer/v1.0.0/settings${window.location.search}`,
|
||||||
headers: {
|
{
|
||||||
"X-Sessionkey": SESSIONS,
|
method: "POST",
|
||||||
"Content-Type": "application/json",
|
headers: {
|
||||||
DeviceType: DeviceType,
|
"X-Sessionkey": SESSIONS,
|
||||||
Device: Device,
|
"Content-Type": "application/json",
|
||||||
OS: OSDevice,
|
DeviceType: DeviceType,
|
||||||
Browser: userAgent,
|
Device: Device,
|
||||||
},
|
OS: OSDevice,
|
||||||
data: {
|
Browser: userAgent,
|
||||||
quiz_id: quizId,
|
},
|
||||||
limit: 100,
|
data: {
|
||||||
page: 0,
|
quiz_id: quizId,
|
||||||
need_config: false,
|
limit: 100,
|
||||||
},
|
page: 0,
|
||||||
});
|
need_config: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
data: { ...data, items: secondResponse.data.items },
|
data: { ...data, items: secondResponse.data.items },
|
||||||
isRecentlyCompleted: false,
|
isRecentlyCompleted: false,
|
||||||
@ -192,7 +201,7 @@ export async function getDataSingle({ quizId, page }: { quizId: string; page?: n
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Последующие запросы
|
// Последующие запросы
|
||||||
const response = await axios<GetQuizDataResponse>(domain + `/answer/v1.0.0/settings`, {
|
const response = await axios<GetQuizDataResponse>(domain + `/answer/v1.0.0/settings${window.location.search}`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"X-Sessionkey": SESSIONS,
|
"X-Sessionkey": SESSIONS,
|
||||||
@ -418,7 +427,7 @@ type Answer = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function sendFile({ questionId, body, qid }: SendFileParams) {
|
export function sendFile({ questionId, body, qid }: SendFileParams) {
|
||||||
if (body.preview) return;
|
if (body.preview) return Promise.resolve();
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
|
||||||
const file = new File([body.file], body.file.name.replace(/\s/g, "_"));
|
const file = new File([body.file], body.file.name.replace(/\s/g, "_"));
|
||||||
@ -439,7 +448,10 @@ export function sendFile({ questionId, body, qid }: SendFileParams) {
|
|||||||
url: domain + `/answer/v1.0.0/answer`,
|
url: domain + `/answer/v1.0.0/answer`,
|
||||||
body: formData,
|
body: formData,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
});
|
}).then((response) => ({
|
||||||
|
fileName: nameImage,
|
||||||
|
response,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
//форма контактов
|
//форма контактов
|
||||||
|
@ -22,12 +22,10 @@ import { useQuizStore } from "@/stores/useQuizStore";
|
|||||||
|
|
||||||
export default function ViewPublicationPage() {
|
export default function ViewPublicationPage() {
|
||||||
const { settings, recentlyCompleted, quizId, preview, changeFaviconAndTitle } = useQuizStore();
|
const { settings, recentlyCompleted, quizId, preview, changeFaviconAndTitle } = useQuizStore();
|
||||||
const answers = useQuizViewStore((state) => state.answers);
|
const { currentQuestion, currentQuestionStepNumber, currentQuizStep, answers, ownVariants } = useQuizViewStore();
|
||||||
const ownVariants = useQuizViewStore((state) => state.ownVariants);
|
const uploadingFiles = useQuizViewStore((state) => state.uploadingFiles);
|
||||||
let currentQuizStep = useQuizViewStore((state) => state.currentQuizStep);
|
const isFileUploading = Object.values(uploadingFiles).some((isUploading) => isUploading);
|
||||||
const {
|
const {
|
||||||
currentQuestion,
|
|
||||||
currentQuestionStepNumber,
|
|
||||||
nextQuestion,
|
nextQuestion,
|
||||||
isNextButtonEnabled,
|
isNextButtonEnabled,
|
||||||
isPreviousButtonEnabled,
|
isPreviousButtonEnabled,
|
||||||
@ -106,7 +104,7 @@ export default function ViewPublicationPage() {
|
|||||||
}
|
}
|
||||||
nextButton={
|
nextButton={
|
||||||
<NextButton
|
<NextButton
|
||||||
isNextButtonEnabled={settings.status === "ai" || isNextButtonEnabled}
|
isNextButtonEnabled={settings.status === "ai" || (isNextButtonEnabled && !isFileUploading)}
|
||||||
moveToNextQuestion={async () => {
|
moveToNextQuestion={async () => {
|
||||||
if (!preview) {
|
if (!preview) {
|
||||||
await sendQuestionAnswer(quizId, currentQuestion, currentAnswer, ownVariants)?.catch((e) => {
|
await sendQuestionAnswer(quizId, currentQuestion, currentAnswer, ownVariants)?.catch((e) => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { useState } from "react";
|
import { useState, Dispatch, SetStateAction } from "react";
|
||||||
import { Box, ButtonBase, Skeleton, Typography, useTheme } from "@mui/material";
|
import { Box, ButtonBase, Skeleton, Typography, useTheme } from "@mui/material";
|
||||||
import { enqueueSnackbar } from "notistack";
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
|
||||||
@ -16,18 +16,28 @@ import Info from "@icons/Info";
|
|||||||
import UploadIcon from "@icons/UploadIcon";
|
import UploadIcon from "@icons/UploadIcon";
|
||||||
|
|
||||||
import type { QuizQuestionFile } from "@model/questionTypes/file";
|
import type { QuizQuestionFile } from "@model/questionTypes/file";
|
||||||
import type { ModalWarningType } from "./index";
|
|
||||||
import { useQuizStore } from "@/stores/useQuizStore";
|
import { useQuizStore } from "@/stores/useQuizStore";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
export type ModalWarningType = "errorType" | "errorSize" | "picture" | "video" | "audio" | "document" | null;
|
||||||
|
|
||||||
type UploadFileProps = {
|
type UploadFileProps = {
|
||||||
currentQuestion: QuizQuestionFile;
|
currentQuestion: QuizQuestionFile;
|
||||||
setModalWarningType: (modalType: ModalWarningType) => void;
|
setModalWarningType: Dispatch<SetStateAction<ModalWarningType>>;
|
||||||
isSending: boolean;
|
isSending: boolean;
|
||||||
setIsSending: (isSending: boolean) => void;
|
setIsSending: Dispatch<SetStateAction<boolean>>;
|
||||||
|
onFileUpload: (file: File) => Promise<void>;
|
||||||
|
isUploading: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const UploadFile = ({ currentQuestion, setModalWarningType, isSending, setIsSending }: UploadFileProps) => {
|
export const UploadFile = ({
|
||||||
|
currentQuestion,
|
||||||
|
setModalWarningType,
|
||||||
|
isSending,
|
||||||
|
setIsSending,
|
||||||
|
onFileUpload,
|
||||||
|
isUploading,
|
||||||
|
}: UploadFileProps) => {
|
||||||
const { quizId, preview } = useQuizStore();
|
const { quizId, preview } = useQuizStore();
|
||||||
const [isDropzoneHighlighted, setIsDropzoneHighlighted] = useState<boolean>(false);
|
const [isDropzoneHighlighted, setIsDropzoneHighlighted] = useState<boolean>(false);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@ -38,51 +48,57 @@ export const UploadFile = ({ currentQuestion, setModalWarningType, isSending, se
|
|||||||
|
|
||||||
const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer as string;
|
const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer as string;
|
||||||
|
|
||||||
const uploadFile = async (file: File | undefined) => {
|
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
if (isSending) return;
|
const file = event.target.files?.[0];
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
if (file.size > MAX_FILE_SIZE) return setModalWarningType("errorSize");
|
|
||||||
|
|
||||||
const isFileTypeAccepted = ACCEPT_SEND_FILE_TYPES_MAP[currentQuestion.content.type].some((fileType) =>
|
const fileType = file.type.split("/")[0];
|
||||||
file.name.toLowerCase().endsWith(fileType)
|
const fileExtension = file.name.split(".").pop()?.toLowerCase();
|
||||||
);
|
|
||||||
|
|
||||||
if (!isFileTypeAccepted) return setModalWarningType("errorType");
|
if (!ACCEPT_SEND_FILE_TYPES_MAP[currentQuestion.content.type].includes(`.${fileExtension}`)) {
|
||||||
|
setModalWarningType("errorType");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.size > 50 * 1024 * 1024) {
|
||||||
|
setModalWarningType("errorSize");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setIsSending(true);
|
setIsSending(true);
|
||||||
try {
|
try {
|
||||||
const data = await sendFile({
|
await onFileUpload(file);
|
||||||
questionId: currentQuestion.id,
|
} finally {
|
||||||
body: {
|
setIsSending(false);
|
||||||
file: file,
|
|
||||||
name: file.name,
|
|
||||||
preview,
|
|
||||||
},
|
|
||||||
qid: quizId,
|
|
||||||
});
|
|
||||||
await sendAnswer({
|
|
||||||
questionId: currentQuestion.id,
|
|
||||||
body: `${data!.data.fileIDMap[currentQuestion.id]}`,
|
|
||||||
qid: quizId,
|
|
||||||
preview,
|
|
||||||
});
|
|
||||||
|
|
||||||
updateAnswer(currentQuestion.id, `${file.name}|${URL.createObjectURL(file)}`, 0);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
enqueueSnackbar(t("The answer was not counted"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsSending(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDrop = (event: React.DragEvent<HTMLDivElement>) => {
|
const onDrop = async (event: React.DragEvent<HTMLLabelElement>) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
setIsDropzoneHighlighted(false);
|
setIsDropzoneHighlighted(false);
|
||||||
|
|
||||||
const file = event.dataTransfer.files[0];
|
const file = event.dataTransfer.files[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
uploadFile(file);
|
const fileType = file.type.split("/")[0];
|
||||||
|
const fileExtension = file.name.split(".").pop()?.toLowerCase();
|
||||||
|
|
||||||
|
if (!ACCEPT_SEND_FILE_TYPES_MAP[currentQuestion.content.type].includes(`.${fileExtension}`)) {
|
||||||
|
setModalWarningType("errorType");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.size > 50 * 1024 * 1024) {
|
||||||
|
setModalWarningType("errorSize");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSending(true);
|
||||||
|
try {
|
||||||
|
await onFileUpload(file);
|
||||||
|
} finally {
|
||||||
|
setIsSending(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -93,52 +109,48 @@ export const UploadFile = ({ currentQuestion, setModalWarningType, isSending, se
|
|||||||
sx={{ width: "100%", height: "120px", maxWidth: "560px" }}
|
sx={{ width: "100%", height: "120px", maxWidth: "560px" }}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<ButtonBase
|
<Box
|
||||||
component="label"
|
component="label"
|
||||||
sx={{ justifyContent: "flex-start", width: "100%" }}
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
height: "300px",
|
||||||
|
border: "2px dashed",
|
||||||
|
borderColor: isDropzoneHighlighted ? "primary.main" : "grey.300",
|
||||||
|
borderRadius: "12px",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
cursor: "pointer",
|
||||||
|
transition: "all 0.2s",
|
||||||
|
backgroundColor: isDropzoneHighlighted ? "action.hover" : "background.paper",
|
||||||
|
opacity: isSending || isUploading ? 0.7 : 1,
|
||||||
|
pointerEvents: isSending || isUploading ? "none" : "auto",
|
||||||
|
"&:hover": {
|
||||||
|
borderColor: "primary.main",
|
||||||
|
backgroundColor: "action.hover",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onDragEnter={() => setIsDropzoneHighlighted(true)}
|
||||||
|
onDragLeave={() => setIsDropzoneHighlighted(false)}
|
||||||
|
onDragOver={(event) => event.preventDefault()}
|
||||||
|
onDrop={onDrop}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
onChange={({ target }) => uploadFile(target.files?.[0])}
|
onChange={handleFileChange}
|
||||||
hidden
|
hidden
|
||||||
accept={ACCEPT_SEND_FILE_TYPES_MAP[currentQuestion.content.type].join(",")}
|
accept={ACCEPT_SEND_FILE_TYPES_MAP[currentQuestion.content.type].join(",")}
|
||||||
multiple
|
|
||||||
type="file"
|
type="file"
|
||||||
/>
|
/>
|
||||||
<Box
|
<UploadIcon color={isDropzoneHighlighted ? "primary" : "grey"} />
|
||||||
onDragEnter={() => !answer?.split("|")[0] && setIsDropzoneHighlighted(true)}
|
<Typography
|
||||||
onDragLeave={() => setIsDropzoneHighlighted(false)}
|
variant="body1"
|
||||||
onDragOver={(event) => event.preventDefault()}
|
color={isDropzoneHighlighted ? "primary" : "text.secondary"}
|
||||||
onDrop={onDrop}
|
sx={{ mt: 2 }}
|
||||||
sx={{
|
|
||||||
width: "100%",
|
|
||||||
height: isMobile ? undefined : "120px",
|
|
||||||
display: "flex",
|
|
||||||
gap: "50px",
|
|
||||||
justifyContent: "flex-start",
|
|
||||||
alignItems: "center",
|
|
||||||
padding: "33px 44px 33px 55px",
|
|
||||||
backgroundColor: "#F2F3F7",
|
|
||||||
border: `1px solid ${isDropzoneHighlighted ? "red" : "#9A9AAF"}`,
|
|
||||||
borderRadius: "8px",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<UploadIcon />
|
{isUploading ? t("Uploading...") : t("Drop file here or click to upload")}
|
||||||
<Box>
|
</Typography>
|
||||||
<Typography sx={{ color: "#9A9AAF", fontWeight: 500 }}>
|
</Box>
|
||||||
{t(UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type].title)}
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
color: "#9A9AAF",
|
|
||||||
fontSize: "16px",
|
|
||||||
lineHeight: "19px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{t(UPLOAD_FILE_DESCRIPTIONS_MAP[currentQuestion.content.type].description)}
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</ButtonBase>
|
|
||||||
)}
|
)}
|
||||||
<Info
|
<Info
|
||||||
sx={{ width: "40px", height: "40px" }}
|
sx={{ width: "40px", height: "40px" }}
|
||||||
|
@ -11,6 +11,8 @@ import { ACCEPT_SEND_FILE_TYPES_MAP } from "@/components/ViewPublicationPage/too
|
|||||||
|
|
||||||
import type { QuizQuestionFile } from "@model/questionTypes/file";
|
import type { QuizQuestionFile } from "@model/questionTypes/file";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { sendFile } from "@api/quizRelase";
|
||||||
|
import { enqueueSnackbar } from "notistack";
|
||||||
|
|
||||||
export type ModalWarningType = "errorType" | "errorSize" | "picture" | "video" | "audio" | "document" | null;
|
export type ModalWarningType = "errorType" | "errorSize" | "picture" | "video" | "audio" | "document" | null;
|
||||||
|
|
||||||
@ -20,13 +22,46 @@ type FileProps = {
|
|||||||
|
|
||||||
export const File = ({ currentQuestion }: FileProps) => {
|
export const File = ({ currentQuestion }: FileProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const { t } = useTranslation();
|
||||||
const answers = useQuizViewStore((state) => state.answers);
|
const answers = useQuizViewStore((state) => state.answers);
|
||||||
|
const updateAnswer = useQuizViewStore((state) => state.updateAnswer);
|
||||||
|
const setFileUploading = useQuizViewStore((state) => state.setFileUploading);
|
||||||
const [modalWarningType, setModalWarningType] = useState<ModalWarningType>(null);
|
const [modalWarningType, setModalWarningType] = useState<ModalWarningType>(null);
|
||||||
const [isSending, setIsSending] = useState<boolean>(false);
|
const [isSending, setIsSending] = useState<boolean>(false);
|
||||||
|
const [isUploading, setIsUploading] = useState<boolean>(false);
|
||||||
const isMobile = useRootContainerSize() < 500;
|
const isMobile = useRootContainerSize() < 500;
|
||||||
|
|
||||||
const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer as string;
|
const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer as string;
|
||||||
|
|
||||||
|
const handleFileUpload = async (file: File) => {
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsUploading(true);
|
||||||
|
setFileUploading(currentQuestion.id, true);
|
||||||
|
const result = await sendFile({
|
||||||
|
questionId: currentQuestion.id,
|
||||||
|
body: {
|
||||||
|
name: file.name,
|
||||||
|
file,
|
||||||
|
preview: false,
|
||||||
|
},
|
||||||
|
qid: window.location.pathname.split("/").pop() || "",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
updateAnswer(currentQuestion.id, result.fileName, 0);
|
||||||
|
enqueueSnackbar(t("File uploaded successfully"));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
enqueueSnackbar(t("Failed to upload file"));
|
||||||
|
} finally {
|
||||||
|
setIsUploading(false);
|
||||||
|
setFileUploading(currentQuestion.id, false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography
|
<Typography
|
||||||
@ -56,6 +91,8 @@ export const File = ({ currentQuestion }: FileProps) => {
|
|||||||
setModalWarningType={setModalWarningType}
|
setModalWarningType={setModalWarningType}
|
||||||
isSending={isSending}
|
isSending={isSending}
|
||||||
setIsSending={setIsSending}
|
setIsSending={setIsSending}
|
||||||
|
onFileUpload={handleFileUpload}
|
||||||
|
isUploading={isUploading}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{answer && currentQuestion.content.type === "picture" && (
|
{answer && currentQuestion.content.type === "picture" && (
|
||||||
|
@ -1,147 +1,163 @@
|
|||||||
import { QuestionVariant } from "@model/questionTypes/shared";
|
import { QuestionVariant } from "@model/questionTypes/shared";
|
||||||
import { QuizStep } from "@model/settingsData";
|
import { QuizStep } from "@model/settingsData";
|
||||||
import type { Moment } from "moment";
|
import type { Moment } from "moment";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { createContext, useContext } from "react";
|
import { createContext, useContext } from "react";
|
||||||
import { createStore, useStore } from "zustand";
|
import { createStore, useStore } from "zustand";
|
||||||
import { immer } from "zustand/middleware/immer";
|
import { immer } from "zustand/middleware/immer";
|
||||||
import { devtools } from "zustand/middleware";
|
import { devtools } from "zustand/middleware";
|
||||||
|
|
||||||
export type Answer = string | string[] | Moment;
|
export type Answer = string | string[] | Moment;
|
||||||
|
|
||||||
export type QuestionAnswer = {
|
export type QuestionAnswer = {
|
||||||
questionId: string;
|
questionId: string;
|
||||||
answer: Answer;
|
answer: Answer;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OwnVariant = {
|
export type OwnVariant = {
|
||||||
id: string;
|
id: string;
|
||||||
variant: QuestionVariant;
|
variant: QuestionVariant;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface QuizViewStore {
|
interface QuizViewStore {
|
||||||
answers: QuestionAnswer[];
|
answers: QuestionAnswer[];
|
||||||
ownVariants: OwnVariant[];
|
ownVariants: OwnVariant[];
|
||||||
pointsSum: number;
|
pointsSum: number;
|
||||||
points: Record<string, number>;
|
points: Record<string, number>;
|
||||||
currentQuizStep: QuizStep;
|
currentQuizStep: QuizStep;
|
||||||
}
|
uploadingFiles: Record<string, boolean>;
|
||||||
|
}
|
||||||
interface QuizViewActions {
|
|
||||||
updateAnswer: (questionId: string, answer: string | string[] | Moment, points: number) => void;
|
interface QuizViewActions {
|
||||||
deleteAnswer: (questionId: string) => void;
|
updateAnswer: (questionId: string, answer: string | string[] | Moment, points: number) => void;
|
||||||
updateOwnVariant: (id: string, answer: string) => void;
|
deleteAnswer: (questionId: string) => void;
|
||||||
deleteOwnVariant: (id: string) => void;
|
updateOwnVariant: (id: string, answer: string) => void;
|
||||||
setCurrentQuizStep: (step: QuizStep) => void;
|
deleteOwnVariant: (id: string) => void;
|
||||||
}
|
setCurrentQuizStep: (step: QuizStep) => void;
|
||||||
|
setFileUploading: (questionId: string, isUploading: boolean) => void;
|
||||||
export const QuizViewContext = createContext<ReturnType<typeof createQuizViewStore> | null>(null);
|
}
|
||||||
|
|
||||||
export function useQuizViewStore<U>(selector: (state: QuizViewStore & QuizViewActions) => U): U {
|
export const QuizViewContext = createContext<ReturnType<typeof createQuizViewStore> | null>(null);
|
||||||
const store = useContext(QuizViewContext);
|
|
||||||
if (!store) throw new Error("QuizViewStore context is null");
|
export function useQuizViewStore<U>(selector: (state: QuizViewStore & QuizViewActions) => U): U {
|
||||||
|
const store = useContext(QuizViewContext);
|
||||||
return useStore(store, selector);
|
if (!store) throw new Error("QuizViewStore context is null");
|
||||||
}
|
|
||||||
|
return useStore(store, selector);
|
||||||
export const createQuizViewStore = () =>
|
}
|
||||||
createStore<QuizViewStore & QuizViewActions>()(
|
|
||||||
immer(
|
export const createQuizViewStore = () =>
|
||||||
devtools(
|
createStore<QuizViewStore & QuizViewActions>()(
|
||||||
(set, get) => ({
|
immer(
|
||||||
answers: [],
|
devtools(
|
||||||
ownVariants: [],
|
(set, get) => ({
|
||||||
points: {},
|
answers: [],
|
||||||
pointsSum: 0,
|
ownVariants: [],
|
||||||
currentQuizStep: "startpage",
|
points: {},
|
||||||
updateAnswer(questionId, answer, points) {
|
pointsSum: 0,
|
||||||
set(
|
currentQuizStep: "startpage",
|
||||||
(state) => {
|
uploadingFiles: {},
|
||||||
const index = state.answers.findIndex((answer) => questionId === answer.questionId);
|
updateAnswer(questionId, answer, points) {
|
||||||
|
set(
|
||||||
if (index < 0) {
|
(state) => {
|
||||||
state.answers.push({ questionId, answer });
|
const index = state.answers.findIndex((answer) => questionId === answer.questionId);
|
||||||
} else {
|
|
||||||
state.answers[index] = { questionId, answer };
|
if (index < 0) {
|
||||||
}
|
state.answers.push({ questionId, answer });
|
||||||
|
} else {
|
||||||
state.points = { ...state.points, ...{ [questionId]: points } };
|
state.answers[index] = { questionId, answer };
|
||||||
|
}
|
||||||
state.pointsSum = Object.values(state.points).reduce((sum, value) => sum + value);
|
|
||||||
},
|
state.points = { ...state.points, ...{ [questionId]: points } };
|
||||||
false,
|
|
||||||
{
|
state.pointsSum = Object.values(state.points).reduce((sum, value) => sum + value);
|
||||||
type: "updateAnswer",
|
},
|
||||||
questionId,
|
false,
|
||||||
answer,
|
{
|
||||||
points,
|
type: "updateAnswer",
|
||||||
}
|
questionId,
|
||||||
);
|
answer,
|
||||||
},
|
points,
|
||||||
deleteAnswer(questionId) {
|
}
|
||||||
set(
|
);
|
||||||
(state) => {
|
},
|
||||||
state.answers = state.answers.filter((answer) => questionId !== answer.questionId);
|
deleteAnswer(questionId) {
|
||||||
},
|
set(
|
||||||
false,
|
(state) => {
|
||||||
{
|
state.answers = state.answers.filter((answer) => questionId !== answer.questionId);
|
||||||
type: "deleteAnswer",
|
},
|
||||||
questionId,
|
false,
|
||||||
}
|
{
|
||||||
);
|
type: "deleteAnswer",
|
||||||
},
|
questionId,
|
||||||
updateOwnVariant(id, answer) {
|
}
|
||||||
set(
|
);
|
||||||
(state) => {
|
},
|
||||||
const index = state.ownVariants.findIndex((variant) => variant.id === id);
|
updateOwnVariant(id, answer) {
|
||||||
|
set(
|
||||||
if (index < 0) {
|
(state) => {
|
||||||
state.ownVariants.push({
|
const index = state.ownVariants.findIndex((variant) => variant.id === id);
|
||||||
id,
|
|
||||||
variant: {
|
if (index < 0) {
|
||||||
id: id,
|
state.ownVariants.push({
|
||||||
answer,
|
id,
|
||||||
extendedText: "",
|
variant: {
|
||||||
hints: "",
|
id: id,
|
||||||
originalImageUrl: "",
|
answer,
|
||||||
},
|
extendedText: "",
|
||||||
});
|
hints: "",
|
||||||
} else {
|
originalImageUrl: "",
|
||||||
state.ownVariants[index].variant.answer = answer;
|
},
|
||||||
}
|
});
|
||||||
},
|
} else {
|
||||||
false,
|
state.ownVariants[index].variant.answer = answer;
|
||||||
{
|
}
|
||||||
type: "updateOwnVariant",
|
},
|
||||||
id,
|
false,
|
||||||
answer,
|
{
|
||||||
}
|
type: "updateOwnVariant",
|
||||||
);
|
id,
|
||||||
},
|
answer,
|
||||||
deleteOwnVariant(id) {
|
}
|
||||||
set(
|
);
|
||||||
(state) => {
|
},
|
||||||
state.ownVariants = state.ownVariants.filter((variant) => variant.id !== id);
|
deleteOwnVariant(id) {
|
||||||
},
|
set(
|
||||||
false,
|
(state) => {
|
||||||
{
|
state.ownVariants = state.ownVariants.filter((variant) => variant.id !== id);
|
||||||
type: "deleteOwnVariant",
|
},
|
||||||
id,
|
false,
|
||||||
}
|
{
|
||||||
);
|
type: "deleteOwnVariant",
|
||||||
},
|
id,
|
||||||
setCurrentQuizStep(step) {
|
}
|
||||||
set({ currentQuizStep: step }, false, {
|
);
|
||||||
type: "setCurrentQuizStep",
|
},
|
||||||
step,
|
setCurrentQuizStep(step) {
|
||||||
});
|
set({ currentQuizStep: step }, false, {
|
||||||
},
|
type: "setCurrentQuizStep",
|
||||||
}),
|
step,
|
||||||
{
|
});
|
||||||
name: "QuizViewStore-" + nanoid(4),
|
},
|
||||||
enabled: import.meta.env.DEV,
|
setFileUploading(questionId, isUploading) {
|
||||||
trace: import.meta.env.DEV,
|
set(
|
||||||
}
|
(state) => {
|
||||||
)
|
state.uploadingFiles[questionId] = isUploading;
|
||||||
)
|
},
|
||||||
);
|
false,
|
||||||
|
{
|
||||||
|
type: "setFileUploading",
|
||||||
|
questionId,
|
||||||
|
isUploading,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: "QuizViewStore-" + nanoid(4),
|
||||||
|
enabled: import.meta.env.DEV,
|
||||||
|
trace: import.meta.env.DEV,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
@ -53,5 +53,6 @@
|
|||||||
"and": "and",
|
"and": "and",
|
||||||
"Get results": "Get results",
|
"Get results": "Get results",
|
||||||
"Data sent successfully": "Data sent successfully",
|
"Data sent successfully": "Data sent successfully",
|
||||||
"Step": "Step"
|
"Step": "Step",
|
||||||
|
"questions are not ready yet": "There are no questions for the audience yet. Please wait"
|
||||||
}
|
}
|
||||||
|
@ -53,5 +53,6 @@
|
|||||||
"and": "и",
|
"and": "и",
|
||||||
"Get results": "Получить результаты",
|
"Get results": "Получить результаты",
|
||||||
"Data sent successfully": "Данные успешно отправлены",
|
"Data sent successfully": "Данные успешно отправлены",
|
||||||
"Step": "Шаг"
|
"Step": "Шаг",
|
||||||
|
"questions are not ready yet": "Вопросы для аудитории ещё не созданы. Пожалуйста, подождите"
|
||||||
}
|
}
|
||||||
|
@ -53,5 +53,6 @@
|
|||||||
"and": "va",
|
"and": "va",
|
||||||
"Get results": "Natijalarni olish",
|
"Get results": "Natijalarni olish",
|
||||||
"Data sent successfully": "Ma'lumotlar muvaffaqiyatli yuborildi",
|
"Data sent successfully": "Ma'lumotlar muvaffaqiyatli yuborildi",
|
||||||
"Step": "Qadam"
|
"Step": "Qadam",
|
||||||
|
"questions are not ready yet": "Tomoshabinlar uchun hozircha savollar yo'q. Iltimos kuting"
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user