diff --git a/src/assets/icons/NameplateLogoFQ.tsx b/src/assets/icons/NameplateLogoFQ.tsx new file mode 100644 index 00000000..db350ab5 --- /dev/null +++ b/src/assets/icons/NameplateLogoFQ.tsx @@ -0,0 +1,23 @@ +import { FC, SVGProps } from "react"; + +export const NameplateLogoFQ: FC> = (props) => ( + + + + + + + + + + + + + + + + + + + +); diff --git a/src/constants/result.ts b/src/constants/result.ts index b9f40159..1e1d5c35 100644 --- a/src/constants/result.ts +++ b/src/constants/result.ts @@ -12,6 +12,7 @@ export const QUIZ_QUESTION_RESULT: Omit innerName: "", text: "", price: [0], - useImage: true + useImage: true, + usage: true }, }; diff --git a/src/model/questionTypes/page.ts b/src/model/questionTypes/page.ts index d422ba95..ba6eb261 100644 --- a/src/model/questionTypes/page.ts +++ b/src/model/questionTypes/page.ts @@ -1,8 +1,4 @@ -import type { - QuizQuestionBase, - QuestionHint, - PreviewRule, -} from "./shared"; +import type { QuizQuestionBase, QuestionHint, PreviewRule } from "./shared"; export interface QuizQuestionPage extends QuizQuestionBase { type: "page"; @@ -15,6 +11,7 @@ export interface QuizQuestionPage extends QuizQuestionBase { text: string; picture: string; originalPicture: string; + useImage: boolean; video: string; hint: QuestionHint; rule: PreviewRule; diff --git a/src/model/questionTypes/result.ts b/src/model/questionTypes/result.ts index 7636bd36..0437b2ce 100644 --- a/src/model/questionTypes/result.ts +++ b/src/model/questionTypes/result.ts @@ -18,5 +18,6 @@ export interface QuizQuestionResult extends QuizQuestionBase { rule: QuestionBranchingRule, hint: QuestionHint; autofill: boolean; + usage: boolean }; } diff --git a/src/pages/Landing/HeaderLanding.tsx b/src/pages/Landing/HeaderLanding.tsx index 6eb1381c..94444268 100644 --- a/src/pages/Landing/HeaderLanding.tsx +++ b/src/pages/Landing/HeaderLanding.tsx @@ -15,13 +15,9 @@ export default function Component() { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(600)); const isTablet = useMediaQuery(theme.breakpoints.down(1000)); - const [select, setSelect] = React.useState(0); const userId = useUserStore((state) => state.userId); - const navigate = useNavigate(); const location = useLocation(); - const onClick = () => (userId ? navigate("/list") : navigate("/signin")); - return ( */} - diff --git a/src/pages/Questions/BranchingQuestionsModal/index.tsx b/src/pages/Questions/BranchingQuestionsModal/index.tsx index 901f71da..ff140d9a 100644 --- a/src/pages/Questions/BranchingQuestionsModal/index.tsx +++ b/src/pages/Questions/BranchingQuestionsModal/index.tsx @@ -6,14 +6,12 @@ interface Props { openedModalQuestions: boolean; setModalQuestionTargetContentId: (contentId: string) => void; setOpenedModalQuestions: (open: boolean) => void; - setModalQuestionParentContentId: (open: string) => void; } export const BranchingQuestionsModal = ({ openedModalQuestions, setOpenedModalQuestions, setModalQuestionTargetContentId, - setModalQuestionParentContentId, }: Props) => { const trashQuestions = useQuestionsStore().questions; const questions = trashQuestions.filter( @@ -25,10 +23,14 @@ export const BranchingQuestionsModal = ({ }; const typedQuestions: AnyTypedQuizQuestion[] = questions.filter( - (question) => question.type && !question.content.rule.parentId && question.type !== "result" + (question) => + question.type && + !question.content.rule.parentId && + question.type !== "result" ) as AnyTypedQuizQuestion[]; - if (typedQuestions.length === 0) return <> + if (typedQuestions.length === 0) return <>; + return ( diff --git a/src/pages/Questions/ButtonsOptions.tsx b/src/pages/Questions/ButtonsOptions.tsx index 66cdef0a..d827fad4 100644 --- a/src/pages/Questions/ButtonsOptions.tsx +++ b/src/pages/Questions/ButtonsOptions.tsx @@ -57,11 +57,6 @@ export default function ButtonsOptions({ if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам updateRootContentId(quiz.id, ""); clearRuleForAll(); - questions.forEach(q => { - if (q.type === "result") { - deleteQuestion(q.id); - } - }); deleteQuestion(question.id); } else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков const clearQuestions = [] as string[]; @@ -69,10 +64,8 @@ export default function ButtonsOptions({ //записываем потомков , а их результаты удаляем const getChildren = (parentQuestion: AnyTypedQuizQuestion) => { questions.forEach((targetQuestion) => { - if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его - if (targetQuestion.type === "result") { - deleteQuestion(targetQuestion.id); - } else { + if (targetQuestion.type !== null && targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его + if (targetQuestion.type !== "result" && targetQuestion.type !== null) { if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id); getChildren(targetQuestion); //и ищем его потомков } diff --git a/src/pages/Questions/ButtonsOptionsAndPict.tsx b/src/pages/Questions/ButtonsOptionsAndPict.tsx index e4fbae65..528932fe 100644 --- a/src/pages/Questions/ButtonsOptionsAndPict.tsx +++ b/src/pages/Questions/ButtonsOptionsAndPict.tsx @@ -58,11 +58,6 @@ export default function ButtonsOptionsAndPict({ if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам updateRootContentId(quiz.id, ""); clearRuleForAll(); - questions.forEach(q => { - if (q.type === "result") { - deleteQuestion(q.id); - } - }); deleteQuestion(question.id); } else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков const clearQuestions = [] as string[]; @@ -71,9 +66,7 @@ export default function ButtonsOptionsAndPict({ const getChildren = (parentQuestion: AnyTypedQuizQuestion) => { questions.forEach((targetQuestion) => { if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его - if (targetQuestion.type === "result") { - deleteQuestion(targetQuestion.id); - } else { + if (targetQuestion.type !== null && targetQuestion.type !== "result") { if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id); getChildren(targetQuestion); //и ищем его потомков } diff --git a/src/pages/Questions/DeleteNodeModal/index.tsx b/src/pages/Questions/DeleteNodeModal/index.tsx new file mode 100644 index 00000000..0e6a21fc --- /dev/null +++ b/src/pages/Questions/DeleteNodeModal/index.tsx @@ -0,0 +1,122 @@ +import { useState, useRef, useEffect, useLayoutEffect } from "react"; +import { + Box, + Button, + FormControl, + FormControlLabel, + Link, + Modal, + Radio, + RadioGroup, + Tooltip, + Typography, + useTheme, + Checkbox, +} from "@mui/material"; +import { enqueueSnackbar } from "notistack"; +import { + AnyTypedQuizQuestion, + createBranchingRuleMain, +} from "../../../model/questionTypes/shared"; +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 { + getQuestionById, + getQuestionByContentId, + updateQuestion, +} from "@root/questions/actions"; +import { updateOpenedModalSettingsId } from "@root/uiTools/actions"; +import { useUiTools } from "@root/uiTools/store"; + +export const DeleteNodeModal = () => { + const { deleteNodeId } = useUiTools(); + const targetQuestion = getQuestionById(deleteNodeId); + + const saveData = () => { + // if (parentQuestion !== null) { + // updateQuestion( + // parentQuestion.content.id, + // (question) => (question.content = parentQuestion.content) + // ); + // } + // handleClose(); + }; + + const handleClose = () => { + updateOpenedModalSettingsId(); + }; + + return ( + <> + + + + + {targetQuestion?.title} + + + + + + + + + + + + + + + + ); +}; diff --git a/src/pages/Questions/DraggableList/QuestionPageCard.tsx b/src/pages/Questions/DraggableList/QuestionPageCard.tsx index bfa3d59e..38343340 100644 --- a/src/pages/Questions/DraggableList/QuestionPageCard.tsx +++ b/src/pages/Questions/DraggableList/QuestionPageCard.tsx @@ -18,18 +18,31 @@ import RatingIcon from "@icons/questionsPage/rating"; import Slider from "@icons/questionsPage/slider"; import ExpandLessIcon from "@mui/icons-material/ExpandLess"; import { - Box, Button, - Checkbox, - FormControl, - FormControlLabel, - IconButton, - InputAdornment, Modal, - Paper, - TextField, Typography, - useMediaQuery, - useTheme, + Box, + Button, + Checkbox, + FormControl, + FormControlLabel, + IconButton, + InputAdornment, + Modal, + Paper, + TextField, + Typography, + useMediaQuery, + useTheme, } from "@mui/material"; -import { copyQuestion, createUntypedQuestion, deleteQuestion, clearRuleForAll, toggleExpandQuestion, updateQuestion, updateUntypedQuestion, getQuestionByContentId, deleteQuestionWithTimeout } from "@root/questions/actions"; +import { + copyQuestion, + createUntypedQuestion, + deleteQuestion, + clearRuleForAll, + toggleExpandQuestion, + updateQuestion, + updateUntypedQuestion, + getQuestionByContentId, + deleteQuestionWithTimeout, +} from "@root/questions/actions"; import { updateRootContentId } from "@root/quizes/actions"; import { useRef, useState } from "react"; import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd"; @@ -51,9 +64,14 @@ interface Props { } export default function QuestionsPageCard({ question, draggableProps, isDragging, index }: Props) { +const maxLengthTextField = 225; + const { questions } = useQuestionsStore(); const [plusVisible, setPlusVisible] = useState(false); const [open, setOpen] = useState(false); + + const [isTextFieldtActive, setIsTextFieldtActive] = useState(false); + const [openDelete, setOpenDelete] = useState(false); const theme = useTheme(); const isTablet = useMediaQuery(theme.breakpoints.down(1000)); @@ -75,21 +93,14 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging updateRootContentId(quiz.id, ""); clearRuleForAll(); deleteQuestion(question.id); - questions.forEach(q => { - if (q.type === "result") { - deleteQuestion(q.id); - } - }); } else if (question.content.rule.parentId.length > 0) { //удалить из стора вопрос из дерева и очистить его потомков const clearQuestions = [] as string[]; //записываем потомков , а их результаты удаляем const getChildren = (parentQuestion: AnyTypedQuizQuestion) => { questions.forEach((targetQuestion) => { - if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его - if (targetQuestion.type === "result") { - deleteQuestion(targetQuestion.id); - } else { + if (targetQuestion.type !== null && targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его + if (targetQuestion.type !== null && targetQuestion.type !== "result") { if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id); getChildren(targetQuestion); //и ищем его потомков } @@ -127,328 +138,332 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging } }; - return ( - <> - - - - setTitle(target.value || " ")} - InputProps={{ - startAdornment: ( - - setOpen((isOpened) => !isOpened)} - > - {IconAndrom(question.expanded, question.type)} - - setOpen(false)} - anchorRef={anchorRef} - question={question} - questionType={question.type} - /> - - ), - }} - sx={{ - margin: isMobile ? "10px 0" : 0, - "& .MuiInputBase-root": { - color: "#000000", - backgroundColor: question.expanded - ? theme.palette.background.default - : "transparent", - height: "48px", - borderRadius: "10px", - ".MuiOutlinedInput-notchedOutline": { - borderWidth: "1px !important", - border: !question.expanded ? "none" : null, - }, - "& .MuiInputBase-input::placeholder": { - color: "#4D4D4D", - opacity: 0.8, - }, - }, - }} - inputProps={{ - sx: { - fontSize: "18px", - lineHeight: "21px", - py: 0, - paddingLeft: question.type === null ? 0 : "18px", - }, - "data-cy": "quiz-question-title", - }} - /> - - - toggleExpandQuestion(question.id)} - > - {question.expanded ? ( - - ) : ( - - )} - - {question.expanded ? ( - <> - ) : ( - - - } - checkedIcon={} - /> - } - label={""} - sx={{ - color: theme.palette.grey2.main, - ml: "-9px", - mr: 0, - userSelect: "none", - }} - /> - copyQuestion(question.id, question.quizId)} - > - - - { - if (question.type === null) deleteQuestion(question.id) - if(question?.content?.rule.parentId.length !== 0) { - setOpenDelete(true) - } else { - deleteQuestionWithTimeout(question.id, deleteFn); - } - }} - data-cy="delete-question" - > - - - setOpenDelete(false)}> - - - Вы удаляете вопрос, участвующий в ветвлении. Все его потомки потеряют данные ветвления. Вы уверены, что хотите удалить вопрос? - - - - - - - - - )} - {question.type !== null && - - {question.page + 1} - - } - - - - - - {question.expanded && ( - { + setIsTextFieldtActive(true); + }; + + const handleInputBlur = (event: React.FocusEvent) => { + setIsTextFieldtActive(false); + }; + + + return ( + <> + + + + setTitle(target.value || " ")} + onFocus={handleInputFocus} + onBlur={handleInputBlur} + inputProps={{ + maxLength: maxLengthTextField, + }} + InputProps={{ + startAdornment: ( + + setOpen((isOpened) => !isOpened)} > - {question.type === null ? ( - - ) : ( - - )} - - )} - - setPlusVisible(true)} - onMouseLeave={() => setPlusVisible(false)} - sx={{ - maxWidth: "825px", - display: "flex", - alignItems: "center", - height: "40px", - cursor: "pointer", - }} - > - createUntypedQuestion(question.quizId, question.id)} - sx={{ - display: plusVisible && !isDragging ? "flex" : "none", - width: "100%", - alignItems: "center", - columnGap: "10px", - }} - data-cy="create-question" - > - + setOpen(false)} + anchorRef={anchorRef} + question={question} + questionType={question.type} /> - - - - - ); + + ), + endAdornment: isTextFieldtActive && question.title.length >= maxLengthTextField - 7 && ( + + {question.title.length} + / + {maxLengthTextField} + + ), + }} + sx={{ + margin: isMobile ? "10px 0" : 0, + "& .MuiInputBase-root": { + color: "#000000", + backgroundColor: question.expanded ? theme.palette.background.default : "transparent", + height: "48px", + borderRadius: "10px", + ".MuiOutlinedInput-notchedOutline": { + borderWidth: "1px !important", + border: !question.expanded ? "none" : null, + }, + "& .MuiInputBase-input::placeholder": { + color: "#4D4D4D", + opacity: 0.8, + }, + }, + }} + /> + + + toggleExpandQuestion(question.id)} + > + {question.expanded ? ( + + ) : ( + + )} + + {question.expanded ? ( + <> + ) : ( + + + } + checkedIcon={} + /> + } + label={""} + sx={{ + color: theme.palette.grey2.main, + ml: "-9px", + mr: 0, + userSelect: "none", + }} + /> + copyQuestion(question.id, question.quizId)}> + + + { + if (question.content.rule.parentId.length !== 0) { + setOpenDelete(true); + } else { + deleteQuestionWithTimeout(question.id, deleteFn); + } + }} + data-cy="delete-question" + > + + + setOpenDelete(false)}> + + + Вы удаляете вопрос, участвующий в ветвлении. Все его потомки потеряют данные ветвления. Вы + уверены, что хотите удалить вопрос? + + + + + + + + + )} + {question.type !== null && ( + + {question.page + 1} + + )} + + + + + + {question.expanded && ( + + {question.type === null ? ( + + ) : ( + + )} + + )} + + setPlusVisible(true)} + onMouseLeave={() => setPlusVisible(false)} + sx={{ + maxWidth: "825px", + display: "flex", + alignItems: "center", + height: "40px", + cursor: "pointer", + }} + > + createUntypedQuestion(question.quizId, question.id)} + sx={{ + display: plusVisible && !isDragging ? "flex" : "none", + width: "100%", + alignItems: "center", + columnGap: "10px", + }} + data-cy="create-question" + > + + + + + + ); + } const IconAndrom = (isExpanded: boolean, questionType: QuestionType | null) => { diff --git a/src/pages/Questions/DropDown/DropDown.tsx b/src/pages/Questions/DropDown/DropDown.tsx index f5e035be..ceab1763 100644 --- a/src/pages/Questions/DropDown/DropDown.tsx +++ b/src/pages/Questions/DropDown/DropDown.tsx @@ -4,22 +4,23 @@ import { AnswerDraggableList } from "../AnswerDraggableList"; import { EnterIcon } from "../../../assets/icons/questionsPage/enterIcon"; import SwitchDropDown from "./switchDropDown"; import ButtonsOptions from "../ButtonsOptions"; -import type { QuizQuestionSelect } from "../../../model/questionTypes/select"; import { addQuestionVariant } from "@root/questions/actions"; +import { useAddAnswer } from "../../../utils/hooks/useAddAnswer"; - +import type { QuizQuestionSelect } from "../../../model/questionTypes/select"; interface Props { question: QuizQuestionSelect; } export default function DropDown({ question }: Props) { + const onClickAddAnAnswer = useAddAnswer(); const [switchState, setSwitchState] = useState("setting"); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down(790)); const SSHC = (data: string) => { setSwitchState(data); - }; + }; return ( <> @@ -60,7 +61,7 @@ export default function DropDown({ question }: Props) { mr: "4px", height: "19px", }} - onClick={() => addQuestionVariant(question.id)} + onClick={() => onClickAddAnAnswer(question)} > Добавьте ответ @@ -87,11 +88,7 @@ export default function DropDown({ question }: Props) { )} - + ); diff --git a/src/pages/Questions/Emoji/Emoji.tsx b/src/pages/Questions/Emoji/Emoji.tsx index ff389b5e..7c9c9bd1 100644 --- a/src/pages/Questions/Emoji/Emoji.tsx +++ b/src/pages/Questions/Emoji/Emoji.tsx @@ -1,14 +1,7 @@ import { EmojiIcons } from "@icons/EmojiIocns"; import AddEmoji from "@icons/questionsPage/addEmoji"; import PlusImage from "@icons/questionsPage/plus"; -import { - Box, - Link, - Popover, - Typography, - useMediaQuery, - useTheme, -} from "@mui/material"; +import { Box, Link, Popover, Typography, useMediaQuery, useTheme } from "@mui/material"; import { addQuestionVariant, updateQuestion } from "@root/questions/actions"; import { EmojiPicker } from "@ui_kit/EmojiPicker"; import { useState } from "react"; @@ -17,225 +10,220 @@ import type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji"; import { AnswerDraggableList } from "../AnswerDraggableList"; import ButtonsOptions from "../ButtonsOptions"; import SwitchEmoji from "./switchEmoji"; - +import { useAddAnswer } from "../../../utils/hooks/useAddAnswer"; interface Props { - question: QuizQuestionEmoji; + question: QuizQuestionEmoji; } export default function Emoji({ question }: Props) { - const [switchState, setSwitchState] = useState("setting"); - const [open, setOpen] = useState(false); - const [anchorElement, setAnchorElement] = useState( - null - ); - const [selectedVariant, setSelectedVariant] = useState(null); - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down(790)); - const isTablet = useMediaQuery(theme.breakpoints.down(1000)); + const [switchState, setSwitchState] = useState("setting"); + const onClickAddAnAnswer = useAddAnswer(); + const [open, setOpen] = useState(false); + const [anchorElement, setAnchorElement] = useState(null); + const [selectedVariant, setSelectedVariant] = useState(null); + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down(790)); + const isTablet = useMediaQuery(theme.breakpoints.down(1000)); - const SSHC = (data: string) => { - setSwitchState(data); - }; + const SSHC = (data: string) => { + setSwitchState(data); + }; - return ( - <> - - ( - <> - {!isTablet && ( - - { - setAnchorElement(currentTarget); - setSelectedVariant(variant.id); - setOpen(true); - }} - > - - {variant.extendedText ? ( - - - {variant.extendedText} - - - - - - ) : ( - - )} - - - - )} - - )} - additionalMobile={(variant) => ( - <> - {isTablet && ( - { - setAnchorElement(currentTarget); - setSelectedVariant(variant.id); - setOpen(true); - }} - sx={{ - display: "flex", - alignItems: "center", - m: "8px", - position: "relative", - }} - > - - {variant.extendedText ? ( - - {variant.extendedText} - - ) : ( - - )} - - + - - - )} - - )} - /> - event.stopPropagation()} - onClose={() => setOpen(false)} - anchorOrigin={{ - vertical: "bottom", - horizontal: "right", + return ( + <> + + ( + <> + {!isTablet && ( + + { + setAnchorElement(currentTarget); + setSelectedVariant(variant.id); + setOpen(true); }} - sx={{ - ".MuiPaper-root.MuiPaper-rounded": { - borderRadius: "10px", - }, - }} - > - { - setOpen(false); - updateQuestion(question.id, question => { - if (question.type !== "emoji") return; - - const variant = question.content.variants.find(v => v.id === selectedVariant); - if (!variant) return; - - variant.extendedText = native; - }); - }} - /> - - + - addQuestionVariant(question.id)} + gap: "5px", + }} > - Добавьте ответ - - {!isTablet && ( - <> - - или нажмите Enter - - - - )} + {variant.extendedText ? ( + + + {variant.extendedText} + + + + + + ) : ( + + )} + + - - - - - ); + )} + + )} + additionalMobile={(variant) => ( + <> + {isTablet && ( + { + setAnchorElement(currentTarget); + setSelectedVariant(variant.id); + setOpen(true); + }} + sx={{ + display: "flex", + alignItems: "center", + m: "8px", + position: "relative", + }} + > + + {variant.extendedText ? ( + + {variant.extendedText} + + ) : ( + + )} + + + + + + )} + + )} + /> + event.stopPropagation()} + onClose={() => setOpen(false)} + anchorOrigin={{ + vertical: "bottom", + horizontal: "right", + }} + sx={{ + ".MuiPaper-root.MuiPaper-rounded": { + borderRadius: "10px", + }, + }} + > + { + setOpen(false); + updateQuestion(question.id, (question) => { + if (question.type !== "emoji") return; + + const variant = question.content.variants.find((v) => v.id === selectedVariant); + if (!variant) return; + + variant.extendedText = native; + }); + }} + /> + + + onClickAddAnAnswer(question)} + > + Добавьте ответ + + {!isTablet && ( + <> + + или нажмите Enter + + + + )} + + + + + + ); } diff --git a/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx b/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx index 14be7e7b..103aef35 100644 --- a/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx +++ b/src/pages/Questions/Form/FormDraggableList/QuestionPageCard.tsx @@ -11,8 +11,23 @@ import OptionsPict from "@icons/questionsPage/options_pict"; import Page from "@icons/questionsPage/page"; import RatingIcon from "@icons/questionsPage/rating"; import Slider from "@icons/questionsPage/slider"; -import { Box, FormControlLabel, IconButton, InputAdornment, Paper, useMediaQuery, useTheme } from "@mui/material"; -import { toggleExpandQuestion, updateQuestion, updateUntypedQuestion } from "@root/questions/actions"; +import { + Box, Checkbox, + FormControl, + FormControlLabel, + IconButton, + InputAdornment, + Paper, TextField, + useMediaQuery, + useTheme +} from "@mui/material"; +import { + copyQuestion, + deleteQuestion, deleteQuestionWithTimeout, + toggleExpandQuestion, + updateQuestion, + updateUntypedQuestion +} from "@root/questions/actions"; import CustomTextField from "@ui_kit/CustomTextField"; import { useRef, useState } from "react"; import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd"; @@ -52,150 +67,246 @@ export default function QuestionsPageCard({ question, questionIndex, draggablePr }, 200); console.log(question) - return ( - <> - - - - setTitle(target.value)} - sx={{ width: "100%" }} - InputProps={{ - startAdornment: ( - - setOpen((isOpened) => !isOpened)} - > - {IconAndrom(question.type)} - - setOpen(false)} - anchorRef={anchorRef} - question={question} - questionType={question.type} - /> - - ), - }} - /> - + - - toggleExpandQuestion(question.id)} - > - {question.expanded ? ( - - ) : ( - - )} - - - {questionIndex + 1} + + + setTitle(target.value)} + sx={{ + width: "100%", + margin: isMobile ? "10px 0" : 0, + "& .MuiInputBase-root": { + color: "#000000", + backgroundColor: question.expanded + ? theme.palette.background.default + : "transparent", + height: "48px", + borderRadius: "10px", + ".MuiOutlinedInput-notchedOutline": { + borderWidth: "1px !important", + border: !question.expanded ? "none" : null, + }, + "& .MuiInputBase-input::placeholder": { + color: "#4D4D4D", + opacity: 0.8, + }, + }, + }} + InputProps={{ + startAdornment: ( + + setOpen((isOpened) => !isOpened)} + > + {IconAndrom(question.type)} + + setOpen(false)} + anchorRef={anchorRef} + question={question} + questionType={question.type} + /> + + ), + }} + /> + + + + toggleExpandQuestion(question.id)} + > + {question.expanded ? ( + + ) : ( + + )} + + + {question.expanded ? ( + <> + ) : ( + + + } + checkedIcon={} + /> + } + label={""} + sx={{ + color: theme.palette.grey2.main, + ml: "-9px", + mr: 0, + userSelect: "none", + }} + /> + copyQuestion(question.id, question.quizId)} + > + + + { + deleteQuestionWithTimeout(question.id, deleteQuestion(question.id)); + + }} + > + + + + )} + + + {questionIndex + 1} + + + + + + + + + {question.expanded && ( + <> + {question.type === null ? ( + + ) : ( + + )} + + )} - - - - - - - - - {question.type === null ? ( - - ) : ( - - )} - - - - ); + + + ); } const IconAndrom = (questionType: QuestionType | null) => { @@ -225,4 +336,4 @@ const IconAndrom = (questionType: QuestionType | null) => { default: return ; } -}; \ No newline at end of file +}; diff --git a/src/pages/Questions/OptionsPicture/OptionsPicture.tsx b/src/pages/Questions/OptionsPicture/OptionsPicture.tsx index 1b262c64..a8a9923d 100644 --- a/src/pages/Questions/OptionsPicture/OptionsPicture.tsx +++ b/src/pages/Questions/OptionsPicture/OptionsPicture.tsx @@ -1,10 +1,4 @@ -import { - Box, - Link, - Typography, - useMediaQuery, - useTheme -} from "@mui/material"; +import { Box, Link, Typography, useMediaQuery, useTheme } from "@mui/material"; import { addQuestionVariant, uploadQuestionImage } from "@root/questions/actions"; import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton"; import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal"; @@ -17,165 +11,153 @@ import { UploadImageModal } from "../UploadImage/UploadImageModal"; import SwitchAnswerOptionsPict from "./switchOptionsPict"; import { useCurrentQuiz } from "@root/quizes/hooks"; import { useDisclosure } from "../../../utils/useDisclosure"; - +import { useAddAnswer } from "../../../utils/hooks/useAddAnswer"; interface Props { - question: QuizQuestionImages; + question: QuizQuestionImages; } export default function OptionsPicture({ question }: Props) { - const theme = useTheme(); - const quizQid = useCurrentQuiz()?.qid; - const [selectedVariantId, setSelectedVariantId] = useState(null); - const [switchState, setSwitchState] = useState("setting"); - const isMobile = useMediaQuery(theme.breakpoints.down(790)); - const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure(); - const { - isCropModalOpen, - openCropModal, - closeCropModal, - imageBlob, - originalImageUrl, - setCropModalImageBlob, - } = useCropModalState(); + const theme = useTheme(); + const onClickAddAnAnswer = useAddAnswer(); + const quizQid = useCurrentQuiz()?.qid; + const [selectedVariantId, setSelectedVariantId] = useState(null); + const [switchState, setSwitchState] = useState("setting"); + const isMobile = useMediaQuery(theme.breakpoints.down(790)); + const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure(); + const { isCropModalOpen, openCropModal, closeCropModal, imageBlob, originalImageUrl, setCropModalImageBlob } = + useCropModalState(); - const SSHC = (data: string) => { - setSwitchState(data); - }; + const SSHC = (data: string) => { + setSwitchState(data); + }; - const handleImageUpload = async (file: File) => { - if (!selectedVariantId) return; + const handleImageUpload = async (file: File) => { + if (!selectedVariantId) return; - const url = await uploadQuestionImage(question.id, quizQid, file, (question, url) => { - if (!("variants" in question.content)) return; + const url = await uploadQuestionImage(question.id, quizQid, file, (question, url) => { + if (!("variants" in question.content)) return; - const variant = question.content.variants.find(variant => variant.id === selectedVariantId); - if (!variant) return; + const variant = question.content.variants.find((variant) => variant.id === selectedVariantId); + if (!variant) return; - variant.extendedText = url; - variant.originalImageUrl = url; - }); - closeImageUploadModal(); - openCropModal(file, url); - }; + variant.extendedText = url; + variant.originalImageUrl = url; + }); + closeImageUploadModal(); + openCropModal(file, url); + }; - function handleCropModalSaveClick(imageBlob: Blob) { - if (!selectedVariantId) return; + function handleCropModalSaveClick(imageBlob: Blob) { + if (!selectedVariantId) return; - uploadQuestionImage(question.id, quizQid, imageBlob, (question, url) => { - if (!("variants" in question.content)) return; + uploadQuestionImage(question.id, quizQid, imageBlob, (question, url) => { + if (!("variants" in question.content)) return; - const variant = question.content.variants.find(variant => variant.id === selectedVariantId); - if (!variant) return; + const variant = question.content.variants.find((variant) => variant.id === selectedVariantId); + if (!variant) return; - variant.extendedText = url; - }); - } + variant.extendedText = url; + }); + } - return ( - <> - - ( - <> - {!isMobile && ( - { - setSelectedVariantId(variant.id); - if (variant.extendedText) { - return openCropModal( - variant.extendedText, - variant.originalImageUrl - ); - } + return ( + <> + + ( + <> + {!isMobile && ( + { + setSelectedVariantId(variant.id); + if (variant.extendedText) { + return openCropModal(variant.extendedText, variant.originalImageUrl); + } - openImageUploadModal(); - }} - onPlusClick={() => { - setSelectedVariantId(variant.id); - openImageUploadModal(); - }} - sx={{ mx: "10px" }} - /> - )} - - )} - additionalMobile={(variant) => ( - <> - {isMobile && ( - { - setSelectedVariantId(variant.id); - if (variant.extendedText) { - return openCropModal( - variant.extendedText, - variant.originalImageUrl - ); - } - - openImageUploadModal(); - }} - onPlusClick={() => { - setSelectedVariantId(variant.id); - openImageUploadModal(); - }} - sx={{ m: "8px", width: "auto" }} - /> - )} - - )} + openImageUploadModal(); + }} + onPlusClick={() => { + setSelectedVariantId(variant.id); + openImageUploadModal(); + }} + sx={{ mx: "10px" }} /> - - - - addQuestionVariant(question.id)} - > - Добавьте ответ - - {isMobile ? null : ( - <> - - или нажмите Enter - - - - )} - - - - - + )} + + )} + additionalMobile={(variant) => ( + <> + {isMobile && ( + { + setSelectedVariantId(variant.id); + if (variant.extendedText) { + return openCropModal(variant.extendedText, variant.originalImageUrl); + } - ); + openImageUploadModal(); + }} + onPlusClick={() => { + setSelectedVariantId(variant.id); + openImageUploadModal(); + }} + sx={{ m: "8px", width: "auto" }} + /> + )} + + )} + /> + + + + onClickAddAnAnswer(question)} + > + Добавьте ответ + + {isMobile ? null : ( + <> + + или нажмите Enter + + + + )} + + + + + + ); } diff --git a/src/pages/Questions/PageOptions/PageOptions.tsx b/src/pages/Questions/PageOptions/PageOptions.tsx index 6e79a496..b7878072 100644 --- a/src/pages/Questions/PageOptions/PageOptions.tsx +++ b/src/pages/Questions/PageOptions/PageOptions.tsx @@ -1,5 +1,5 @@ import { VideofileIcon } from "@icons/questionsPage/VideofileIcon"; -import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { Box, Button, Typography, useMediaQuery, useTheme } from "@mui/material"; import { updateQuestion, uploadQuestionImage } from "@root/questions/actions"; import { useCurrentQuiz } from "@root/quizes/hooks"; import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton"; @@ -14,253 +14,252 @@ import { UploadVideoModal } from "../UploadVideoModal"; import SwitchPageOptions from "./switchPageOptions"; import { useDisclosure } from "../../../utils/useDisclosure"; - type Props = { - disableInput?: boolean; - question: QuizQuestionPage; + disableInput?: boolean; + question: QuizQuestionPage; }; export default function PageOptions({ disableInput, question }: Props) { - const [openVideoModal, setOpenVideoModal] = useState(false); - const [switchState, setSwitchState] = useState("setting"); - const theme = useTheme(); - const isTablet = useMediaQuery(theme.breakpoints.down(980)); - const isFigmaTablet = useMediaQuery(theme.breakpoints.down(990)); - const isMobile = useMediaQuery(theme.breakpoints.down(780)); - const quizQid = useCurrentQuiz()?.qid; - const { - isCropModalOpen, - openCropModal, - closeCropModal, - imageBlob, - originalImageUrl, - setCropModalImageBlob, - } = useCropModalState(); - const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure(); + const [openVideoModal, setOpenVideoModal] = useState(false); + const [switchState, setSwitchState] = useState("setting"); + const theme = useTheme(); + const isTablet = useMediaQuery(theme.breakpoints.down(980)); + const isFigmaTablet = useMediaQuery(theme.breakpoints.down(990)); + const isMobile = useMediaQuery(theme.breakpoints.down(780)); + const quizQid = useCurrentQuiz()?.qid; + const { isCropModalOpen, openCropModal, closeCropModal, imageBlob, originalImageUrl, setCropModalImageBlob } = + useCropModalState(); + const [isImageUploadOpen, openImageUploadModal, closeImageUploadModal] = useDisclosure(); - const setText = useDebouncedCallback((value) => { - updateQuestion(question.id, question => { - if (question.type !== "page") return; + const setText = useDebouncedCallback((value) => { + updateQuestion(question.id, (question) => { + if (question.type !== "page") return; - question.content.text = value; - }); - }, 200); + question.content.text = value; + }); + }, 200); - const SSHC = (data: string) => { - setSwitchState(data); - }; + const SSHC = (data: string) => { + setSwitchState(data); + }; - async function handleImageUpload(file: File) { - const url = await uploadQuestionImage(question.id, quizQid, file, (question, url) => { - if (question.type !== "page") return; + async function handleImageUpload(file: File) { + const url = await uploadQuestionImage(question.id, quizQid, file, (question, url) => { + if (question.type !== "page") return; - question.content.picture = url; - question.content.originalPicture = url; - }); - closeImageUploadModal(); - openCropModal(file, url); - } + question.content.picture = url; + question.content.originalPicture = url; + }); + closeImageUploadModal(); + openCropModal(file, url); + } - function handleCropModalSaveClick(imageBlob: Blob) { - uploadQuestionImage(question.id, quizQid, imageBlob, (question, url) => { - if (question.type !== "page") return; + function handleCropModalSaveClick(imageBlob: Blob) { + uploadQuestionImage(question.id, quizQid, imageBlob, (question, url) => { + if (question.type !== "page") return; - question.content.picture = url; - }); - } + question.content.picture = url; + }); + } - return ( - <> - + + + setText(target.value)} + /> + + + + + { + if (question.content.picture) { + return openCropModal(question.content.picture, question.content.originalPicture); + } + + openImageUploadModal(); + }} + onPlusClick={() => { + openImageUploadModal(); + }} + /> + + + updateQuestion(question.id, (question) => ((question as QuizQuestionPage).content.useImage = true)) + } > - - setText(target.value)} - /> - - + Изображение + + + + + или + + {isMobile ? ( + - - { - if (question.content.picture) { - return openCropModal( - question.content.picture, - question.content.originalPicture - ); - } - - openImageUploadModal(); - }} - onPlusClick={() => { - openImageUploadModal(); - }} - /> - - - Изображение - - - - - или - - {isMobile ? ( - - - - - - + - - - ) : ( - - - - - - + - - - )} - - - Видео - - - setOpenVideoModal(false)} - video={question.content.video} - onUpload={(url) => { - updateQuestion(question.id, question => { - if (question.type !== "page") return; - - question.content.video = url; - }); - }} - /> + - - - - - ); + + + + + + ) : ( + + + + + setOpenVideoModal(true)} + style={{ + display: "flex", + alignItems: "center", + justifyContent: "center", + background: "#7E2AEA", + height: "100%", + width: "25px", + color: "white", + fontSize: "15px", + }} + > + + + + + )} + + + updateQuestion(question.id, (question) => ((question as QuizQuestionPage).content.useImage = false)) + } + > + Видео + + + setOpenVideoModal(false)} + video={question.content.video} + onUpload={(url) => { + updateQuestion(question.id, (question) => { + if (question.type !== "page") return; + + question.content.video = url; + }); + }} + /> + + + + + + ); } diff --git a/src/pages/Questions/SliderOptions/SliderOptions.tsx b/src/pages/Questions/SliderOptions/SliderOptions.tsx index bcc23b7d..4a7225fe 100644 --- a/src/pages/Questions/SliderOptions/SliderOptions.tsx +++ b/src/pages/Questions/SliderOptions/SliderOptions.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { Box, Typography, useMediaQuery, useTheme } from "@mui/material"; import ButtonsOptions from "../ButtonsOptions"; import CustomNumberField from "@ui_kit/CustomNumberField"; @@ -16,6 +16,38 @@ export default function SliderOptions({ question }: Props) { const isMobile = useMediaQuery(theme.breakpoints.down(790)); const [switchState, setSwitchState] = useState("setting"); const [stepError, setStepError] = useState(""); + const [startError, setStartError] = useState(false); + const [minError, setMinError] = useState(false); + const [maxError, setMaxError] = useState(false); + + useEffect(() => { + const min = Number(question.content.range.split("—")[0]); + const max = Number(question.content.range.split("—")[1]); + const start = Number(question.content.start); + + if (start < min || start > max) { + setStartError(true); + } + + if (start >= min && start <= max) { + setStartError(false); + } + }, [question.content.range, question.content.start]); + + useEffect(() => { + const min = Number(question.content.range.split("—")[0]); + const max = Number(question.content.range.split("—")[1]); + const step = Number(question.content.step); + const range = max - min; + + if (range % step) { + setStepError( + `Шаг должен делить без остатка диапазон ${max} - ${min} = ${max - min}` + ); + } else { + setStepError(""); + } + }, [question]); const SSHC = (data: string) => { setSwitchState(data); @@ -44,42 +76,43 @@ export default function SliderOptions({ question }: Props) { marginRight: isMobile ? "10px" : "0px", }} > - + Выбор значения из диапазона - + { - updateQuestion(question.id, (question) => { - if (question.type !== "number") return; - - question.content.range = `${target.value}—${question.content.range.split("—")[1]}`; - }); - }} - onBlur={({ target }) => { - const start = question.content.start; const min = Number(target.value); const max = Number(question.content.range.split("—")[1]); + updateQuestion(question.id, (question) => { + if (question.type !== "number") return; + + question.content.range = `${target.value}—${ + question.content.range.split("—")[1] + }`; + }); + if (min >= max) { - updateQuestion(question.id, (question) => { - if (question.type !== "number") return; - - question.content.range = `${max - 1 >= 0 ? max - 1 : 0}—${question.content.range.split("—")[1]}`; - }); - } - - if (start < min) { - updateQuestion(question.id, (question) => { - if (question.type !== "number") return; - - question.content.start = min; - }); + setMinError(true); + } else { + setMinError(false); + setMaxError(false); } }} /> @@ -90,35 +123,30 @@ export default function SliderOptions({ question }: Props) { min={0} max={100000000000} value={question.content.range.split("—")[1]} + emptyError={maxError} onChange={({ target }) => { + const min = Number(question.content.range.split("—")[0]); + const max = Number(target.value); + updateQuestion(question.id, (question) => { if (question.type !== "number") return; - question.content.range = `${question.content.range.split("—")[0]}—${target.value}`; + question.content.range = `${ + question.content.range.split("—")[0] + }—${target.value}`; }); + + if (max <= min) { + setMaxError(true); + } else { + setMaxError(false); + setMinError(false); + } }} onBlur={({ target }) => { - const start = question.content.start; const step = question.content.step; const min = Number(question.content.range.split("—")[0]); const max = Number(target.value); - const range = max - min; - - if (max <= min) { - updateQuestion(question.id, (question) => { - if (question.type !== "number") return; - - question.content.range = `${min}—${min + 1 >= 100 ? 100 : min + 1}`; - }); - } - - if (start > max) { - updateQuestion(question.id, (question) => { - if (question.type !== "number") return; - - question.content.start = max; - }); - } if (step > max) { updateQuestion(question.id, (question) => { @@ -126,12 +154,6 @@ export default function SliderOptions({ question }: Props) { question.content.step = min; }); - - if (range % step) { - setStepError(`Шаг должен делить без остатка диапазон ${max} - ${min} = ${max - min}`); - } else { - setStepError(""); - } } }} /> @@ -147,15 +169,21 @@ export default function SliderOptions({ question }: Props) { }} > - + Начальное значение { updateQuestion(question.id, (question) => { if (question.type !== "number") return; @@ -178,8 +206,8 @@ export default function SliderOptions({ question }: Props) { { - const min = Number(question.content.range.split("—")[0]); const max = Number(question.content.range.split("—")[1]); - const range = max - min; const step = Number(target.value); if (step > max) { @@ -203,18 +229,16 @@ export default function SliderOptions({ question }: Props) { question.content.step = max; }); } - - if (range % step) { - setStepError(`Шаг должен делить без остатка диапазон ${max} - ${min} = ${max - min}`); - } else { - setStepError(""); - } }} /> - + ); diff --git a/src/pages/Questions/UploadVideoModal.tsx b/src/pages/Questions/UploadVideoModal.tsx index d26dc7f4..2426e42f 100644 --- a/src/pages/Questions/UploadVideoModal.tsx +++ b/src/pages/Questions/UploadVideoModal.tsx @@ -1,11 +1,4 @@ -import { - Box, - Button, - ButtonBase, - Modal, - Typography, - useTheme, -} from "@mui/material"; +import { Box, Button, ButtonBase, Modal, Typography, useTheme } from "@mui/material"; import SelectableButton from "@ui_kit/SelectableButton"; import CustomTextField from "@ui_kit/CustomTextField"; import { useState } from "react"; @@ -22,14 +15,8 @@ type HelpQuestionsProps = { onUpload: (number: string) => void; }; -export const UploadVideoModal = ({ - open, - onClose, - video, - onUpload, -}: HelpQuestionsProps) => { - const [backgroundTypeModal, setBackgroundTypeModal] = - useState("linkVideo"); +export const UploadVideoModal = ({ open, onClose, video, onUpload }: HelpQuestionsProps) => { + const [backgroundTypeModal, setBackgroundTypeModal] = useState("linkVideo"); const theme = useTheme(); const handleDrop = (event: DragEvent) => { @@ -42,12 +29,7 @@ export const UploadVideoModal = ({ }; return ( - + - Видео можно вставить с любого хостинга: YouTube, Vimeo или загрузить - собственное + Видео можно вставить с любого хостинга: YouTube, Vimeo или загрузить собственное - + {backgroundTypeModal === "linkVideo" ? ( - - Ссылка на видео - + Ссылка на видео ) : ( - - Загрузите видео - - + Загрузите видео + { if (target.files?.length) { @@ -123,9 +99,7 @@ export const UploadVideoModal = ({ type="file" /> ) => - event.preventDefault() - } + onDragOver={(event: DragEvent) => event.preventDefault()} onDrop={handleDrop} sx={{ width: "580px", @@ -140,12 +114,8 @@ export const UploadVideoModal = ({ > - - Добавить видео - - - Принимает .mp4 и .mov формат — максимум 100мб - + Добавить видео + Принимает .mp4 и .mov формат — максимум 100мб diff --git a/src/pages/Questions/answerOptions/AnswerOptions.tsx b/src/pages/Questions/answerOptions/AnswerOptions.tsx index 8df98d4b..31648df5 100755 --- a/src/pages/Questions/answerOptions/AnswerOptions.tsx +++ b/src/pages/Questions/answerOptions/AnswerOptions.tsx @@ -6,90 +6,87 @@ import ButtonsOptionsAndPict from "../ButtonsOptionsAndPict"; import SwitchAnswerOptions from "./switchAnswerOptions"; import type { QuizQuestionVariant } from "../../../model/questionTypes/variant"; import { addQuestionVariant } from "@root/questions/actions"; - +import { useAddAnswer } from "../../../utils/hooks/useAddAnswer"; interface Props { - question: QuizQuestionVariant; + question: QuizQuestionVariant; } export default function AnswerOptions({ question }: Props) { - const [switchState, setSwitchState] = useState("setting"); - const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down(790)); + const onClickAddAnAnswer = useAddAnswer(); + 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); + }; - return ( - <> - - {question.content.variants.length === 0 ? ( - - Добавьте ответ - - ) : ( - - )} + return ( + <> + + {question.content.variants.length === 0 ? ( + + Добавьте ответ + + ) : ( + + )} - - addQuestionVariant(question.id)} - > - Добавьте ответ - - {isMobile ? null : ( - <> - - или нажмите Enter - - - - )} - - - - - - ); + + onClickAddAnAnswer(question)} + > + Добавьте ответ + + {isMobile ? null : ( + <> + + или нажмите Enter + + + + )} + + + + + + ); } diff --git a/src/pages/ResultPage/FirstEntry.tsx b/src/pages/ResultPage/FirstEntry.tsx index b11eb41a..2960f3cb 100644 --- a/src/pages/ResultPage/FirstEntry.tsx +++ b/src/pages/ResultPage/FirstEntry.tsx @@ -1,38 +1,10 @@ -import { ResultSettings } from "./ResultSettings"; -import { createFrontResult } from "@root/questions/actions"; -import { useQuestionsStore } from "@root/questions/store"; -import { useCurrentQuiz } from "@root/quizes/hooks"; import { Box, Typography, useTheme, useMediaQuery, Button } from "@mui/material"; import image from "../../assets/Rectangle 110.png"; -import { enqueueSnackbar } from "notistack"; -import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"; -import ArrowLeft from "../../assets/icons/questionsPage/arrowLeft"; -import { decrementCurrentStep } from "@root/quizes/actions"; export const FirstEntry = () => { const theme = useTheme(); - const quiz = useCurrentQuiz(); - const { questions } = useQuestionsStore(); const isSmallMonitor = useMediaQuery(theme.breakpoints.down(1250)); - const create = () => { - if (quiz?.config.haveRoot) { - questions - .filter((question: AnyTypedQuizQuestion) => { - return ( - question.type !== null && - question.content.rule.parentId.length !== 0 && - question.content.rule.children.length === 0 - ); - }) - .forEach((question) => { - createFrontResult(quiz.backendId, question.content.id); - }); - } else { - createFrontResult(quiz.backendId, "line"); - } - }; - return ( <> { }} /> - - - - - ); }; diff --git a/src/pages/ResultPage/ResultPage.tsx b/src/pages/ResultPage/ResultPage.tsx index 06eb39bb..913fe71a 100644 --- a/src/pages/ResultPage/ResultPage.tsx +++ b/src/pages/ResultPage/ResultPage.tsx @@ -1,15 +1,43 @@ -import { useQuestionsStore } from "@root/questions/store"; import { FirstEntry } from "./FirstEntry" import { ResultSettings } from "./ResultSettings" +import { decrementCurrentStep, incrementCurrentStep } from "@root/quizes/actions"; +import { Box, Button } from "@mui/material"; +import ArrowLeft from "@icons/questionsPage/arrowLeft" export const ResultPage = () => { - const { questions } = useQuestionsStore(); - //ищём хотя бы один result - const haveResult = questions.some((question) => question.type === "result") return ( - haveResult ? - - : + <> - ); + + + + + + + + ) } \ No newline at end of file diff --git a/src/pages/ResultPage/ResultSettings.tsx b/src/pages/ResultPage/ResultSettings.tsx index 4bf2cb0a..157f424d 100644 --- a/src/pages/ResultPage/ResultSettings.tsx +++ b/src/pages/ResultPage/ResultSettings.tsx @@ -12,45 +12,50 @@ import { useEffect, useRef, useState } from "react"; import { WhenCard } from "./cards/WhenCard"; import { ResultCard, checkEmptyData } from "./cards/ResultCard"; import { EmailSettingsCard } from "./cards/EmailSettingsCard"; -import { useCurrentQuiz } from "@root/quizes/hooks" +import { useCurrentQuiz } from "@root/quizes/hooks"; import { useQuestionsStore } from "@root/questions/store"; -import { createFrontResult, deleteQuestion } from "@root/questions/actions"; +import { deleteQuestion } from "@root/questions/actions"; import { QuizQuestionResult } from "@model/questionTypes/result"; export const ResultSettings = () => { - const { questions } = useQuestionsStore() - const quiz = useCurrentQuiz() - const results = useQuestionsStore().questions.filter((q): q is QuizQuestionResult => q.type === "result") - const [quizExpand, setQuizExpand] = useState(true) - const [resultContract, setResultContract] = useState(true) + const { questions } = useQuestionsStore(); + const quiz = useCurrentQuiz(); + const results = useQuestionsStore().questions.filter((q): q is QuizQuestionResult => q.type === "result"); + const [quizExpand, setQuizExpand] = useState(true); + const [resultContract, setResultContract] = useState(true); const isReadyToLeaveRef = useRef(true); - useEffect(function calcIsReadyToLeave(){ - let isReadyToLeave = true; - results.forEach((result) => { - if (checkEmptyData({ resultData: result })) { - isReadyToLeave = false; - } - }); - isReadyToLeaveRef.current = isReadyToLeave; - }, [results]) + console.log(quiz) + console.log(results) + useEffect( + function calcIsReadyToLeave() { + let isReadyToLeave = true; + results.forEach((result) => { + if (checkEmptyData({ resultData: result })) { + isReadyToLeave = false; + } + }); + isReadyToLeaveRef.current = isReadyToLeave; + }, + [results] + ); useEffect(() => { - return () => { - if (isReadyToLeaveRef.current === false) alert("Пожалуйста, проверьте, что вы заполнили все результаты"); - }; + return () => { + if (isReadyToLeaveRef.current === false) alert("Пожалуйста, проверьте, что вы заполнили все результаты"); + }; }, []); return ( - - - Настройки результатов - + + Настройки результатов - {quiz.config.resultInfo.when === "email" && } - - + Создайте результат @@ -105,9 +106,9 @@ export const ResultSettings = () => { - { - results.map((resultQuestion) => ) - } + {results.map((resultQuestion) => ( + + ))} { Показывать результат - + { ))} {typeActive === "e-mail" ? ( - + ) : ( <> - + - + )} - + Создайте результат diff --git a/src/pages/ViewPublicationPage/ContactForm.tsx b/src/pages/ViewPublicationPage/ContactForm.tsx index ddef90fd..728c7ba5 100644 --- a/src/pages/ViewPublicationPage/ContactForm.tsx +++ b/src/pages/ViewPublicationPage/ContactForm.tsx @@ -6,6 +6,7 @@ import TextIcon from "@icons/ContactFormIcon/TextIcon"; import AddressIcon from "@icons/ContactFormIcon/AddressIcon"; import { useCurrentQuiz } from "@root/quizes/hooks"; +import { NameplateLogo } from "@icons/NameplateLogo"; import CustomCheckbox from "@ui_kit/CustomCheckbox"; import { useState } from "react"; import { useQuestionsStore } from "@root/questions/store"; @@ -71,19 +72,19 @@ export const ContactForm = ({ }} > {quiz?.config.formContact.title || "Заполните форму, чтобы получить результаты теста"} - + { quiz?.config.formContact.desc && - - {quiz?.config.formContact.desc} - + + {quiz?.config.formContact.desc} + } @@ -138,6 +139,16 @@ export const ContactForm = ({ + + + Сделано на PenaQuiz + @@ -154,14 +165,14 @@ const Inputs = () => { if (FC.used) someUsed.push() return }) - + if (someUsed.length) { return <>{someUsed} } else { return <> - {Icons[0]} - {Icons[1]} - {Icons[2]} + {Icons[0]} + {Icons[1]} + {Icons[2]} } } diff --git a/src/pages/ViewPublicationPage/Footer.tsx b/src/pages/ViewPublicationPage/Footer.tsx index 8a1f2dea..6833117d 100644 --- a/src/pages/ViewPublicationPage/Footer.tsx +++ b/src/pages/ViewPublicationPage/Footer.tsx @@ -8,7 +8,7 @@ import { useQuestionsStore } from "@root/questions/store"; import type { AnyTypedQuizQuestion, QuizQuestionBase } from "../../model/questionTypes/shared"; import { getQuestionByContentId } from "@root/questions/actions"; import { enqueueSnackbar } from "notistack"; -import { NameplateLogo } from "@icons/NameplateLogo"; +import { NameplateLogoFQ } from "@icons/NameplateLogoFQ"; type FooterProps = { setCurrentQuestion: (step: AnyTypedQuizQuestion) => void; @@ -189,22 +189,11 @@ export const Footer = ({ setCurrentQuestion, question, setShowContactForm, setSh position: "relative", padding: "15px 0", borderTop: `1px solid ${theme.palette.grey[400]}`, + height: '75px', + + display: "flex" }} > - - - Сделано на PenaQuiz - + { QUESTIONS_MAP[currentQuestion.type as Exclude]; return ( - + {!showContactForm && !showResultForm && ( { @@ -100,31 +101,59 @@ export const ResultForm = ({ - { - quiz?.config.resultInfo.when === "before" && + - + + Сделано на PenaQuiz + + - } + + { + quiz?.config.resultInfo.when === "before" && + <> + + + + + } + + + ); }; diff --git a/src/pages/ViewPublicationPage/questions/Number.tsx b/src/pages/ViewPublicationPage/questions/Number.tsx index 28936e83..6056beab 100644 --- a/src/pages/ViewPublicationPage/questions/Number.tsx +++ b/src/pages/ViewPublicationPage/questions/Number.tsx @@ -18,21 +18,29 @@ export const Number = ({ currentQuestion }: NumberProps) => { const [maxRange, setMaxRange] = useState("100000000000"); const theme = useTheme(); const { answers } = useQuizViewStore(); - const updateMinRangeDebounced = useDebouncedCallback((value, crowded = false) => { - if (crowded) { - setMinRange(maxRange); - } + const updateMinRangeDebounced = useDebouncedCallback( + (value, crowded = false) => { + if (crowded) { + setMinRange(maxRange); + } - updateAnswer(currentQuestion.content.id, value); - }, 1000); - const updateMaxRangeDebounced = useDebouncedCallback((value, crowded = false) => { - if (crowded) { - setMaxRange(minRange); - } + updateAnswer(currentQuestion.content.id, value); + }, + 1000 + ); + const updateMaxRangeDebounced = useDebouncedCallback( + (value, crowded = false) => { + if (crowded) { + setMaxRange(minRange); + } - updateAnswer(currentQuestion.content.id, value); - }, 1000); - const answer = answers.find(({ questionId }) => questionId === currentQuestion.content.id)?.answer as string; + updateAnswer(currentQuestion.content.id, value); + }, + 1000 + ); + const answer = answers.find( + ({ questionId }) => questionId === currentQuestion.content.id + )?.answer as string; const min = window.Number(currentQuestion.content.range.split("—")[0]); const max = window.Number(currentQuestion.content.range.split("—")[1]); diff --git a/src/pages/auth/Signin.tsx b/src/pages/auth/Signin.tsx index 439936ed..533dc56d 100644 --- a/src/pages/auth/Signin.tsx +++ b/src/pages/auth/Signin.tsx @@ -12,171 +12,171 @@ import { Link as RouterLink, useNavigate, useLocation } from "react-router-dom"; import { object, string } from "yup"; interface Values { - email: string; - password: string; + email: string; + password: string; } const initialValues: Values = { - email: "", - password: "", + email: "", + password: "", }; const validationSchema = object({ - email: string().required("Поле обязательно").email("Введите корректный email"), - password: string().required("Поле обязательно").min(8, "Минимум 8 символов"), + email: string().required("Поле обязательно").email("Введите корректный email"), + password: string().required("Поле обязательно").min(8, "Минимум 8 символов"), }); export default function SigninDialog() { - const [isDialogOpen, setIsDialogOpen] = useState(true); - const user = useUserStore((state) => state.user); - const theme = useTheme(); - const upMd = useMediaQuery(theme.breakpoints.up("md")); - const navigate = useNavigate(); - const location = useLocation(); + const [isDialogOpen, setIsDialogOpen] = useState(true); + const user = useUserStore((state) => state.user); + const theme = useTheme(); + const upMd = useMediaQuery(theme.breakpoints.up("md")); + const navigate = useNavigate(); + const location = useLocation(); - const formik = useFormik({ - initialValues, - validationSchema, - onSubmit: async (values, formikHelpers) => { - const [loginResponse, loginError] = await login(values.email.trim(), values.password.trim()); + const formik = useFormik({ + initialValues, + validationSchema, + onSubmit: async (values, formikHelpers) => { + const [loginResponse, loginError] = await login(values.email.trim(), values.password.trim()); - formikHelpers.setSubmitting(false); + formikHelpers.setSubmitting(false); - if (loginError) { - return enqueueSnackbar(loginError); - } + if (loginError) { + return enqueueSnackbar(loginError); + } - if (loginResponse) { - setUserId(loginResponse._id); - } - }, - }); - - useEffect( - function redirectIfSignedIn() { - if (user) navigate("/list", { replace: true }); - }, - [navigate, user] - ); - - function handleClose() { - setIsDialogOpen(false); - setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen); - } - - return ( - - - navigate("/"), theme.transitions.duration.leavingScreen); + } + + return ( + - - - - - - - Вход в личный кабинет - - - - - {/* + + + + + + + + Вход в личный кабинет + + + + + {/* Забыли пароль? */} - - Вы еще не присоединились? - - - Регистрация - - - Забыли пароль - - - - - - ); + + Вы еще не присоединились? + + + Регистрация + + + Забыли пароль + + + + + + ); } diff --git a/src/pages/auth/Signup.tsx b/src/pages/auth/Signup.tsx index c0e1f93b..3f1a0262 100644 --- a/src/pages/auth/Signup.tsx +++ b/src/pages/auth/Signup.tsx @@ -12,210 +12,215 @@ import { Link as RouterLink, useLocation, useNavigate } from "react-router-dom"; import { object, ref, string } from "yup"; interface Values { - email: string; - password: string; - repeatPassword: string; + email: string; + password: string; + repeatPassword: string; } const initialValues: Values = { - email: "", - password: "", - repeatPassword: "", + email: "", + password: "", + repeatPassword: "", }; const validationSchema = object({ - email: string().required("Поле обязательно").email("Введите корректный email"), - password: string() - .min(8, "Минимум 8 символов") - .matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы") - .required("Поле обязательно"), - repeatPassword: string() - .oneOf([ref("password"), undefined], "Пароли не совпадают") - .required("Повторите пароль"), + email: string().required("Поле обязательно").email("Введите корректный email"), + password: string() + .min(8, "Минимум 8 символов") + .matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы") + .required("Поле обязательно"), + repeatPassword: string() + .oneOf([ref("password"), undefined], "Пароли не совпадают") + .required("Повторите пароль"), }); export default function SignupDialog() { - const [isDialogOpen, setIsDialogOpen] = useState(true); - const user = useUserStore((state) => state.user); - const theme = useTheme(); - const upMd = useMediaQuery(theme.breakpoints.up("md")); - const location = useLocation(); + const [isDialogOpen, setIsDialogOpen] = useState(true); + const user = useUserStore((state) => state.user); + const theme = useTheme(); + const upMd = useMediaQuery(theme.breakpoints.up("md")); + const location = useLocation(); - const navigate = useNavigate(); - const formik = useFormik({ - initialValues, - validationSchema, - onSubmit: async (values, formikHelpers) => { - const [registerResponse, registerError] = await register(values.email.trim(), values.password.trim(), "+7"); + const navigate = useNavigate(); + const formik = useFormik({ + initialValues, + validationSchema, + onSubmit: async (values, formikHelpers) => { + const [registerResponse, registerError] = await register(values.email.trim(), values.password.trim(), "+7"); - formikHelpers.setSubmitting(false); + formikHelpers.setSubmitting(false); - if (registerError) { - return enqueueSnackbar(registerError); - } + if (registerError) { + return enqueueSnackbar(registerError); + } - if (registerResponse) { - setUserId(registerResponse._id); - } - }, - }); - - useEffect( - function redirectIfSignedIn() { - if (user) navigate("/list", { replace: true }); - }, - [navigate, user] - ); - - function handleClose() { - setIsDialogOpen(false); - setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen); - } - - return ( - - - - - - - - - - Регистрация - - - - - + }); - navigate("/"), theme.transitions.duration.leavingScreen); + } + + return ( + - Вход в личный кабинет - - - Забыли пароль - - - - ); + + + + + + + + + Регистрация + + + + + + + + Вход в личный кабинет + + + Забыли пароль + + + + ); } diff --git a/src/pages/createQuize/QuizCard.tsx b/src/pages/createQuize/QuizCard.tsx index 5ef59b0e..40dba8a8 100755 --- a/src/pages/createQuize/QuizCard.tsx +++ b/src/pages/createQuize/QuizCard.tsx @@ -46,7 +46,12 @@ export default function QuizCard({ quiz, openCount = 0, applicationCount = 0, co 0px 2.76726px 8.55082px rgba(210, 208, 225, 0.0674749)`, }} > - {quiz.name} + + {quiz.name} + q.type !== null && q.content?.rule.parentId === "line")) createResult(quiz?.backendId, "line") }; getData(); }, []); +console.log(quiz) +console.log(questions) + const { openBranchingPanel, whyCantCreatePublic, canCreatePublic } = useUiTools(); const theme = useTheme(); const navigate = useNavigate(); @@ -78,10 +82,12 @@ export default function EditPage() { }, [navigate, editQuizId]); useEffect( - () => () => { - resetEditConfig(); - cleanQuestions(); - updateModalInfoWhyCantCreate(false) + () => { + return () => { + resetEditConfig(); + cleanQuestions(); + updateModalInfoWhyCantCreate(false) + } }, [] ); @@ -123,7 +129,7 @@ export default function EditPage() { useEffect(() => { updateQuestionHint(questions) }, [questions]); - + async function handleLogoutClick() { const [, logoutError] = await logout(); @@ -376,11 +382,11 @@ export default function EditPage() { height: "34px", minWidth: "130px", }} - onClick={() => Object.keys(whyCantCreatePublic).length === 0 ? () => {} : updateModalInfoWhyCantCreate(true)} + onClick={() => Object.keys(whyCantCreatePublic).length === 0 ? () => { } : updateModalInfoWhyCantCreate(true)} > Тестовый просмотр - : + : - {quiz?.status === "start" && https://hbpn.link/{quiz.qid} - } - - + {quiz?.status === "start" ? "Стоп" : "Старт"} + + {quiz?.status === "start" && https://hbpn.link/{quiz.qid} + } + + ); diff --git a/src/pages/startPage/Restore.tsx b/src/pages/startPage/Restore.tsx index 31cc8ad3..2769338a 100644 --- a/src/pages/startPage/Restore.tsx +++ b/src/pages/startPage/Restore.tsx @@ -1,183 +1,195 @@ import { FC, useState } from "react"; import CloseIcon from "@mui/icons-material/Close"; -import { Box, Button, Dialog, IconButton, Typography, useMediaQuery, useTheme } from "@mui/material"; +import { Box, Button, Dialog, IconButton, Link, Typography, useMediaQuery, useTheme } from "@mui/material"; import InputTextfield from "@ui_kit/InputTextfield"; import PasswordInput from "@ui_kit/passwordInput"; import { useFormik } from "formik"; import { object, ref, string } from "yup"; import Logotip from "../Landing/images/icons/QuizLogo"; -import { useNavigate } from "react-router-dom"; +import { useNavigate, Link as RouterLink, useLocation } from "react-router-dom"; interface Values { - email: string; - password: string; - repeatPassword: string; + email: string; + password: string; + repeatPassword: string; } const initialValues: Values = { - email: "", - password: "", - repeatPassword: "", + email: "", + password: "", + repeatPassword: "", }; const validationSchema = object({ - email: string().required("Поле обязательно").email("Введите корректный email"), - password: string() - .min(8, "Минимум 8 символов") - .matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы") - .required("Поле обязательно"), - repeatPassword: string() - .oneOf([ref("password"), undefined], "Пароли не совпадают") - .required("Повторите пароль"), + email: string().required("Поле обязательно").email("Введите корректный email"), + password: string() + .min(8, "Минимум 8 символов") + .matches(/^[.,:;-_+\d\w]+$/, "Некорректные символы") + .required("Поле обязательно"), + repeatPassword: string() + .oneOf([ref("password"), undefined], "Пароли не совпадают") + .required("Повторите пароль"), }); export const Restore: FC = () => { - const [isDialogOpen, setIsDialogOpen] = useState(true); - const navigate = useNavigate(); - const theme = useTheme(); - const upMd = useMediaQuery(theme.breakpoints.up("md")); + const [isDialogOpen, setIsDialogOpen] = useState(true); + const navigate = useNavigate(); + const theme = useTheme(); + const upMd = useMediaQuery(theme.breakpoints.up("md")); + const location = useLocation(); - const formik = useFormik({ - initialValues, - validationSchema, - onSubmit: (values) => { - }, - }); - - function handleClose() { - setIsDialogOpen(false); - setTimeout(() => navigate("/"), theme.transitions.duration.leavingScreen); - } - - return ( - ({ + initialValues, + validationSchema, + onSubmit: (values) => { }, - }} - slotProps={{ - backdrop: { - style: { - backgroundColor: "rgb(0 0 0 / 0.7)", - }, - }, - }} - > - - navigate("/"), theme.transitions.duration.leavingScreen); + } + + return ( + - - - - - - - Восстановление пароля - - - - - - - - ); + + + + + + + + + Восстановление пароля + + + + + + + У меня уже есть аккаунт + + + + ); }; diff --git a/src/stores/questions/actions.ts b/src/stores/questions/actions.ts index c59e00db..456f4566 100644 --- a/src/stores/questions/actions.ts +++ b/src/stores/questions/actions.ts @@ -15,6 +15,7 @@ import { useCurrentQuiz } from "@root/quizes/hooks"; import { QuestionsStore, useQuestionsStore } from "./store"; import { useUiTools } from "../uiTools/store"; import { withErrorBoundary } from "react-error-boundary"; +import { QuizQuestionResult } from "@model/questionTypes/result"; export const setQuestions = (questions: RawQuestion[] | null) => setProducedState(state => { @@ -481,7 +482,12 @@ export const getQuestionByContentId = (questionContentId: string | null) => { export const clearRuleForAll = () => { const { questions } = useQuestionsStore.getState(); questions.forEach(question => { - if (question.type !== null && (question.content.rule.main.length > 0 || question.content.rule.default.length > 0 || question.content.rule.parentId.length > 0)) { + if (question.type !== null && + (question.content.rule.main.length > 0 + || question.content.rule.default.length > 0 + || question.content.rule.parentId.length > 0) + && question.type !== "result") { + updateQuestion(question.content.id, question => { question.content.rule.parentId = ""; question.content.rule.main = []; @@ -491,63 +497,48 @@ export const clearRuleForAll = () => { }); }; - -export const createFrontResult = (quizId: number, parentContentId?: string) => setProducedState(state => { - const frontId = nanoid(); - const content = JSON.parse(JSON.stringify(defaultQuestionByType["result"].content)); - content.id = frontId; - if (parentContentId) content.rule.parentId = parentContentId; - state.questions.push({ - id: frontId, - quizId, - type: "result", - title: "", - description: "", - deleted: false, - expanded: true, - page: 101, - required: true, - content - }); -}, { - type: "createFrontResult", - quizId, -}); - - -export const createBackResult = async ( - questionId: string, - type: QuestionType, +export const createResult = async ( + quizId: number, + parentContentId?: string ) => requestQueue.enqueue(async () => { - const question = useQuestionsStore.getState().questions.find(q => q.id === questionId); - if (!question) return; - if (question.type !== "result") throw new Error("Cannot upgrade already typed question"); - - try { - const createdQuestion = await questionApi.create({ - quiz_id: question.quizId, - type, - title: question.title, - description: question.description, - page: 0, - required: true, - content: JSON.stringify(defaultQuestionByType[type].content), - }); - - setProducedState(state => { - const questionIndex = state.questions.findIndex(q => q.id === questionId); - if (questionIndex !== -1) state.questions.splice( - questionIndex, - 1, - rawQuestionToQuestion(createdQuestion) - ); - }, { - type: "createBackResult", - question, - }); - } catch (error) { - devlog("Error creating question", error); - enqueueSnackbar("Не удалось создать вопрос"); + if (!quizId || !parentContentId) { + console.error("Нет данных для создания результата. quizId: ", quizId, ", quizId: ", parentContentId) } + + //Мы получили запрос на создание резулта. Анализируем существует ли такой. Если да - просто делаем его активным + const question = useQuestionsStore.getState().questions.find(q=> q.type !== null && q?.content.rule.parentContentId === parentContentId) + + if (question) {//существует, делаем активным + + updateQuestion(question.id, (q) => { + q.content.usage = true + }) + + } else {//не существует, создаём + const content = JSON.parse(JSON.stringify(defaultQuestionByType["result"].content)); + content.rule.parentId = parentContentId; + + try { + const createdQuestion:RawQuestion = await questionApi.create({ + quiz_id: quizId, + type: "result", + title: "", + description: "", + page: 101, + required: true, + content: JSON.stringify(content), + }); + + setProducedState(state => { + state.questions.push(rawQuestionToQuestion(createdQuestion)) + }, { + type: "createBackResult", + createdQuestion, + }); + } catch (error) { + devlog("Error creating question", error); + enqueueSnackbar("Не удалось создать вопрос"); + } + } }); diff --git a/src/stores/quizes/actions.ts b/src/stores/quizes/actions.ts index e0367bfd..fd718a1d 100644 --- a/src/stores/quizes/actions.ts +++ b/src/stores/quizes/actions.ts @@ -9,8 +9,9 @@ import { NavigateFunction } from "react-router-dom"; import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError"; import { RequestQueue } from "../../utils/requestQueue"; import { QuizStore, useQuizStore } from "./store"; -import { createUntypedQuestion } from "@root/questions/actions"; +import { createUntypedQuestion, updateQuestion } from "@root/questions/actions"; import { useCurrentQuiz } from "./hooks" +import { useQuestionsStore } from "@root/questions/store"; export const setEditQuizId = (quizId: number | null) => setProducedState(state => { @@ -176,12 +177,40 @@ export const deleteQuiz = async (quizId: string) => requestQueue.enqueue(async ( enqueueSnackbar(`Не удалось удалить квиз. ${message}`); } }); -export const updateRootContentId = (quizId: string, id:string) => updateQuiz( - quizId, - quiz => { - quiz.config.haveRoot = id - }, -); +export const updateRootContentId = (quizId: string, id: string) => { + + if (id.length === 0) {//дерева больше не существует, все результаты неактивны кроме результата линейности + useQuestionsStore.getState().questions.forEach((q) => { + if (q.type !== null && q.type === "result") { + if (q.content.rule.parentId === "line" && q.content.usage === false) { + updateQuestion(q.id, (q) => { + q.content.usage === true + }) + } else { + updateQuestion(q.id, (q) => { + q.content.usage === false + }) + } + } + }) + } else { //было создано дерево, результат линейности неактивен + useQuestionsStore.getState().questions.forEach((q) => { + if (q.type !== null && q.content.rule.parentId === "line") { + updateQuestion(q.id, (q) => { + q.content.usage === false + }) + } + + }) + } + + updateQuiz( + quizId, + quiz => { + quiz.config.haveRoot = id + }, + ); +} // TODO copy quiz diff --git a/src/stores/uiTools/actions.ts b/src/stores/uiTools/actions.ts index ab1bedee..cd6da193 100644 --- a/src/stores/uiTools/actions.ts +++ b/src/stores/uiTools/actions.ts @@ -36,4 +36,5 @@ export const updateOpenedModalSettingsId = (id?: string) => useUiTools.setState( export const updateCanCreatePublic = (can: boolean) => useUiTools.setState({ canCreatePublic: can }); -export const updateModalInfoWhyCantCreate = (can: boolean) => useUiTools.setState({ openModalInfoWhyCantCreate: can }); \ No newline at end of file +export const updateModalInfoWhyCantCreate = (can: boolean) => useUiTools.setState({ openModalInfoWhyCantCreate: can }); +export const updateDeleteId = (deleteNodeId: string | null = null) => useUiTools.setState({ deleteNodeId }); diff --git a/src/stores/uiTools/store.ts b/src/stores/uiTools/store.ts index 92e0d173..c8cd887f 100644 --- a/src/stores/uiTools/store.ts +++ b/src/stores/uiTools/store.ts @@ -1,7 +1,6 @@ import { create } from "zustand"; import { devtools } from "zustand/middleware"; - export type UiTools = { openedModalSettingsId: string | null; dragQuestionContentId: string | null; @@ -11,6 +10,8 @@ export type UiTools = { canCreatePublic: boolean; whyCantCreatePublic: Record//ид вопроса и список претензий к нему openModalInfoWhyCantCreate: boolean; +lastDeletionNodeTime: number | null; +deleteNodeId: string | null; }; export type WhyCantCreatePublic = { name: string; @@ -26,16 +27,15 @@ const initialState: UiTools = { editSomeQuestion: null as null, canCreatePublic: false, whyCantCreatePublic: {}, - openModalInfoWhyCantCreate: false + openModalInfoWhyCantCreate: false, +lastDeletionNodeTime: null, +deleteNodeId: null, }; export const useUiTools = create()( - devtools( - () => initialState, - { - name: "UiTools", - enabled: process.env.NODE_ENV === "development", - trace: process.env.NODE_ENV === "development", - } - ) + devtools(() => initialState, { + name: "UiTools", + enabled: process.env.NODE_ENV === "development", + trace: process.env.NODE_ENV === "development", + }) ); diff --git a/src/ui_kit/CustomNumberField.tsx b/src/ui_kit/CustomNumberField.tsx index 8797d74f..c74be2eb 100644 --- a/src/ui_kit/CustomNumberField.tsx +++ b/src/ui_kit/CustomNumberField.tsx @@ -9,6 +9,7 @@ interface CustomNumberFieldProps { onKeyDown?: (event: KeyboardEvent) => void; onBlur?: (event: FocusEvent) => void; error?: string; + emptyError?: boolean; value: string; sx?: SxProps; min?: number; @@ -20,6 +21,7 @@ export default function CustomNumberField({ value, sx, error, + emptyError, onChange, onKeyDown, onBlur, @@ -57,6 +59,7 @@ export default function CustomNumberField({ placeholder={placeholder} sx={sx} error={error} + emptyError={emptyError} onChange={onInputChange} onKeyDown={onKeyDown} onBlur={onInputBlur} diff --git a/src/ui_kit/CustomTextField.tsx b/src/ui_kit/CustomTextField.tsx index 2b9c041e..bf7ea992 100755 --- a/src/ui_kit/CustomTextField.tsx +++ b/src/ui_kit/CustomTextField.tsx @@ -7,6 +7,7 @@ interface CustomTextFieldProps { placeholder: string; value?: string; error?: string; + emptyError?: boolean; onChange?: (event: ChangeEvent) => void; onKeyDown?: (event: KeyboardEvent) => void; onBlur?: (event: FocusEvent) => void; @@ -25,6 +26,7 @@ export default function CustomTextField({ text, sx, error, + emptyError, InputProps, maxLength = 200, }: CustomTextFieldProps) { @@ -62,7 +64,7 @@ export default function CustomTextField({ value={inputValue} placeholder={placeholder} onChange={handleInputChange} - error={!!error} + error={!!error || emptyError} label={error} onFocus={handleInputFocus} onBlur={handleInputBlur} diff --git a/src/utils/hooks/useAddAnswer.ts b/src/utils/hooks/useAddAnswer.ts new file mode 100644 index 00000000..e73cb096 --- /dev/null +++ b/src/utils/hooks/useAddAnswer.ts @@ -0,0 +1,17 @@ +import { useSnackbar } from "notistack"; +import { addQuestionVariant } from "@root/questions/actions"; +import { QuizQuestionsWithVariants } from "@model/questionTypes/shared"; + +export const useAddAnswer = () => { + const { enqueueSnackbar } = useSnackbar(); + + const onClickAddAnAnswer = (question: QuizQuestionsWithVariants) => { + if (question.content.variants.length >= 10) { + enqueueSnackbar("100 максимальное количество вопросов"); + } else { + addQuestionVariant(question.id); + } + }; + + return onClickAddAnAnswer; +};