Merge branch 'data-rollback' into dev

This commit is contained in:
Nastya 2024-02-27 01:03:46 +03:00
commit 85c99344f1
34 changed files with 235 additions and 484 deletions

@ -1,29 +1,24 @@
import { MessageIcon } from "@icons/messagIcon";
import { PointsIcon } from "@icons/questionsPage/PointsIcon"; import { PointsIcon } from "@icons/questionsPage/PointsIcon";
import { DeleteIcon } from "@icons/questionsPage/deleteIcon"; import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
import { TextareaAutosize } from "@mui/base/TextareaAutosize";
import { import {
Box, Box,
FormControl, FormControl,
IconButton, IconButton,
InputAdornment, InputAdornment,
Popover,
TextField as MuiTextField, TextField as MuiTextField,
TextFieldProps,
useMediaQuery, useMediaQuery,
useTheme, useTheme,
TextFieldProps,
} from "@mui/material"; } from "@mui/material";
import { import {
addQuestionVariant, addQuestionVariant,
deleteQuestionVariant, deleteQuestionVariant,
setQuestionVariantField, setQuestionVariantField,
} from "@root/questions/actions"; } from "@root/questions/actions";
import { enqueueSnackbar } from "notistack";
import type { ChangeEvent, FC, KeyboardEvent, ReactNode } from "react"; import type { ChangeEvent, FC, KeyboardEvent, ReactNode } from "react";
import { useState } from "react";
import { Draggable } from "react-beautiful-dnd"; import { Draggable } from "react-beautiful-dnd";
import type { QuestionVariant } from "../../../model/questionTypes/shared"; import type { QuestionVariant } from "../../../model/questionTypes/shared";
import { useDebouncedCallback } from "use-debounce";
import { enqueueSnackbar } from "notistack";
const TextField = MuiTextField as unknown as FC<TextFieldProps>; const TextField = MuiTextField as unknown as FC<TextFieldProps>;
@ -48,21 +43,6 @@ export const AnswerItem = ({
}: AnswerItemProps) => { }: AnswerItemProps) => {
const theme = useTheme(); const theme = useTheme();
const isTablet = useMediaQuery(theme.breakpoints.down(790)); const isTablet = useMediaQuery(theme.breakpoints.down(790));
const [isOpen, setIsOpen] = useState(false);
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
const [inputValue, setInputValue] = useState(variant.answer);
const setQuestionVariantAnswer = useDebouncedCallback((value) => {
setQuestionVariantField(questionId, variant.id, "answer", value);
}, 200);
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
setAnchorEl(event.currentTarget);
setIsOpen(true);
};
const handleClose = () => {
setIsOpen(false);
};
return ( return (
<Draggable draggableId={String(index)} index={index}> <Draggable draggableId={String(index)} index={index}>
@ -80,16 +60,20 @@ export const AnswerItem = ({
}} }}
> >
<TextField <TextField
value={inputValue} value={variant.answer}
fullWidth fullWidth
focused={false} focused={false}
placeholder={"Добавьте ответ"} placeholder={"Добавьте ответ"}
multiline={largeCheck} multiline={largeCheck}
onChange={({ target }: ChangeEvent<HTMLInputElement>) => { onChange={({ target }: ChangeEvent<HTMLInputElement>) => {
if (target.value.length <= 1000) { if (target.value.length <= 1000) {
setInputValue(target.value); setQuestionVariantField(
questionId,
variant.id,
"answer",
target.value || " ",
);
} }
setQuestionVariantAnswer(target.value || " ");
}} }}
onKeyDown={(event: KeyboardEvent<HTMLInputElement>) => { onKeyDown={(event: KeyboardEvent<HTMLInputElement>) => {
if (disableKeyDown) { if (disableKeyDown) {

@ -1,6 +1,3 @@
import { DoubleArrowRight } from "@icons/questionsPage/DoubleArrowRight";
import { DoubleTick } from "@icons/questionsPage/DoubleTick";
import { VectorQuestions } from "@icons/questionsPage/VectorQuestions";
import { DeleteIcon } from "@icons/questionsPage/deleteIcon"; import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
import type { SxProps } from "@mui/material"; import type { SxProps } from "@mui/material";
import { import {
@ -8,7 +5,6 @@ import {
Button, Button,
IconButton, IconButton,
Modal, Modal,
Tooltip,
Typography, Typography,
useMediaQuery, useMediaQuery,
useTheme, useTheme,
@ -17,27 +13,17 @@ import {
copyQuestion, copyQuestion,
deleteQuestion, deleteQuestion,
deleteQuestionWithTimeout, deleteQuestionWithTimeout,
clearRuleForAll,
updateQuestion,
getQuestionByContentId,
} from "@root/questions/actions"; } from "@root/questions/actions";
import { useQuestionsStore } from "@root/questions/store";
import { useCurrentQuiz } from "@root/quizes/hooks";
import { updateDesireToOpenABranchingModal } from "@root/uiTools/actions"; import { updateDesireToOpenABranchingModal } from "@root/uiTools/actions";
import MiniButtonSetting from "@ui_kit/MiniButtonSetting"; import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
import { DeleteFunction } from "@utils/deleteFunc";
import { useState } from "react";
import { CopyIcon } from "../../assets/icons/questionsPage/CopyIcon"; import { CopyIcon } from "../../assets/icons/questionsPage/CopyIcon";
import Branching from "../../assets/icons/questionsPage/branching"; import Branching from "../../assets/icons/questionsPage/branching";
import Clue from "../../assets/icons/questionsPage/clue";
import { HideIcon } from "../../assets/icons/questionsPage/hideIcon";
import SettingIcon from "../../assets/icons/questionsPage/settingIcon"; import SettingIcon from "../../assets/icons/questionsPage/settingIcon";
import type { AnyTypedQuizQuestion } from "../../model/questionTypes/shared"; import type { AnyTypedQuizQuestion } from "../../model/questionTypes/shared";
import { useCurrentQuiz } from "@root/quizes/hooks";
import { enqueueSnackbar } from "notistack";
import { useQuestionsStore } from "@root/questions/store";
import { updateOpenedModalSettingsId } from "@root/uiTools/actions";
import { updateRootContentId } from "@root/quizes/actions";
import { useUiTools } from "@root/uiTools/store";
import { useState } from "react";
import { updateSomeWorkBackend } from "@root/uiTools/actions";
import { DeleteFunction } from "@utils/deleteFunc";
interface Props { interface Props {
switchState: string; switchState: string;
@ -306,7 +292,7 @@ export default function ButtonsOptions({
setOpenDelete(true); setOpenDelete(true);
} else { } else {
deleteQuestionWithTimeout(question.id, () => deleteQuestionWithTimeout(question.id, () =>
DeleteFunction(questions, question, quiz), DeleteFunction(question),
); );
} }
}} }}
@ -351,7 +337,7 @@ export default function ButtonsOptions({
sx={{ minWidth: "150px" }} sx={{ minWidth: "150px" }}
onClick={() => { onClick={() => {
deleteQuestionWithTimeout(question.id, () => deleteQuestionWithTimeout(question.id, () =>
DeleteFunction(questions, question, quiz), DeleteFunction(question),
); );
}} }}
> >

@ -1,6 +1,3 @@
import { DoubleArrowRight } from "@icons/questionsPage/DoubleArrowRight";
import { DoubleTick } from "@icons/questionsPage/DoubleTick";
import { VectorQuestions } from "@icons/questionsPage/VectorQuestions";
import { DeleteIcon } from "@icons/questionsPage/deleteIcon"; import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
import type { SxProps } from "@mui/material"; import type { SxProps } from "@mui/material";
import { import {
@ -8,7 +5,6 @@ import {
Button, Button,
IconButton, IconButton,
Modal, Modal,
Tooltip,
Typography, Typography,
useMediaQuery, useMediaQuery,
useTheme, useTheme,
@ -17,27 +13,17 @@ import {
copyQuestion, copyQuestion,
deleteQuestion, deleteQuestion,
deleteQuestionWithTimeout, deleteQuestionWithTimeout,
clearRuleForAll,
updateQuestion,
getQuestionByContentId,
} from "@root/questions/actions"; } from "@root/questions/actions";
import { useQuestionsStore } from "@root/questions/store";
import { useCurrentQuiz } from "@root/quizes/hooks";
import { updateDesireToOpenABranchingModal } from "@root/uiTools/actions"; import { updateDesireToOpenABranchingModal } from "@root/uiTools/actions";
import MiniButtonSetting from "@ui_kit/MiniButtonSetting"; import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
import { CopyIcon } from "../../assets/icons/questionsPage/CopyIcon";
import Branching from "../../assets/icons/questionsPage/branching";
import Clue from "../../assets/icons/questionsPage/clue";
import { HideIcon } from "../../assets/icons/questionsPage/hideIcon";
import SettingIcon from "../../assets/icons/questionsPage/settingIcon";
import type { AnyTypedQuizQuestion } from "../../model/questionTypes/shared";
import { useCurrentQuiz } from "@root/quizes/hooks";
import { enqueueSnackbar } from "notistack";
import { useQuestionsStore } from "@root/questions/store";
import { updateOpenedModalSettingsId } from "@root/uiTools/actions";
import { updateRootContentId } from "@root/quizes/actions";
import { useUiTools } from "@root/uiTools/store";
import { useState } from "react";
import { updateSomeWorkBackend } from "@root/uiTools/actions";
import { DeleteFunction } from "@utils/deleteFunc"; import { DeleteFunction } from "@utils/deleteFunc";
import { useState } from "react";
import { CopyIcon } from "../../../assets/icons/questionsPage/CopyIcon";
import Branching from "../../../assets/icons/questionsPage/branching";
import SettingIcon from "../../../assets/icons/questionsPage/settingIcon";
import type { AnyTypedQuizQuestion } from "../../../model/questionTypes/shared";
interface Props { interface Props {
switchState: string; switchState: string;
@ -213,7 +199,7 @@ export default function ButtonsOptions({
setOpenDelete(true); setOpenDelete(true);
} else { } else {
deleteQuestionWithTimeout(question.id, () => deleteQuestionWithTimeout(question.id, () =>
DeleteFunction(questions, question, quiz), DeleteFunction(question),
); );
} }
}} }}
@ -258,7 +244,7 @@ export default function ButtonsOptions({
sx={{ minWidth: "150px" }} sx={{ minWidth: "150px" }}
onClick={() => { onClick={() => {
deleteQuestionWithTimeout(question.id, () => deleteQuestionWithTimeout(question.id, () =>
DeleteFunction(questions, question, quiz), DeleteFunction(question),
); );
}} }}
> >

@ -1,13 +1,10 @@
import { DoubleArrowRight } from "@icons/questionsPage/DoubleArrowRight"; import { QuizQuestionVariant } from "@model/questionTypes/variant";
import { DoubleTick } from "@icons/questionsPage/DoubleTick";
import { VectorQuestions } from "@icons/questionsPage/VectorQuestions";
import { QuizQuestionVarImg } from "@model/questionTypes/varimg"; import { QuizQuestionVarImg } from "@model/questionTypes/varimg";
import { import {
Box, Box,
Button, Button,
IconButton, IconButton,
Modal, Modal,
Tooltip,
Typography, Typography,
useMediaQuery, useMediaQuery,
useTheme, useTheme,
@ -15,31 +12,20 @@ import {
import { import {
copyQuestion, copyQuestion,
deleteQuestion, deleteQuestion,
updateQuestion,
clearRuleForAll,
getQuestionByContentId,
deleteQuestionWithTimeout, deleteQuestionWithTimeout,
} from "@root/questions/actions"; } from "@root/questions/actions";
import { useQuestionsStore } from "@root/questions/store";
import { useCurrentQuiz } from "@root/quizes/hooks";
import { updateDesireToOpenABranchingModal } from "@root/uiTools/actions";
import MiniButtonSetting from "@ui_kit/MiniButtonSetting"; import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
import { ReallyChangingModal } from "@ui_kit/Modal/ReallyChangingModal/ReallyChangingModal"; import { ReallyChangingModal } from "@ui_kit/Modal/ReallyChangingModal/ReallyChangingModal";
import { DeleteFunction } from "@utils/deleteFunc";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { CopyIcon } from "../../assets/icons/questionsPage/CopyIcon"; import { CopyIcon } from "../../assets/icons/questionsPage/CopyIcon";
import Branching from "../../assets/icons/questionsPage/branching"; import Branching from "../../assets/icons/questionsPage/branching";
import Clue from "../../assets/icons/questionsPage/clue";
import { DeleteIcon } from "../../assets/icons/questionsPage/deleteIcon"; import { DeleteIcon } from "../../assets/icons/questionsPage/deleteIcon";
import { HideIcon } from "../../assets/icons/questionsPage/hideIcon";
import ImgIcon from "../../assets/icons/questionsPage/imgIcon"; import ImgIcon from "../../assets/icons/questionsPage/imgIcon";
import SettingIcon from "../../assets/icons/questionsPage/settingIcon"; import SettingIcon from "../../assets/icons/questionsPage/settingIcon";
import { QuizQuestionVariant } from "@model/questionTypes/variant";
import { updateOpenedModalSettingsId } from "@root/questions/actions";
import { updateDesireToOpenABranchingModal } from "@root/uiTools/actions";
import { useQuestionsStore } from "@root/questions/store";
import { enqueueSnackbar } from "notistack";
import { useCurrentQuiz } from "@root/quizes/hooks";
import { updateRootContentId } from "@root/quizes/actions";
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
import { updateSomeWorkBackend } from "@root/uiTools/actions";
import { DeleteFunction } from "@utils/deleteFunc";
interface Props { interface Props {
switchState: string; switchState: string;
@ -337,7 +323,7 @@ export default function ButtonsOptionsAndPict({
setOpenDelete(true); setOpenDelete(true);
} else { } else {
deleteQuestionWithTimeout(question.id, () => deleteQuestionWithTimeout(question.id, () =>
DeleteFunction(questions, question, quiz), DeleteFunction(question),
); );
} }
}} }}
@ -382,7 +368,7 @@ export default function ButtonsOptionsAndPict({
sx={{ minWidth: "150px" }} sx={{ minWidth: "150px" }}
onClick={() => { onClick={() => {
deleteQuestionWithTimeout(question.id, () => deleteQuestionWithTimeout(question.id, () =>
DeleteFunction(questions, question, quiz), DeleteFunction(question),
); );
}} }}
> >

@ -1,15 +1,6 @@
import { import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
Box, import { updateQuestion } from "@root/questions/actions";
Tooltip,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { setQuestionInnerName, updateQuestion } from "@root/questions/actions";
import CustomCheckbox from "@ui_kit/CustomCheckbox"; import CustomCheckbox from "@ui_kit/CustomCheckbox";
import CustomTextField from "@ui_kit/CustomTextField";
import { useDebouncedCallback } from "use-debounce";
import InfoIcon from "../../../assets/icons/InfoIcon";
import type { QuizQuestionDate } from "../../../model/questionTypes/date"; import type { QuizQuestionDate } from "../../../model/questionTypes/date";
type SettingsDataProps = { type SettingsDataProps = {
@ -22,10 +13,6 @@ export default function SettingsData({ question }: SettingsDataProps) {
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
const setInnerName = useDebouncedCallback((value) => {
setQuestionInnerName(question.id, value);
}, 200);
return ( return (
<Box <Box
sx={{ sx={{

@ -3,13 +3,12 @@ import {
UntypedQuizQuestion, UntypedQuizQuestion,
} from "@model/questionTypes/shared"; } from "@model/questionTypes/shared";
import { Box, ListItem, Typography, useTheme } from "@mui/material"; import { Box, ListItem, Typography, useTheme } from "@mui/material";
import { cancelQuestionDeletion } from "@root/questions/actions";
import { updateEditSomeQuestion } from "@root/uiTools/actions";
import { useUiTools } from "@root/uiTools/store";
import { memo, useEffect } from "react"; import { memo, useEffect } from "react";
import { Draggable } from "react-beautiful-dnd"; import { Draggable } from "react-beautiful-dnd";
import QuestionsPageCard from "./QuestionPageCard"; import QuestionsPageCard from "./QuestionPageCard";
import { cancelQuestionDeletion } from "@root/questions/actions";
import { updateEditSomeQuestion } from "@root/uiTools/actions";
import { useQuestionsStore } from "@root/questions/store";
import { useUiTools } from "@root/uiTools/store";
type Props = { type Props = {
question: AnyTypedQuizQuestion | UntypedQuizQuestion; question: AnyTypedQuizQuestion | UntypedQuizQuestion;
@ -27,7 +26,7 @@ function DraggableListItem({
setOpenBranchingPage, setOpenBranchingPage,
}: Props) { }: Props) {
const theme = useTheme(); const theme = useTheme();
const { editSomeQuestion } = useUiTools(); const editSomeQuestion = useUiTools((state) => state.editSomeQuestion);
useEffect(() => { useEffect(() => {
let counter = 0; let counter = 0;

@ -13,6 +13,7 @@ import OptionsPict from "@icons/questionsPage/options_pict";
import Page from "@icons/questionsPage/page"; import Page from "@icons/questionsPage/page";
import RatingIcon from "@icons/questionsPage/rating"; import RatingIcon from "@icons/questionsPage/rating";
import Slider from "@icons/questionsPage/slider"; import Slider from "@icons/questionsPage/slider";
import { QuestionType } from "@model/question/question";
import ExpandLessIcon from "@mui/icons-material/ExpandLess"; import ExpandLessIcon from "@mui/icons-material/ExpandLess";
import { import {
Box, Box,
@ -21,8 +22,9 @@ import {
IconButton, IconButton,
InputAdornment, InputAdornment,
Modal, Modal,
TextField as MuiTextField,
Paper, Paper,
TextField, TextFieldProps,
Typography, Typography,
useMediaQuery, useMediaQuery,
useTheme, useTheme,
@ -31,26 +33,24 @@ import {
copyQuestion, copyQuestion,
createUntypedQuestion, createUntypedQuestion,
deleteQuestion, deleteQuestion,
deleteQuestionWithTimeout,
toggleExpandQuestion, toggleExpandQuestion,
updateQuestion, updateQuestion,
updateUntypedQuestion, updateUntypedQuestion,
deleteQuestionWithTimeout,
} from "@root/questions/actions"; } from "@root/questions/actions";
import { useRef, useState } from "react"; import { DeleteFunction } from "@utils/deleteFunc";
import { FC, useRef, useState } from "react";
import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd"; import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
import { useDebouncedCallback } from "use-debounce";
import { ReactComponent as PlusIcon } from "../../../assets/icons/plus.svg"; import { ReactComponent as PlusIcon } from "../../../assets/icons/plus.svg";
import type { import type {
AnyTypedQuizQuestion, AnyTypedQuizQuestion,
UntypedQuizQuestion, UntypedQuizQuestion,
} from "../../../model/questionTypes/shared"; } from "../../../model/questionTypes/shared";
import SwitchQuestionsPage from "../SwitchQuestionsPage"; import SwitchQuestionsPage from "../SwitchQuestionsPage";
import { ChooseAnswerModal } from "./ChooseAnswerModal";
import TypeQuestions from "../TypeQuestions"; import TypeQuestions from "../TypeQuestions";
import { QuestionType } from "@model/question/question"; import { ChooseAnswerModal } from "./ChooseAnswerModal";
import { useCurrentQuiz } from "@root/quizes/hooks";
import { useQuestionsStore } from "@root/questions/store"; const TextField = MuiTextField as unknown as FC<TextFieldProps>;
import { DeleteFunction } from "@utils/deleteFunc";
interface Props { interface Props {
question: AnyTypedQuizQuestion | UntypedQuizQuestion; question: AnyTypedQuizQuestion | UntypedQuizQuestion;
@ -71,7 +71,6 @@ export default function QuestionsPageCard({
}: Props) { }: Props) {
const maxLengthTextField = 225; const maxLengthTextField = 225;
const { questions } = useQuestionsStore();
const [plusVisible, setPlusVisible] = useState<boolean>(false); const [plusVisible, setPlusVisible] = useState<boolean>(false);
const [open, setOpen] = useState<boolean>(false); const [open, setOpen] = useState<boolean>(false);
@ -82,16 +81,15 @@ export default function QuestionsPageCard({
const isTablet = useMediaQuery(theme.breakpoints.down(1000)); const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const anchorRef = useRef(null); const anchorRef = useRef(null);
const quiz = useCurrentQuiz();
const setTitle = useDebouncedCallback((title) => { const setTitle = (title: string) => {
const updateQuestionFn = const updateQuestionFn =
question.type === null ? updateUntypedQuestion : updateQuestion; question.type === null ? updateUntypedQuestion : updateQuestion;
updateQuestionFn(question.id, (question) => { updateQuestionFn(question.id, (question) => {
question.title = title; question.title = title;
}); });
}, 200); };
const handleInputFocus = () => { const handleInputFocus = () => {
setIsTextFieldtActive(true); setIsTextFieldtActive(true);
@ -134,11 +132,9 @@ export default function QuestionsPageCard({
> >
<TextField <TextField
id="questionTitle" id="questionTitle"
defaultValue={question.title} value={question.title}
placeholder={"Заголовок вопроса"} placeholder={"Заголовок вопроса"}
onChange={({ target }: { target: HTMLInputElement }) => onChange={({ target }) => setTitle(target.value || " ")}
setTitle(target.value || " ")
}
onFocus={handleInputFocus} onFocus={handleInputFocus}
onBlur={handleInputBlur} onBlur={handleInputBlur}
inputProps={{ inputProps={{
@ -303,7 +299,7 @@ export default function QuestionsPageCard({
setOpenDelete(true); setOpenDelete(true);
} else { } else {
deleteQuestionWithTimeout(question.id, () => deleteQuestionWithTimeout(question.id, () =>
DeleteFunction(questions, question, quiz), DeleteFunction(question),
); );
} }
}} }}
@ -350,7 +346,7 @@ export default function QuestionsPageCard({
sx={{ minWidth: "150px" }} sx={{ minWidth: "150px" }}
onClick={() => { onClick={() => {
deleteQuestionWithTimeout(question.id, () => deleteQuestionWithTimeout(question.id, () =>
DeleteFunction(questions, question, quiz), DeleteFunction(question),
); );
}} }}
> >

@ -1,15 +1,7 @@
import { import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
Box, import { updateQuestion } from "@root/questions/actions";
Tooltip,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { setQuestionInnerName, updateQuestion } from "@root/questions/actions";
import CustomCheckbox from "@ui_kit/CustomCheckbox"; import CustomCheckbox from "@ui_kit/CustomCheckbox";
import CustomTextField from "@ui_kit/CustomTextField"; import CustomTextField from "@ui_kit/CustomTextField";
import { useDebouncedCallback } from "use-debounce";
import InfoIcon from "../../../assets/icons/InfoIcon";
import type { QuizQuestionSelect } from "../../../model/questionTypes/select"; import type { QuizQuestionSelect } from "../../../model/questionTypes/select";
type SettingDropDownProps = { type SettingDropDownProps = {
@ -21,17 +13,13 @@ export default function SettingDropDown({ question }: SettingDropDownProps) {
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const debounced = useDebouncedCallback((value) => { const setContentDefault = (value: string) => {
setQuestionInnerName(question.id, value);
}, 200);
const debounceAnswer = useDebouncedCallback((value) => {
updateQuestion(question.id, (question) => { updateQuestion(question.id, (question) => {
if (question.type !== "select") return; if (question.type !== "select") return;
question.content.default = value; question.content.default = value;
}); });
}, 200); };
return ( return (
<> <>
@ -98,9 +86,9 @@ export default function SettingDropDown({ question }: SettingDropDownProps) {
</Typography> </Typography>
<CustomTextField <CustomTextField
placeholder={"Выберите вариант"} placeholder={"Выберите вариант"}
text={question.content.default} value={question.content.default}
maxLength={60} maxLength={60}
onChange={({ target }) => debounceAnswer(target.value)} onChange={({ target }) => setContentDefault(target.value)}
/> />
</Box> </Box>
</Box> </Box>
@ -178,7 +166,7 @@ export default function SettingDropDown({ question }: SettingDropDownProps) {
<CustomTextField <CustomTextField
placeholder={"Выберите вариант"} placeholder={"Выберите вариант"}
text={question.content.default} text={question.content.default}
onChange={({ target }) => debounceAnswer(target.value)} onChange={({ target }) => setContentDefault(target.value)}
/> />
</Box> </Box>
{/*{question.content.innerNameCheck && (*/} {/*{question.content.innerNameCheck && (*/}

@ -1,15 +1,6 @@
import { import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
Box, import { updateQuestion } from "@root/questions/actions";
Tooltip,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { setQuestionInnerName, updateQuestion } from "@root/questions/actions";
import CustomCheckbox from "@ui_kit/CustomCheckbox"; import CustomCheckbox from "@ui_kit/CustomCheckbox";
import CustomTextField from "@ui_kit/CustomTextField";
import { useDebouncedCallback } from "use-debounce";
import InfoIcon from "../../../assets/icons/InfoIcon";
import type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji"; import type { QuizQuestionEmoji } from "../../../model/questionTypes/emoji";
type SettingEmojiProps = { type SettingEmojiProps = {
@ -19,14 +10,9 @@ type SettingEmojiProps = {
export default function SettingEmoji({ question }: SettingEmojiProps) { export default function SettingEmoji({ question }: SettingEmojiProps) {
const theme = useTheme(); const theme = useTheme();
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980)); const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
const isTablet = useMediaQuery(theme.breakpoints.down(985));
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const setInnerName = useDebouncedCallback((value) => {
setQuestionInnerName(question.id, value);
}, 200);
return ( return (
<Box <Box
sx={{ sx={{

@ -19,10 +19,11 @@ import {
IconButton, IconButton,
InputAdornment, InputAdornment,
Paper, Paper,
TextField, TextField as MuiTextField,
Typography, Typography,
useMediaQuery, useMediaQuery,
useTheme, useTheme,
TextFieldProps,
} from "@mui/material"; } from "@mui/material";
import { import {
copyQuestion, copyQuestion,
@ -33,7 +34,7 @@ import {
updateUntypedQuestion, updateUntypedQuestion,
} from "@root/questions/actions"; } from "@root/questions/actions";
import CustomTextField from "@ui_kit/CustomTextField"; import CustomTextField from "@ui_kit/CustomTextField";
import { useRef, useState } from "react"; import { FC, useRef, useState } from "react";
import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd"; import type { DraggableProvidedDragHandleProps } from "react-beautiful-dnd";
import { useDebouncedCallback } from "use-debounce"; import { useDebouncedCallback } from "use-debounce";
import type { import type {
@ -55,6 +56,8 @@ import {
SignalCellularNullOutlined, SignalCellularNullOutlined,
} from "@mui/icons-material"; } from "@mui/icons-material";
const TextField = MuiTextField as unknown as FC<TextFieldProps>;
interface Props { interface Props {
question: AnyTypedQuizQuestion | UntypedQuizQuestion; question: AnyTypedQuizQuestion | UntypedQuizQuestion;
questionIndex: number; questionIndex: number;
@ -74,14 +77,14 @@ export default function QuestionsPageCard({
const isTablet = useMediaQuery(theme.breakpoints.down(1000)); const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const setTitle = useDebouncedCallback((title) => { const setTitle = (title: string) => {
const updateQuestionFn = const updateQuestionFn =
question.type === null ? updateUntypedQuestion : updateQuestion; question.type === null ? updateUntypedQuestion : updateQuestion;
updateQuestionFn(question.id, (question) => { updateQuestionFn(question.id, (question) => {
question.title = title; question.title = title;
}); });
}, 200); };
const handleInputFocus = () => { const handleInputFocus = () => {
setIsTextFieldtActive(true); setIsTextFieldtActive(true);
@ -134,10 +137,9 @@ export default function QuestionsPageCard({
> >
<TextField <TextField
placeholder={`Заголовок ${questionIndex + 1} вопроса`} placeholder={`Заголовок ${questionIndex + 1} вопроса`}
defaultValue={question.title} value={question.title}
onChange={({ target }) => { onChange={({ target }) => {
if ((target.value, toString().length <= 225)) if (target.value.length <= 225) setTitle(target.value);
setTitle(target.value);
}} }}
onFocus={handleInputFocus} onFocus={handleInputFocus}
onBlur={handleInputBlur} onBlur={handleInputBlur}

@ -1,15 +1,7 @@
import { import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
Box, import { updateQuestion } from "@root/questions/actions";
Tooltip,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { setQuestionInnerName, updateQuestion } from "@root/questions/actions";
import CustomCheckbox from "@ui_kit/CustomCheckbox"; import CustomCheckbox from "@ui_kit/CustomCheckbox";
import CustomTextField from "@ui_kit/CustomTextField"; import CustomTextField from "@ui_kit/CustomTextField";
import { useDebouncedCallback } from "use-debounce";
import InfoIcon from "../../../assets/icons/InfoIcon";
import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg"; import type { QuizQuestionVarImg } from "../../../model/questionTypes/varimg";
type SettingOptionsAndPictProps = { type SettingOptionsAndPictProps = {
@ -24,17 +16,13 @@ export default function SettingOptionsAndPict({
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
const isMobile = useMediaQuery(theme.breakpoints.down(680)); const isMobile = useMediaQuery(theme.breakpoints.down(680));
const setReplText = useDebouncedCallback((replText) => { const setReplText = (replText: string) => {
updateQuestion(question.id, (question) => { updateQuestion(question.id, (question) => {
if (question.type !== "varimg") return; if (question.type !== "varimg") return;
question.content.replText = replText; question.content.replText = replText;
}); });
}, 200); };
const setDescription = useDebouncedCallback((value) => {
setQuestionInnerName(question.id, value);
}, 200);
return ( return (
<> <>

@ -1,18 +1,12 @@
import { import {
Box, Box,
Button, Button,
Tooltip,
Typography, Typography,
useMediaQuery, useMediaQuery,
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import { setQuestionInnerName, updateQuestion } from "@root/questions/actions"; import { updateQuestion } from "@root/questions/actions";
import CustomCheckbox from "@ui_kit/CustomCheckbox"; import CustomCheckbox from "@ui_kit/CustomCheckbox";
import CustomTextField from "@ui_kit/CustomTextField";
import { useDebouncedCallback } from "use-debounce";
import InfoIcon from "../../../assets/icons/InfoIcon";
import FormatIcon1 from "../../../assets/icons/questionsPage/FormatIcon1";
import FormatIcon2 from "../../../assets/icons/questionsPage/FormatIcon2";
import ProportionsIcon11 from "../../../assets/icons/questionsPage/ProportionsIcon11"; import ProportionsIcon11 from "../../../assets/icons/questionsPage/ProportionsIcon11";
import ProportionsIcon12 from "../../../assets/icons/questionsPage/ProportionsIcon12"; import ProportionsIcon12 from "../../../assets/icons/questionsPage/ProportionsIcon12";
import ProportionsIcon21 from "../../../assets/icons/questionsPage/ProportionsIcon21"; import ProportionsIcon21 from "../../../assets/icons/questionsPage/ProportionsIcon21";
@ -43,18 +37,6 @@ export default function SettingOpytionsPict({
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
const debounced = useDebouncedCallback((value) => {
setQuestionInnerName(question.id, value);
}, 200);
const updateProportions = (proportions: Proportion) => {
updateQuestion(question.id, (question) => {
if (question.type !== "images") return;
question.content.xy = proportions;
});
};
return ( return (
<> <>
<Box <Box

@ -7,14 +7,12 @@ import {
} from "@mui/material"; } from "@mui/material";
import { updateQuestion } from "@root/questions/actions"; import { updateQuestion } from "@root/questions/actions";
import CustomTextField from "@ui_kit/CustomTextField"; import CustomTextField from "@ui_kit/CustomTextField";
import TooltipClickInfo from "@ui_kit/Toolbars/TooltipClickInfo";
import { useState } from "react"; import { useState } from "react";
import { useDebouncedCallback } from "use-debounce";
import InfoIcon from "../../../assets/icons/InfoIcon"; import InfoIcon from "../../../assets/icons/InfoIcon";
import type { QuizQuestionText } from "../../../model/questionTypes/text"; import type { QuizQuestionText } from "../../../model/questionTypes/text";
import ButtonsOptions from "../ButtonsOptions";
import SwitchTextField from "./switchTextField";
import TooltipClickInfo from "@ui_kit/Toolbars/TooltipClickInfo";
import ButtonsOptionsAndPict from "../ButtonsOptionsAndPict"; import ButtonsOptionsAndPict from "../ButtonsOptionsAndPict";
import SwitchTextField from "./switchTextField";
interface Props { interface Props {
question: QuizQuestionText; question: QuizQuestionText;
@ -32,13 +30,13 @@ export default function OwnTextField({
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
const setPlaceholder = useDebouncedCallback((value) => { const setPlaceholder = (value: string) => {
updateQuestion(question.id, (question) => { updateQuestion(question.id, (question) => {
if (question.type !== "text") return; if (question.type !== "text") return;
question.content.placeholder = value; question.content.placeholder = value;
}); });
}, 200); };
const SSHC = (data: string) => { const SSHC = (data: string) => {
setSwitchState(data); setSwitchState(data);
@ -60,7 +58,7 @@ export default function OwnTextField({
> >
<CustomTextField <CustomTextField
placeholder={"Пример ответа"} placeholder={"Пример ответа"}
text={question.content.placeholder} value={question.content.placeholder}
onChange={({ target }) => setPlaceholder(target.value)} onChange={({ target }) => setPlaceholder(target.value)}
sx={{ sx={{
maxWidth: isFigmaTablte ? "549px" : "640px", maxWidth: isFigmaTablte ? "549px" : "640px",

@ -1,21 +1,6 @@
import { import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
Box, import { updateQuestion } from "@root/questions/actions";
FormControl,
FormControlLabel,
Radio,
RadioGroup,
Tooltip,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { setQuestionInnerName, updateQuestion } from "@root/questions/actions";
import CustomCheckbox from "@ui_kit/CustomCheckbox"; import CustomCheckbox from "@ui_kit/CustomCheckbox";
import CustomTextField from "@ui_kit/CustomTextField";
import CheckedIcon from "@ui_kit/RadioCheck";
import CheckIcon from "@ui_kit/RadioIcon";
import { useDebouncedCallback } from "use-debounce";
import InfoIcon from "../../../assets/icons/InfoIcon";
import type { QuizQuestionText } from "../../../model/questionTypes/text"; import type { QuizQuestionText } from "../../../model/questionTypes/text";
type SettingTextFieldProps = { type SettingTextFieldProps = {
@ -38,10 +23,6 @@ export default function SettingTextField({ question }: SettingTextFieldProps) {
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
const debounced = useDebouncedCallback((value) => {
setQuestionInnerName(question.id, value);
}, 200);
return ( return (
<Box <Box
sx={{ sx={{

@ -15,18 +15,12 @@ import {
} from "@root/questions/actions"; } from "@root/questions/actions";
import CustomTextField from "@ui_kit/CustomTextField"; import CustomTextField from "@ui_kit/CustomTextField";
import { useState } from "react";
import { useDebouncedCallback } from "use-debounce";
import type { QuizQuestionPage } from "../../../model/questionTypes/page";
import ButtonsOptions from "../ButtonsOptions";
import SwitchPageOptions from "./switchPageOptions";
import { MediaSelectionAndDisplay } from "@ui_kit/MediaSelectionAndDisplay";
import { CopyIcon } from "@icons/questionsPage/CopyIcon"; import { CopyIcon } from "@icons/questionsPage/CopyIcon";
import { DeleteFunction } from "@utils/deleteFunc";
import { DeleteIcon } from "@icons/questionsPage/deleteIcon"; import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
import { useCurrentQuiz } from "@root/quizes/hooks"; import { MediaSelectionAndDisplay } from "@ui_kit/MediaSelectionAndDisplay";
import { useQuestionsStore } from "@root/questions/store"; import { DeleteFunction } from "@utils/deleteFunc";
import { updateDesireToOpenABranchingModal } from "@root/uiTools/actions"; import { useState } from "react";
import type { QuizQuestionPage } from "../../../model/questionTypes/page";
type Props = { type Props = {
disableInput?: boolean; disableInput?: boolean;
@ -36,30 +30,18 @@ type Props = {
}; };
export default function PageOptions({ disableInput, question }: Props) { export default function PageOptions({ disableInput, question }: Props) {
const [switchState, setSwitchState] = useState("setting");
const theme = useTheme(); const theme = useTheme();
const isTablet = useMediaQuery(theme.breakpoints.down(980)); const isTablet = useMediaQuery(theme.breakpoints.down(980));
const isFigmaTablet = useMediaQuery(theme.breakpoints.down(990)); const isFigmaTablet = useMediaQuery(theme.breakpoints.down(990));
const isMobile = useMediaQuery(theme.breakpoints.down(780)); const isMobile = useMediaQuery(theme.breakpoints.down(780));
const [openDelete, setOpenDelete] = useState<boolean>(false);
const setText = useDebouncedCallback((value) => { const setText = (value: string) => {
updateQuestion(question.id, (question) => { updateQuestion(question.id, (question) => {
if (question.type !== "page") return; if (question.type !== "page") return;
question.content.text = value; question.content.text = value;
}); });
}, 200);
const quiz = useCurrentQuiz();
const { questions } = useQuestionsStore.getState();
const [openDelete, setOpenDelete] = useState<boolean>(false);
const openedModal = () => {
updateDesireToOpenABranchingModal(question.content.id);
};
const SSHC = (data: string) => {
setSwitchState(data);
}; };
return ( return (
@ -82,7 +64,7 @@ export default function PageOptions({ disableInput, question }: Props) {
<CustomTextField <CustomTextField
id="addText" id="addText"
placeholder={"Можно добавить текст"} placeholder={"Можно добавить текст"}
text={question.content.text} value={question.content.text}
onChange={({ target }) => setText(target.value)} onChange={({ target }) => setText(target.value)}
maxLength={50} maxLength={50}
/> />
@ -126,7 +108,7 @@ export default function PageOptions({ disableInput, question }: Props) {
setOpenDelete(true); setOpenDelete(true);
} else { } else {
deleteQuestionWithTimeout(question.id, () => deleteQuestionWithTimeout(question.id, () =>
DeleteFunction(questions, question, quiz), DeleteFunction(question),
); );
} }
}} }}
@ -171,7 +153,7 @@ export default function PageOptions({ disableInput, question }: Props) {
sx={{ minWidth: "150px" }} sx={{ minWidth: "150px" }}
onClick={() => { onClick={() => {
deleteQuestionWithTimeout(question.id, () => deleteQuestionWithTimeout(question.id, () =>
DeleteFunction(questions, question, quiz), DeleteFunction(question),
); );
}} }}
> >

@ -1,16 +1,5 @@
import { import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
Box,
Tooltip,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import CustomCheckbox from "@ui_kit/CustomCheckbox";
import CustomTextField from "@ui_kit/CustomTextField";
import { useDebouncedCallback } from "use-debounce";
import InfoIcon from "../../../assets/icons/InfoIcon";
import type { QuizQuestionPage } from "../../../model/questionTypes/page"; import type { QuizQuestionPage } from "../../../model/questionTypes/page";
import { setQuestionInnerName, updateQuestion } from "@root/questions/actions";
type SettingPageOptionsProps = { type SettingPageOptionsProps = {
question: QuizQuestionPage; question: QuizQuestionPage;
@ -22,10 +11,6 @@ export default function SettingPageOptions({
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const setInnerName = useDebouncedCallback((value) => {
setQuestionInnerName(question.id, value);
}, 200);
return ( return (
<Box <Box
sx={{ sx={{

@ -1,14 +1,9 @@
import { useEffect, useLayoutEffect } from "react";
import { Box, useMediaQuery, useTheme } from "@mui/material"; import { Box, useMediaQuery, useTheme } from "@mui/material";
import { deleteTimeoutedQuestions } from "@utils/deleteTimeoutedQuestions";
import { useCallback } from "react";
import { BranchingMap } from "./BranchingMap";
import { DraggableList } from "./DraggableList"; import { DraggableList } from "./DraggableList";
import { SwitchBranchingPanel } from "./SwitchBranchingPanel"; import { SwitchBranchingPanel } from "./SwitchBranchingPanel";
import { BranchingMap } from "./BranchingMap";
import { useQuestionsStore } from "@root/questions/store";
import { useUiTools } from "@root/uiTools/store";
import { useQuestions } from "@root/questions/hooks";
import { useCurrentQuiz } from "@root/quizes/hooks";
import { deleteTimeoutedQuestions } from "@utils/deleteTimeoutedQuestions";
interface Props { interface Props {
openBranchingPage: boolean; openBranchingPage: boolean;
@ -21,17 +16,15 @@ export const QuestionSwitchWindowTool = ({
setOpenBranchingPage, setOpenBranchingPage,
widthMain, widthMain,
}: Props) => { }: Props) => {
const { questions } = useQuestionsStore.getState();
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(600)); const isMobile = useMediaQuery(theme.breakpoints.down(600));
const quiz = useCurrentQuiz();
const openBranchingPageHC = () => { const openBranchingPageHC = useCallback(() => {
if (!openBranchingPage) { if (!openBranchingPage) {
deleteTimeoutedQuestions(questions, quiz); deleteTimeoutedQuestions();
} }
setOpenBranchingPage(!openBranchingPage); setOpenBranchingPage(!openBranchingPage);
}; }, [openBranchingPage, setOpenBranchingPage]);
return ( return (
<Box <Box

@ -1,24 +1,25 @@
import { useState, useEffect, useRef } from "react";
import { useParams } from "react-router-dom";
import { import {
Box, Box,
TextField as MuiTextField,
TextFieldProps,
Typography, Typography,
TextField,
useMediaQuery, useMediaQuery,
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import { useDebouncedCallback } from "use-debounce"; import { updateQuestion } from "@root/questions/actions";
import { FC, useLayoutEffect, useRef, useState } from "react";
import FlagIcon from "../../../assets/icons/questionsPage/FlagIcon";
import StarIconMini from "../../../assets/icons/questionsPage/StarIconMini";
import HashtagIcon from "../../../assets/icons/questionsPage/hashtagIcon";
import HeartIcon from "../../../assets/icons/questionsPage/heartIcon";
import LightbulbIcon from "../../../assets/icons/questionsPage/lightbulbIcon";
import LikeIcon from "../../../assets/icons/questionsPage/likeIcon";
import TropfyIcon from "../../../assets/icons/questionsPage/tropfyIcon";
import type { QuizQuestionRating } from "../../../model/questionTypes/rating";
import ButtonsOptions from "../ButtonsOptions"; import ButtonsOptions from "../ButtonsOptions";
import SwitchRating from "./switchRating"; import SwitchRating from "./switchRating";
import TropfyIcon from "../../../assets/icons/questionsPage/tropfyIcon";
import FlagIcon from "../../../assets/icons/questionsPage/FlagIcon"; const TextField = MuiTextField as unknown as FC<TextFieldProps>;
import HeartIcon from "../../../assets/icons/questionsPage/heartIcon";
import LikeIcon from "../../../assets/icons/questionsPage/likeIcon";
import LightbulbIcon from "../../../assets/icons/questionsPage/lightbulbIcon";
import HashtagIcon from "../../../assets/icons/questionsPage/hashtagIcon";
import StarIconMini from "../../../assets/icons/questionsPage/StarIconMini";
import type { QuizQuestionRating } from "../../../model/questionTypes/rating";
import { updateQuestion } from "@root/questions/actions";
interface Props { interface Props {
question: QuizQuestionRating; question: QuizQuestionRating;
@ -37,43 +38,36 @@ export default function RatingOptions({
setOpenBranchingPage, setOpenBranchingPage,
}: Props) { }: Props) {
const [switchState, setSwitchState] = useState("setting"); const [switchState, setSwitchState] = useState("setting");
const [negativeText, setNegativeText] = useState<string>("");
const [positiveText, setPositiveText] = useState<string>("");
const [negativeTextWidth, setNegativeTextWidth] = useState<number>(0); const [negativeTextWidth, setNegativeTextWidth] = useState<number>(0);
const [positiveTextWidth, setPositiveTextWidth] = useState<number>(0); const [positiveTextWidth, setPositiveTextWidth] = useState<number>(0);
const quizId = Number(useParams().quizId);
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const negativeRef = useRef<HTMLDivElement>(null); const negativeRef = useRef<HTMLDivElement>(null);
const positiveRef = useRef<HTMLDivElement>(null); const positiveRef = useRef<HTMLDivElement>(null);
const debounceNegativeDescription = useDebouncedCallback((value) => { const setNegativeDescription = (value: string) => {
updateQuestion(question.id, (question) => { updateQuestion(question.id, (question) => {
if (question.type !== "rating") return; if (question.type !== "rating") return;
question.content.ratingNegativeDescription = value.substring(0, 15); question.content.ratingNegativeDescription = value.substring(0, 15);
}); });
}, 200); };
const debouncePositiveDescription = useDebouncedCallback((value) => {
const setPositiveDescription = (value: string) => {
updateQuestion(question.id, (question) => { updateQuestion(question.id, (question) => {
if (question.type !== "rating") return; if (question.type !== "rating") return;
question.content.ratingPositiveDescription = value.substring(0, 15); question.content.ratingPositiveDescription = value.substring(0, 15);
}); });
}, 200); };
useEffect(() => { useLayoutEffect(() => {
setNegativeText(question.content.ratingNegativeDescription);
setPositiveText(question.content.ratingPositiveDescription);
}, []);
useEffect(() => {
setNegativeTextWidth(negativeRef.current?.offsetWidth || 0); setNegativeTextWidth(negativeRef.current?.offsetWidth || 0);
}, [negativeText]); }, [question.content.ratingNegativeDescription]);
useEffect(() => { useLayoutEffect(() => {
setPositiveTextWidth(positiveRef.current?.offsetWidth || 0); setPositiveTextWidth(positiveRef.current?.offsetWidth || 0);
}, [positiveText]); }, [question.content.ratingPositiveDescription]);
const buttonRatingForm: ButtonRatingFrom[] = [ const buttonRatingForm: ButtonRatingFrom[] = [
{ {
@ -221,21 +215,17 @@ export default function RatingOptions({
fontSize: "16px", fontSize: "16px",
}} }}
> >
{negativeText} {question.content.ratingNegativeDescription}
</Typography> </Typography>
<TextField <TextField
defaultValue={question.content.ratingNegativeDescription} value={question.content.ratingNegativeDescription}
value={negativeText}
placeholder="Негативно" placeholder="Негативно"
onChange={({ target }: { target: HTMLInputElement }) => { onChange={({ target }) => {
if (target.value.length <= 15) { if (target.value.length <= 15) {
setNegativeText(target.value); setNegativeDescription(target.value);
debounceNegativeDescription(target.value);
} }
}} }}
onBlur={({ target }: { target: HTMLInputElement }) => onBlur={({ target }) => setNegativeDescription(target.value)}
debounceNegativeDescription(target.value)
}
sx={{ sx={{
width: negativeTextWidth + 10 + "px", width: negativeTextWidth + 10 + "px",
maxWidth: isMobile ? "140px" : "230px", maxWidth: isMobile ? "140px" : "230px",
@ -279,20 +269,17 @@ export default function RatingOptions({
fontSize: "16px", fontSize: "16px",
}} }}
> >
{positiveText} {question.content.ratingPositiveDescription}
</Typography> </Typography>
<TextField <TextField
value={positiveText} value={question.content.ratingPositiveDescription}
placeholder="Позитивно" placeholder="Позитивно"
onChange={({ target }: { target: HTMLInputElement }) => { onChange={({ target }) => {
if (target.value.length <= 15) { if (target.value.length <= 15) {
setPositiveText(target.value); setPositiveDescription(target.value);
debouncePositiveDescription(target.value);
} }
}} }}
onBlur={({ target }: { target: HTMLInputElement }) => onBlur={({ target }) => setPositiveDescription(target.value)}
debouncePositiveDescription(target.value)
}
sx={{ sx={{
width: positiveTextWidth + 10 + "px", width: positiveTextWidth + 10 + "px",
maxWidth: isMobile ? "140px" : "230px", maxWidth: isMobile ? "140px" : "230px",

@ -3,16 +3,12 @@ import {
Box, Box,
ButtonBase, ButtonBase,
Slider, Slider,
Tooltip,
Typography, Typography,
useMediaQuery, useMediaQuery,
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import { setQuestionInnerName, updateQuestion } from "@root/questions/actions"; import { updateQuestion } from "@root/questions/actions";
import CustomCheckbox from "@ui_kit/CustomCheckbox"; import CustomCheckbox from "@ui_kit/CustomCheckbox";
import CustomTextField from "@ui_kit/CustomTextField";
import { useDebouncedCallback } from "use-debounce";
import InfoIcon from "../../../assets/icons/InfoIcon";
import FlagIcon from "../../../assets/icons/questionsPage/FlagIcon"; import FlagIcon from "../../../assets/icons/questionsPage/FlagIcon";
import StarIconMini from "../../../assets/icons/questionsPage/StarIconMini"; import StarIconMini from "../../../assets/icons/questionsPage/StarIconMini";
import HashtagIcon from "../../../assets/icons/questionsPage/hashtagIcon"; import HashtagIcon from "../../../assets/icons/questionsPage/hashtagIcon";
@ -32,10 +28,6 @@ export default function SettingSlider({ question }: SettingSliderProps) {
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const isWrappColumn = useMediaQuery(theme.breakpoints.down(980)); const isWrappColumn = useMediaQuery(theme.breakpoints.down(980));
const setInnerName = useDebouncedCallback((value) => {
setQuestionInnerName(question.id, value);
}, 200);
const buttonRatingForm: ButtonRatingFrom[] = [ const buttonRatingForm: ButtonRatingFrom[] = [
{ name: "star", icon: <StarIconMini color={theme.palette.grey3.main} /> }, { name: "star", icon: <StarIconMini color={theme.palette.grey3.main} /> },
{ name: "trophie", icon: <TropfyIcon color={theme.palette.grey3.main} /> }, { name: "trophie", icon: <TropfyIcon color={theme.palette.grey3.main} /> },

@ -1,15 +1,6 @@
import { import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
Box, import { updateQuestion } from "@root/questions/actions";
Tooltip,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { setQuestionInnerName, updateQuestion } from "@root/questions/actions";
import CustomCheckbox from "@ui_kit/CustomCheckbox"; import CustomCheckbox from "@ui_kit/CustomCheckbox";
import CustomTextField from "@ui_kit/CustomTextField";
import { useDebouncedCallback } from "use-debounce";
import InfoIcon from "../../../assets/icons/InfoIcon";
import type { QuizQuestionNumber } from "../../../model/questionTypes/number"; import type { QuizQuestionNumber } from "../../../model/questionTypes/number";
type SettingSliderProps = { type SettingSliderProps = {
@ -22,10 +13,6 @@ export default function SettingSlider({ question }: SettingSliderProps) {
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const setInnerName = useDebouncedCallback((value) => {
setQuestionInnerName(question.id, value);
}, 200);
return ( return (
<Box <Box
sx={{ sx={{

@ -123,6 +123,9 @@ export default function SwitchQuestionsPage({
/> />
); );
case "result":
return null;
default: default:
notReachable(question); notReachable(question);
} }

@ -1,15 +1,6 @@
import { import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
Box, import { updateQuestion } from "@root/questions/actions";
Tooltip,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { setQuestionInnerName, updateQuestion } from "@root/questions/actions";
import CustomCheckbox from "@ui_kit/CustomCheckbox"; import CustomCheckbox from "@ui_kit/CustomCheckbox";
import CustomTextField from "@ui_kit/CustomTextField";
import { useDebouncedCallback } from "use-debounce";
import InfoIcon from "../../../assets/icons/InfoIcon";
import type { QuizQuestionFile } from "../../../model/questionTypes/file"; import type { QuizQuestionFile } from "../../../model/questionTypes/file";
type SettingsUploadProps = { type SettingsUploadProps = {
@ -20,10 +11,6 @@ export default function SettingsUpload({ question }: SettingsUploadProps) {
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const setInnerName = useDebouncedCallback((value) => {
setQuestionInnerName(question.id, value);
}, 200);
return ( return (
<Box <Box
sx={{ sx={{

@ -1,15 +1,6 @@
import { import { Box, Typography, useMediaQuery, useTheme } from "@mui/material";
Box, import { updateQuestion } from "@root/questions/actions";
Tooltip,
Typography,
useMediaQuery,
useTheme,
} from "@mui/material";
import { setQuestionInnerName, updateQuestion } from "@root/questions/actions";
import CustomCheckbox from "@ui_kit/CustomCheckbox"; import CustomCheckbox from "@ui_kit/CustomCheckbox";
import CustomTextField from "@ui_kit/CustomTextField";
import { useDebouncedCallback } from "use-debounce";
import InfoIcon from "../../../assets/icons/InfoIcon";
import type { QuizQuestionVariant } from "../../../model/questionTypes/variant"; import type { QuizQuestionVariant } from "../../../model/questionTypes/variant";
interface Props { interface Props {
@ -23,10 +14,6 @@ export default function ResponseSettings({ question }: Props) {
const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990)); const isFigmaTablte = useMediaQuery(theme.breakpoints.down(990));
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const updateQuestionInnerName = useDebouncedCallback((value) => {
setQuestionInnerName(question.id, value);
}, 200);
return ( return (
<Box <Box
sx={{ sx={{

@ -5,7 +5,6 @@ import CustomTextField from "@ui_kit/CustomTextField";
import SelectableButton from "@ui_kit/SelectableButton"; import SelectableButton from "@ui_kit/SelectableButton";
import UploadBox from "@ui_kit/UploadBox"; import UploadBox from "@ui_kit/UploadBox";
import { useState } from "react"; import { useState } from "react";
import { useDebouncedCallback } from "use-debounce";
import UploadIcon from "../../assets/icons/UploadIcon"; import UploadIcon from "../../assets/icons/UploadIcon";
import { UploadVideoModal } from "./UploadVideoModal"; import { UploadVideoModal } from "./UploadVideoModal";
@ -19,11 +18,11 @@ export default function HelpQuestions({ question }: HelpQuestionsProps) {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [backgroundType, setBackgroundType] = useState<BackgroundType>("text"); const [backgroundType, setBackgroundType] = useState<BackgroundType>("text");
const updateQuestionHint = useDebouncedCallback((value) => { const updateQuestionHint = (value: string) => {
updateQuestion(question.id, (question) => { updateQuestion(question.id, (question) => {
question.content.hint.text = value; question.content.hint.text = value;
}); });
}, 200); };
return ( return (
<Box <Box
@ -60,7 +59,7 @@ export default function HelpQuestions({ question }: HelpQuestionsProps) {
<> <>
<CustomTextField <CustomTextField
placeholder={"Текст консультанта"} placeholder={"Текст консультанта"}
text={question.content.hint.text} value={question.content.hint.text}
onChange={({ target }) => updateQuestionHint(target.value || " ")} onChange={({ target }) => updateQuestionHint(target.value || " ")}
maxLength={100} maxLength={100}
/> />

@ -3,7 +3,7 @@ import Sidebar from "@ui_kit/Sidebar/Sidebar";
import Box from "@mui/material/Box"; import Box from "@mui/material/Box";
import { useTheme, useMediaQuery, IconButton } from "@mui/material"; import { useTheme, useMediaQuery, IconButton } from "@mui/material";
import HeaderFull from "@ui_kit/Header/HeaderFull"; import HeaderFull from "@ui_kit/Header/HeaderFull";
import { useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { SidebarMobile } from "../ui_kit/Sidebar/SidebarMobile"; import { SidebarMobile } from "../ui_kit/Sidebar/SidebarMobile";
import { setShowConfirmLeaveModal } from "@root/uiTools/actions"; import { setShowConfirmLeaveModal } from "@root/uiTools/actions";
import { setCurrentStep, setQuizes } from "@root/quizes/actions"; import { setCurrentStep, setQuizes } from "@root/quizes/actions";
@ -35,7 +35,6 @@ export default function Main({ sidebar, header, footer, Page }: Props) {
const theme = useTheme(); const theme = useTheme();
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
const quizConfig = quiz?.config; const quizConfig = quiz?.config;
const { questions } = useQuestionsStore();
const { editQuizId } = useQuizStore(); const { editQuizId } = useQuizStore();
const currentStep = useQuizStore((state) => state.currentStep); const currentStep = useQuizStore((state) => state.currentStep);
const { isTestServer } = useDomainDefine(); const { isTestServer } = useDomainDefine();
@ -73,12 +72,12 @@ export default function Main({ sidebar, header, footer, Page }: Props) {
const [nextStep, setNextStep] = useState<number>(0); const [nextStep, setNextStep] = useState<number>(0);
const [openBranchingPage, setOpenBranchingPage] = useState<boolean>(false); const [openBranchingPage, setOpenBranchingPage] = useState<boolean>(false);
const openBranchingPageHC = () => { const openBranchingPageHC = useCallback(() => {
if (!openBranchingPage) { if (!openBranchingPage) {
deleteTimeoutedQuestions(questions, quiz); deleteTimeoutedQuestions();
} }
setOpenBranchingPage((old) => !old); setOpenBranchingPage((old) => !old);
}; }, [openBranchingPage, setOpenBranchingPage]);
const isConditionMet = const isConditionMet =
[1].includes(currentStep) && quizConfig?.type !== "form"; [1].includes(currentStep) && quizConfig?.type !== "form";

@ -59,7 +59,7 @@ export default function EditPage({
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
const { editQuizId } = useQuizStore(); const { editQuizId } = useQuizStore();
const { questions } = useQuestionsStore(); const { questions } = useQuestionsStore();
const { whyCantCreatePublic, showConfirmLeaveModal, nextStep } = useUiTools(); const { showConfirmLeaveModal, nextStep } = useUiTools();
const theme = useTheme(); const theme = useTheme();
const navigate = useNavigate(); const navigate = useNavigate();
const currentStep = useQuizStore((state) => state.currentStep); const currentStep = useQuizStore((state) => state.currentStep);

@ -380,15 +380,15 @@ export const findQuestionById = (quizId: number) => {
let found = null; let found = null;
questionStore questionStore
.getState() .getState()
["listQuestions"][quizId].some( [
(quiz: AnyTypedQuizQuestion, index: number) => { "listQuestions"
if (quiz.backendId === quizId) { ][quizId].some((quiz: AnyTypedQuizQuestion, index: number) => {
found = { quiz, index }; if (quiz.backendId === quizId) {
return true; found = { quiz, index };
} return true;
return false; }
}, return false;
); });
return found; return found;
}; };

@ -13,21 +13,14 @@ import {
UntypedQuizQuestion, UntypedQuizQuestion,
createQuestionVariant, createQuestionVariant,
} from "@model/questionTypes/shared"; } from "@model/questionTypes/shared";
import { defaultQuestionByType } from "../../constants/default";
import { produce } from "immer"; import { produce } from "immer";
import { nanoid } from "nanoid"; import { nanoid } from "nanoid";
import { enqueueSnackbar } from "notistack"; import { enqueueSnackbar } from "notistack";
import { defaultQuestionByType } from "../../constants/default";
import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError"; import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError";
import { RequestQueue } from "../../utils/requestQueue";
import { updateRootContentId } from "@root/quizes/actions";
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";
import { replaceEmptyLinesToSpace } from "../../utils/replaceEmptyLinesToSpace"; import { replaceEmptyLinesToSpace } from "../../utils/replaceEmptyLinesToSpace";
import { useQuizPreviewStore } from "@root/quizPreview"; import { RequestQueue } from "../../utils/requestQueue";
import { useQuizStore } from "@root/quizes/store"; import { QuestionsStore, useQuestionsStore } from "./store";
export const setQuestions = (questions: RawQuestion[] | null | undefined) => export const setQuestions = (questions: RawQuestion[] | null | undefined) =>
setProducedState( setProducedState(
@ -244,15 +237,16 @@ export const cancelQuestionDeletion = (questionId: string) =>
}, },
); );
const REQUEST_DEBOUNCE = 200;
const requestQueue = new RequestQueue(); const requestQueue = new RequestQueue();
let requestTimeoutId: ReturnType<typeof setTimeout>; let rollbackQuestions: ReturnType<typeof useQuestionsStore.getState>;
export const updateQuestion = async <T = AnyTypedQuizQuestion>( export const updateQuestion = async <T = AnyTypedQuizQuestion>(
questionId: string, questionId: string,
updateFn: (question: T) => void, updateFn: (question: T) => void,
skipQueue = false, skipQueue = false,
) => { ) => {
if (!rollbackQuestions) rollbackQuestions = useQuestionsStore.getState();
setProducedState( setProducedState(
(state) => { (state) => {
const question = const question =
@ -275,8 +269,6 @@ export const updateQuestion = async <T = AnyTypedQuizQuestion>(
}, },
); );
// clearTimeout(requestTimeoutId);
const request = async () => { const request = async () => {
const q = const q =
useQuestionsStore.getState().questions.find((q) => q.id === questionId) || useQuestionsStore.getState().questions.find((q) => q.id === questionId) ||
@ -291,6 +283,7 @@ export const updateQuestion = async <T = AnyTypedQuizQuestion>(
const response = await questionApi.edit( const response = await questionApi.edit(
questionToEditQuestionRequest(replaceEmptyLinesToSpace(q)), questionToEditQuestionRequest(replaceEmptyLinesToSpace(q)),
); );
rollbackQuestions = useQuestionsStore.getState();
//Если мы делаем листочек веточкой - удаляем созданный к нему результ //Если мы делаем листочек веточкой - удаляем созданный к нему результ
const questionResult = useQuestionsStore const questionResult = useQuestionsStore
@ -311,6 +304,8 @@ export const updateQuestion = async <T = AnyTypedQuizQuestion>(
} catch (error) { } catch (error) {
if (isAxiosCanceledError(error)) return; if (isAxiosCanceledError(error)) return;
useQuestionsStore.setState(rollbackQuestions);
devlog("Error editing question", { error, questionId }); devlog("Error editing question", { error, questionId });
enqueueSnackbar("Не удалось сохранить вопрос"); enqueueSnackbar("Не удалось сохранить вопрос");
} }
@ -321,9 +316,7 @@ export const updateQuestion = async <T = AnyTypedQuizQuestion>(
return; return;
} }
// requestTimeoutId = setTimeout(() => { requestQueue.enqueue(`updateQuestion-${questionId}`, request);
requestQueue.enqueue(request);
// }, REQUEST_DEBOUNCE);
}; };
export const addQuestionVariant = (questionId: string) => { export const addQuestionVariant = (questionId: string) => {
@ -453,7 +446,7 @@ export const createTypedQuestion = async (
questionId: string, questionId: string,
type: QuestionType, type: QuestionType,
) => ) =>
requestQueue.enqueue(async () => { requestQueue.enqueue(`createTypedQuestion-${questionId}`, async () => {
const questions = useQuestionsStore.getState().questions; const questions = useQuestionsStore.getState().questions;
const question = questions.find((q) => q.id === questionId); const question = questions.find((q) => q.id === questionId);
if (!question) return; if (!question) return;
@ -501,7 +494,7 @@ export const createTypedQuestion = async (
}); });
export const deleteQuestion = async (questionId: string) => export const deleteQuestion = async (questionId: string) =>
requestQueue.enqueue(async () => { requestQueue.enqueue(`deleteQuestion-${questionId}`, async () => {
const question = useQuestionsStore const question = useQuestionsStore
.getState() .getState()
.questions.find((q) => q.id === questionId); .questions.find((q) => q.id === questionId);
@ -525,7 +518,7 @@ export const deleteQuestion = async (questionId: string) =>
}); });
export const copyQuestion = async (questionId: string, quizId: number) => export const copyQuestion = async (questionId: string, quizId: number) =>
requestQueue.enqueue(async () => { requestQueue.enqueue(`copyQuestion-${quizId}-${questionId}`, async () => {
const question = useQuestionsStore const question = useQuestionsStore
.getState() .getState()
.questions.find((q) => q.id === questionId); .questions.find((q) => q.id === questionId);
@ -585,7 +578,7 @@ export const copyQuestion = async (questionId: string, quizId: number) =>
} }
}); });
function setProducedState<A extends string | { type: unknown }>( function setProducedState<A extends string | { type: string }>(
recipe: (state: QuestionsStore) => void, recipe: (state: QuestionsStore) => void,
action?: A, action?: A,
) { ) {
@ -635,7 +628,7 @@ export const createResult = async (
quizId: number | null | undefined, quizId: number | null | undefined,
parentContentId?: string, parentContentId?: string,
) => ) =>
requestQueue.enqueue(async () => { requestQueue.enqueue(`createResult-${quizId}`, async () => {
if (!quizId || !parentContentId) { if (!quizId || !parentContentId) {
console.error( console.error(
"Нет данных для создания результата. quizId: ", "Нет данных для создания результата. quizId: ",

@ -3,15 +3,14 @@ import { devlog, getMessageFromFetchError } from "@frontend/kitui";
import { quizToEditQuizRequest } from "@model/quiz/edit"; import { quizToEditQuizRequest } from "@model/quiz/edit";
import { Quiz, RawQuiz, rawQuizToQuiz } from "@model/quiz/quiz"; import { Quiz, RawQuiz, rawQuizToQuiz } from "@model/quiz/quiz";
import { QuizConfig, maxQuizSetupSteps } from "@model/quizSettings"; import { QuizConfig, maxQuizSetupSteps } from "@model/quizSettings";
import { createUntypedQuestion, updateQuestion } from "@root/questions/actions";
import { useQuestionsStore } from "@root/questions/store";
import { produce } from "immer"; import { produce } from "immer";
import { enqueueSnackbar } from "notistack"; import { enqueueSnackbar } from "notistack";
import { NavigateFunction } from "react-router-dom"; import { NavigateFunction } from "react-router-dom";
import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError"; import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError";
import { RequestQueue } from "../../utils/requestQueue"; import { RequestQueue } from "../../utils/requestQueue";
import { QuizStore, useQuizStore } from "./store"; import { QuizStore, useQuizStore } from "./store";
import { createUntypedQuestion, updateQuestion } from "@root/questions/actions";
import { useCurrentQuiz } from "./hooks";
import { useQuestionsStore } from "@root/questions/store";
export const setEditQuizId = (quizId: number | null) => export const setEditQuizId = (quizId: number | null) =>
setProducedState( setProducedState(
@ -157,7 +156,7 @@ export const updateQuiz = (
clearTimeout(requestTimeoutId); clearTimeout(requestTimeoutId);
requestTimeoutId = setTimeout(async () => { requestTimeoutId = setTimeout(async () => {
requestQueue requestQueue
.enqueue(async () => { .enqueue(`updateQuiz-${quizId}`, async () => {
const quiz = useQuizStore const quiz = useQuizStore
.getState() .getState()
.quizes.find((q) => q.id === quizId); .quizes.find((q) => q.id === quizId);
@ -178,7 +177,7 @@ export const updateQuiz = (
}; };
export const createQuiz = async (navigate: NavigateFunction) => export const createQuiz = async (navigate: NavigateFunction) =>
requestQueue.enqueue(async () => { requestQueue.enqueue("createQuiz", async () => {
try { try {
const rawQuiz = await quizApi.create(); const rawQuiz = await quizApi.create();
const quiz = rawQuizToQuiz(rawQuiz); const quiz = rawQuizToQuiz(rawQuiz);
@ -196,7 +195,7 @@ export const createQuiz = async (navigate: NavigateFunction) =>
}); });
export const deleteQuiz = async (quizId: string) => export const deleteQuiz = async (quizId: string) =>
requestQueue.enqueue(async () => { requestQueue.enqueue(`deleteQuiz-${quizId}`, async () => {
const quiz = useQuizStore.getState().quizes.find((q) => q.id === quizId); const quiz = useQuizStore.getState().quizes.find((q) => q.id === quizId);
if (!quiz) return; if (!quiz) return;

@ -34,3 +34,11 @@ export function useCurrentQuiz() {
return quiz; return quiz;
} }
export function getCurrentQuiz() {
const { quizes, editQuizId } = useQuizStore.getState();
const quiz = quizes.find((q) => q.backendId === editQuizId);
return quiz;
}

@ -35,7 +35,7 @@ const removeResult = (resultId: string) =>
}); });
export const deleteResult = async (resultId: number) => export const deleteResult = async (resultId: number) =>
requestQueue.enqueue(async () => { requestQueue.enqueue(`deleteResult-${resultId}`, async () => {
const result = useResultStore const result = useResultStore
.getState() .getState()
.results.find((r) => r.id === resultId); .results.find((r) => r.id === resultId);
@ -56,32 +56,35 @@ export const obsolescenceResult = async (
resultId: string, resultId: string,
editQuizId: number, editQuizId: number,
) => ) =>
requestQueue.enqueue(async () => { requestQueue.enqueue(
const result = useResultStore `obsolescenceResult-${resultId}-${editQuizId}`,
.getState() async () => {
.results.find((r) => r.id === resultId); const result = useResultStore
if (!result) return; .getState()
if (result.new === false) return; .results.find((r) => r.id === resultId);
let lossDebouncer: null | ReturnType<typeof setTimeout> = null; if (!result) return;
let lossId: string[] = [] as string[]; if (result.new === false) return;
if (!lossId.includes(resultId)) lossId.push(resultId); let lossDebouncer: null | ReturnType<typeof setTimeout> = null;
if (typeof lossDebouncer === "number") clearTimeout(lossDebouncer); let lossId: string[] = [] as string[];
lossDebouncer = setTimeout(async () => { if (!lossId.includes(resultId)) lossId.push(resultId);
//стреляем на лишение новизны if (typeof lossDebouncer === "number") clearTimeout(lossDebouncer);
try { lossDebouncer = setTimeout(async () => {
await resultApi.obsolescence(lossId); //стреляем на лишение новизны
//сбрасываем массив try {
lossId = []; await resultApi.obsolescence(lossId);
} catch (error) { //сбрасываем массив
devlog("Error", error); lossId = [];
} catch (error) {
devlog("Error", error);
const message = getMessageFromFetchError(error) ?? ""; const message = getMessageFromFetchError(error) ?? "";
enqueueSnackbar(`Ошибка. ${message}`); enqueueSnackbar(`Ошибка. ${message}`);
} }
}, 3000); }, 3000);
const resultList = await resultApi.getList(editQuizId); const resultList = await resultApi.getList(editQuizId);
setResults(resultList); setResults(resultList);
}); },
);
export const ExportResults = async ( export const ExportResults = async (
filterNew: string, filterNew: string,
@ -110,7 +113,7 @@ export const ExportResults = async (
} }
}; };
function setProducedState<A extends string | { type: unknown }>( function setProducedState<A extends string | { type: string }>(
recipe: (state: ResultStore) => void, recipe: (state: ResultStore) => void,
action?: A, action?: A,
) { ) {

@ -6,15 +6,17 @@ import {
getQuestionByContentId, getQuestionByContentId,
updateQuestion, updateQuestion,
} from "@root/questions/actions"; } from "@root/questions/actions";
import { useQuestionsStore } from "@root/questions/store";
import { updateRootContentId } from "@root/quizes/actions"; import { updateRootContentId } from "@root/quizes/actions";
import { getCurrentQuiz } from "@root/quizes/hooks";
//Всё здесь нужно сделать последовательно. И пусть весь мир ждёт. //Всё здесь нужно сделать последовательно. И пусть весь мир ждёт.
export const DeleteFunction = async ( export const DeleteFunction = async (question: any) => {
questions: any, const questions = useQuestionsStore.getState().questions;
question: any, const quiz = getCurrentQuiz();
quiz: any, if (!quiz) throw new Error("Quiz is null");
) => {
if (question.type !== null) { if (question.type !== null) {
if (question.content.rule.parentId === "root") { if (question.content.rule.parentId === "root") {
//удалить из стора root и очистить rule всем вопросам //удалить из стора root и очистить rule всем вопросам

@ -1,17 +1,11 @@
import { import { AnyTypedQuizQuestion } from "@model/questionTypes/shared";
AnyTypedQuizQuestion, import { useQuestionsStore } from "@root/questions/store";
UntypedQuizQuestion,
} from "@model/questionTypes/shared";
import { Quiz } from "@model/quiz/quiz";
import { updateSomeWorkBackend } from "@root/uiTools/actions"; import { updateSomeWorkBackend } from "@root/uiTools/actions";
import { DeleteFunction } from "@utils/deleteFunc"; import { DeleteFunction } from "@utils/deleteFunc";
type allQuestionsTypes = AnyTypedQuizQuestion | UntypedQuizQuestion; export const deleteTimeoutedQuestions = async () => {
const questions = useQuestionsStore.getState().questions;
export const deleteTimeoutedQuestions = async (
questions: allQuestionsTypes[],
quiz: Quiz | undefined,
) => {
const questionsForDeletion = questions.filter( const questionsForDeletion = questions.filter(
({ type, deleted }) => type && type !== "result" && deleted, ({ type, deleted }) => type && type !== "result" && deleted,
) as AnyTypedQuizQuestion[]; ) as AnyTypedQuizQuestion[];
@ -19,9 +13,7 @@ export const deleteTimeoutedQuestions = async (
updateSomeWorkBackend(true); updateSomeWorkBackend(true);
await Promise.allSettled( await Promise.allSettled(
questionsForDeletion.map((question) => questionsForDeletion.map((question) => DeleteFunction(question)),
DeleteFunction(questions, question, quiz),
),
); );
updateSomeWorkBackend(false); updateSomeWorkBackend(false);

@ -1,14 +1,15 @@
export class RequestQueue<T = unknown> { export class RequestQueue<IdType, T = unknown> {
private pendingPromise = false; private pendingPromise = false;
private items: Array<{ private items: Array<{
id: IdType;
action: () => Promise<T>; action: () => Promise<T>;
resolve: (value: T) => void; resolve: (value: T) => void;
reject: (reason?: any) => void; reject: (reason?: any) => void;
}> = []; }> = [];
enqueue(action: () => Promise<T>) { enqueue(id: IdType, action: () => Promise<T>) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.items.push({ action, resolve, reject }); this.items.push({ action, resolve, reject, id });
this.dequeue(); this.dequeue();
}); });
} }
@ -19,6 +20,9 @@ export class RequestQueue<T = unknown> {
const item = this.items.shift(); const item = this.items.shift();
if (!item) return; if (!item) return;
// remove tasks with same id since they are outdated
this.items = this.items.filter((i) => i.id !== item.id);
try { try {
this.pendingPromise = true; this.pendingPromise = true;
const payload = await item.action(); const payload = await item.action();