Merge branch 'dev' into 'staging'

Dev

See merge request frontend/squzanswerer!170
This commit is contained in:
Nastya 2024-09-12 16:30:01 +00:00
commit 2b163c8a37
19 changed files with 607 additions and 104 deletions

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

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

@ -1,6 +1,16 @@
import type { QuestionVariant } from "@/model/questionTypes/shared";
import { useQuizSettings } from "@contexts/QuizDataContext";
import { Box, FormControl, FormControlLabel, Radio, Typography, useTheme } from "@mui/material";
import {
Box,
Checkbox,
FormControl,
FormControlLabel,
Input,
Radio,
TextareaAutosize,
Typography,
useTheme,
} from "@mui/material";
import { useQuizViewStore } from "@stores/quizView";
import RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon";
@ -14,18 +24,96 @@ type EmojiVariantProps = {
questionId: string;
variant: QuestionVariant;
index: number;
isMulti: boolean;
own: boolean;
questionLargeCheck: boolean;
ownPlaceholder: string;
answer: string | string[] | undefined;
};
export const EmojiVariant = ({ variant, index, questionId }: EmojiVariantProps) => {
interface OwnInputProps {
questionId: string;
variant: QuestionVariant;
largeCheck: boolean;
ownPlaceholder: string;
}
const OwnInput = ({ questionId, variant, largeCheck, ownPlaceholder }: 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 ? (
<Box sx={{ overflow: "auto" }}>
<TextareaAutosize
placeholder={ownPlaceholder}
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);
}}
/>
</Box>
) : (
<Input
placeholder={ownPlaceholder}
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 EmojiVariant = ({
answer,
variant,
index,
questionId,
isMulti,
own,
questionLargeCheck,
ownPlaceholder,
}: EmojiVariantProps) => {
const { settings } = useQuizSettings();
const answers = useQuizViewStore((state) => state.answers);
const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state);
const theme = useTheme();
const { answer } = answers.find((answer) => answer.questionId === questionId) ?? {};
const onVariantClick = async (event: MouseEvent<HTMLDivElement>) => {
event.preventDefault();
const variantId = variant.id;
if (isMulti) {
const currentAnswer = typeof answer !== "string" ? answer || [] : [];
return updateAnswer(
questionId,
currentAnswer.includes(variantId)
? currentAnswer?.filter((item) => item !== variantId)
: [...currentAnswer, variantId],
variant.points || 0
);
}
updateAnswer(questionId, variant.id, variant.points || 0);
if (answer === variant.id) {
@ -39,7 +127,7 @@ export const EmojiVariant = ({ variant, index, questionId }: EmojiVariantProps)
sx={{
borderRadius: "12px",
border: `1px solid`,
borderColor: answer === variant.id ? theme.palette.primary.main : "#9A9AAF",
borderColor: answer?.includes(variant.id) ? theme.palette.primary.main : "#9A9AAF",
overflow: "hidden",
maxWidth: "317px",
width: "100%",
@ -85,6 +173,7 @@ export const EmojiVariant = ({ variant, index, questionId }: EmojiVariantProps)
alignItems: variant.answer.length <= 60 ? "center" : "flex-start",
position: "relative",
height: "80px",
overflow: "auto",
justifyContent: "center",
"& .MuiFormControlLabel-label": {
wordBreak: "break-word",
@ -101,16 +190,34 @@ export const EmojiVariant = ({ variant, index, questionId }: EmojiVariantProps)
}}
value={index}
control={
<Radio
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
icon={<RadioIcon />}
sx={{ position: "absolute", top: "-162px", right: "12px" }}
/>
isMulti ? (
<Checkbox
checked={!!answer?.includes(variant.id)}
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
icon={<RadioIcon />}
sx={{ position: "absolute", top: "-162px", right: "12px" }}
/>
) : (
<Radio
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
icon={<RadioIcon />}
sx={{ position: "absolute", top: "-162px", right: "12px" }}
/>
)
}
label={
<Box sx={{ display: "flex", gap: "10px" }}>
<Typography sx={{ wordBreak: "break-word", lineHeight: "normal" }}>{variant.answer}</Typography>
</Box>
own ? (
<OwnInput
questionId={questionId}
variant={variant}
largeCheck={questionLargeCheck}
ownPlaceholder={ownPlaceholder}
/>
) : (
<Box sx={{ display: "flex", gap: "10px" }}>
<Typography sx={{ wordBreak: "break-word", lineHeight: "normal" }}>{variant.answer}</Typography>
</Box>
)
}
/>
</FormControl>

@ -3,6 +3,7 @@ import { Box, RadioGroup, Typography, useTheme } from "@mui/material";
import { useQuizViewStore } from "@stores/quizView";
import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill";
import { EmojiVariant } from "./EmojiVariant";
import moment from "moment";
polyfillCountryFlagEmojis();
@ -16,6 +17,8 @@ export const Emoji = ({ currentQuestion }: EmojiProps) => {
const theme = useTheme();
const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.id) ?? {};
if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question");
return (
<Box>
<Typography
@ -44,14 +47,24 @@ export const Emoji = ({ currentQuestion }: EmojiProps) => {
}}
>
<Box sx={{ display: "flex", width: "100%", gap: "42px", flexWrap: "wrap" }}>
{currentQuestion.content.variants.map((variant, index) => (
<EmojiVariant
key={variant.id}
questionId={currentQuestion.id}
variant={variant}
index={index}
/>
))}
{currentQuestion.content.variants
.filter((v) => {
if (!v.isOwn) return true;
return v.isOwn && currentQuestion.content.own;
})
.map((variant, index) => (
<EmojiVariant
key={variant.id}
questionId={currentQuestion.id}
variant={variant}
index={index}
isMulti={Boolean(currentQuestion.content.multi)}
own={Boolean(variant.isOwn)}
questionLargeCheck={true}
answer={answer}
ownPlaceholder={currentQuestion.content?.ownPlaceholder || ""}
/>
))}
</Box>
</RadioGroup>
</Box>

@ -1,6 +1,7 @@
import { CheckboxIcon } from "@/assets/icons/Checkbox";
import type { QuestionVariant } from "@/model/questionTypes/shared";
import { useQuizSettings } from "@contexts/QuizDataContext";
import { Box, FormControlLabel, Radio, useTheme } from "@mui/material";
import { Box, Checkbox, FormControlLabel, Input, Radio, TextareaAutosize, useTheme } from "@mui/material";
import { useQuizViewStore } from "@stores/quizView";
import RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon";
@ -11,21 +12,99 @@ type ImagesProps = {
questionId: string;
variant: QuestionVariant;
index: number;
answer: string | string[] | undefined;
isMulti: boolean;
own: boolean;
questionLargeCheck: boolean;
ownPlaceholder: string;
};
export const ImageVariant = ({ questionId, variant, index }: ImagesProps) => {
interface OwnInputProps {
questionId: string;
variant: QuestionVariant;
largeCheck: boolean;
ownPlaceholder: string;
}
const OwnInput = ({ questionId, variant, largeCheck, ownPlaceholder }: 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 ? (
<Box sx={{ overflow: "auto" }}>
<TextareaAutosize
placeholder={ownPlaceholder}
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);
}}
/>
</Box>
) : (
<Input
placeholder={ownPlaceholder}
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 ImageVariant = ({
questionId,
answer,
isMulti,
variant,
index,
own,
questionLargeCheck,
ownPlaceholder,
}: ImagesProps) => {
const { settings } = useQuizSettings();
const answers = useQuizViewStore((state) => state.answers);
const { deleteAnswer, updateAnswer } = useQuizViewStore((state) => state);
const theme = useTheme();
const answer = answers.find((answer) => answer.questionId === questionId)?.answer;
const answers = useQuizViewStore((state) => state.answers);
const onVariantClick = async (event: MouseEvent<HTMLDivElement>) => {
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);
}
};
@ -36,7 +115,7 @@ export const ImageVariant = ({ questionId, variant, index }: ImagesProps) => {
cursor: "pointer",
borderRadius: "12px",
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 },
background:
settings.cfg.design && !quizThemes[settings.cfg.theme].isLight
@ -77,6 +156,7 @@ export const ImageVariant = ({ questionId, variant, index }: ImagesProps) => {
justifyContent: "center",
position: "relative",
height: "80px",
overflow: "auto",
"& .MuiFormControlLabel-label": {
wordBreak: "break-word",
height: variant.answer.length <= 60 ? undefined : "60px",
@ -92,17 +172,41 @@ export const ImageVariant = ({ questionId, variant, index }: ImagesProps) => {
}}
value={index}
control={
<Radio
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
icon={<RadioIcon />}
sx={{
position: "absolute",
top: "-297px",
right: 0,
}}
/>
isMulti ? (
<Checkbox
checked={!!answer?.includes(variant.id)}
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
icon={<RadioIcon />}
sx={{
position: "absolute",
top: "-297px",
right: 0,
}}
/>
) : (
<Radio
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
icon={<RadioIcon />}
sx={{
position: "absolute",
top: "-297px",
right: 0,
}}
/>
)
}
label={
own ? (
<OwnInput
questionId={questionId}
variant={variant}
largeCheck={questionLargeCheck}
ownPlaceholder={ownPlaceholder}
/>
) : (
variant.answer
)
}
label={variant.answer}
/>
</Box>
);

@ -1,8 +1,9 @@
import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
import type { QuizQuestionImages } from "@model/questionTypes/images";
import { Box, RadioGroup, Typography, useTheme } from "@mui/material";
import { useQuizViewStore } from "@stores/quizView";
import { createQuizViewStore, useQuizViewStore } from "@stores/quizView";
import { ImageVariant } from "./ImageVariant";
import moment from "moment";
type ImagesProps = {
currentQuestion: QuizQuestionImages;
@ -15,6 +16,8 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
const isTablet = useRootContainerSize() < 1000;
const isMobile = useRootContainerSize() < 500;
if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question");
return (
<Box>
<Typography
@ -43,14 +46,24 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
width: "100%",
}}
>
{currentQuestion.content.variants.map((variant, index) => (
<ImageVariant
key={variant.id}
questionId={currentQuestion.id}
variant={variant}
index={index}
/>
))}
{currentQuestion.content.variants
.filter((v) => {
if (!v.isOwn) return true;
return v.isOwn && currentQuestion.content.own;
})
.map((variant, index) => (
<ImageVariant
key={variant.id}
questionId={currentQuestion.id}
variant={variant}
index={index}
answer={answer}
isMulti={Boolean(currentQuestion.content.multi)}
own={Boolean(variant.isOwn)}
questionLargeCheck={true}
ownPlaceholder={currentQuestion.content?.ownPlaceholder || ""}
/>
))}
</Box>
</RadioGroup>
</Box>

@ -1,7 +1,16 @@
import { useQuizSettings } from "@contexts/QuizDataContext";
import { CheckboxIcon } from "@icons/Checkbox";
import type { QuestionVariant } from "@model/questionTypes/shared";
import { Checkbox, FormControlLabel, TextField as MuiTextField, Radio, TextFieldProps, useTheme } from "@mui/material";
import {
Checkbox,
FormControlLabel,
Input,
TextField as MuiTextField,
Radio,
TextFieldProps,
TextareaAutosize,
useTheme,
} from "@mui/material";
import { useQuizViewStore } from "@stores/quizView";
import RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon";
@ -10,6 +19,56 @@ import type { FC, MouseEvent } from "react";
const TextField = MuiTextField as unknown as FC<TextFieldProps>;
interface OwnInputProps {
questionId: string;
variant: QuestionVariant;
largeCheck: boolean;
ownPlaceholder: string;
}
const OwnInput = ({ questionId, variant, largeCheck, ownPlaceholder }: 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={ownPlaceholder}
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
placeholder={ownPlaceholder}
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 VariantItem = ({
questionId,
isMulti,
@ -17,13 +76,17 @@ export const VariantItem = ({
answer,
index,
own = false,
questionLargeCheck,
ownPlaceholder,
}: {
isMulti: boolean;
questionId: string;
variant: QuestionVariant;
answer: string | string[] | undefined;
index: number;
own?: boolean;
own: boolean;
questionLargeCheck: boolean;
ownPlaceholder: string;
}) => {
const { settings } = useQuizSettings();
const theme = useTheme();
@ -78,6 +141,7 @@ export const VariantItem = ({
"&:hover": { borderColor: theme.palette.primary.main },
"&.MuiFormControl-root": { width: "100%" },
"& .MuiFormControlLabel-label": {
width: "100%",
wordBreak: "break-word",
height: variant.answer.length <= 60 ? undefined : "60px",
overflow: "auto",
@ -93,15 +157,10 @@ export const VariantItem = ({
labelPlacement="start"
control={
isMulti ? (
<Checkbox
<Radio
checked={!!answer?.includes(variant.id)}
checkedIcon={
<CheckboxIcon
checked
color={theme.palette.primary.main}
/>
}
icon={<CheckboxIcon />}
checkedIcon={<RadioCheck color={theme.palette.primary.main} />}
icon={<RadioIcon />}
/>
) : (
<Radio
@ -110,7 +169,18 @@ export const VariantItem = ({
/>
)
}
label={own ? <TextField label="Другое..." /> : variant.answer}
label={
own ? (
<OwnInput
questionId={questionId}
variant={variant}
largeCheck={questionLargeCheck}
ownPlaceholder={ownPlaceholder}
/>
) : (
variant.answer
)
}
onClick={sendVariant}
/>
);

@ -73,26 +73,24 @@ export const Variant = ({ currentQuestion }: VariantProps) => {
gap: "20px",
}}
>
{currentQuestion.content.variants.map((variant, index) => (
<VariantItem
key={variant.id}
questionId={currentQuestion.id}
isMulti={currentQuestion.content.multi}
variant={variant}
answer={answer}
index={index}
/>
))}
{currentQuestion.content.own && ownVariant && (
<VariantItem
own
questionId={currentQuestion.id}
isMulti={currentQuestion.content.multi}
variant={ownVariant.variant}
answer={answer}
index={currentQuestion.content.variants.length + 2}
/>
)}
{currentQuestion.content.variants
.filter((v) => {
if (!v.isOwn) return true;
return v.isOwn && currentQuestion.content.own;
})
.map((variant, index) => (
<VariantItem
key={variant.id}
questionId={currentQuestion.id}
isMulti={currentQuestion.content.multi}
variant={variant}
answer={answer}
index={index}
own={Boolean(variant.isOwn)}
questionLargeCheck={currentQuestion.content.largeCheck}
ownPlaceholder={currentQuestion.content?.ownPlaceholder || ""}
/>
))}
</Box>
</Group>
{currentQuestion.content.back && currentQuestion.content.back !== " " && (

@ -1,11 +1,12 @@
import type { QuestionVariant } from "@/model/questionTypes/shared";
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 RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon";
import { quizThemes } from "@utils/themes/Publication/themePublication";
import type { MouseEvent } from "react";
import { useDebouncedCallback } from "use-debounce";
type VarimgVariantProps = {
questionId: string;
@ -13,17 +14,77 @@ type VarimgVariantProps = {
index: number;
isSending: boolean;
setIsSending: (isSending: boolean) => void;
questionLargeCheck: boolean;
isMulti: boolean;
answer: string | string[] | undefined;
ownPlaceholder: string;
};
export const VarimgVariant = ({ questionId, variant, index, isSending, setIsSending }: VarimgVariantProps) => {
interface OwnInputProps {
questionId: string;
variant: QuestionVariant;
largeCheck: boolean;
ownPlaceholder: string;
}
const OwnInput = ({ questionId, variant, largeCheck, ownPlaceholder }: 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={ownPlaceholder}
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
placeholder={ownPlaceholder}
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,
questionLargeCheck,
ownPlaceholder,
answer,
}: VarimgVariantProps) => {
const { settings } = useQuizSettings();
const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state);
const answers = useQuizViewStore((state) => state.answers);
const theme = useTheme();
const { answer } = answers.find((answer) => answer.questionId === questionId) ?? {};
const sendVariant = async (event: MouseEvent<HTMLLabelElement>) => {
event.preventDefault();
@ -61,6 +122,7 @@ export const VarimgVariant = ({ questionId, variant, index, isSending, setIsSend
height: variant.answer.length <= 60 ? undefined : "60px",
overflow: "auto",
lineHeight: "normal",
width: "100%",
"&::-webkit-scrollbar": { width: "4px" },
"&::-webkit-scrollbar-thumb": { backgroundColor: "#b8babf" },
},
@ -71,7 +133,18 @@ export const VarimgVariant = ({ questionId, variant, index, isSending, setIsSend
labelPlacement="start"
value={index}
onClick={sendVariant}
label={variant.answer}
label={
variant?.isOwn ? (
<OwnInput
questionId={questionId}
variant={variant}
largeCheck={questionLargeCheck}
ownPlaceholder={ownPlaceholder}
/>
) : (
variant.answer
)
}
control={
<Radio
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 { VarimgVariant } from "./VarimgVariant";
@ -9,6 +9,7 @@ import { useRootContainerSize } from "@contexts/RootContainerWidthContext";
import BlankImage from "@icons/BlankImage";
import type { QuizQuestionVarImg } from "@model/questionTypes/varimg";
import moment from "moment";
type VarimgProps = {
currentQuestion: QuizQuestionVarImg;
@ -17,13 +18,25 @@ type VarimgProps = {
export const Varimg = ({ currentQuestion }: VarimgProps) => {
const [isSending, setIsSending] = useState<boolean>(false);
const answers = useQuizViewStore((state) => state.answers);
const ownVariants = useQuizViewStore((state) => state.ownVariants);
const updateOwnVariant = useQuizViewStore((state) => state.updateOwnVariant);
const theme = useTheme();
const isMobile = useRootContainerSize() < 650;
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);
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 (
<Box>
<Typography
@ -64,16 +77,25 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
"&:active": { color: theme.palette.text.primary },
}}
>
{currentQuestion.content.variants.map((variant, index) => (
<VarimgVariant
key={variant.id}
questionId={currentQuestion.id}
variant={variant}
isSending={isSending}
setIsSending={setIsSending}
index={index}
/>
))}
{currentQuestion.content.variants
.filter((v) => {
if (!v.isOwn) return true;
return v.isOwn && currentQuestion.content.own;
})
.map((variant, index) => (
<VarimgVariant
key={variant.id}
questionId={currentQuestion.id}
variant={variant}
isSending={isSending}
setIsSending={setIsSending}
index={index}
questionLargeCheck={currentQuestion.content.largeCheck}
ownPlaceholder={currentQuestion.content?.ownPlaceholder || ""}
isMulti={Boolean(currentQuestion.content?.multi)}
answer={answer}
/>
))}
</Box>
</RadioGroup>
<Box

@ -20,5 +20,7 @@ export interface QuizQuestionEmoji extends QuizQuestionBase {
back: string | null;
originalBack: string | null;
autofill: boolean;
ownPlaceholder?: string;
isLargeCheck?: boolean;
};
}

@ -28,5 +28,7 @@ export interface QuizQuestionImages extends QuizQuestionBase {
originalBack: string | null;
autofill: boolean;
largeCheck: boolean;
ownPlaceholder?: string;
isLargeCheck?: boolean;
};
}

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

@ -23,5 +23,6 @@ export interface QuizQuestionVariant extends QuizQuestionBase {
back: string | null;
originalBack: string | null;
autofill: boolean;
ownPlaceholder?: string;
};
}

@ -20,5 +20,8 @@ export interface QuizQuestionVarImg extends QuizQuestionBase {
autofill: boolean;
largeCheck: boolean;
replText: string;
/** Чекбокс "Можно несколько" */
multi?: boolean;
ownPlaceholder?: string;
};
}

@ -14,7 +14,7 @@ export type QuestionAnswer = {
answer: Answer;
};
type OwnVariant = {
export type OwnVariant = {
id: string;
variant: QuestionVariant;
};
@ -99,7 +99,7 @@ export const createQuizViewStore = () =>
state.ownVariants.push({
id,
variant: {
id: nanoid(),
id: id,
answer,
extendedText: "",
hints: "",

@ -33,6 +33,8 @@ export function useQuestionFlowControl() {
//Изменение стейта (переменной currentQuestionId) ведёт к пересчёту что же за объект сейчас используется. Мы каждый раз просто ищем в списке
const currentQuestion = sortedQuestions.find((question) => question.id === currentQuestionId) ?? sortedQuestions[0];
// console.log(currentQuestion)
//Индекс текущего вопроса только если квиз линейный
const linearQuestionIndex = //: number | null
currentQuestion && sortedQuestions.every(({ content }) => content.rule.parentId !== "root") // null when branching enabled

@ -1,13 +1,14 @@
import { sendAnswer } from "@/api/quizRelase";
import { RealTypedQuizQuestion } from "@/model/questionTypes/shared";
import { QuestionAnswer } from "@/stores/quizView";
import { OwnVariant, QuestionAnswer, createQuizViewStore } from "@/stores/quizView";
import moment from "moment";
import { notReachable } from "./notReachable";
export function sendQuestionAnswer(
quizId: string,
question: RealTypedQuizQuestion,
questionAnswer: QuestionAnswer | undefined
questionAnswer: QuestionAnswer | undefined,
ownVariants: OwnVariant[]
) {
if (!questionAnswer) {
return sendAnswer({
@ -27,6 +28,37 @@ export function sendQuestionAnswer(
});
}
case "emoji": {
if (question.content.multi) {
const answer = questionAnswer.answer;
const ownVariant = Array.isArray(answer)
? ownVariants[ownVariants.findIndex((variant) => answer.some((a: string) => a === variant.id))]?.variant || ""
: ownVariants[ownVariants.findIndex((variant) => variant.id === questionAnswer.answer)]?.variant || "";
if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question");
//Оставляем только выбранные варианты
const selectedVariants = question.content.variants.filter((v) => answer.includes(v.id));
let answerString = ``;
selectedVariants.forEach((e) => {
if (e.isOwn) {
if (question.content.own && selectedVariants.some((v) => v.isOwn)) {
answerString += `\`${e.extendedText} ${ownVariant?.answer ?? ""}\`,`;
}
} else {
answerString += `\`${e.extendedText} ${e.answer ?? ""}\`,`;
}
});
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);
if (!variant) throw new Error(`Cannot find variant with id ${questionAnswer.answer} in question ${question.id}`);
@ -40,7 +72,38 @@ export function sendQuestionAnswer(
return;
}
case "images": {
if (question.content.multi) {
const answer = questionAnswer.answer;
const ownAnswer = Array.isArray(answer)
? ownVariants[ownVariants.findIndex((variant) => answer.some((a: string) => a === variant.id))]?.variant
?.answer || ""
: ownVariants[ownVariants.findIndex((variant) => variant.id === questionAnswer.answer)]?.variant?.answer ||
"";
if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question");
//Оставляем только выбранные варианты
const selectedVariants = question.content.variants.filter((v) => answer.includes(v.id));
let answerString = ``;
selectedVariants.forEach((e) => {
if (!e.isOwn) answerString += `\`${e.answer}\`,`;
});
if (question.content.own && selectedVariants.some((v) => v.isOwn)) {
answerString += `\`${ownAnswer}\`,`;
}
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);
if (!variant) throw new Error(`Cannot find variant with id ${questionAnswer.answer} in question ${question.id}`);
const body = {
Image: variant.extendedText,
@ -99,13 +162,30 @@ export function sendQuestionAnswer(
case "variant": {
if (question.content.multi) {
const answer = questionAnswer.answer;
if (!Array.isArray(answer)) throw new Error("Cannot send answer in select question");
if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question");
const ownAnswer = Array.isArray(answer)
? ownVariants[ownVariants.findIndex((variant) => answer.some((a: string) => a === variant.id))]?.variant
?.answer || ""
: ownVariants[ownVariants.findIndex((variant) => variant.id === questionAnswer.answer)]?.variant?.answer ||
"";
//Оставляем только выбранные варианты
const selectedVariants = question.content.variants.filter((v) => answer.includes(v.id));
let answerString = ``;
selectedVariants.forEach((e) => {
if (!e.isOwn) answerString += `\`${e.answer}\`,`;
});
if (question.content.own && selectedVariants.some((v) => v.isOwn)) {
answerString += `\`${ownAnswer}\`,`;
}
answerString = answerString.slice(0, -1);
return sendAnswer({
questionId: question.id,
body: selectedVariants.map((v) => v.answer).join(", "),
body: answerString,
qid: quizId,
});
}
@ -121,10 +201,13 @@ export function sendQuestionAnswer(
}
case "varimg": {
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}`);
const body = {
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`);

1
src/ CHANGELOG.md Normal file

@ -0,0 +1 @@
1.0.0 Добавлены фичи "мультиответ", "перенос строки в своём ответе", "свой ответ", "плейсхолдер своего ответа"