From 2103fe8977960a9d59db5de8e3adc38a857bbf26 Mon Sep 17 00:00:00 2001 From: nflnkr Date: Wed, 15 Nov 2023 21:38:02 +0300 Subject: [PATCH] WIP use new store actions --- package.json | 1 + src/api/question.ts | 4 +- src/constants/default.ts | 26 +- src/model/question/edit.ts | 5 +- src/model/question/question.ts | 4 +- src/model/questionTypes/shared.ts | 16 +- .../AnswerDraggableList/AnswerItem.tsx | 113 +-- .../Questions/AnswerDraggableList/index.tsx | 26 +- src/pages/Questions/ButtonsOptionsAndPict.tsx | 645 ++++++++-------- .../DraggableList/QuestionPageCard.tsx | 62 +- src/pages/Questions/DropDown/DropDown.tsx | 2 +- src/pages/Questions/Emoji/Emoji.tsx | 2 +- .../FormDraggableList/ChooseAnswerModal.tsx | 4 +- .../FormDraggableList/QuestionPageCard.tsx | 6 +- .../Questions/Form/FormTypeQuestions.tsx | 4 +- .../OptionsAndPicture/OptionsAndPicture.tsx | 2 +- .../OptionsPicture/OptionsPicture.tsx | 241 +++--- src/pages/Questions/SwitchQuestionsPage.tsx | 77 +- src/pages/Questions/TypeQuestions.tsx | 164 ++--- src/pages/Questions/UploadImage/index.tsx | 25 +- .../Questions/answerOptions/AnswerOptions.tsx | 183 +++-- .../answerOptions/responseSettings.tsx | 325 ++++----- .../answerOptions/switchAnswerOptions.tsx | 41 +- src/pages/Questions/branchingQuestions.tsx | 688 ++++++++---------- src/pages/Questions/helpQuestions.tsx | 183 +++-- src/stores/questions.ts | 6 +- src/stores/questions/actions.ts | 201 ++++- src/stores/questions/store.ts | 2 + yarn.lock | 5 + 29 files changed, 1516 insertions(+), 1547 deletions(-) diff --git a/package.json b/package.json index b12ec2c0..46011cf8 100755 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "html-to-image": "^1.11.11", "immer": "^10.0.3", "jszip": "^3.10.1", + "nanoid": "^5.0.3", "notistack": "^3.0.1", "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", diff --git a/src/api/question.ts b/src/api/question.ts index 88f6acd7..0afed6ed 100644 --- a/src/api/question.ts +++ b/src/api/question.ts @@ -38,10 +38,10 @@ function editQuestion(body: EditQuestionRequest, signal?: AbortSignal) { }); } -function copyQuestion(copyQuestionBody: CopyQuestionRequest) { +function copyQuestion(questionId: number, quizId: number) { return makeRequest({ url: `${baseUrl}/question/copy`, - body: copyQuestionBody, + body: { id: questionId, quiz_id: quizId }, method: "POST", }); } diff --git a/src/constants/default.ts b/src/constants/default.ts index 9ef97e28..7161e79a 100644 --- a/src/constants/default.ts +++ b/src/constants/default.ts @@ -1,4 +1,5 @@ import { QuestionType } from "@model/question/question"; +import { AnyQuizQuestion } from "@model/questionTypes/shared"; import { QUIZ_QUESTION_DATE } from "./date"; import { QUIZ_QUESTION_EMOJI } from "./emoji"; import { QUIZ_QUESTION_FILE } from "./file"; @@ -10,19 +11,18 @@ import { QUIZ_QUESTION_SELECT } from "./select"; import { QUIZ_QUESTION_TEXT } from "./text"; import { QUIZ_QUESTION_VARIANT } from "./variant"; import { QUIZ_QUESTION_VARIMG } from "./varimg"; -import { AnyQuestionContent } from "@model/questionTypes/shared"; -export const defaultQuestionContentByType: Record = { - "date": QUIZ_QUESTION_DATE.content, - "emoji": QUIZ_QUESTION_EMOJI.content, - "file": QUIZ_QUESTION_FILE.content, - "images": QUIZ_QUESTION_IMAGES.content, - "number": QUIZ_QUESTION_NUMBER.content, - "page": QUIZ_QUESTION_PAGE.content, - "rating": QUIZ_QUESTION_RATING.content, - "select": QUIZ_QUESTION_SELECT.content, - "text": QUIZ_QUESTION_TEXT.content, - "variant": QUIZ_QUESTION_VARIANT.content, - "varimg": QUIZ_QUESTION_VARIMG.content, +export const defaultQuestionByType: Record> = { + "date": QUIZ_QUESTION_DATE, + "emoji": QUIZ_QUESTION_EMOJI, + "file": QUIZ_QUESTION_FILE, + "images": QUIZ_QUESTION_IMAGES, + "number": QUIZ_QUESTION_NUMBER, + "page": QUIZ_QUESTION_PAGE, + "rating": QUIZ_QUESTION_RATING, + "select": QUIZ_QUESTION_SELECT, + "text": QUIZ_QUESTION_TEXT, + "variant": QUIZ_QUESTION_VARIANT, + "varimg": QUIZ_QUESTION_VARIMG, } as const; diff --git a/src/model/question/edit.ts b/src/model/question/edit.ts index 2d9c6446..ff451bba 100644 --- a/src/model/question/edit.ts +++ b/src/model/question/edit.ts @@ -1,11 +1,12 @@ -import { AnyQuizQuestion, DefiniteQuestionType } from "@model/questionTypes/shared"; +import { AnyQuizQuestion } from "@model/questionTypes/shared"; +import { QuestionType } from "./question"; export interface EditQuestionRequest { id: number; title?: string; desc?: string; - type?: DefiniteQuestionType; + type?: QuestionType; required?: boolean; page?: number; } diff --git a/src/model/question/question.ts b/src/model/question/question.ts index 2f1e5faa..b0cdf97a 100644 --- a/src/model/question/question.ts +++ b/src/model/question/question.ts @@ -1,5 +1,5 @@ import { AnyQuizQuestion } from "@model/questionTypes/shared"; -import { defaultQuestionContentByType } from "../../constants/default"; +import { defaultQuestionByType } from "../../constants/default"; export type QuestionType = @@ -44,7 +44,7 @@ export interface RawQuestion { } export function rawQuestionToQuestion(rawQuestion: RawQuestion): AnyQuizQuestion { - let content = defaultQuestionContentByType[rawQuestion.type]; + let content = defaultQuestionByType[rawQuestion.type].content; try { content = JSON.parse(rawQuestion.content); diff --git a/src/model/questionTypes/shared.ts b/src/model/questionTypes/shared.ts index e5176371..d0dacc6b 100644 --- a/src/model/questionTypes/shared.ts +++ b/src/model/questionTypes/shared.ts @@ -10,6 +10,7 @@ import type { QuizQuestionSelect } from "./select"; import type { QuizQuestionText } from "./text"; import type { QuizQuestionVariant } from "./variant"; import type { QuizQuestionVarImg } from "./varimg"; +import { nanoid } from "nanoid"; export interface QuestionBranchingRule { @@ -32,6 +33,7 @@ export interface QuestionHint { } export type QuestionVariant = { + id: string; /** Текст */ answer: string; /** Текст подсказки */ @@ -84,8 +86,14 @@ export type AnyQuizQuestion = | QuizQuestionRating; // | QuizQuestionInitial; -export type QuizQuestionType = AnyQuizQuestion["type"]; +export const createQuestionVariant: () => QuestionVariant = () => ({ + id: nanoid(), + answer: "", + extendedText: "", + hints: "", +}); -export type AnyQuestionContent = AnyQuizQuestion["content"]; - -export type DefiniteQuestionType = Exclude; +export const createQuestionImageVariant: () => ImageQuestionVariant = () => ({ + ...createQuestionVariant(), + originalImageUrl: "", +}); diff --git a/src/pages/Questions/AnswerDraggableList/AnswerItem.tsx b/src/pages/Questions/AnswerDraggableList/AnswerItem.tsx index 407bc66e..94e284ce 100644 --- a/src/pages/Questions/AnswerDraggableList/AnswerItem.tsx +++ b/src/pages/Questions/AnswerDraggableList/AnswerItem.tsx @@ -1,68 +1,51 @@ -import { useState } from "react"; -import { useParams } from "react-router-dom"; -import { Draggable } from "react-beautiful-dnd"; -import { - Box, - TextField, - FormControl, - InputAdornment, - IconButton, - Popover, - useTheme, - useMediaQuery, -} from "@mui/material"; -import { useDebouncedCallback } from "use-debounce"; - -import { questionStore, updateQuestionsList } from "@root/questions"; - +import { MessageIcon } from "@icons/messagIcon"; import { PointsIcon } from "@icons/questionsPage/PointsIcon"; import { DeleteIcon } from "@icons/questionsPage/deleteIcon"; -import { MessageIcon } from "@icons/messagIcon"; import TextareaAutosize from "@mui/base/TextareaAutosize"; -import type { ChangeEvent, KeyboardEvent, ReactNode } from "react"; - - -import type { DroppableProvided } from "react-beautiful-dnd"; +import { + Box, + FormControl, + IconButton, + InputAdornment, + Popover, + TextField, + useMediaQuery, + useTheme, +} from "@mui/material"; +import { addQuestionVariant, deleteQuestionVariant, setQuestionVariantField } from "@root/questions/actions"; +import type { KeyboardEvent, ReactNode } from "react"; +import { useState } from "react"; +import { Draggable } from "react-beautiful-dnd"; +import { useDebouncedCallback } from "use-debounce"; import type { ImageQuestionVariant, QuestionVariant } from "../../../model/questionTypes/shared"; + type AnswerItemProps = { index: number; - totalIndex: number; - variants: (QuestionVariant | ImageQuestionVariant)[]; + questionId: number; variant: QuestionVariant | ImageQuestionVariant; - provided: DroppableProvided; + largeCheck: boolean; additionalContent?: ReactNode; additionalMobile?: ReactNode; }; export const AnswerItem = ({ index, - totalIndex, - variants, variant, - provided, + questionId, + largeCheck, additionalContent, additionalMobile, }: AnswerItemProps) => { - const quizId = Number(useParams().quizId); - const { listQuestions } = questionStore(); const theme = useTheme(); - const question = listQuestions[quizId][totalIndex]; const isTablet = useMediaQuery(theme.breakpoints.down(790)); - const debounced = useDebouncedCallback((value) => { - const answerNew = variants.slice(); - answerNew[index].answer = value; - updateQuestionsList(quizId, totalIndex, { - content: { - ...question.content, - variants: answerNew, - }, - }); - }, 1000); - const [isOpen, setIsOpen] = useState(false); const [anchorEl, setAnchorEl] = useState(null); + const setQuestionVariantAnswer = useDebouncedCallback((value) => { + setQuestionVariantField(questionId, variant.id,"answer", value); + }, 1000); + const handleClick = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); setIsOpen(true); @@ -72,41 +55,6 @@ export const AnswerItem = ({ setIsOpen(false); }; - - const addNewAnswer = () => { - const answerNew = variants.slice(); - - if (["images", "varimg"].includes(question.type)) { - answerNew.push({ answer: "", hints: "", extendedText: "", originalImageUrl: "" }); - } else { - answerNew.push({ answer: "", extendedText: "", hints: "" }); - } - - updateQuestionsList(quizId, totalIndex, { - content: { ...question.content, variants: answerNew }, - }); - }; - - const deleteAnswer = () => { - const answerNew = variants.slice(); - answerNew.splice(index, 1); - - updateQuestionsList(quizId, totalIndex, { - content: { ...question.content, variants: answerNew }, - }); - }; - - const changeAnswerHint = (event: ChangeEvent) => { - const answerNew = variants.slice(); - answerNew[index].hints = event.target.value; - - updateQuestionsList(quizId, totalIndex, { - content: { ...question.content, variants: answerNew }, - }); - }; - - const largeCheck = ("largeCheck" in question.content) ? question.content.largeCheck : false - return ( {(provided) => ( @@ -128,10 +76,10 @@ export const AnswerItem = ({ focused={false} placeholder={"Добавьте ответ"} multiline={largeCheck} - onChange={({ target }) => debounced(target.value)} + onChange={({ target }) => setQuestionVariantAnswer(target.value)} onKeyDown={(event: KeyboardEvent) => { if (event.code === "Enter" && !largeCheck) { - addNewAnswer(); + addQuestionVariant(questionId); } }} InputProps={{ @@ -174,13 +122,16 @@ export const AnswerItem = ({ style={{ margin: "10px" }} placeholder="Подсказка для этого ответа" value={variant.hints} - onChange={changeAnswerHint} + onChange={e => setQuestionVariantField(questionId, variant.id, "hints", e.target.value)} onKeyDown={( event: KeyboardEvent ) => event.stopPropagation()} /> - + deleteQuestionVariant(questionId, variant.id)} + > ReactNode; additionalMobile?: (variant: QuestionVariant | ImageQuestionVariant, index: number) => ReactNode; }; - - export const AnswerDraggableList = ({ variants, - totalIndex, + question, additionalContent, additionalMobile, }: AnswerDraggableListProps) => { - const quizId = Number(useParams().quizId); - const onDragEnd = ({ destination, source }: DropResult) => { if (destination) { - reorderVariants(quizId, totalIndex, source.index, destination.index); + reorderQuestionVariants(question.id, source.index, destination.index); } }; @@ -40,12 +33,11 @@ export const AnswerDraggableList = ({ {variants.map((variant, index) => ( diff --git a/src/pages/Questions/ButtonsOptionsAndPict.tsx b/src/pages/Questions/ButtonsOptionsAndPict.tsx index b636101b..41a5344a 100644 --- a/src/pages/Questions/ButtonsOptionsAndPict.tsx +++ b/src/pages/Questions/ButtonsOptionsAndPict.tsx @@ -1,347 +1,332 @@ -import { useState, useEffect } from "react"; -import MiniButtonSetting from "@ui_kit/MiniButtonSetting"; -import SettingIcon from "../../assets/icons/questionsPage/settingIcon"; -import Clue from "../../assets/icons/questionsPage/clue"; -import Branching from "../../assets/icons/questionsPage/branching"; -import { - Box, - IconButton, - Tooltip, - Typography, - useMediaQuery, - useTheme, -} from "@mui/material"; -import { HideIcon } from "../../assets/icons/questionsPage/hideIcon"; -import { CopyIcon } from "../../assets/icons/questionsPage/CopyIcon"; -import { DeleteIcon } from "../../assets/icons/questionsPage/deleteIcon"; -import ImgIcon from "../../assets/icons/questionsPage/imgIcon"; -import { useParams } from "react-router-dom"; -import { quizStore } from "@root/quizes"; -import { - questionStore, - copyQuestion, - removeQuestion, - removeQuestionForce, - updateQuestionsList, -} from "@root/questions"; import { DoubleArrowRight } from "@icons/questionsPage/DoubleArrowRight"; import { DoubleTick } from "@icons/questionsPage/DoubleTick"; import { VectorQuestions } from "@icons/questionsPage/VectorQuestions"; +import { + Box, + IconButton, + Tooltip, + Typography, + useMediaQuery, + useTheme, +} from "@mui/material"; +import { copyQuestion, deleteQuestion, updateQuestionWithFnOptimistic } from "@root/questions/actions"; +import MiniButtonSetting from "@ui_kit/MiniButtonSetting"; import { ReallyChangingModal } from "@ui_kit/Modal/ReallyChangingModal/ReallyChangingModal"; +import { useEffect, useState } from "react"; +import { CopyIcon } from "../../assets/icons/questionsPage/CopyIcon"; +import Branching from "../../assets/icons/questionsPage/branching"; +import Clue from "../../assets/icons/questionsPage/clue"; +import { DeleteIcon } from "../../assets/icons/questionsPage/deleteIcon"; +import { HideIcon } from "../../assets/icons/questionsPage/hideIcon"; +import ImgIcon from "../../assets/icons/questionsPage/imgIcon"; +import SettingIcon from "../../assets/icons/questionsPage/settingIcon"; +import type { AnyQuizQuestion } from "../../model/questionTypes/shared"; -import type { QuizQuestionBase } from "../../model/questionTypes/shared"; interface Props { - switchState: string; - SSHC: (data: string) => void; - totalIndex: number; + switchState: string; + SSHC: (data: string) => void; + question: AnyQuizQuestion; } export default function ButtonsOptionsAndPict({ - SSHC, - switchState, - totalIndex, + SSHC, + switchState, + question, }: Props) { - const [buttonHover, setButtonHover] = useState(""); - const quizId = Number(useParams().quizId); - const { listQuizes } = quizStore(); - const { listQuestions } = questionStore(); - const [openedReallyChangingModal, setOpenedReallyChangingModal] = - useState(false); - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down(790)); - const isIconMobile = useMediaQuery(theme.breakpoints.down(1050)); - const quize = listQuizes[quizId]; - const question = listQuestions[quizId][totalIndex] as QuizQuestionBase; + const [buttonHover, setButtonHover] = useState(""); + const [openedReallyChangingModal, setOpenedReallyChangingModal] = + useState(false); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down(790)); + const isIconMobile = useMediaQuery(theme.breakpoints.down(1050)); - useEffect(() => { - if (question.deleteTimeoutId) { - clearTimeout(question.deleteTimeoutId); - } - }, [listQuestions]); + useEffect(() => { + if (question.deleteTimeoutId) { + clearTimeout(question.deleteTimeoutId); + } + }, [question]); - const openedModal = () => { - updateQuestionsList(quizId, totalIndex, { - openedModalSettings: true, - }); - }; - - return ( - - - setButtonHover("setting")} - onMouseLeave={() => setButtonHover("")} - onClick={() => { - SSHC("setting"); - }} - sx={{ - maxWidth: "104px", - minWidth: isIconMobile ? "30px" : "64px", - height: "30px", - backgroundColor: - switchState === "setting" - ? theme.palette.brightPurple.main - : "transparent", - color: - switchState === "setting" ? "#ffffff" : theme.palette.grey3.main, - "&:hover": { - color: - switchState === "setting" ? theme.palette.grey3.main : null, - }, - }} - > - - {isIconMobile ? null : "Настройки"} - - setButtonHover("help")} - onMouseLeave={() => setButtonHover("")} - onClick={() => { - SSHC("help"); - }} - sx={{ - minWidth: isIconMobile ? "30px" : "64px", - maxWidth: "102px", - height: "30px", - backgroundColor: - switchState === "help" - ? theme.palette.brightPurple.main - : "transparent", - color: - switchState === "help" ? "#ffffff" : theme.palette.grey3.main, - "&:hover": { - color: switchState === "help" ? theme.palette.grey3.main : null, - }, - }} - > - - {isIconMobile ? null : "Подсказка"} - - <> - - - Будет показан при условии - - - Название - - - Условие 1, Условие 2 - - - Все условия обязательны - - - } - > - setButtonHover("branching")} - onMouseLeave={() => setButtonHover("")} - onClick={() => { - SSHC("branching"); - openedModal(); - }} - sx={{ - height: "30px", - maxWidth: "103px", - minWidth: isIconMobile ? "30px" : "64px", - backgroundColor: - switchState === "branching" - ? theme.palette.brightPurple.main - : "transparent", - color: - switchState === "branching" - ? "#ffffff" - : theme.palette.grey3.main, - "&:hover": { - color: - switchState === "branching" - ? theme.palette.grey3.main - : null, - }, - }} + > + - - {isIconMobile ? null : "Ветвление"} - - - setButtonHover("image")} - onMouseLeave={() => setButtonHover("")} - onClick={() => { - SSHC("image"); - }} - sx={{ - height: "30px", - maxWidth: "123px", - minWidth: isIconMobile ? "30px" : "64px", - backgroundColor: - switchState === "image" - ? theme.palette.brightPurple.main - : "transparent", - color: - switchState === "image" ? "#ffffff" : theme.palette.grey3.main, - "&:hover": { - color: - switchState === "image" ? theme.palette.grey3.main : null, - }, - }} - > - setButtonHover("setting")} + onMouseLeave={() => setButtonHover("")} + onClick={() => { + SSHC("setting"); + }} + sx={{ + maxWidth: "104px", + minWidth: isIconMobile ? "30px" : "64px", + height: "30px", + backgroundColor: + switchState === "setting" + ? theme.palette.brightPurple.main + : "transparent", + color: + switchState === "setting" ? "#ffffff" : theme.palette.grey3.main, + "&:hover": { + color: + switchState === "setting" ? theme.palette.grey3.main : null, + }, + }} + > + + {isIconMobile ? null : "Настройки"} + + setButtonHover("help")} + onMouseLeave={() => setButtonHover("")} + onClick={() => { + SSHC("help"); + }} + sx={{ + minWidth: isIconMobile ? "30px" : "64px", + maxWidth: "102px", + height: "30px", + backgroundColor: + switchState === "help" + ? theme.palette.brightPurple.main + : "transparent", + color: + switchState === "help" ? "#ffffff" : theme.palette.grey3.main, + "&:hover": { + color: switchState === "help" ? theme.palette.grey3.main : null, + }, + }} + > + + {isIconMobile ? null : "Подсказка"} + + <> + + + Будет показан при условии + + + Название + + + Условие 1, Условие 2 + + + Все условия обязательны + + + } + > + setButtonHover("branching")} + onMouseLeave={() => setButtonHover("")} + onClick={() => { + SSHC("branching"); + updateQuestionWithFnOptimistic(question.id, question => { + question.openedModalSettings = true; + }); + }} + sx={{ + height: "30px", + maxWidth: "103px", + minWidth: isIconMobile ? "30px" : "64px", + backgroundColor: + switchState === "branching" + ? theme.palette.brightPurple.main + : "transparent", + color: + switchState === "branching" + ? "#ffffff" + : theme.palette.grey3.main, + "&:hover": { + color: + switchState === "branching" + ? theme.palette.grey3.main + : null, + }, + }} + > + + {isIconMobile ? null : "Ветвление"} + + + setButtonHover("image")} + onMouseLeave={() => setButtonHover("")} + onClick={() => { + SSHC("image"); + }} + sx={{ + height: "30px", + maxWidth: "123px", + minWidth: isIconMobile ? "30px" : "64px", + backgroundColor: + switchState === "image" + ? theme.palette.brightPurple.main + : "transparent", + color: + switchState === "image" ? "#ffffff" : theme.palette.grey3.main, + "&:hover": { + color: + switchState === "image" ? theme.palette.grey3.main : null, + }, + }} + > + + {isIconMobile ? null : "Изображение"} + + setOpenedReallyChangingModal(true)} + sx={{ + minWidth: "30px", + height: "30px", + backgroundColor: "#FEDFD0", + }} + > + + + setOpenedReallyChangingModal(true)} + sx={{ + minWidth: "30px", + height: "30px", + backgroundColor: "#FEDFD0", + }} + > + + + setOpenedReallyChangingModal(true)} + sx={{ + minWidth: "30px", + height: "30px", + backgroundColor: "#FEDFD0", + }} + > + + + + + + + + + copyQuestion(question.id, question.quizId)} + > + + + { // TODO + // const removedId = question.id; + // if (question.deleteTimeoutId) { + // clearTimeout(question.deleteTimeoutId); + // } + + // removeQuestion(quizId, totalIndex); + + // const newTimeoutId = window.setTimeout(() => { + // removeQuestionForce(quizId, removedId); + // }, 5000); + + // updateQuestionsList(quizId, totalIndex, { + // ...question, + // deleteTimeoutId: newTimeoutId, + // }); + + deleteQuestion(question.id); + }} + > + + + + setOpenedReallyChangingModal(false)} /> - {isIconMobile ? null : "Изображение"} - - setOpenedReallyChangingModal(true)} - sx={{ - minWidth: "30px", - height: "30px", - backgroundColor: "#FEDFD0", - }} - > - - - setOpenedReallyChangingModal(true)} - sx={{ - minWidth: "30px", - height: "30px", - backgroundColor: "#FEDFD0", - }} - > - - - setOpenedReallyChangingModal(true)} - sx={{ - minWidth: "30px", - height: "30px", - backgroundColor: "#FEDFD0", - }} - > - - - - - - - - - copyQuestion(quizId, totalIndex)} - > - - - { - const removedId = question.id; - if (question.deleteTimeoutId) { - clearTimeout(question.deleteTimeoutId); - } - - removeQuestion(quizId, totalIndex); - - const newTimeoutId = window.setTimeout(() => { - removeQuestionForce(quizId, removedId); - }, 5000); - - updateQuestionsList(quizId, totalIndex, { - ...question, - deleteTimeoutId: newTimeoutId, - }); - }} - > - - - - setOpenedReallyChangingModal(false)} - /> - - ); + + ); } diff --git a/src/pages/Questions/DraggableList/QuestionPageCard.tsx b/src/pages/Questions/DraggableList/QuestionPageCard.tsx index 1e89d0f1..a44d56e6 100644 --- a/src/pages/Questions/DraggableList/QuestionPageCard.tsx +++ b/src/pages/Questions/DraggableList/QuestionPageCard.tsx @@ -11,21 +11,7 @@ import { useTheme, } from "@mui/material"; import { useRef, useState } from "react"; -import { useParams } from "react-router-dom"; import { useDebouncedCallback } from "use-debounce"; - -import SwitchQuestionsPage from "../SwitchQuestionsPage"; -import TypeQuestions from "../TypeQuestions"; -import { ChooseAnswerModal } from "./ChooseAnswerModal"; - -import { - copyQuestion, - createQuestion, - removeQuestion, - removeQuestionForce, - updateQuestionsList -} from "@root/questions"; - import { CrossedEyeIcon } from "@icons/CrossedEyeIcon"; import { ArrowDownIcon } from "@icons/questionsPage/ArrowDownIcon"; import { CopyIcon } from "@icons/questionsPage/CopyIcon"; @@ -45,9 +31,12 @@ import Page from "@icons/questionsPage/page"; import RatingIcon from "@icons/questionsPage/rating"; import Slider from "@icons/questionsPage/slider"; import ExpandLessIcon from "@mui/icons-material/ExpandLess"; +import { copyQuestion, deleteQuestion, toggleExpandQuestion } from "@root/questions/actions"; import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd"; import { ReactComponent as PlusIcon } from "../../../assets/icons/plus.svg"; import type { AnyQuizQuestion } from "../../../model/questionTypes/shared"; +import SwitchQuestionsPage from "../SwitchQuestionsPage"; +import { ChooseAnswerModal } from "./ChooseAnswerModal"; interface Props { @@ -59,7 +48,6 @@ interface Props { export default function QuestionsPageCard({ question, draggableProps, isDragging }: Props) { const [plusVisible, setPlusVisible] = useState(false); const [open, setOpen] = useState(false); - const quizId = Number(useParams().quizId); const theme = useTheme(); const isTablet = useMediaQuery(theme.breakpoints.down(1000)); const isMobile = useMediaQuery(theme.breakpoints.down(790)); @@ -166,11 +154,7 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging sx={{ padding: "0", margin: "5px" }} disableRipple data-cy="expand-question" - onClick={() => - updateQuestionsList(quizId, totalIndex, { - expanded: !question.expanded, - }) - } + onClick={() => toggleExpandQuestion(question.id)} > {question.expanded ? ( copyQuestion(quizId, totalIndex)} + onClick={() => copyQuestion(question.id, question.quizId)} > { - const removedId = question.id; - if (question.deleteTimeoutId) { - clearTimeout(question.deleteTimeoutId); - } + onClick={() => { // TODO + // const removedId = question.id; + // if (question.deleteTimeoutId) { + // clearTimeout(question.deleteTimeoutId); + // } - removeQuestion(quizId, totalIndex); + // removeQuestion(quizId, totalIndex); - const newTimeoutId = window.setTimeout(() => { - removeQuestionForce(quizId, removedId); - }, 5000); + // const newTimeoutId = window.setTimeout(() => { + // removeQuestionForce(quizId, removedId); + // }, 5000); - updateQuestionsList(quizId, totalIndex, { - ...question, - deleteTimeoutId: newTimeoutId, - }); + // updateQuestionsList(quizId, totalIndex, { + // ...question, + // deleteTimeoutId: newTimeoutId, + // }); + + deleteQuestion(question.id); }} > - {question.type === "nonselected" ? ( + {/* {question.type === "nonselected" ? ( - ) : ( - - )} + ) : ( */} + + {/* )} */} )} diff --git a/src/pages/Questions/DropDown/DropDown.tsx b/src/pages/Questions/DropDown/DropDown.tsx index 74e93071..630fc76c 100644 --- a/src/pages/Questions/DropDown/DropDown.tsx +++ b/src/pages/Questions/DropDown/DropDown.tsx @@ -58,7 +58,7 @@ export default function DropDown({ totalIndex }: Props) { ) : ( )} ( <> {!isTablet && ( diff --git a/src/pages/Questions/Form/FormDraggableList/ChooseAnswerModal.tsx b/src/pages/Questions/Form/FormDraggableList/ChooseAnswerModal.tsx index 38a15a48..aa956efe 100644 --- a/src/pages/Questions/Form/FormDraggableList/ChooseAnswerModal.tsx +++ b/src/pages/Questions/Form/FormDraggableList/ChooseAnswerModal.tsx @@ -24,9 +24,9 @@ import { BUTTON_TYPE_QUESTIONS } from "../../TypeQuestions"; import type { RefObject } from "react"; import type { - QuizQuestionType, QuizQuestionBase, } from "../../../../model/questionTypes/shared"; +import { QuestionType } from "@model/question/question"; type ChooseAnswerModalProps = { open: boolean; @@ -44,7 +44,7 @@ export const ChooseAnswerModal = ({ switchState, }: ChooseAnswerModalProps) => { const [openModal, setOpenModal] = useState(false); - const [selectedValue, setSelectedValue] = useState("text"); + const [selectedValue, setSelectedValue] = useState("text"); const quizId = Number(useParams().quizId); const { listQuestions } = questionStore(); const theme = useTheme(); diff --git a/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx b/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx index d5cf3c16..9fbed6ff 100644 --- a/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx +++ b/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx @@ -158,11 +158,11 @@ export default function QuestionsPageCard({ ), }} /> - {question.type === "nonselected" ? ( + {/* {question.type === "" ? ( - ) : ( + ) : ( */} - )} + {/* )} */} diff --git a/src/pages/Questions/Form/FormTypeQuestions.tsx b/src/pages/Questions/Form/FormTypeQuestions.tsx index e70b2325..949f45d2 100644 --- a/src/pages/Questions/Form/FormTypeQuestions.tsx +++ b/src/pages/Questions/Form/FormTypeQuestions.tsx @@ -22,9 +22,9 @@ import { } from "@root/questions"; import type { - QuizQuestionType, QuizQuestionBase, } from "../../../model/questionTypes/shared"; +import { QuestionType } from "@model/question/question"; interface Props { totalIndex: number; @@ -33,7 +33,7 @@ interface Props { type ButtonTypeQuestion = { icon: JSX.Element; title: string; - value: QuizQuestionType; + value: QuestionType; }; const BUTTON_TYPE_SHORT_QUESTIONS: ButtonTypeQuestion[] = [ diff --git a/src/pages/Questions/OptionsAndPicture/OptionsAndPicture.tsx b/src/pages/Questions/OptionsAndPicture/OptionsAndPicture.tsx index 68583c1d..eec66c9d 100644 --- a/src/pages/Questions/OptionsAndPicture/OptionsAndPicture.tsx +++ b/src/pages/Questions/OptionsAndPicture/OptionsAndPicture.tsx @@ -71,7 +71,7 @@ export default function OptionsAndPicture({ totalIndex }: Props) { ( <> {!isMobile && ( diff --git a/src/pages/Questions/OptionsPicture/OptionsPicture.tsx b/src/pages/Questions/OptionsPicture/OptionsPicture.tsx index c938b39b..b0fe7d33 100644 --- a/src/pages/Questions/OptionsPicture/OptionsPicture.tsx +++ b/src/pages/Questions/OptionsPicture/OptionsPicture.tsx @@ -1,44 +1,39 @@ import { - Box, - Link, - Typography, - Button, - useTheme, - useMediaQuery, + Box, + Link, + Typography, + useMediaQuery, + useTheme } from "@mui/material"; +import { openCropModal } from "@root/cropModal"; +import { setVariantImageUrl, setVariantOriginalImageUrl, updateQuestionsList } from "@root/questions"; +import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton"; +import { CropModal } from "@ui_kit/Modal/CropModal"; import { useState } from "react"; import { useParams } from "react-router-dom"; - -import { questionStore, setVariantImageUrl, updateQuestionsList, setVariantOriginalImageUrl } from "@root/questions"; -import { CropModal } from "@ui_kit/Modal/CropModal"; +import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon"; +import type { QuizQuestionImages } from "../../../model/questionTypes/images"; import { AnswerDraggableList } from "../AnswerDraggableList"; import ButtonsOptions from "../ButtonsOptions"; import { UploadImageModal } from "../UploadImage/UploadImageModal"; - -import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon"; import SwitchAnswerOptionsPict from "./switchOptionsPict"; -import { openCropModal } from "@root/cropModal"; -import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton"; -import type { QuizQuestionImages } from "../../../model/questionTypes/images"; interface Props { - totalIndex: number; + question: QuizQuestionImages; } -export default function OptionsPicture({ totalIndex }: Props) { +export default function OptionsPicture({ question }: Props) { const [isUploadImageModalOpen, setIsUploadImageModalOpen] = useState(false); const [currentIndex, setCurrentIndex] = useState(0); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(790)); const quizId = Number(useParams().quizId); const [switchState, setSwitchState] = useState("setting"); - const { listQuestions } = questionStore(); - const question = listQuestions[quizId][totalIndex] as QuizQuestionImages; - const SSHC = (data: string) => { - setSwitchState(data); - }; + const SSHC = (data: string) => { + setSwitchState(data); + }; const handleImageUpload = (files: FileList | null) => { if (!files?.length) return; @@ -56,117 +51,117 @@ export default function OptionsPicture({ totalIndex }: Props) { const answerNew = question.content.variants.slice(); answerNew.push({ answer: "", hints: "", extendedText: "", originalImageUrl: "" }); - updateQuestionsList(quizId, totalIndex, { - content: { ...question.content, variants: answerNew }, - }); - }; + updateQuestionsList(quizId, totalIndex, { + content: { ...question.content, variants: answerNew }, + }); + }; function handleCropModalSaveClick(url: string) { setVariantImageUrl(quizId, totalIndex, currentIndex, url); } return ( - <> - - ( - <> - {!isMobile && ( - { - if (!("originalImageUrl" in variant)) return; + <> + + ( + <> + {!isMobile && ( + { + if (!("originalImageUrl" in variant)) return; - setCurrentIndex(index); - if (variant.extendedText) { - return openCropModal( - variant.extendedText, - variant.originalImageUrl - ); - } + setCurrentIndex(index); + if (variant.extendedText) { + return openCropModal( + variant.extendedText, + variant.originalImageUrl + ); + } - setIsUploadImageModalOpen(true); - }} - onPlusClick={() => { - setCurrentIndex(index); - setIsUploadImageModalOpen(true); - }} - sx={{ mx: "10px" }} - /> - )} - - )} - additionalMobile={(variant, index) => ( - <> - {isMobile && ( - { - if (!("originalImageUrl" in variant)) return; + setIsUploadImageModalOpen(true); + }} + onPlusClick={() => { + setCurrentIndex(index); + setIsUploadImageModalOpen(true); + }} + sx={{ mx: "10px" }} + /> + )} + + )} + additionalMobile={(variant, index) => ( + <> + {isMobile && ( + { + if (!("originalImageUrl" in variant)) return; - setCurrentIndex(index); - if (variant.extendedText) { - return openCropModal( - variant.extendedText, - variant.originalImageUrl - ); - } + setCurrentIndex(index); + if (variant.extendedText) { + return openCropModal( + variant.extendedText, + variant.originalImageUrl + ); + } - setIsUploadImageModalOpen(true); - }} - onPlusClick={() => { - setCurrentIndex(index); - setIsUploadImageModalOpen(true); - }} - sx={{ m: "8px", width: "auto" }} - /> - )} - - )} - /> - setIsUploadImageModalOpen(false)} - imgHC={handleImageUpload} - /> - - - - Добавьте ответ - - {isMobile ? null : ( - <> - - или нажмите Enter - - - - )} - - - - - + setIsUploadImageModalOpen(true); + }} + onPlusClick={() => { + setCurrentIndex(index); + setIsUploadImageModalOpen(true); + }} + sx={{ m: "8px", width: "auto" }} + /> + )} + + )} + /> + setIsUploadImageModalOpen(false)} + imgHC={handleImageUpload} + /> + + + + Добавьте ответ + + {isMobile ? null : ( + <> + + или нажмите Enter + + + + )} + + + + + - ); + ); } diff --git a/src/pages/Questions/SwitchQuestionsPage.tsx b/src/pages/Questions/SwitchQuestionsPage.tsx index c4bfacd1..52e06fc1 100644 --- a/src/pages/Questions/SwitchQuestionsPage.tsx +++ b/src/pages/Questions/SwitchQuestionsPage.tsx @@ -1,63 +1,58 @@ -import * as React from "react"; - -import AnswerOptions from "./answerOptions/AnswerOptions"; -import OptionsPicture from "./OptionsPicture/OptionsPicture"; +import { AnyQuizQuestion } from "@model/questionTypes/shared"; import DataOptions from "./DataOptions/DataOptions"; -import SliderOptions from "./SliderOptions/SliderOptions"; +import DropDown from "./DropDown/DropDown"; +import Emoji from "./Emoji/Emoji"; +import OptionsAndPicture from "./OptionsAndPicture/OptionsAndPicture"; +import OptionsPicture from "./OptionsPicture/OptionsPicture"; import OwnTextField from "./OwnTextField/OwnTextField"; import PageOptions from "./PageOptions/PageOptions"; -import OptionsAndPicture from "./OptionsAndPicture/OptionsAndPicture"; import RatingOptions from "./RatingOptions/RatingOptions"; -import Emoji from "./Emoji/Emoji"; -import DropDown from "./DropDown/DropDown"; +import SliderOptions from "./SliderOptions/SliderOptions"; import UploadFile from "./UploadFile/UploadFile"; -import { useParams } from "react-router-dom"; -import { questionStore } from "@root/questions"; +import AnswerOptions from "./answerOptions/AnswerOptions"; + interface Props { - totalIndex: number; + question: AnyQuizQuestion; } -export default function SwitchQuestionsPage({ totalIndex }: Props) { - const quizId = Number(useParams().quizId); - const { listQuestions } = questionStore(); +export default function SwitchQuestionsPage({ question }: Props) { - const switchState = listQuestions[quizId][totalIndex].type; - switch (switchState) { - case "variant": - return ; + switch (question.type) { + case "variant": + return ; - case "images": - return ; + case "images": + return ; - case "varimg": - return ; + case "varimg": + return ; - case "emoji": - return ; + case "emoji": + return ; - case "text": - return ; + case "text": + return ; - case "select": - return ; + case "select": + return ; - case "date": - return ; + case "date": + return ; - case "number": - return ; + case "number": + return ; - case "file": - return ; + case "file": + return ; - case "page": - return ; + case "page": + return ; - case "rating": - return ; + case "rating": + return ; - default: - return <>; - } + default: + return <>; + } } diff --git a/src/pages/Questions/TypeQuestions.tsx b/src/pages/Questions/TypeQuestions.tsx index 9b79d94f..c31c1905 100755 --- a/src/pages/Questions/TypeQuestions.tsx +++ b/src/pages/Questions/TypeQuestions.tsx @@ -11,100 +11,102 @@ import OptionsPict from "../../assets/icons/questionsPage/options_pict"; import Page from "../../assets/icons/questionsPage/page"; import RatingIcon from "../../assets/icons/questionsPage/rating"; import Slider from "../../assets/icons/questionsPage/slider"; -import { setQuestionFieldOptimistic } from "@root/questions/actions"; +import { updateQuestionWithFnOptimistic } from "@root/questions/actions"; import type { AnyQuizQuestion, - QuizQuestionType } from "../../model/questionTypes/shared"; +import { QuestionType } from "@model/question/question"; interface Props { - question: AnyQuizQuestion; + question: AnyQuizQuestion; } type ButtonTypeQuestion = { - icon: JSX.Element; - title: string; - value: QuizQuestionType; + icon: JSX.Element; + title: string; + value: QuestionType; }; export default function TypeQuestions({ question }: Props) { - return ( - - {BUTTON_TYPE_QUESTIONS.map(({ icon, title, value }) => ( - setQuestionFieldOptimistic(question.id, "type", value)} - icon={icon} - text={title} - /> - ))} - - ); + return ( + + {BUTTON_TYPE_QUESTIONS.map(({ icon, title, value }) => ( + updateQuestionWithFnOptimistic(question.id, question => { + question.type = value; + })} + icon={icon} + text={title} + /> + ))} + + ); } export const BUTTON_TYPE_QUESTIONS: ButtonTypeQuestion[] = [ - { - icon: , - title: "Варианты ответов", - value: "variant", - }, - { - icon: , - title: "Варианты с картинками", - value: "images", - }, - { - icon: , - title: "Варианты и картинка", - value: "varimg", - }, - { - icon: , - title: "Эмоджи", - value: "emoji", - }, - { - icon: , - title: "Своё поле для ввода", - value: "text", - }, - { - icon: , - title: "Выпадающий список", - value: "select", - }, - { - icon: , - title: "Дата", - value: "date", - }, - { - icon: , - title: "Ползунок", - value: "number", - }, - { - icon: , - title: "Загрузка файла", - value: "file", - }, - { - icon: , - title: "Страница", - value: "page", - }, - { - icon: , - title: "Рейтинг", - value: "rating", - }, + { + icon: , + title: "Варианты ответов", + value: "variant", + }, + { + icon: , + title: "Варианты с картинками", + value: "images", + }, + { + icon: , + title: "Варианты и картинка", + value: "varimg", + }, + { + icon: , + title: "Эмоджи", + value: "emoji", + }, + { + icon: , + title: "Своё поле для ввода", + value: "text", + }, + { + icon: , + title: "Выпадающий список", + value: "select", + }, + { + icon: , + title: "Дата", + value: "date", + }, + { + icon: , + title: "Ползунок", + value: "number", + }, + { + icon: , + title: "Загрузка файла", + value: "file", + }, + { + icon: , + title: "Страница", + value: "page", + }, + { + icon: , + title: "Рейтинг", + value: "rating", + }, ]; diff --git a/src/pages/Questions/UploadImage/index.tsx b/src/pages/Questions/UploadImage/index.tsx index d255b0c7..88a2b9ee 100644 --- a/src/pages/Questions/UploadImage/index.tsx +++ b/src/pages/Questions/UploadImage/index.tsx @@ -1,26 +1,21 @@ +import { QuizQuestionVariant } from "@model/questionTypes/variant"; import { Box, ButtonBase, Typography, useTheme } from "@mui/material"; -import { questionStore, setQuestionBackgroundImage, setQuestionOriginalBackgroundImage } from "@root/questions"; +import { openCropModal } from "@root/cropModal"; +import { setQuestionBackgroundImage, setQuestionOriginalBackgroundImage } from "@root/questions/actions"; import { CropModal } from "@ui_kit/Modal/CropModal"; import UploadBox from "@ui_kit/UploadBox"; -import * as React from "react"; -import { useParams } from "react-router-dom"; +import { useState, type DragEvent } from "react"; import UploadIcon from "../../../assets/icons/UploadIcon"; import { UploadImageModal } from "./UploadImageModal"; -import { openCropModal } from "@root/cropModal"; -import { QuizQuestionBase } from "model/questionTypes/shared"; -import type { DragEvent } from "react"; type UploadImageProps = { - totalIndex: number; + question: QuizQuestionVariant; }; -export default function UploadImage({ totalIndex }: UploadImageProps) { - const quizId = Number(useParams().quizId); +export default function UploadImage({ question }: UploadImageProps) { const theme = useTheme(); - const [isUploadImageModalOpen, setIsUploadImageModalOpen] = React.useState(false); - const { listQuestions } = questionStore(); - const question = listQuestions[quizId][totalIndex] as QuizQuestionBase; + const [isUploadImageModalOpen, setIsUploadImageModalOpen] = useState(false); const handleImageUpload = (files: FileList | null) => { if (!files?.length) return; @@ -29,8 +24,8 @@ export default function UploadImage({ totalIndex }: UploadImageProps) { const url = URL.createObjectURL(file); - setQuestionBackgroundImage(quizId, totalIndex, url); - setQuestionOriginalBackgroundImage(quizId, totalIndex, url); + setQuestionBackgroundImage(question.id, url); + setQuestionOriginalBackgroundImage(question.id, url); setIsUploadImageModalOpen(false); openCropModal(url, url); }; @@ -43,7 +38,7 @@ export default function UploadImage({ totalIndex }: UploadImageProps) { }; function handleCropModalSaveClick(url: string) { - setQuestionBackgroundImage(quizId, totalIndex, url); + setQuestionBackgroundImage(question.id, url); } return ( diff --git a/src/pages/Questions/answerOptions/AnswerOptions.tsx b/src/pages/Questions/answerOptions/AnswerOptions.tsx index ae339175..66ab0cbd 100755 --- a/src/pages/Questions/answerOptions/AnswerOptions.tsx +++ b/src/pages/Questions/answerOptions/AnswerOptions.tsx @@ -1,111 +1,98 @@ +import { Box, Link, Typography, useMediaQuery, useTheme } from "@mui/material"; import { useState } from "react"; -import { useParams } from "react-router-dom"; -import { Box, Typography, Link, useTheme, useMediaQuery } from "@mui/material"; import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon"; -import SwitchAnswerOptions from "./switchAnswerOptions"; import { AnswerDraggableList } from "../AnswerDraggableList"; import ButtonsOptionsAndPict from "../ButtonsOptionsAndPict"; -import { questionStore, updateQuestionsList } from "@root/questions"; - +import SwitchAnswerOptions from "./switchAnswerOptions"; import type { QuizQuestionVariant } from "../../../model/questionTypes/variant"; +import { addQuestionVariant } from "@root/questions/actions"; + interface Props { - totalIndex: number; + question: QuizQuestionVariant; } -export default function AnswerOptions({ totalIndex }: Props) { - const [switchState, setSwitchState] = useState("setting"); - const quizId = Number(useParams().quizId); - const { listQuestions } = questionStore(); - const question = listQuestions[quizId][totalIndex] as QuizQuestionVariant; - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down(790)); +export default function AnswerOptions({ question }: Props) { + const [switchState, setSwitchState] = useState("setting"); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down(790)); - const SSHC = (data: string) => { - setSwitchState(data); - }; + const SSHC = (data: string) => { + setSwitchState(data); + }; - const addNewAnswer = () => { - const answerNew = question.content.variants.slice(); - answerNew.push({ answer: "", extendedText: "", hints: "" }); + return ( + <> + + {question.content.variants.length === 0 ? ( + + Добавьте ответ + + ) : ( + + )} - updateQuestionsList(quizId, totalIndex, { - content: { ...question.content, variants: answerNew }, - }); - }; - - return ( - <> - - {question.content.variants.length === 0 ? ( - - Добавьте ответ - - ) : ( - - )} - - - - Добавьте ответ - - {isMobile ? null : ( - <> - - или нажмите Enter - - - - )} - - - - - - ); + + addQuestionVariant(question.id)} + > + Добавьте ответ + + {isMobile ? null : ( + <> + + или нажмите Enter + + + + )} + + + + + + ); } diff --git a/src/pages/Questions/answerOptions/responseSettings.tsx b/src/pages/Questions/answerOptions/responseSettings.tsx index 12d679c8..aa0cf893 100644 --- a/src/pages/Questions/answerOptions/responseSettings.tsx +++ b/src/pages/Questions/answerOptions/responseSettings.tsx @@ -1,181 +1,178 @@ -import { useParams } from "react-router-dom"; import { - Box, - Typography, - Tooltip, - useMediaQuery, - useTheme, + Box, + Tooltip, + Typography, + useMediaQuery, + useTheme, } from "@mui/material"; -import { useDebouncedCallback } from "use-debounce"; +import { updateQuestionWithFnOptimistic } from "@root/questions/actions"; import CustomCheckbox from "@ui_kit/CustomCheckbox"; -import InfoIcon from "../../../assets/icons/InfoIcon"; import CustomTextField from "@ui_kit/CustomTextField"; -import { questionStore, updateQuestionsList } from "@root/questions"; - +import { useDebouncedCallback } from "use-debounce"; +import InfoIcon from "../../../assets/icons/InfoIcon"; import type { QuizQuestionVariant } from "../../../model/questionTypes/variant"; + interface Props { - totalIndex: number; + question: QuizQuestionVariant; } -export default function ResponseSettings({ totalIndex }: Props) { - const quizId = Number(useParams().quizId); - const { listQuestions } = questionStore(); - const theme = useTheme(); - const isTablet = useMediaQuery(theme.breakpoints.down(900)); +export default function ResponseSettings({ question }: Props) { + const theme = useTheme(); + const isTablet = useMediaQuery(theme.breakpoints.down(900)); - const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); - const isMobile = useMediaQuery(theme.breakpoints.down(790)); - const question = listQuestions[quizId][totalIndex] as QuizQuestionVariant; - const debounced = useDebouncedCallback((value) => { - updateQuestionsList(quizId, totalIndex, { - content: { ...question.content, innerName: value }, - }); - }, 1000); + const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); + const isMobile = useMediaQuery(theme.breakpoints.down(790)); - return ( - - - - Настройки ответов - - { - updateQuestionsList(quizId, totalIndex, { - content: { - ...question.content, - largeCheck: target.checked, - }, - }); - }} - /> - { - updateQuestionsList(quizId, totalIndex, { - content: { ...question.content, multi: target.checked }, - }); - }} - /> - { - updateQuestionsList(quizId, totalIndex, { - content: { ...question.content, own: target.checked }, - }); - }} - /> - - - - Настройки вопросов - - { - updateQuestionsList(quizId, totalIndex, { - required: !target.checked, - }); - }} - /> + const updateQuestionInnerName = useDebouncedCallback((value) => { + updateQuestionWithFnOptimistic(question.id, question => { + question.content.innerName = value; + }); + }, 1000); + + return ( - { - updateQuestionsList(quizId, totalIndex, { - content: { - ...question.content, - innerNameCheck: target.checked, - innerName: target.checked ? question.content.innerName : "", - }, - }); - }} - /> - {isMobile && ( - + - - - - - )} + + Настройки ответов + + { + updateQuestionWithFnOptimistic(question.id, question => { + if (!("largeCheck" in question.content)) return; + + question.content.largeCheck = target.checked; + }); + }} + /> + { + updateQuestionWithFnOptimistic(question.id, question => { + if (!("multi" in question.content)) return; + + question.content.multi = target.checked; + }); + }} + /> + { + updateQuestionWithFnOptimistic(question.id, question => { + if (!("own" in question.content)) return; + + question.content.own = target.checked; + }); + }} + /> + + + + Настройки вопросов + + { + updateQuestionWithFnOptimistic(question.id, question => { + question.required = !target.checked; + }); + }} + /> + + { + updateQuestionWithFnOptimistic(question.id, question => { + question.content.innerNameCheck = target.checked; + question.content.innerName = target.checked ? question.content.innerName : ""; + }); + }} + /> + {isMobile && ( + + + + + + )} + + {question.content.innerNameCheck && ( + updateQuestionInnerName(target.value)} + /> + )} + - {question.content.innerNameCheck && ( - debounced(target.value)} - /> - )} - - - ); + ); } diff --git a/src/pages/Questions/answerOptions/switchAnswerOptions.tsx b/src/pages/Questions/answerOptions/switchAnswerOptions.tsx index b25b3008..347134f1 100644 --- a/src/pages/Questions/answerOptions/switchAnswerOptions.tsx +++ b/src/pages/Questions/answerOptions/switchAnswerOptions.tsx @@ -1,32 +1,29 @@ -import * as React from "react"; +import { QuizQuestionVariant } from "@model/questionTypes/variant"; import UploadImage from "../UploadImage"; +import BranchingQuestions from "../branchingQuestions"; import HelpQuestions from "../helpQuestions"; import ResponseSettings from "./responseSettings"; -import BranchingQuestions from "../branchingQuestions"; + interface Props { - switchState: string; - totalIndex: number; + switchState: string; + question: QuizQuestionVariant; } export default function SwitchAnswerOptions({ - switchState = "setting", - totalIndex, + switchState = "setting", + question, }: Props) { - switch (switchState) { - case "setting": - return ; - break; - case "help": - return ; - break; - case "branching": - return ; - break; - case "image": - return ; - break; - default: - return <>; - } + switch (switchState) { + case "setting": + return ; + case "help": + return ; + case "branching": + return ; + case "image": + return ; + default: + return <>; + } } diff --git a/src/pages/Questions/branchingQuestions.tsx b/src/pages/Questions/branchingQuestions.tsx index 4283523f..2eee5fb8 100644 --- a/src/pages/Questions/branchingQuestions.tsx +++ b/src/pages/Questions/branchingQuestions.tsx @@ -1,394 +1,338 @@ -import { useState, useRef, useEffect } from "react"; -import { useParams } from "react-router-dom"; -import { - Box, - Button, - Chip, - FormControl, - FormControlLabel, - IconButton, - Link, - Modal, - Radio, - RadioGroup, - Tooltip, - Typography, - useTheme, -} from "@mui/material"; -import { questionStore, updateQuestionsList } from "@root/questions"; -import { Select } from "./Select"; - -import RadioCheck from "@ui_kit/RadioCheck"; -import RadioIcon from "@ui_kit/RadioIcon"; import InfoIcon from "@icons/Info"; import { DeleteIcon } from "@icons/questionsPage/deleteIcon"; +import { QuizQuestionVariant } from "@model/questionTypes/variant"; +import { + Box, + Button, + Chip, + FormControl, + FormControlLabel, + IconButton, + Link, + Modal, + Radio, + RadioGroup, + Tooltip, + Typography, + useTheme, +} from "@mui/material"; +import { updateQuestionWithFnOptimistic } from "@root/questions/actions"; +import RadioCheck from "@ui_kit/RadioCheck"; +import RadioIcon from "@ui_kit/RadioIcon"; +import { useEffect, useRef, useState } from "react"; +import { Select } from "./Select"; -import type { QuizQuestionBase } from "../../model/questionTypes/shared"; type BranchingQuestionsProps = { - totalIndex: number; + question: QuizQuestionVariant; }; const ACTIONS = ["Показать", "Скрыть"]; const STIPULATIONS = ["Условие 1", "Условие 2", "Условие 3"]; const ANSWERS = ["Ответ 1", "Ответ 2", "Ответ 3"]; const CONDITIONS = [ - "Все условия обязательны", - "Обязательно хотя бы одно условие", + "Все условия обязательны", + "Обязательно хотя бы одно условие", ]; export default function BranchingQuestions({ - totalIndex, + question, }: BranchingQuestionsProps) { - const theme = useTheme(); - const [title, setTitle] = useState(""); - const [titleInputWidth, setTitleInputWidth] = useState(0); - const quizId = Number(useParams().quizId); - const { listQuestions } = questionStore(); - const titleRef = useRef(null); - const question = listQuestions[quizId][totalIndex] as QuizQuestionBase; + const theme = useTheme(); + const [title, setTitle] = useState(""); + const [titleInputWidth, setTitleInputWidth] = useState(0); + const titleRef = useRef(null); - useEffect(() => { - setTitleInputWidth(titleRef.current?.offsetWidth || 0); - }, [title]); + useEffect(() => { + setTitleInputWidth(titleRef.current?.offsetWidth || 0); + }, [title]); - const handleClose = () => { - updateQuestionsList(quizId, totalIndex, { - openedModalSettings: false, - }); - }; + const handleClose = () => { + updateQuestionWithFnOptimistic(question.id, question => { + question.openedModalSettings = false; + }); + }; - return ( - <> - - - - - ( - - - {title} - - setTitle(target.value)} - style={{ - width: titleInputWidth ? titleInputWidth : 170, - outline: "none", - background: "transparent", - border: "none", - fontSize: "18px", - minWidth: "50px", - maxWidth: "500px", - fontFamily: "Rubik", - transition: ".2s", - }} - /> - - ) - - - - - - - - - - { - const clonedContent = { ...question.content }; - - clonedContent.rule.reqs[index] = { - id: String( - STIPULATIONS.findIndex((item) => - item.includes(stipulation) - ) - ), - vars: request.vars, - }; - - updateQuestionsList(quizId, totalIndex, { - content: clonedContent, - }); - }} - sx={{ marginBottom: "15px" }} - /> - {request.id && ( - <> - - - Дан ответ - - - (Укажите один или несколько вариантов) - - - setTitle(target.value)} + style={{ + width: titleInputWidth ? titleInputWidth : 170, + outline: "none", + background: "transparent", + border: "none", + fontSize: "18px", + minWidth: "50px", + maxWidth: "500px", + fontFamily: "Rubik", + transition: ".2s", + }} + /> + + ) + + + + + + + + + + { + updateQuestionWithFnOptimistic(question.id, question => { + question.content.rule.reqs[index].id = String( + STIPULATIONS.findIndex((item) => item.includes(stipulation)) + ); + }); + }} + sx={{ marginBottom: "15px" }} + /> + {request.id && ( + <> + + + Дан ответ + + + (Укажите один или несколько вариантов) + + +