4 варианта с фичами работают

This commit is contained in:
Nastya 2024-09-12 18:55:25 +03:00
parent 2fc986c88e
commit 00dace0d3e
11 changed files with 292 additions and 54 deletions

@ -1,6 +1,16 @@
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, 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 { 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";
@ -14,18 +24,96 @@ type EmojiVariantProps = {
questionId: string; questionId: string;
variant: QuestionVariant; variant: QuestionVariant;
index: number; 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 { settings } = useQuizSettings();
const answers = useQuizViewStore((state) => state.answers); const answers = useQuizViewStore((state) => state.answers);
const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state); const { updateAnswer, deleteAnswer } = useQuizViewStore((state) => state);
const theme = useTheme(); const theme = useTheme();
const { answer } = answers.find((answer) => answer.questionId === questionId) ?? {};
const onVariantClick = async (event: MouseEvent<HTMLDivElement>) => { const onVariantClick = async (event: MouseEvent<HTMLDivElement>) => {
event.preventDefault(); 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); updateAnswer(questionId, variant.id, variant.points || 0);
if (answer === variant.id) { if (answer === variant.id) {
@ -39,7 +127,7 @@ export const EmojiVariant = ({ variant, index, questionId }: EmojiVariantProps)
sx={{ sx={{
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",
overflow: "hidden", overflow: "hidden",
maxWidth: "317px", maxWidth: "317px",
width: "100%", width: "100%",
@ -85,6 +173,7 @@ export const EmojiVariant = ({ variant, index, questionId }: EmojiVariantProps)
alignItems: variant.answer.length <= 60 ? "center" : "flex-start", alignItems: variant.answer.length <= 60 ? "center" : "flex-start",
position: "relative", position: "relative",
height: "80px", height: "80px",
overflow: "auto",
justifyContent: "center", justifyContent: "center",
"& .MuiFormControlLabel-label": { "& .MuiFormControlLabel-label": {
wordBreak: "break-word", wordBreak: "break-word",
@ -101,16 +190,34 @@ export const EmojiVariant = ({ variant, index, questionId }: EmojiVariantProps)
}} }}
value={index} value={index}
control={ control={
<Radio isMulti ? (
checkedIcon={<RadioCheck color={theme.palette.primary.main} />} <Checkbox
icon={<RadioIcon />} checked={!!answer?.includes(variant.id)}
sx={{ position: "absolute", top: "-162px", right: "12px" }} 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={ label={
<Box sx={{ display: "flex", gap: "10px" }}> own ? (
<Typography sx={{ wordBreak: "break-word", lineHeight: "normal" }}>{variant.answer}</Typography> <OwnInput
</Box> 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> </FormControl>

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

@ -1,7 +1,7 @@
import { CheckboxIcon } from "@/assets/icons/Checkbox"; 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, Checkbox, FormControlLabel, Radio, useTheme } from "@mui/material"; import { Box, Checkbox, FormControlLabel, Input, Radio, TextareaAutosize, 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";
@ -12,11 +12,75 @@ type ImagesProps = {
questionId: string; questionId: string;
variant: QuestionVariant; variant: QuestionVariant;
index: number; index: number;
isMulti: boolean;
answer: string | string[] | undefined; answer: string | string[] | undefined;
isMulti: boolean;
own: boolean;
questionLargeCheck: boolean;
ownPlaceholder: string;
}; };
export const ImageVariant = ({ questionId, answer, isMulti, 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 { settings } = useQuizSettings();
const { deleteAnswer, updateAnswer } = useQuizViewStore((state) => state); const { deleteAnswer, updateAnswer } = useQuizViewStore((state) => state);
const theme = useTheme(); const theme = useTheme();
@ -92,6 +156,7 @@ export const ImageVariant = ({ questionId, answer, isMulti, variant, index }: Im
justifyContent: "center", justifyContent: "center",
position: "relative", position: "relative",
height: "80px", height: "80px",
overflow: "auto",
"& .MuiFormControlLabel-label": { "& .MuiFormControlLabel-label": {
wordBreak: "break-word", wordBreak: "break-word",
height: variant.answer.length <= 60 ? undefined : "60px", height: variant.answer.length <= 60 ? undefined : "60px",
@ -130,7 +195,18 @@ export const ImageVariant = ({ questionId, answer, isMulti, variant, index }: Im
/> />
) )
} }
label={variant.answer} label={
own ? (
<OwnInput
questionId={questionId}
variant={variant}
largeCheck={questionLargeCheck}
ownPlaceholder={ownPlaceholder}
/>
) : (
variant.answer
)
}
/> />
</Box> </Box>
); );

@ -16,8 +16,6 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
const isTablet = useRootContainerSize() < 1000; const isTablet = useRootContainerSize() < 1000;
const isMobile = useRootContainerSize() < 500; const isMobile = useRootContainerSize() < 500;
console.log(currentQuestion);
if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question"); if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question");
return ( return (
@ -48,16 +46,24 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
width: "100%", width: "100%",
}} }}
> >
{currentQuestion.content.variants.map((variant, index) => ( {currentQuestion.content.variants
<ImageVariant .filter((v) => {
key={variant.id} if (!v.isOwn) return true;
questionId={currentQuestion.id} return v.isOwn && currentQuestion.content.own;
variant={variant} })
index={index} .map((variant, index) => (
answer={answer} <ImageVariant
isMulti={Boolean(currentQuestion.content.multi)} 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> </Box>
</RadioGroup> </RadioGroup>
</Box> </Box>

@ -25,7 +25,7 @@ interface OwnInputProps {
largeCheck: boolean; largeCheck: boolean;
ownPlaceholder: string; ownPlaceholder: string;
} }
const OwnInput = ({ questionId, variant, largeCheck }: OwnInputProps) => { const OwnInput = ({ questionId, variant, largeCheck, ownPlaceholder }: OwnInputProps) => {
const theme = useTheme(); const theme = useTheme();
const ownVariants = useQuizViewStore((state) => state.ownVariants); const ownVariants = useQuizViewStore((state) => state.ownVariants);
const { updateOwnVariant } = useQuizViewStore((state) => state); const { updateOwnVariant } = useQuizViewStore((state) => state);
@ -34,7 +34,7 @@ const OwnInput = ({ questionId, variant, largeCheck }: OwnInputProps) => {
return largeCheck ? ( return largeCheck ? (
<TextareaAutosize <TextareaAutosize
placeholder="" placeholder={ownPlaceholder}
style={{ style={{
resize: "none", resize: "none",
width: "100%", width: "100%",
@ -53,6 +53,7 @@ const OwnInput = ({ questionId, variant, largeCheck }: OwnInputProps) => {
/> />
) : ( ) : (
<Input <Input
placeholder={ownPlaceholder}
sx={{ sx={{
width: "100%", width: "100%",
fontSize: "18px", fontSize: "18px",
@ -83,7 +84,7 @@ export const VariantItem = ({
variant: QuestionVariant; variant: QuestionVariant;
answer: string | string[] | undefined; answer: string | string[] | undefined;
index: number; index: number;
own?: boolean; own: boolean;
questionLargeCheck: boolean; questionLargeCheck: boolean;
ownPlaceholder: string; ownPlaceholder: string;
}) => { }) => {

@ -86,7 +86,7 @@ export const Variant = ({ currentQuestion }: VariantProps) => {
variant={variant} variant={variant}
answer={answer} answer={answer}
index={index} index={index}
own={variant.isOwn} own={Boolean(variant.isOwn)}
questionLargeCheck={currentQuestion.content.largeCheck} questionLargeCheck={currentQuestion.content.largeCheck}
ownPlaceholder={currentQuestion.content?.ownPlaceholder || ""} ownPlaceholder={currentQuestion.content?.ownPlaceholder || ""}
/> />

@ -26,7 +26,7 @@ interface OwnInputProps {
largeCheck: boolean; largeCheck: boolean;
ownPlaceholder: string; ownPlaceholder: string;
} }
const OwnInput = ({ questionId, variant, largeCheck }: OwnInputProps) => { const OwnInput = ({ questionId, variant, largeCheck, ownPlaceholder }: OwnInputProps) => {
const theme = useTheme(); const theme = useTheme();
const ownVariants = useQuizViewStore((state) => state.ownVariants); const ownVariants = useQuizViewStore((state) => state.ownVariants);
const { updateOwnVariant } = useQuizViewStore((state) => state); const { updateOwnVariant } = useQuizViewStore((state) => state);
@ -35,7 +35,7 @@ const OwnInput = ({ questionId, variant, largeCheck }: OwnInputProps) => {
return largeCheck ? ( return largeCheck ? (
<TextareaAutosize <TextareaAutosize
placeholder="" placeholder={ownPlaceholder}
style={{ style={{
resize: "none", resize: "none",
width: "100%", width: "100%",
@ -54,6 +54,7 @@ const OwnInput = ({ questionId, variant, largeCheck }: OwnInputProps) => {
/> />
) : ( ) : (
<Input <Input
placeholder={ownPlaceholder}
sx={{ sx={{
width: "100%", width: "100%",
fontSize: "18px", fontSize: "18px",

@ -28,8 +28,6 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
const ownVariant = ownVariants.find((variant) => variant.id === 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);
console.log(variant);
useEffect(() => { useEffect(() => {
if (!ownVariant) { if (!ownVariant) {
updateOwnVariant(currentQuestion.id, ""); updateOwnVariant(currentQuestion.id, "");

@ -95,7 +95,7 @@ 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 || !index) { if (index < 0) {
state.ownVariants.push({ state.ownVariants.push({
id, id,
variant: { variant: {

@ -28,6 +28,37 @@ export function sendQuestionAnswer(
}); });
} }
case "emoji": { 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); 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}`);
@ -43,19 +74,25 @@ export function sendQuestionAnswer(
case "images": { case "images": {
if (question.content.multi) { if (question.content.multi) {
const answer = questionAnswer.answer; const answer = questionAnswer.answer;
if (!Array.isArray(answer)) throw new Error("Cannot send answer in select 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 ||
"";
if (moment.isMoment(answer)) throw new Error("Answer is Moment in Variant question");
//Оставляем только выбранные варианты //Оставляем только выбранные варианты
const selectedVariants = question.content.variants.filter((v) => answer.includes(v.id)); const selectedVariants = question.content.variants.filter((v) => answer.includes(v.id));
let answerString = ``; let answerString = ``;
selectedVariants.forEach((variant) => { selectedVariants.forEach((e) => {
const body = JSON.stringify({ if (!e.isOwn) answerString += `\`${e.answer}\`,`;
Image: variant.extendedText,
Description: variant.answer,
});
answerString += `\`${body}\`,`;
}); });
if (question.content.own && selectedVariants.some((v) => v.isOwn)) {
answerString += `\`${ownAnswer}\`,`;
}
answerString = answerString.slice(0, -1); answerString = answerString.slice(0, -1);
return sendAnswer({ return sendAnswer({
@ -133,8 +170,6 @@ export function sendQuestionAnswer(
: ownVariants[ownVariants.findIndex((variant) => variant.id === questionAnswer.answer)]?.variant?.answer || : ownVariants[ownVariants.findIndex((variant) => variant.id === questionAnswer.answer)]?.variant?.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)); const selectedVariants = question.content.variants.filter((v) => answer.includes(v.id));

1
src/ CHANGELOG.md Normal file

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