варианты и картинка умеют в свой ответ

This commit is contained in:
Nastya 2024-09-12 11:43:50 +03:00
parent be376586cc
commit 949364e100
10 changed files with 196 additions and 54 deletions

@ -16,6 +16,9 @@ export const Footer = ({ stepNumber, nextButton, prevButton }: FooterProps) => {
const { questions, settings } = useQuizSettings(); const { questions, settings } = useQuizSettings();
const questionsAmount = questions.filter(({ type }) => type !== "result").length; const questionsAmount = questions.filter(({ type }) => type !== "result").length;
console.log("questions");
console.log(questions);
return ( return (
<Box <Box
sx={{ sx={{
@ -43,7 +46,10 @@ export const Footer = ({ stepNumber, nextButton, prevButton }: FooterProps) => {
<Typography sx={{ color: theme.palette.text.primary }}> <Typography sx={{ color: theme.palette.text.primary }}>
Вопрос {stepNumber} из {questionsAmount} Вопрос {stepNumber} из {questionsAmount}
</Typography> </Typography>
<Stepper activeStep={stepNumber} steps={questionsAmount} /> <Stepper
activeStep={stepNumber}
steps={questionsAmount}
/>
</Box> </Box>
)} )}
{prevButton} {prevButton}

@ -20,8 +20,9 @@ import NextButton from "./tools/NextButton";
import PrevButton from "./tools/PrevButton"; import PrevButton from "./tools/PrevButton";
export default function ViewPublicationPage() { export default function ViewPublicationPage() {
const { settings, recentlyCompleted, quizId, preview, changeFaviconAndTitle } = useQuizSettings(); const { settings, recentlyCompleted, quizId, preview, changeFaviconAndTitle, questions } = useQuizSettings();
const answers = useQuizViewStore((state) => state.answers); const answers = useQuizViewStore((state) => state.answers);
const ownVariants = useQuizViewStore((state) => state.ownVariants);
let currentQuizStep = useQuizViewStore((state) => state.currentQuizStep); let currentQuizStep = useQuizViewStore((state) => state.currentQuizStep);
const { const {
currentQuestion, currentQuestion,
@ -99,7 +100,7 @@ export default function ViewPublicationPage() {
if (preview) return; if (preview) return;
sendQuestionAnswer(quizId, currentQuestion, currentAnswer)?.catch((e) => { sendQuestionAnswer(quizId, currentQuestion, currentAnswer, ownVariants)?.catch((e) => {
enqueueSnackbar("Ошибка при отправке ответа"); enqueueSnackbar("Ошибка при отправке ответа");
console.error("Error sending answer", e); console.error("Error sending answer", e);
}); });

@ -1,6 +1,7 @@
import { CheckboxIcon } from "@/assets/icons/Checkbox";
import type { QuestionVariant } from "@/model/questionTypes/shared"; import type { QuestionVariant } from "@/model/questionTypes/shared";
import { useQuizSettings } from "@contexts/QuizDataContext"; import { useQuizSettings } from "@contexts/QuizDataContext";
import { Box, FormControlLabel, Radio, useTheme } from "@mui/material"; import { Box, Checkbox, FormControlLabel, Radio, useTheme } from "@mui/material";
import { useQuizViewStore } from "@stores/quizView"; import { useQuizViewStore } from "@stores/quizView";
import RadioCheck from "@ui_kit/RadioCheck"; import RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon"; import RadioIcon from "@ui_kit/RadioIcon";
@ -11,35 +12,46 @@ type ImagesProps = {
questionId: string; questionId: string;
variant: QuestionVariant; variant: QuestionVariant;
index: number; index: number;
isMulti: boolean;
answer: string | string[] | undefined;
}; };
export const ImageVariant = ({ questionId, variant, index }: ImagesProps) => { export const ImageVariant = ({ questionId, answer, isMulti, variant, index }: ImagesProps) => {
const { settings } = useQuizSettings(); const { settings } = useQuizSettings();
const answers = useQuizViewStore((state) => state.answers);
const { deleteAnswer, updateAnswer } = useQuizViewStore((state) => state); const { deleteAnswer, updateAnswer } = useQuizViewStore((state) => state);
const theme = useTheme(); const theme = useTheme();
const answer = answers.find((answer) => answer.questionId === questionId)?.answer; const answers = useQuizViewStore((state) => state.answers);
const onVariantClick = async (event: MouseEvent<HTMLDivElement>) => { const onVariantClick = async (event: MouseEvent<HTMLDivElement>) => {
event.preventDefault(); event.preventDefault();
updateAnswer(questionId, variant.id, variant.points || 0); const variantId = variant.id;
if (isMulti) {
const currentAnswer = typeof answer !== "string" ? answer || [] : [];
if (answer === variant.id) { return updateAnswer(
questionId,
currentAnswer.includes(variantId)
? currentAnswer?.filter((item) => item !== variantId)
: [...currentAnswer, variantId],
variant.points || 0
);
}
updateAnswer(questionId, variantId, variant.points || 0);
if (answer === variantId) {
deleteAnswer(questionId); deleteAnswer(questionId);
} }
}; };
console.log("answers");
console.log(answers);
return ( return (
<Box <Box
sx={{ sx={{
cursor: "pointer", cursor: "pointer",
borderRadius: "12px", borderRadius: "12px",
border: `1px solid`, border: `1px solid`,
borderColor: answer === variant.id ? theme.palette.primary.main : "#9A9AAF", borderColor: !!answer?.includes(variant.id) ? theme.palette.primary.main : "#9A9AAF",
"&:hover": { borderColor: theme.palette.primary.main }, "&:hover": { borderColor: theme.palette.primary.main },
background: background:
settings.cfg.design && !quizThemes[settings.cfg.theme].isLight settings.cfg.design && !quizThemes[settings.cfg.theme].isLight
@ -95,15 +107,28 @@ export const ImageVariant = ({ questionId, variant, index }: ImagesProps) => {
}} }}
value={index} value={index}
control={ control={
<Radio isMulti ? (
checkedIcon={<RadioCheck color={theme.palette.primary.main} />} <Checkbox
icon={<RadioIcon />} checked={!!answer?.includes(variant.id)}
sx={{ checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
position: "absolute", icon={<RadioIcon />}
top: "-297px", sx={{
right: 0, position: "absolute",
}} top: "-297px",
/> right: 0,
}}
/>
) : (
<Radio
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
icon={<RadioIcon />}
sx={{
position: "absolute",
top: "-297px",
right: 0,
}}
/>
)
} }
label={variant.answer} label={variant.answer}
/> />

@ -3,6 +3,7 @@ import type { QuizQuestionImages } from "@model/questionTypes/images";
import { Box, RadioGroup, Typography, useTheme } from "@mui/material"; import { Box, RadioGroup, Typography, useTheme } from "@mui/material";
import { createQuizViewStore, useQuizViewStore } from "@stores/quizView"; import { createQuizViewStore, useQuizViewStore } from "@stores/quizView";
import { ImageVariant } from "./ImageVariant"; import { ImageVariant } from "./ImageVariant";
import moment from "moment";
type ImagesProps = { type ImagesProps = {
currentQuestion: QuizQuestionImages; currentQuestion: QuizQuestionImages;
@ -14,10 +15,11 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer; const answer = answers.find(({ questionId }) => questionId === currentQuestion.id)?.answer;
const isTablet = useRootContainerSize() < 1000; const isTablet = useRootContainerSize() < 1000;
const isMobile = useRootContainerSize() < 500; const isMobile = useRootContainerSize() < 500;
const a = createQuizViewStore().getState();
console.log(currentQuestion); console.log(currentQuestion);
console.log("store");
console.log(a); if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question");
return ( return (
<Box> <Box>
<Typography <Typography
@ -52,6 +54,8 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
questionId={currentQuestion.id} questionId={currentQuestion.id}
variant={variant} variant={variant}
index={index} index={index}
answer={answer}
isMulti={Boolean(currentQuestion.content.multi)}
/> />
))} ))}
</Box> </Box>

@ -93,15 +93,10 @@ export const VariantItem = ({
labelPlacement="start" labelPlacement="start"
control={ control={
isMulti ? ( isMulti ? (
<Checkbox <Radio
checked={!!answer?.includes(variant.id)} checked={!!answer?.includes(variant.id)}
checkedIcon={ checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
<CheckboxIcon icon={<RadioIcon />}
checked
color={theme.palette.primary.main}
/>
}
icon={<CheckboxIcon />}
/> />
) : ( ) : (
<Radio <Radio

@ -1,11 +1,12 @@
import type { QuestionVariant } from "@/model/questionTypes/shared"; import type { QuestionVariant } from "@/model/questionTypes/shared";
import { useQuizSettings } from "@contexts/QuizDataContext"; import { useQuizSettings } from "@contexts/QuizDataContext";
import { FormControlLabel, Radio, useTheme } from "@mui/material"; import { FormControlLabel, TextareaAutosize, Radio, useTheme, Box, Input } from "@mui/material";
import { useQuizViewStore } from "@stores/quizView"; import { useQuizViewStore } from "@stores/quizView";
import RadioCheck from "@ui_kit/RadioCheck"; import RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon"; import RadioIcon from "@ui_kit/RadioIcon";
import { quizThemes } from "@utils/themes/Publication/themePublication"; import { quizThemes } from "@utils/themes/Publication/themePublication";
import type { MouseEvent } from "react"; import type { MouseEvent } from "react";
import { useDebouncedCallback } from "use-debounce";
type VarimgVariantProps = { type VarimgVariantProps = {
questionId: string; questionId: string;
@ -13,17 +14,75 @@ type VarimgVariantProps = {
index: number; index: number;
isSending: boolean; isSending: boolean;
setIsSending: (isSending: boolean) => void; setIsSending: (isSending: boolean) => void;
questionOwn: boolean;
questionLargeCheck: boolean;
isMulti: boolean;
answer: string | string[] | undefined;
}; };
export const VarimgVariant = ({ questionId, variant, index, isSending, setIsSending }: VarimgVariantProps) => { interface OwnInputProps {
questionId: string;
variant: QuestionVariant;
largeCheck: boolean;
}
const OwnInput = ({ questionId, variant, largeCheck }: OwnInputProps) => {
const theme = useTheme();
const ownVariants = useQuizViewStore((state) => state.ownVariants);
const { updateOwnVariant } = useQuizViewStore((state) => state);
const ownAnswer = ownVariants[ownVariants.findIndex((v) => v.id === variant.id)].variant.answer || "";
return largeCheck ? (
<TextareaAutosize
placeholder=""
style={{
resize: "none",
width: "100%",
fontSize: "16px",
color: theme.palette.text.primary,
letterSpacing: "-0.4px",
wordSpacing: "-3px",
border: "none",
outline: "0px none",
}}
value={ownAnswer}
onClick={(e: React.MouseEvent<HTMLTextAreaElement>) => e.stopPropagation()}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
updateOwnVariant(variant.id, e.target.value);
}}
/>
) : (
<Input
sx={{
width: "100%",
fontSize: "18px",
color: theme.palette.text.primary,
}}
value={ownAnswer}
disableUnderline
onClick={(e: React.MouseEvent<HTMLElement>) => e.stopPropagation()}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
updateOwnVariant(variant.id, e.target.value);
}}
/>
);
};
export const VarimgVariant = ({
questionId,
variant,
index,
isSending,
setIsSending,
questionOwn,
questionLargeCheck,
answer,
}: VarimgVariantProps) => {
const { settings } = useQuizSettings(); const { settings } = useQuizSettings();
const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state); const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state);
const answers = useQuizViewStore((state) => state.answers);
const theme = useTheme(); const theme = useTheme();
const { answer } = answers.find((answer) => answer.questionId === questionId) ?? {};
const sendVariant = async (event: MouseEvent<HTMLLabelElement>) => { const sendVariant = async (event: MouseEvent<HTMLLabelElement>) => {
event.preventDefault(); event.preventDefault();
@ -61,6 +120,7 @@ export const VarimgVariant = ({ questionId, variant, index, isSending, setIsSend
height: variant.answer.length <= 60 ? undefined : "60px", height: variant.answer.length <= 60 ? undefined : "60px",
overflow: "auto", overflow: "auto",
lineHeight: "normal", lineHeight: "normal",
width: "100%",
"&::-webkit-scrollbar": { width: "4px" }, "&::-webkit-scrollbar": { width: "4px" },
"&::-webkit-scrollbar-thumb": { backgroundColor: "#b8babf" }, "&::-webkit-scrollbar-thumb": { backgroundColor: "#b8babf" },
}, },
@ -71,7 +131,17 @@ export const VarimgVariant = ({ questionId, variant, index, isSending, setIsSend
labelPlacement="start" labelPlacement="start"
value={index} value={index}
onClick={sendVariant} onClick={sendVariant}
label={variant.answer} label={
variant?.isOwn ? (
<OwnInput
questionId={questionId}
variant={variant}
largeCheck={questionLargeCheck}
/>
) : (
variant.answer
)
}
control={ control={
<Radio <Radio
checkedIcon={<RadioCheck color={theme.palette.primary.main} />} checkedIcon={<RadioCheck color={theme.palette.primary.main} />}

@ -1,4 +1,4 @@
import { useState } from "react"; import { useEffect, useState } from "react";
import { Box, RadioGroup, Typography, useTheme } from "@mui/material"; import { Box, RadioGroup, Typography, useTheme } from "@mui/material";
import { VarimgVariant } from "./VarimgVariant"; import { VarimgVariant } from "./VarimgVariant";
@ -9,6 +9,7 @@ import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
import BlankImage from "@icons/BlankImage"; import BlankImage from "@icons/BlankImage";
import type { QuizQuestionVarImg } from "@model/questionTypes/varimg"; import type { QuizQuestionVarImg } from "@model/questionTypes/varimg";
import moment from "moment";
type VarimgProps = { type VarimgProps = {
currentQuestion: QuizQuestionVarImg; currentQuestion: QuizQuestionVarImg;
@ -17,13 +18,25 @@ type VarimgProps = {
export const Varimg = ({ currentQuestion }: VarimgProps) => { export const Varimg = ({ currentQuestion }: VarimgProps) => {
const [isSending, setIsSending] = useState<boolean>(false); const [isSending, setIsSending] = useState<boolean>(false);
const answers = useQuizViewStore((state) => state.answers); const answers = useQuizViewStore((state) => state.answers);
const ownVariants = useQuizViewStore((state) => state.ownVariants);
const updateOwnVariant = useQuizViewStore((state) => state.updateOwnVariant);
const theme = useTheme(); const theme = useTheme();
const isMobile = useRootContainerSize() < 650; const isMobile = useRootContainerSize() < 650;
const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {}; 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 variant = currentQuestion.content.variants.find(({ id }) => answer === id);
useEffect(() => {
if (!ownVariant) {
updateOwnVariant(currentQuestion.id, "");
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question");
return ( return (
<Box> <Box>
<Typography <Typography
@ -72,6 +85,10 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
isSending={isSending} isSending={isSending}
setIsSending={setIsSending} setIsSending={setIsSending}
index={index} index={index}
questionOwn={currentQuestion.content.own}
questionLargeCheck={currentQuestion.content.largeCheck}
isMulti={Boolean(currentQuestion.content.multi)}
answer={answer}
/> />
))} ))}
</Box> </Box>

@ -43,6 +43,8 @@ export type QuestionVariant = {
hints: string; hints: string;
/** Дополнительное поле для текста, emoji, ссылки на картинку */ /** Дополнительное поле для текста, emoji, ссылки на картинку */
extendedText: string; extendedText: string;
isOwn?: boolean;
isMulti?: boolean;
/** Оригинал изображения (до кропа) */ /** Оригинал изображения (до кропа) */
originalImageUrl: string; originalImageUrl: string;
points?: number; points?: number;

@ -14,7 +14,7 @@ export type QuestionAnswer = {
answer: Answer; answer: Answer;
}; };
type OwnVariant = { export type OwnVariant = {
id: string; id: string;
variant: QuestionVariant; variant: QuestionVariant;
}; };
@ -57,10 +57,7 @@ export const createQuizViewStore = () =>
updateAnswer(questionId, answer, points) { updateAnswer(questionId, answer, points) {
set( set(
(state) => { (state) => {
console.log(state);
const index = state.answers.findIndex((answer) => questionId === answer.questionId); const index = state.answers.findIndex((answer) => questionId === answer.questionId);
console.log("state");
console.log(state.answers);
if (index < 0) { if (index < 0) {
state.answers.push({ questionId, answer }); state.answers.push({ questionId, answer });
@ -98,11 +95,11 @@ export const createQuizViewStore = () =>
(state) => { (state) => {
const index = state.ownVariants.findIndex((variant) => variant.id === id); const index = state.ownVariants.findIndex((variant) => variant.id === id);
if (index < 0) { if (index < 0 || !index) {
state.ownVariants.push({ state.ownVariants.push({
id, id,
variant: { variant: {
id: nanoid(), id: id,
answer, answer,
extendedText: "", extendedText: "",
hints: "", hints: "",

@ -1,13 +1,14 @@
import { sendAnswer } from "@/api/quizRelase"; import { sendAnswer } from "@/api/quizRelase";
import { RealTypedQuizQuestion } from "@/model/questionTypes/shared"; import { RealTypedQuizQuestion } from "@/model/questionTypes/shared";
import { QuestionAnswer } from "@/stores/quizView"; import { OwnVariant, QuestionAnswer, createQuizViewStore } from "@/stores/quizView";
import moment from "moment"; import moment from "moment";
import { notReachable } from "./notReachable"; import { notReachable } from "./notReachable";
export function sendQuestionAnswer( export function sendQuestionAnswer(
quizId: string, quizId: string,
question: RealTypedQuizQuestion, question: RealTypedQuizQuestion,
questionAnswer: QuestionAnswer | undefined questionAnswer: QuestionAnswer | undefined,
ownVariants: OwnVariant[]
) { ) {
if (!questionAnswer) { if (!questionAnswer) {
return sendAnswer({ return sendAnswer({
@ -40,7 +41,32 @@ export function sendQuestionAnswer(
return; return;
} }
case "images": { case "images": {
if (question.content.multi) {
const answer = questionAnswer.answer;
if (!Array.isArray(answer)) throw new Error("Cannot send answer in select question");
//Оставляем только выбранные варианты
const selectedVariants = question.content.variants.filter((v) => answer.includes(v.id));
let answerString = ``;
selectedVariants.forEach((variant) => {
const body = JSON.stringify({
Image: variant.extendedText,
Description: variant.answer,
});
answerString += `\`${body}\`,`;
});
answerString = answerString.slice(0, -1);
return sendAnswer({
questionId: question.id,
body: answerString,
qid: quizId,
});
}
const variant = question.content.variants.find((v) => v.id === questionAnswer.answer); const variant = question.content.variants.find((v) => v.id === questionAnswer.answer);
if (!variant) throw new Error(`Cannot find variant with id ${questionAnswer.answer} in question ${question.id}`); if (!variant) throw new Error(`Cannot find variant with id ${questionAnswer.answer} in question ${question.id}`);
const body = { const body = {
Image: variant.extendedText, Image: variant.extendedText,
@ -101,12 +127,9 @@ export function sendQuestionAnswer(
const answer = questionAnswer.answer; const answer = questionAnswer.answer;
if (!Array.isArray(answer)) throw new Error("Cannot send answer in select question"); if (!Array.isArray(answer)) throw new Error("Cannot send answer in select question");
console.log("до фильтра");
console.log(question.content.variants);
//Оставляем только выбранные варианты //Оставляем только выбранные варианты
const selectedVariants = question.content.variants.filter((v) => answer.includes(v.id)); const selectedVariants = question.content.variants.filter((v) => answer.includes(v.id));
console.log("после фильтра");
console.log(selectedVariants);
let answerString = ``; let answerString = ``;
selectedVariants.forEach((e) => { selectedVariants.forEach((e) => {
answerString += `\`${e.answer}\`,`; answerString += `\`${e.answer}\`,`;
@ -116,7 +139,6 @@ export function sendQuestionAnswer(
return sendAnswer({ return sendAnswer({
questionId: question.id, questionId: question.id,
body: answerString, body: answerString,
// body: selectedVariants.map((v) => v.answer).join(", "),
qid: quizId, qid: quizId,
}); });
} }
@ -132,10 +154,13 @@ export function sendQuestionAnswer(
} }
case "varimg": { case "varimg": {
const variant = question.content.variants.find((v) => v.id === questionAnswer.answer); const variant = question.content.variants.find((v) => v.id === questionAnswer.answer);
const ownAnswer =
ownVariants[ownVariants.findIndex((variant) => variant.id === questionAnswer.answer)].variant.answer || "";
if (!variant) throw new Error(`Cannot find variant with id ${questionAnswer.answer} in question ${question.id}`); if (!variant) throw new Error(`Cannot find variant with id ${questionAnswer.answer} in question ${question.id}`);
const body = { const body = {
Image: variant.extendedText, Image: variant.extendedText,
Description: variant.answer, Description: question.content.own ? ownAnswer : variant.answer,
}; };
if (!body) throw new Error(`Body of answer in question ${question.id} is undefined`); if (!body) throw new Error(`Body of answer in question ${question.id} is undefined`);