Merge branch 'view-fixes' into backend-integration

This commit is contained in:
Nastya 2023-12-11 19:12:31 +03:00
commit 6482598841
24 changed files with 755 additions and 413 deletions

@ -18,6 +18,7 @@ export const QUIZ_QUESTION_BASE: Omit<QuizQuestionBase, "id" | "backendId"> = {
video: "", video: "",
}, },
rule: { rule: {
children: [],
main: [] as QuestionBranchingRuleMain[], main: [] as QuestionBranchingRuleMain[],
parentId: "", parentId: "",
default: "" default: ""

@ -23,6 +23,7 @@ export interface QuestionBranchingRuleMain {
} }
export interface QuestionBranchingRule { export interface QuestionBranchingRule {
children: string[],
//список условий //список условий
main: QuestionBranchingRuleMain[]; main: QuestionBranchingRuleMain[];
parentId: string | null | "root"; parentId: string | null | "root";

@ -1,12 +1,13 @@
import { useEffect, useLayoutEffect, useRef, useState } from "react"; import { useEffect, useLayoutEffect, useRef, useState } from "react";
import Cytoscape from "cytoscape"; import Cytoscape from "cytoscape";
import { Button } from "@mui/material";
import CytoscapeComponent from "react-cytoscapejs"; import CytoscapeComponent from "react-cytoscapejs";
import popper from "cytoscape-popper"; import popper from "cytoscape-popper";
import { useCurrentQuiz } from "@root/quizes/hooks"; import { useCurrentQuiz } from "@root/quizes/hooks";
import { updateRootContentId } from "@root/quizes/actions" import { updateRootContentId } from "@root/quizes/actions"
import { AnyTypedQuizQuestion } from "@model/questionTypes/shared" import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"
import { useQuestionsStore } from "@root/questions/store"; import { useQuestionsStore } from "@root/questions/store";
import { cleardragQuestionContentId, updateQuestion, updateOpenedModalSettingsId, getQuestionByContentId, clearRuleForAll } from "@root/questions/actions"; import { deleteQuestion, cleardragQuestionContentId, updateQuestion, updateOpenedModalSettingsId, getQuestionByContentId, clearRuleForAll } from "@root/questions/actions";
import { withErrorBoundary } from "react-error-boundary"; import { withErrorBoundary } from "react-error-boundary";
import { storeToNodes } from "./helper"; import { storeToNodes } from "./helper";
@ -111,13 +112,13 @@ interface Props {
} }
function CsComponent ({ function CsComponent({
modalQuestionParentContentId, modalQuestionParentContentId,
modalQuestionTargetContentId, modalQuestionTargetContentId,
setOpenedModalQuestions, setOpenedModalQuestions,
setModalQuestionParentContentId, setModalQuestionParentContentId,
setModalQuestionTargetContentId setModalQuestionTargetContentId
}: Props) { }: Props) {
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
const { dragQuestionContentId, desireToOpenABranchingModal } = useQuestionsStore() const { dragQuestionContentId, desireToOpenABranchingModal } = useQuestionsStore()
@ -186,18 +187,32 @@ function CsComponent ({
} }
const clearDataAfterAddNode = ({ parentNodeContentId, targetQuestion, parentNodeChildren }: { parentNodeContentId: string, targetQuestion: AnyTypedQuizQuestion, parentNodeChildren: number }) => { const clearDataAfterAddNode = ({ parentNodeContentId, targetQuestion, parentNodeChildren }: { parentNodeContentId: string, targetQuestion: AnyTypedQuizQuestion, parentNodeChildren: number }) => {
const parentQuestion = { ...getQuestionByContentId(parentNodeContentId) } as AnyTypedQuizQuestion
//смотрим не добавлен ли родителю result. Если да - убираем его. Веточкам result не нужен
trashQuestions.forEach((targetQuestion) => {
if (targetQuestion.type === "result" && targetQuestion.content.rule.parentId === parentQuestion.content.id) {
deleteQuestion(targetQuestion.id);
}
})
//предупреждаем добавленный вопрос о том, кто его родитель //предупреждаем добавленный вопрос о том, кто его родитель
updateQuestion(targetQuestion.content.id, question => { updateQuestion(targetQuestion.content.id, question => {
question.content.rule.parentId = parentNodeContentId question.content.rule.parentId = parentNodeContentId
question.content.rule.main = [] question.content.rule.main = []
}) })
//предупреждаем родителя о новом потомке (если он ещё не знает о нём)
if (!parentQuestion.content.rule.children.includes(targetQuestion.content.id)) updateQuestion(parentNodeContentId, question => {
question.content.rule.children = [...question.content.rule.children, targetQuestion.content.id]
})
//Если детей больше 1 - предупреждаем стор вопросов об открытии модалки ветвления //Если детей больше 1 - предупреждаем стор вопросов об открытии модалки ветвления
if (parentNodeChildren >= 1) { if (parentQuestion.content.rule.children >= 1) {
updateOpenedModalSettingsId(targetQuestion.content.id) updateOpenedModalSettingsId(targetQuestion.content.id)
} else {
//Если ребёнок первый - добавляем его родителю как дефолтный
updateQuestion(parentNodeContentId, question => question.content.rule.default = targetQuestion.content.id)
} }
} }
@ -231,10 +246,18 @@ function CsComponent ({
updateQuestion(targetNodeContentId, question => { updateQuestion(targetNodeContentId, question => {
question.content.rule.parentId = "" question.content.rule.parentId = ""
question.content.rule.main = [] question.content.rule.main = []
question.content.rule.children = []
question.content.rule.default = "" question.content.rule.default = ""
}) })
trashQuestions.forEach(q => {
if (q.type === "result") {
deleteQuestion(q.id);
}
});
clearRuleForAll() clearRuleForAll()
} else { } else {
const parentQuestionContentId = cy?.$('edge[target = "' + targetNodeContentId + '"]')?.toArray()?.[0]?.data()?.source const parentQuestionContentId = cy?.$('edge[target = "' + targetNodeContentId + '"]')?.toArray()?.[0]?.data()?.source
if (targetNodeContentId && parentQuestionContentId) { if (targetNodeContentId && parentQuestionContentId) {
@ -244,7 +267,7 @@ function CsComponent ({
} }
//После всех манипуляций удаляем грани из CS и ноды из бекенда //После всех манипуляций удаляем грани и ноды из CS Чистим rule потомков на беке
deleteNodes.forEach((nodeId) => {//Ноды deleteNodes.forEach((nodeId) => {//Ноды
cy?.remove(cy?.$("#" + nodeId)) cy?.remove(cy?.$("#" + nodeId))
@ -253,7 +276,10 @@ function CsComponent ({
question.content.rule.parentId = "" question.content.rule.parentId = ""
question.content.rule.main = [] question.content.rule.main = []
question.content.rule.default = "" question.content.rule.default = ""
question.content.rule.children = []
}) })
}) })
deleteEdges.forEach((edge: any) => {//Грани deleteEdges.forEach((edge: any) => {//Грани
@ -263,28 +289,41 @@ function CsComponent ({
removeButtons(targetNodeContentId) removeButtons(targetNodeContentId)
cy?.data('changed', true) cy?.data('changed', true)
cy?.layout(lyopts).run() cy?.layout(lyopts).run()
//удаляем result всех потомков
trashQuestions.forEach((qr) => {
if (qr.type === "result") {
if (deleteNodes.includes(qr.content.rule.parentId) || qr.content.rule.parentId === targetQuestion.content.id) {
deleteQuestion(qr.id);
}
}
})
} }
const clearDataAfterRemoveNode = ({ targetQuestionContentId, parentQuestionContentId }: { targetQuestionContentId: string, parentQuestionContentId: string }) => { const clearDataAfterRemoveNode = ({ targetQuestionContentId, parentQuestionContentId }: { targetQuestionContentId: string, parentQuestionContentId: string }) => {
console.log("target ",targetQuestionContentId, "parent ", parentQuestionContentId) console.log("target ", targetQuestionContentId, "parent ", parentQuestionContentId)
updateQuestion(targetQuestionContentId, question => { updateQuestion(targetQuestionContentId, question => {
question.content.rule.parentId = "" question.content.rule.parentId = ""
question.content.rule.children = []
question.content.rule.main = [] question.content.rule.main = []
question.content.rule.default = "" question.content.rule.default = ""
}) })
//чистим rule родителя //чистим rule родителя
const parentQuestion = getQuestionByContentId(parentQuestionContentId) const parentQuestion = getQuestionByContentId(parentQuestionContentId)
console.log(parentQuestion.content.rule.parentId)
const newRule = {} const newRule = {}
const newChildren = [...parentQuestion.content.rule.children]
newChildren.splice(parentQuestion.content.rule.children.indexOf(targetQuestionContentId), 1);
newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== targetQuestionContentId) //удаляем условия перехода от родителя к этому вопросу newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== targetQuestionContentId) //удаляем условия перехода от родителя к этому вопросу
newRule.parentId = parentQuestion.content.rule.parentId newRule.parentId = parentQuestion.content.rule.parentId
newRule.default = questions.filter((q) => { newRule.default = parentQuestion.content.rule.default === targetQuestionContentId ? "" : parentQuestion.content.rule.default
return q.content.rule.parentId === parentQuestionContentId && q.content.id !== targetQuestionContentId newRule.children = newChildren
})[0]?.content.id || ""
//Если этот вопрос был дефолтным у родителя - чистим дефолт
//Смотрим можем ли мы заменить id на один из main
updateQuestion(parentQuestionContentId, (PQ) => { updateQuestion(parentQuestionContentId, (PQ) => {
PQ.content.rule = newRule PQ.content.rule = newRule
@ -335,7 +374,7 @@ function CsComponent ({
positions: (e) => { positions: (e) => {
if (!e.cy().data('changed')) { if (!e.cy().data('changed')) {
return e.data('oldPos') return e.data('oldPos')
} }
const id = e.id() const id = e.id()
const incomming = e.cy().edges(`[target="${id}"]`) const incomming = e.cy().edges(`[target="${id}"]`)
const layer = 0 const layer = 0
@ -366,7 +405,7 @@ function CsComponent ({
while (queue.length) { while (queue.length) {
const task = queue.pop() const task = queue.pop()
if (task.children.length === 0) { if (task.children.length === 0) {
task.parent.data('subtreeWidth', task.parent.height()+50) task.parent.data('subtreeWidth', task.parent.height() + 50)
continue continue
} }
const unprocessed = task?.children.filter(e => { const unprocessed = task?.children.filter(e => {
@ -385,19 +424,19 @@ function CsComponent ({
const pos = { x: 0, y: 0 } const pos = { x: 0, y: 0 }
e.data('oldPos', pos) e.data('oldPos', pos)
queue.push({task: children, parent: e}) queue.push({ task: children, parent: e })
while (queue.length) { while (queue.length) {
const task = queue.pop() const task = queue.pop()
const oldPos = task.parent.data('oldPos') const oldPos = task.parent.data('oldPos')
let yoffset = oldPos.y - task.parent.data('subtreeWidth') / 2 let yoffset = oldPos.y - task.parent.data('subtreeWidth') / 2
task.task.forEach(n => { task.task.forEach(n => {
const width = n.data('subtreeWidth') const width = n.data('subtreeWidth')
console.log('ORORORORO',n.data(), yoffset, width, oldPos, task.parent.data('subtreeWidth')) console.log('ORORORORO', n.data(), yoffset, width, oldPos, task.parent.data('subtreeWidth'))
n.data('oldPos',{x: 250 * n.data('layer'),y: yoffset + width/2}) n.data('oldPos', { x: 250 * n.data('layer'), y: yoffset + width / 2 })
yoffset+=width yoffset += width
queue.push({task: n.cy().edges(`[source="${n.id()}"]`).targets(), parent: n}) queue.push({ task: n.cy().edges(`[source="${n.id()}"]`).targets(), parent: n })
}) })
} }
e.cy().data('changed', false) e.cy().data('changed', false)
@ -407,7 +446,7 @@ function CsComponent ({
const opos = e.data('oldPos') const opos = e.data('oldPos')
if (opos) { if (opos) {
return opos return opos
} }
} }
}, // map of (node id) => (position obj); or function(node){ return somPos; } }, // map of (node id) => (position obj); or function(node){ return somPos; }
zoom: undefined, // the zoom level to set (prob want fit = false if set) zoom: undefined, // the zoom level to set (prob want fit = false if set)
@ -426,7 +465,7 @@ function CsComponent ({
console.log('KEKEKE') console.log('KEKEKE')
document.querySelector("#root")?.addEventListener("mouseup", cleardragQuestionContentId); document.querySelector("#root")?.addEventListener("mouseup", cleardragQuestionContentId);
const cy = cyRef.current; const cy = cyRef.current;
const eles = cy?.add(storeToNodes(questions.filter((question:AnyTypedQuizQuestion) => (question.type !== "result" && question.type !== null)))) const eles = cy?.add(storeToNodes(questions.filter((question: AnyTypedQuizQuestion) => (question.type !== "result" && question.type !== null))))
cy.data('changed', true) cy.data('changed', true)
// cy.data('changed', true) // cy.data('changed', true)
const elecs = eles.layout(lyopts).run() const elecs = eles.layout(lyopts).run()
@ -735,6 +774,23 @@ let pressed = false
return ( return (
<> <>
<Button
sx={{
mb: "20px",
height: "27px",
color: "#7E2AEA",
textDecoration: "underline",
fontSize: "16px",
}}
variant="text"
onClick={() => {
//код сюда
}}
>
Выровнять
</Button>
<CytoscapeComponent <CytoscapeComponent
wheelSensitivity={0.1} wheelSensitivity={0.1}
elements={[]} elements={[]}
@ -745,7 +801,7 @@ let pressed = false
cy={(cy) => { cy={(cy) => {
cyRef.current = cy; cyRef.current = cy;
}} }}
autoungrabify={true} autoungrabify={true}
/> />
<button onClick={() => { <button onClick={() => {
console.log("NODES____________________________") console.log("NODES____________________________")
@ -761,15 +817,15 @@ let pressed = false
); );
}; };
function Clear () { function Clear() {
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
updateRootContentId(quiz.id, "") updateRootContentId(quiz.id, "")
clearRuleForAll() clearRuleForAll()
return <></> return <></>
} }
export default withErrorBoundary(CsComponent, { export default withErrorBoundary(CsComponent, {
fallback: <Clear/>, fallback: <Clear />,
onError: (error, info) => { onError: (error, info) => {
enqueueSnackbar("Дерево порвалось") enqueueSnackbar("Дерево порвалось")
console.log(info) console.log(info)

@ -26,7 +26,7 @@ export const BranchingMap = () => {
borderRadius: "12px", borderRadius: "12px",
boxShadow: "0px 8px 24px rgba(210, 208, 225, 0.4)", boxShadow: "0px 8px 24px rgba(210, 208, 225, 0.4)",
marginBottom: "40px", marginBottom: "40px",
height: "521px", height: "568px",
border: dragQuestionContentId === null ? "none" : "#7e2aea 2px dashed" border: dragQuestionContentId === null ? "none" : "#7e2aea 2px dashed"
}} }}
> >

@ -46,20 +46,6 @@ export default function ButtonsOptions({
updateOpenedModalSettingsId(question.id) updateOpenedModalSettingsId(question.id)
}; };
const handleClickBranching = (_, value) => {
const parentId = question.content.rule.parentId
if (parentId.length === 0 ){
return enqueueSnackbar("Вопрос не учавствует в ветвлении")
}
if (parentId === "root") {
return enqueueSnackbar("У корня нет условий ветвления")
}
if (parentId.length !== 0) {
// updateOpenBranchingPanel(value)
openedModal()
}
}
const buttonSetting: { const buttonSetting: {
icon: JSX.Element; icon: JSX.Element;
@ -300,7 +286,59 @@ export default function ButtonsOptions({
// deleteTimeoutId: newTimeoutId, // deleteTimeoutId: newTimeoutId,
// }); // });
deleteQuestion(question.id, quiz.id); if (question.type !== null) {
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[]
//записываем потомков , а их результаты удаляем
const getChildren = (parentQuestion: AnyTypedQuizQuestion) => {
questions.forEach((targetQuestion) => {
if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его
if (targetQuestion.type === "result") {
deleteQuestion(targetQuestion.id);
} else {
if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id)
getChildren(targetQuestion) //и ищем его потомков
}
}
})
}
getChildren(question)
//чистим потомков от инфы ветвления
clearQuestions.forEach((id) => {
updateQuestion(id, question => {
question.content.rule.parentId = ""
question.content.rule.main = []
question.content.rule.default = ""
})
})
//чистим rule родителя
const parentQuestion = getQuestionByContentId(question.content.rule.parentId)
const newRule = {}
newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== question.content.id) //удаляем условия перехода от родителя к этому вопросу
newRule.parentId = parentQuestion.content.rule.parentId
newRule.default = parentQuestion.content.rule.parentId === question.content.id ? "" : parentQuestion.content.rule.parentId
newRule.children = [...parentQuestion.content.rule.children].splice(parentQuestion.content.rule.children.indexOf(question.content.id), 1);
updateQuestion(question.content.rule.parentId, (PQ) => {
PQ.content.rule = newRule
})
deleteQuestion(question.id)
}
deleteQuestion(question.id)
}
}} }}
data-cy="delete-question" data-cy="delete-question"
> >

@ -10,7 +10,7 @@ import {
useMediaQuery, useMediaQuery,
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import { copyQuestion, deleteQuestion, updateQuestion } from "@root/questions/actions"; import { copyQuestion, deleteQuestion, updateQuestion, clearRuleForAll, getQuestionByContentId } from "@root/questions/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 { useEffect, useState } from "react"; import { useEffect, useState } from "react";
@ -27,6 +27,7 @@ import { updateOpenBranchingPanel, updateDesireToOpenABranchingModal } from "@ro
import { useQuestionsStore } from "@root/questions/store"; import { useQuestionsStore } from "@root/questions/store";
import { enqueueSnackbar } from "notistack"; import { enqueueSnackbar } from "notistack";
import { useCurrentQuiz } from "@root/quizes/hooks"; import { useCurrentQuiz } from "@root/quizes/hooks";
import { updateRootContentId } from "@root/quizes/actions";
interface Props { interface Props {
@ -46,7 +47,7 @@ export default function ButtonsOptionsAndPict({
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down(790)); const isMobile = useMediaQuery(theme.breakpoints.down(790));
const isIconMobile = useMediaQuery(theme.breakpoints.down(1050)); const isIconMobile = useMediaQuery(theme.breakpoints.down(1050));
const { openBranchingPanel } = useQuestionsStore.getState() const { questions } = useQuestionsStore.getState()
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
useEffect(() => { useEffect(() => {
@ -323,7 +324,59 @@ export default function ButtonsOptionsAndPict({
// deleteTimeoutId: newTimeoutId, // deleteTimeoutId: newTimeoutId,
// }); // });
deleteQuestion(question.id, quiz?.id); if (question.type !== null) {
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[]
//записываем потомков , а их результаты удаляем
const getChildren = (parentQuestion: AnyTypedQuizQuestion) => {
questions.forEach((targetQuestion) => {
if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его
if (targetQuestion.type === "result") {
deleteQuestion(targetQuestion.id);
} else {
if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id)
getChildren(targetQuestion) //и ищем его потомков
}
}
})
}
getChildren(question)
//чистим потомков от инфы ветвления
clearQuestions.forEach((id) => {
updateQuestion(id, question => {
question.content.rule.parentId = ""
question.content.rule.main = []
question.content.rule.default = ""
})
})
//чистим rule родителя
const parentQuestion = getQuestionByContentId(question.content.rule.parentId)
const newRule = {}
newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== question.content.id) //удаляем условия перехода от родителя к этому вопросу
newRule.parentId = parentQuestion.content.rule.parentId
newRule.default = parentQuestion.content.rule.parentId === question.content.id ? "" : parentQuestion.content.rule.parentId
newRule.children = [...parentQuestion.content.rule.children].splice(parentQuestion.content.rule.children.indexOf(question.content.id), 1);
updateQuestion(question.content.rule.parentId, (PQ) => {
PQ.content.rule = newRule
})
deleteQuestion(question.id)
}
deleteQuestion(question.id)
}
}} }}
data-cy="delete-question" data-cy="delete-question"
> >

@ -29,7 +29,8 @@ import {
useMediaQuery, useMediaQuery,
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import { copyQuestion, createUntypedQuestion, deleteQuestion, toggleExpandQuestion, updateQuestion, updateUntypedQuestion } from "@root/questions/actions"; import { copyQuestion, createUntypedQuestion, deleteQuestion, clearRuleForAll, toggleExpandQuestion, updateQuestion, updateUntypedQuestion, getQuestionByContentId } from "@root/questions/actions";
import { updateRootContentId } from "@root/quizes/actions";
import { useRef, useState } from "react"; import { 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";
@ -40,6 +41,7 @@ import { ChooseAnswerModal } from "./ChooseAnswerModal";
import TypeQuestions from "../TypeQuestions"; import TypeQuestions from "../TypeQuestions";
import { QuestionType } from "@model/question/question"; import { QuestionType } from "@model/question/question";
import { useCurrentQuiz } from "@root/quizes/hooks"; import { useCurrentQuiz } from "@root/quizes/hooks";
import { useQuestionsStore } from "@root/questions/store";
interface Props { interface Props {
question: AnyTypedQuizQuestion | UntypedQuizQuestion; question: AnyTypedQuizQuestion | UntypedQuizQuestion;
@ -49,6 +51,7 @@ interface Props {
} }
export default function QuestionsPageCard({ question, draggableProps, isDragging, index }: Props) { export default function QuestionsPageCard({ question, draggableProps, isDragging, index }: Props) {
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);
const theme = useTheme(); const theme = useTheme();
@ -254,8 +257,63 @@ export default function QuestionsPageCard({ question, draggableProps, isDragging
// ...question, // ...question,
// deleteTimeoutId: newTimeoutId, // deleteTimeoutId: newTimeoutId,
// }); // });
console.log(question.type)
if (question.type !== null) {
if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам
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[]
deleteQuestion(question.id, quiz.id); //записываем потомков , а их результаты удаляем
const getChildren = (parentQuestion: AnyTypedQuizQuestion) => {
questions.forEach((targetQuestion) => {
if (targetQuestion.content.rule.parentId === parentQuestion.content.id) {//если у вопроса совпал родитель с родителем => он потомок, в кучу его
if (targetQuestion.type === "result") {
deleteQuestion(targetQuestion.id);
} else {
if (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id)
getChildren(targetQuestion) //и ищем его потомков
}
}
})
}
getChildren(question)
//чистим потомков от инфы ветвления
clearQuestions.forEach((id) => {
updateQuestion(id, question => {
question.content.rule.parentId = ""
question.content.rule.main = []
question.content.rule.default = ""
})
})
//чистим rule родителя
const parentQuestion = getQuestionByContentId(question.content.rule.parentId)
const newRule = {}
newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== question.content.id) //удаляем условия перехода от родителя к этому вопросу
newRule.parentId = parentQuestion.content.rule.parentId
newRule.default = parentQuestion.content.rule.parentId === question.content.id ? "" : parentQuestion.content.rule.parentId
newRule.children = [...parentQuestion.content.rule.children].splice(parentQuestion.content.rule.children.indexOf(question.content.id), 1);
updateQuestion(question.content.rule.parentId, (PQ) => {
PQ.content.rule = newRule
})
deleteQuestion(question.id)
}
deleteQuestion(question.id)
} else {
console.log("удаляю безтипогово")
deleteQuestion(question.id)
}
}} }}
data-cy="delete-question" data-cy="delete-question"
> >

@ -49,7 +49,7 @@ export default function SettingSlider({ question }: SettingSliderProps) {
height: isMobile ? "100%" : "auto", height: isMobile ? "100%" : "auto",
alignItems: isMobile ? "flex-start" : "center", alignItems: isMobile ? "flex-start" : "center",
}} }}
label={"Выбор диапозона (два ползунка)"} label={"Выбор диапазона (два ползунка)"}
checked={question.content.chooseRange} checked={question.content.chooseRange}
handleChange={({ target }) => { handleChange={({ target }) => {
updateQuestion(question.id, question => { updateQuestion(question.id, question => {

@ -16,17 +16,18 @@ export const FirstEntry = () => {
const create = () => { const create = () => {
if (quiz?.config.haveRoot) { if (quiz?.config.haveRoot) {
if (questions.length === 0) { console.log("createFrontResult")
enqueueSnackbar("У вас не добавлено ни одного вопроса")
return
}
questions questions
.filter((question:AnyTypedQuizQuestion) => question.type !== null && question.content.rule.parentId.length !== 0 && question.content.rule.default.length === 0) .filter((question:AnyTypedQuizQuestion) => {
console.log(question)
return question.type !== null && question.content.rule.parentId.length !== 0 && question.content.rule.children.length === 0
})
.forEach(question => { .forEach(question => {
createFrontResult(quiz.id, question.content.id) createFrontResult(quiz.id, question.content.id)
}) })
} else { } else {
createFrontResult(quiz.id) console.log("createFrontResult")
createFrontResult(quiz.id, "line")
} }
} }

@ -2,31 +2,46 @@ import IconPlus from "@icons/IconPlus";
import Info from "@icons/Info"; import Info from "@icons/Info";
import Plus from "@icons/Plus"; import Plus from "@icons/Plus";
import ArrowLeft from "@icons/questionsPage/arrowLeft"; import ArrowLeft from "@icons/questionsPage/arrowLeft";
import { Box, Button, Typography, Paper, FormControl, TextField } from "@mui/material"; import { Box, Button, Typography, Paper, Modal, TextField } from "@mui/material";
import { incrementCurrentStep } from "@root/quizes/actions"; import { incrementCurrentStep } from "@root/quizes/actions";
import CustomWrapper from "@ui_kit/CustomWrapper"; import CustomWrapper from "@ui_kit/CustomWrapper";
import { DescriptionForm } from "./DescriptionForm/DescriptionForm"; import { DescriptionForm } from "./DescriptionForm/DescriptionForm";
import { ResultListForm } from "./ResultListForm"; import { ResultListForm } from "./ResultListForm";
import { SettingForm } from "./SettingForm"; import { SettingForm } from "./SettingForm";
import { useState } from "react"; import { useEffect, useRef, useState } from "react";
import { WhenCard } from "./cards/WhenCard"; import { WhenCard } from "./cards/WhenCard";
import { ResultCard } from "./cards/ResultCard"; import { ResultCard, checkEmptyData } from "./cards/ResultCard";
import { EmailSettingsCard } from "./cards/EmailSettingsCard"; import { EmailSettingsCard } from "./cards/EmailSettingsCard";
import { useCurrentQuiz } from "@root/quizes/hooks" import { useCurrentQuiz } from "@root/quizes/hooks"
import { useQuestionsStore } from "@root/questions/store"; import { useQuestionsStore } from "@root/questions/store";
import { createFrontResult, deleteQuestion } from "@root/questions/actions";
import { QuizQuestionResult } from "@model/questionTypes/result"; import { QuizQuestionResult } from "@model/questionTypes/result";
export const ResultSettings = () => { export const ResultSettings = () => {
const { questions } = useQuestionsStore()
const quiz = useCurrentQuiz() const quiz = useCurrentQuiz()
const results = useQuestionsStore().questions.filter(q => q.type === "result") const results = useQuestionsStore().questions.filter((q): q is QuizQuestionResult => q.type === "result")
console.log("опросник ", quiz) console.log("опросник ", quiz)
const [quizExpand, setQuizExpand] = useState(true) const [quizExpand, setQuizExpand] = useState(true)
const [resultContract, setResultContract] = useState(true) const [resultContract, setResultContract] = useState(true)
const [readyLeave, setReadyLeave] = useState(true) const isReadyToLeaveRef = useRef(true);
const setAlertLeave = () => { useEffect(function calcIsReadyToLeave(){
setReadyLeave(false) let isReadyToLeave = true;
} results.forEach((result) => {
if (checkEmptyData({ resultData: result })) {
isReadyToLeave = false;
}
});
console.log(`setting isReadyToLeaveRef to ${isReadyToLeave}`);
isReadyToLeaveRef.current = isReadyToLeave;
}, [results])
useEffect(() => {
return () => {
if (isReadyToLeaveRef.current === false) alert("Пожалуйста, проверьте, что вы заполнили все результаты");
};
}, []);
return ( return (
<Box sx={{ maxWidth: "796px" }}> <Box sx={{ maxWidth: "796px" }}>
@ -92,10 +107,17 @@ export const ResultSettings = () => {
</Button> </Button>
</Box> </Box>
{ {
results.map((resultQuestion) => <ResultCard resultContract={resultContract} resultData={resultQuestion} key={resultQuestion.id} setAlertLeave={setAlertLeave}/>) results.map((resultQuestion) => <ResultCard resultContract={resultContract} resultData={resultQuestion} key={resultQuestion.id} />)
} }
<Modal
open={false}
// onClose={handleClose}
aria-labelledby="modal-modal-title"
aria-describedby="modal-modal-description"
>
<></>
</Modal>
</Box> </Box>
); );
}; };

@ -1,18 +1,12 @@
import * as React from "react"; import * as React from "react";
import { updateQuiz } from "@root/quizes/actions"
import { getQuestionByContentId, updateQuestion, uploadQuestionImage } from "@root/questions/actions" import { getQuestionByContentId, updateQuestion, uploadQuestionImage } from "@root/questions/actions"
import { useCurrentQuiz } from "@root/quizes/hooks" import { useCurrentQuiz } from "@root/quizes/hooks"
import { SwitchSetting } from "../SwichResult";
import CustomTextField from "@ui_kit/CustomTextField"; import CustomTextField from "@ui_kit/CustomTextField";
import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal"; import { CropModal, useCropModalState } from "@ui_kit/Modal/CropModal";
import { useDebouncedCallback } from "use-debounce";
import type { QuizQuestionPage } from "../../../model/questionTypes/page";
import { UploadImageModal } from "../../Questions/UploadImage/UploadImageModal"; import { UploadImageModal } from "../../Questions/UploadImage/UploadImageModal";
import { UploadVideoModal } from "../../Questions/UploadVideoModal";
import { useDisclosure } from "../../../utils/useDisclosure"; import { useDisclosure } from "../../../utils/useDisclosure";
import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton"; import AddOrEditImageButton from "@ui_kit/AddOrEditImageButton";
@ -32,21 +26,18 @@ import MiniButtonSetting from "@ui_kit/MiniButtonSetting";
import ExpandLessIconBG from "@icons/ExpandLessIconBG"; import ExpandLessIconBG from "@icons/ExpandLessIconBG";
import ExpandLessIcon from "@mui/icons-material/ExpandLess"; import ExpandLessIcon from "@mui/icons-material/ExpandLess";
import { OneIcon } from "@icons/questionsPage/OneIcon";
import { DeleteIcon } from "@icons/questionsPage/deleteIcon";
import Trash from "@icons/trash"; import Trash from "@icons/trash";
import Info from "@icons/Info"; import Info from "@icons/Info";
import ImageAndVideoButtons from "../DescriptionForm/ImageAndVideoButtons";
import SettingIcon from "@icons/questionsPage/settingIcon"; import SettingIcon from "@icons/questionsPage/settingIcon";
import { QuizQuestionResult } from "@model/questionTypes/result"; import { QuizQuestionResult } from "@model/questionTypes/result";
import { MutableRefObject } from "react";
interface Props { interface Props {
resultContract: boolean; resultContract: boolean;
resultData: QuizQuestionResult; resultData: QuizQuestionResult;
setAlertLeave: () => void;
} }
const checkEmptyData = ({ resultData }: { resultData: QuizQuestionResult }) => { export const checkEmptyData = ({ resultData }: { resultData: QuizQuestionResult }) => {
let check = true let check = true
if ( if (
resultData.title.length > 0 || resultData.title.length > 0 ||
@ -77,11 +68,6 @@ const InfoView = ({ resultData }: { resultData: QuizQuestionResult }) => {
const open = Boolean(anchorEl); const open = Boolean(anchorEl);
const id = open ? 'simple-popover' : undefined; const id = open ? 'simple-popover' : undefined;
return ( return (
<> <>
<Info <Info
@ -115,7 +101,11 @@ const InfoView = ({ resultData }: { resultData: QuizQuestionResult }) => {
}} }}
> >
<Typography> <Typography>
Заголовок вопроса, после которого появится результат: "{question?.title || "нет заголовка"}" {resultData?.content.rule.parentId === "line" ? "Единый результат в конце прохождения опросника без ветвления"
:
`Заголовок вопроса, после которого появится результат: "${question?.title || "нет заголовка"}"`
}
</Typography> </Typography>
{checkEmpty && {checkEmpty &&
<Typography color="red"> <Typography color="red">
@ -129,13 +119,9 @@ const InfoView = ({ resultData }: { resultData: QuizQuestionResult }) => {
) )
} }
export const ResultCard = ({ resultContract, resultData, setAlertLeave }: Props) => { export const ResultCard = ({ resultContract, resultData }: Props) => {
console.log("resultData", resultData) console.log("resultData", resultData)
React.useEffect(() => {
if (checkEmptyData({resultData})) setAlertLeave()
}, [resultData])
const quizQid = useCurrentQuiz()?.qid; const quizQid = useCurrentQuiz()?.qid;
const theme = useTheme(); const theme = useTheme();
@ -144,7 +130,7 @@ export const ResultCard = ({ resultContract, resultData, setAlertLeave }: Props)
const [expand, setExpand] = React.useState(true) const [expand, setExpand] = React.useState(true)
const [resultCardSettings, setResultCardSettings] = React.useState(false) const [resultCardSettings, setResultCardSettings] = React.useState(false)
const [buttonPlus, setButtonPlus] = React.useState(false) const [buttonPlus, setButtonPlus] = React.useState(true)
React.useEffect(() => { React.useEffect(() => {
setExpand(true) setExpand(true)
@ -621,4 +607,4 @@ export const ResultCard = ({ resultContract, resultData, setAlertLeave }: Props)
} }
</Paper > </Paper >
) )
} }

@ -20,8 +20,6 @@ import { useCurrentQuiz } from "@root/quizes/hooks";
import { getQuestionByContentId } from "@root/questions/actions"; import { getQuestionByContentId } from "@root/questions/actions";
type QuestionProps = { type QuestionProps = {
stepNumber: number;
setStepNumber: (step: number) => void;
questions: AnyTypedQuizQuestion[]; questions: AnyTypedQuizQuestion[];
}; };

@ -11,7 +11,9 @@ import type { AnyTypedQuizQuestion } from "../../model/questionTypes/shared";
export const ViewPage = () => { export const ViewPage = () => {
const quiz = useCurrentQuiz(); const quiz = useCurrentQuiz();
const { questions } = useQuestions(); const { questions } = useQuestions();
const [visualStartPage, setVisualStartPage] = useState<boolean>(!quiz?.config.noStartPage); const [visualStartPage, setVisualStartPage] = useState<boolean>(
!quiz?.config.noStartPage
);
useEffect(() => { useEffect(() => {
const link = document.querySelector('link[rel="icon"]'); const link = document.querySelector('link[rel="icon"]');
@ -21,9 +23,9 @@ export const ViewPage = () => {
} }
}, [quiz?.config.startpage.favIcon]); }, [quiz?.config.startpage.favIcon]);
const filteredQuestions = questions.filter( const filteredQuestions = (
({ type }) => type questions.filter(({ type }) => type) as AnyTypedQuizQuestion[]
) as AnyTypedQuizQuestion[]; ).sort((previousItem, item) => previousItem.page - item.page);
return ( return (
<Box> <Box>
@ -33,9 +35,7 @@ export const ViewPage = () => {
showNextButton={!!filteredQuestions.length} showNextButton={!!filteredQuestions.length}
/> />
) : ( ) : (
<Question <Question questions={filteredQuestions} />
questions={filteredQuestions}
/>
)} )}
</Box> </Box>
); );

@ -1,11 +1,12 @@
import DatePicker from "react-datepicker"; import { DatePicker } from "@mui/x-date-pickers";
import { Box, Typography } from "@mui/material"; import { Box, Typography } from "@mui/material";
import { useQuizViewStore, updateAnswer } from "@root/quizView"; import { useQuizViewStore, updateAnswer } from "@root/quizView";
import "react-datepicker/dist/react-datepicker.css"; // import "react-datepicker/dist/react-datepicker.css";
import type { QuizQuestionDate } from "../../../model/questionTypes/date"; import type { QuizQuestionDate } from "../../../model/questionTypes/date";
import CalendarIcon from "@icons/CalendarIcon";
type DateProps = { type DateProps = {
currentQuestion: QuizQuestionDate; currentQuestion: QuizQuestionDate;
@ -31,6 +32,9 @@ export const Date = ({ currentQuestion }: DateProps) => {
}} }}
> >
<DatePicker <DatePicker
slots={{
openPickerIcon: () => <CalendarIcon />,
}}
selected={ selected={
answer answer
? new window.Date(`${month}.${day}.${year}`) ? new window.Date(`${month}.${day}.${year}`)
@ -48,6 +52,30 @@ export const Date = ({ currentQuestion }: DateProps) => {
) )
) )
} }
slotProps={{
openPickerButton: {
sx: {
p: 0,
},
"data-cy": "open-datepicker",
},
}}
sx={{
"& .MuiInputBase-root": {
backgroundColor: "#F2F3F7",
borderRadius: "10px",
maxWidth: "250px",
pr: "22px",
"& input": {
py: "11px",
pl: "20px",
lineHeight: "19px",
},
"& fieldset": {
borderColor: "#9A9AAF",
},
},
}}
/> />
</Box> </Box>
</Box> </Box>

@ -1,14 +1,14 @@
import { useEffect } from "react";
import { import {
Box, Box,
Typography, Typography,
RadioGroup, RadioGroup,
FormControlLabel, FormControlLabel,
Radio, Radio,
useTheme, FormControl, useTheme,
FormControl,
} from "@mui/material"; } from "@mui/material";
import { useQuizViewStore, updateAnswer } from "@root/quizView"; import { useQuizViewStore, updateAnswer, deleteAnswer } from "@root/quizView";
import RadioCheck from "@ui_kit/RadioCheck"; import RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon"; import RadioIcon from "@ui_kit/RadioIcon";
@ -22,20 +22,19 @@ type EmojiProps = {
export const Emoji = ({ currentQuestion }: EmojiProps) => { export const Emoji = ({ currentQuestion }: EmojiProps) => {
const { answers } = useQuizViewStore(); const { answers } = useQuizViewStore();
const theme = useTheme(); const theme = useTheme();
const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.content.id) ?? {}; const { answer } =
answers.find(
useEffect(() => { ({ questionId }) => questionId === currentQuestion.content.id
if (!answer) { ) ?? {};
updateAnswer(currentQuestion.content.id, currentQuestion.content.variants[0].id);
}
}, []);
return ( return (
<Box> <Box>
<Typography variant="h5">{currentQuestion.title}</Typography> <Typography variant="h5">{currentQuestion.title}</Typography>
<RadioGroup <RadioGroup
name={currentQuestion.id} name={currentQuestion.id}
value={currentQuestion.content.variants.findIndex(({ id }) => answer === id)} value={currentQuestion.content.variants.findIndex(
({ id }) => answer === id
)}
onChange={({ target }) => onChange={({ target }) =>
updateAnswer( updateAnswer(
currentQuestion.content.id, currentQuestion.content.id,
@ -51,50 +50,73 @@ export const Emoji = ({ currentQuestion }: EmojiProps) => {
}} }}
> >
<Box sx={{ display: "flex", width: "100%", gap: "42px" }}> <Box sx={{ display: "flex", width: "100%", gap: "42px" }}>
{currentQuestion.content.variants.map( {currentQuestion.content.variants.map((variant, index) => (
({ id, answer, extendedText }, index) => ( <FormControl
<FormControl key={variant.id}
key={id} sx={{
sx={{ borderRadius: "12px",
borderRadius: "12px", border: `1px solid ${theme.palette.grey2.main}`,
border: `1px solid ${theme.palette.grey2.main}`, overflow: "hidden",
overflow: "hidden", maxWidth: "317px",
maxWidth: "317px", width: "100%",
width: "100%", height: "255px",
height: "255px" }}
}} >
<Box
sx={{
display: "flex",
alignItems: "center",
height: "193px",
background: "#ffffff",
}}
>
<Box
sx={{
width: "100%",
display: "flex",
justifyContent: "center",
}}
> >
<Box {variant.extendedText && (
sx={{ display: "flex", alignItems: "center", height: "193px", background: "#ffffff" }} <Typography fontSize={"100px"}>
> {variant.extendedText}
<Box sx={{ width: "100%", display: "flex", justifyContent: "center" }}> </Typography>
{extendedText && ( )}
<Typography fontSize={"100px"}>{extendedText}</Typography> </Box>
)} </Box>
</Box> <FormControlLabel
key={variant.id}
sx={{
margin: 0,
padding: "15px",
color: "#4D4D4D",
display: "flex",
gap: "10px",
}}
value={index}
onClick={(event) => {
event.preventDefault();
updateAnswer(
currentQuestion.content.id,
currentQuestion.content.variants[index].id
);
if (answer === currentQuestion.content.variants[index].id) {
deleteAnswer(currentQuestion.content.id);
}
}}
control={
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
}
label={
<Box sx={{ display: "flex", gap: "10px" }}>
<Typography>{variant.answer}</Typography>
</Box> </Box>
<FormControlLabel }
key={id} />
sx={{ </FormControl>
margin: 0, ))}
padding: "15px",
color: "#4D4D4D",
display: "flex",
gap: "10px",
}}
value={index}
control={
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
}
label={
<Box sx={{ display: "flex", gap: "10px" }}>
<Typography>{answer}</Typography>
</Box>
}
/>
</FormControl>
)
)}
</Box> </Box>
</RadioGroup> </RadioGroup>
</Box> </Box>

@ -1,15 +1,14 @@
import { useEffect } from "react";
import { import {
Box, Box,
Typography, Typography,
RadioGroup, RadioGroup,
FormControlLabel, FormControlLabel,
Radio, Radio,
useTheme, useTheme,
useMediaQuery, FormControl, useMediaQuery,
} from "@mui/material"; } from "@mui/material";
import { useQuizViewStore, updateAnswer } from "@root/quizView"; import { useQuizViewStore, updateAnswer, deleteAnswer } from "@root/quizView";
import RadioCheck from "@ui_kit/RadioCheck"; import RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon"; import RadioIcon from "@ui_kit/RadioIcon";
@ -22,28 +21,21 @@ type ImagesProps = {
export const Images = ({ currentQuestion }: ImagesProps) => { export const Images = ({ currentQuestion }: ImagesProps) => {
const { answers } = useQuizViewStore(); const { answers } = useQuizViewStore();
const theme = useTheme(); const theme = useTheme();
const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.content.id) ?? {}; const { answer } =
answers.find(
({ questionId }) => questionId === currentQuestion.content.id
) ?? {};
const isTablet = useMediaQuery(theme.breakpoints.down(1000)); const isTablet = useMediaQuery(theme.breakpoints.down(1000));
const isMobile = useMediaQuery(theme.breakpoints.down(500)); const isMobile = useMediaQuery(theme.breakpoints.down(500));
useEffect(() => {
if (!answer) {
updateAnswer(currentQuestion.content.id, currentQuestion.content.variants[0].id);
}
}, []);
return ( return (
<Box> <Box>
<Typography variant="h5">{currentQuestion.title}</Typography> <Typography variant="h5">{currentQuestion.title}</Typography>
<RadioGroup <RadioGroup
name={currentQuestion.id} name={currentQuestion.id}
value={currentQuestion.content.variants.findIndex(({ id }) => answer === id)} value={currentQuestion.content.variants.findIndex(
onChange={({ target }) => ({ id }) => answer === id
updateAnswer( )}
currentQuestion.content.id,
currentQuestion.content.variants[Number(target.value)].id
)
}
sx={{ sx={{
display: "flex", display: "flex",
flexWrap: "wrap", flexWrap: "wrap",
@ -64,50 +56,58 @@ export const Images = ({ currentQuestion }: ImagesProps) => {
width: "100%", width: "100%",
}} }}
> >
{currentQuestion.content.variants.map( {currentQuestion.content.variants.map((variant, index) => (
({ id, answer, extendedText }, index) => ( <Box
<Box key={index}
key={index} sx={{
sx={{ borderRadius: "5px",
borderRadius: "5px", border: `1px solid ${theme.palette.grey2.main}`,
border: `1px solid ${theme.palette.grey2.main}`, }}
}} >
> <Box sx={{ display: "flex", alignItems: "center", gap: "10px" }}>
<Box <Box sx={{ width: "100%", height: "300px" }}>
sx={{ display: "flex", alignItems: "center", gap: "10px" }} {variant.extendedText && (
> <img
<Box sx={{ width: "100%", height: "300px" }}> src={variant.extendedText}
{extendedText && ( alt=""
<img style={{
src={extendedText} display: "block",
alt="" width: "100%",
style={{ height: "100%",
display: "block", objectFit: "cover",
width: "100%", }}
height: "100%", />
objectFit: "cover", )}
}}
/>
)}
</Box>
</Box> </Box>
<FormControlLabel
key={id}
sx={{
display: "block",
textAlign: "center",
color: theme.palette.grey2.main,
marginTop: "10px",
}}
value={index}
control={
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
}
label={answer}
/>
</Box> </Box>
) <FormControlLabel
)} key={variant.id}
sx={{
display: "block",
textAlign: "center",
color: theme.palette.grey2.main,
marginTop: "10px",
}}
onClick={(event) => {
event.preventDefault();
updateAnswer(
currentQuestion.content.id,
currentQuestion.content.variants[index].id
);
if (answer === currentQuestion.content.variants[index].id) {
deleteAnswer(currentQuestion.content.id);
}
}}
value={index}
control={
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
}
label={variant.answer}
/>
</Box>
))}
</Box> </Box>
</RadioGroup> </RadioGroup>
</Box> </Box>

@ -1,5 +1,6 @@
import { useEffect } from "react"; import { useState, useEffect } from "react";
import { Box, Typography, Slider, useTheme } from "@mui/material"; import { Box, Typography, Slider, useTheme } from "@mui/material";
import { useDebouncedCallback } from "use-debounce";
import CustomTextField from "@ui_kit/CustomTextField"; import CustomTextField from "@ui_kit/CustomTextField";
@ -12,8 +13,30 @@ type NumberProps = {
}; };
export const Number = ({ currentQuestion }: NumberProps) => { export const Number = ({ currentQuestion }: NumberProps) => {
const [minRange, setMinRange] = useState<string>("0");
const [maxRange, setMaxRange] = useState<string>("100");
const theme = useTheme(); const theme = useTheme();
const { answers } = useQuizViewStore(); const { answers } = useQuizViewStore();
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 { answer } = const { answer } =
answers.find( answers.find(
({ questionId }) => questionId === currentQuestion.content.id ({ questionId }) => questionId === currentQuestion.content.id
@ -23,6 +46,11 @@ export const Number = ({ currentQuestion }: NumberProps) => {
const max = window.Number(currentQuestion.content.range.split("—")[1]); const max = window.Number(currentQuestion.content.range.split("—")[1]);
useEffect(() => { useEffect(() => {
console.log("ans", currentQuestion.content.start);
if (answer) {
setMinRange(answer.split("—")[0]);
setMaxRange(answer.split("—")[1]);
}
if (!answer) { if (!answer) {
updateAnswer( updateAnswer(
currentQuestion.content.id, currentQuestion.content.id,
@ -31,8 +59,11 @@ export const Number = ({ currentQuestion }: NumberProps) => {
: String(currentQuestion.content.start), : String(currentQuestion.content.start),
false false
); );
setMinRange(String(currentQuestion.content.start));
setMaxRange(String(max));
} }
}, [answer]); }, []);
return ( return (
<Box> <Box>
@ -45,14 +76,66 @@ export const Number = ({ currentQuestion }: NumberProps) => {
marginTop: "20px", marginTop: "20px",
}} }}
> >
<Slider
value={
currentQuestion.content.chooseRange
? answer?.split("—").length || 0 > 1
? answer?.split("—").map((item) => window.Number(item))
: [min, min + 1]
: window.Number(answer || 1)
}
min={min}
max={max}
step={currentQuestion.content.step || 1}
sx={{
color: theme.palette.brightPurple.main,
padding: "0",
marginTop: "75px",
"& .MuiSlider-valueLabel":{
background: theme.palette.brightPurple.main,
borderRadius: "8px",
width: "60px",
height: "36px"
},
"& .MuiSlider-valueLabel::before": {
width: "6px",
height: "2px",
transform: "translate(-50%, 50%) rotate(90deg)",
bottom: "-5px"
},
"& .MuiSlider-rail": {
backgroundColor: "#F2F3F7",
border: `1px solid #9A9AAF`,
height: "12px"
},
"& .MuiSlider-thumb": {
border: "3px #f2f3f7 solid",
height: "23px",
width: "23px"
},
"& .MuiSlider-track": {
height: "12px"
}
}}
onChange={(_, value) => {
const range = String(value).replace(",", "—");
updateAnswer(currentQuestion.content.id, range);
}}
onChangeCommitted={(_, value) => {
if (currentQuestion.content.chooseRange) {
const range = value as number[];
setMinRange(String(range[0]));
setMaxRange(String(range[1]));
}
}}
/>
{!currentQuestion.content.chooseRange && ( {!currentQuestion.content.chooseRange && (
<CustomTextField <CustomTextField
placeholder="0" placeholder="0"
value={ value={answer}
currentQuestion.content.chooseRange
? answer?.split("—")[0]
: answer
}
onChange={({ target }) => { onChange={({ target }) => {
updateAnswer( updateAnswer(
currentQuestion.content.id, currentQuestion.content.id,
@ -69,6 +152,7 @@ export const Number = ({ currentQuestion }: NumberProps) => {
}} }}
/> />
)} )}
{currentQuestion.content.chooseRange && ( {currentQuestion.content.chooseRange && (
<Box <Box
sx={{ sx={{
@ -79,40 +163,39 @@ export const Number = ({ currentQuestion }: NumberProps) => {
> >
<CustomTextField <CustomTextField
placeholder="0" placeholder="0"
value={ value={minRange}
currentQuestion.content.chooseRange
? answer?.split("—")[0]
: answer
}
onChange={({ target }) => { onChange={({ target }) => {
updateAnswer( setMinRange(target.value);
currentQuestion.content.id,
window.Number(target.value) > if (window.Number(target.value) >= window.Number(maxRange)) {
window.Number(answer?.split("—")[1]) updateMinRangeDebounced(`${maxRange}${maxRange}`, true);
? `${answer?.split("—")[1]}${answer?.split("—")[1]}`
: window.Number(target.value) < min return;
? `${min}${answer?.split("—")[1]}` }
: `${target.value}${answer?.split("—")[1]}`
); updateMinRangeDebounced(`${target.value}${maxRange}`);
}} }}
sx={{ sx={{
maxWidth: "80px", maxWidth: "80px",
"& .MuiInputBase-input": { textAlign: "center" }, "& .MuiInputBase-input": { textAlign: "center" },
}} }}
/> />
до
<CustomTextField <CustomTextField
placeholder="0" placeholder="0"
value={answer?.split("—")[1]} value={maxRange}
onChange={({ target }) => { onChange={({ target }) => {
updateAnswer( setMaxRange(target.value);
currentQuestion.content.id,
window.Number(target.value) > max if (window.Number(target.value) <= window.Number(minRange)) {
? `${answer?.split("—")[0]}${max}` updateMaxRangeDebounced(`${minRange}${minRange}`, true);
: window.Number(target.value) <
window.Number(answer?.split("—")[0]) return;
? `${answer?.split("—")[0]}${answer?.split("—")[0]}` }
: `${answer?.split("—")[0]}${target.value}`
); updateMaxRangeDebounced(`${minRange}${target.value}`);
}} }}
sx={{ sx={{
maxWidth: "80px", maxWidth: "80px",
@ -121,29 +204,7 @@ export const Number = ({ currentQuestion }: NumberProps) => {
/> />
</Box> </Box>
)} )}
<Slider
value={
currentQuestion.content.chooseRange
? answer?.split("—").length || 0 > 1
? answer?.split("—").map((item) => window.Number(item))
: [min, min + 1]
: window.Number(answer || 1)
}
min={min}
max={max}
step={currentQuestion.content.step || 1}
sx={{
color: theme.palette.brightPurple.main,
padding: "0",
marginTop: "25px",
}}
onChange={(_, value) => {
updateAnswer(
currentQuestion.content.id,
String(value).replace(",", "—")
);
}}
/>
</Box> </Box>
</Box> </Box>
); );

@ -18,36 +18,38 @@ type RatingProps = {
export const Rating = ({ currentQuestion }: RatingProps) => { export const Rating = ({ currentQuestion }: RatingProps) => {
const { answers } = useQuizViewStore(); const { answers } = useQuizViewStore();
const theme = useTheme(); const theme = useTheme();
const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.content.id) ?? {}; const { answer } =
answers.find(
({ questionId }) => questionId === currentQuestion.content.id
) ?? {};
return ( return (
<Box> <Box>
<Typography variant="h5">{currentQuestion.title}</Typography> <Typography variant="h5">{currentQuestion.title}</Typography>
<Box <Box
sx={{ sx={{
display: "flex", display: "inline-block",
flexDirection: "column",
width: "100%", width: "100%",
marginTop: "20px", marginTop: "20px",
}} }}
> >
<RatingComponent <RatingComponent
value={Number(answer || 0)} value={Number(answer || 0)}
onChange={(_, value) => updateAnswer(currentQuestion.content.id, String(value))} onChange={(_, value) =>
updateAnswer(currentQuestion.content.id, String(value))
}
sx={{ height: "50px" }} sx={{ height: "50px" }}
max={currentQuestion.content.steps} max={currentQuestion.content.steps}
icon={ icon={
<StarIconMini <StarIconMini
color={theme.palette.brightPurple.main} color={theme.palette.brightPurple.main}
width={50} width={50}
sx={{ transform: "scale(0.8)" }}
/> />
} }
emptyIcon={ emptyIcon={
<StarIconMini <StarIconMini
color={theme.palette.grey2.main} color={theme.palette.grey2.main}
width={50} width={50}
sx={{ transform: "scale(0.8)" }}
/> />
} }
/> />
@ -59,8 +61,12 @@ export const Rating = ({ currentQuestion }: RatingProps) => {
color: theme.palette.grey2.main, color: theme.palette.grey2.main,
}} }}
> >
<Typography>{currentQuestion.content.ratingNegativeDescription}</Typography> <Typography>
<Typography>{currentQuestion.content.ratingPositiveDescription}</Typography> {currentQuestion.content.ratingNegativeDescription}
</Typography>
<Typography>
{currentQuestion.content.ratingPositiveDescription}
</Typography>
</Box> </Box>
</Box> </Box>
</Box> </Box>

@ -12,7 +12,10 @@ type SelectProps = {
export const Select = ({ currentQuestion }: SelectProps) => { export const Select = ({ currentQuestion }: SelectProps) => {
const { answers } = useQuizViewStore(); const { answers } = useQuizViewStore();
const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.content.id) ?? {}; const { answer } =
answers.find(
({ questionId }) => questionId === currentQuestion.content.id
) ?? {};
return ( return (
<Box> <Box>
@ -26,7 +29,7 @@ export const Select = ({ currentQuestion }: SelectProps) => {
}} }}
> >
<SelectComponent <SelectComponent
activeItemIndex={Number(answer) || 0} activeItemIndex={answer ? Number(answer) : -1}
items={currentQuestion.content.variants.map(({ answer }) => answer)} items={currentQuestion.content.variants.map(({ answer }) => answer)}
onChange={(_, value) => { onChange={(_, value) => {
updateAnswer(currentQuestion.content.id, String(value)); updateAnswer(currentQuestion.content.id, String(value));

@ -1,4 +1,3 @@
import { useEffect } from "react";
import { import {
Box, Box,
Typography, Typography,
@ -8,7 +7,7 @@ import {
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import { useQuizViewStore, updateAnswer } from "@root/quizView"; import { useQuizViewStore, updateAnswer, deleteAnswer } from "@root/quizView";
import RadioCheck from "@ui_kit/RadioCheck"; import RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon"; import RadioIcon from "@ui_kit/RadioIcon";
@ -23,13 +22,10 @@ type VariantProps = {
export const Variant = ({ currentQuestion }: VariantProps) => { export const Variant = ({ currentQuestion }: VariantProps) => {
const { answers } = useQuizViewStore(); const { answers } = useQuizViewStore();
const theme = useTheme(); const theme = useTheme();
const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.content.id) ?? {}; const { answer } =
answers.find(
useEffect(() => { ({ questionId }) => questionId === currentQuestion.content.id
if (!answer) { ) ?? {};
updateAnswer(currentQuestion.content.id, currentQuestion.content.variants[0].id);
}
}, []);
return ( return (
<Box> <Box>
@ -37,13 +33,9 @@ export const Variant = ({ currentQuestion }: VariantProps) => {
<Box sx={{ display: "flex" }}> <Box sx={{ display: "flex" }}>
<RadioGroup <RadioGroup
name={currentQuestion.id} name={currentQuestion.id}
value={currentQuestion.content.variants.findIndex(({ id }) => answer === id)} value={currentQuestion.content.variants.findIndex(
onChange={({ target }) => ({ id }) => answer === id
updateAnswer( )}
currentQuestion.content.id,
currentQuestion.content.variants[Number(target.value)].id
)
}
sx={{ sx={{
display: "flex", display: "flex",
flexWrap: "wrap", flexWrap: "wrap",
@ -53,10 +45,18 @@ export const Variant = ({ currentQuestion }: VariantProps) => {
marginTop: "20px", marginTop: "20px",
}} }}
> >
<Box sx={{ display: "flex", flexDirection: "row", flexWrap: "wrap", width: "100%", gap: "20px", }}> <Box
{currentQuestion.content.variants.map(({ id, answer }, index) => ( sx={{
display: "flex",
flexDirection: "row",
flexWrap: "wrap",
width: "100%",
gap: "20px",
}}
>
{currentQuestion.content.variants.map((variant, index) => (
<FormControlLabel <FormControlLabel
key={id} key={variant.id}
sx={{ sx={{
margin: "0", margin: "0",
borderRadius: "12px", borderRadius: "12px",
@ -65,14 +65,26 @@ export const Variant = ({ currentQuestion }: VariantProps) => {
display: "flex", display: "flex",
maxWidth: "685px", maxWidth: "685px",
justifyContent: "space-between", justifyContent: "space-between",
width: "100%" width: "100%",
}} }}
value={index} value={index}
labelPlacement="start" labelPlacement="start"
control={ control={
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} /> <Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
} }
label={answer} label={variant.answer}
onClick={(event) => {
event.preventDefault();
updateAnswer(
currentQuestion.content.id,
currentQuestion.content.variants[index].id
);
if (answer === currentQuestion.content.variants[index].id) {
deleteAnswer(currentQuestion.content.id);
}
}}
/> />
))} ))}
</Box> </Box>

@ -1,4 +1,3 @@
import { useEffect } from "react";
import { import {
Box, Box,
Typography, Typography,
@ -8,7 +7,7 @@ import {
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import { useQuizViewStore, updateAnswer } from "@root/quizView"; import { useQuizViewStore, updateAnswer, deleteAnswer } from "@root/quizView";
import RadioCheck from "@ui_kit/RadioCheck"; import RadioCheck from "@ui_kit/RadioCheck";
import RadioIcon from "@ui_kit/RadioIcon"; import RadioIcon from "@ui_kit/RadioIcon";
@ -22,28 +21,23 @@ type VarimgProps = {
export const Varimg = ({ currentQuestion }: VarimgProps) => { export const Varimg = ({ currentQuestion }: VarimgProps) => {
const { answers } = useQuizViewStore(); const { answers } = useQuizViewStore();
const theme = useTheme(); const theme = useTheme();
const { answer } = answers.find(({ questionId }) => questionId === currentQuestion.content.id) ?? {}; const { answer } =
const variant = currentQuestion.content.variants.find(({ id }) => answer === id); answers.find(
({ questionId }) => questionId === currentQuestion.content.id
useEffect(() => { ) ?? {};
if (!answer) { const variant = currentQuestion.content.variants.find(
updateAnswer(currentQuestion.content.id, currentQuestion.content.variants[0].id); ({ id }) => answer === id
} );
}, []);
return ( return (
<Box> <Box>
<Typography variant="h5">{currentQuestion.title}</Typography> <Typography variant="h5">{currentQuestion.title}</Typography>
<Box sx={{ display: "flex", marginTop: "20px", }}> <Box sx={{ display: "flex", marginTop: "20px" }}>
<RadioGroup <RadioGroup
name={currentQuestion.id} name={currentQuestion.id}
value={currentQuestion.content.variants.findIndex(({ id }) => answer === id)} value={currentQuestion.content.variants.findIndex(
onChange={({ target }) => ({ id }) => answer === id
updateAnswer( )}
currentQuestion.content.id,
currentQuestion.content.variants[Number(target.value)].id
)
}
sx={{ sx={{
display: "flex", display: "flex",
flexWrap: "wrap", flexWrap: "wrap",
@ -53,9 +47,9 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
}} }}
> >
<Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}> <Box sx={{ display: "flex", flexDirection: "column", width: "100%" }}>
{currentQuestion.content.variants.map(({ id, answer }, index) => ( {currentQuestion.content.variants.map((variant, index) => (
<FormControlLabel <FormControlLabel
key={id} key={variant.id}
sx={{ sx={{
marginBottom: "15px", marginBottom: "15px",
borderRadius: "5px", borderRadius: "5px",
@ -65,18 +59,41 @@ export const Varimg = ({ currentQuestion }: VarimgProps) => {
display: "flex", display: "flex",
}} }}
value={index} value={index}
onClick={(event) => {
event.preventDefault();
updateAnswer(
currentQuestion.content.id,
currentQuestion.content.variants[index].id
);
if (answer === currentQuestion.content.variants[index].id) {
deleteAnswer(currentQuestion.content.id);
}
}}
control={ control={
<Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} /> <Radio checkedIcon={<RadioCheck />} icon={<RadioIcon />} />
} }
label={answer} label={variant.answer}
/> />
))} ))}
</Box> </Box>
</RadioGroup> </RadioGroup>
{(variant?.extendedText || currentQuestion.content.back) && ( {(variant?.extendedText || currentQuestion.content.back) && (
<Box sx={{ maxWidth: "450px", width: "100%", height: "450px", border: "1px solid #E3E3E3", borderRadius: "12px", overflow: "hidden", }}> <Box
sx={{
maxWidth: "450px",
width: "100%",
height: "450px",
border: "1px solid #E3E3E3",
borderRadius: "12px",
overflow: "hidden",
}}
>
<img <img
src={answer ? variant?.extendedText : currentQuestion.content.back} src={
answer ? variant?.extendedText : currentQuestion.content.back
}
style={{ width: "100%", height: "100%", objectFit: "cover" }} style={{ width: "100%", height: "100%", objectFit: "cover" }}
alt="" alt=""
/> />

@ -10,8 +10,8 @@ import { nanoid } from "nanoid";
import { enqueueSnackbar } from "notistack"; import { enqueueSnackbar } from "notistack";
import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError"; import { isAxiosCanceledError } from "../../utils/isAxiosCanceledError";
import { RequestQueue } from "../../utils/requestQueue"; import { RequestQueue } from "../../utils/requestQueue";
import { updateRootContentId } from "@root/quizes/actions" import { updateRootContentId } from "@root/quizes/actions";
import { useCurrentQuiz } from "@root/quizes/hooks" import { useCurrentQuiz } from "@root/quizes/hooks";
import { QuestionsStore, useQuestionsStore } from "./store"; import { QuestionsStore, useQuestionsStore } from "./store";
import { withErrorBoundary } from "react-error-boundary"; import { withErrorBoundary } from "react-error-boundary";
@ -90,7 +90,7 @@ const updateQuestionOrders = () => {
const questions = useQuestionsStore.getState().questions.filter( const questions = useQuestionsStore.getState().questions.filter(
(question): question is AnyTypedQuizQuestion => question.type !== null && question.type !== "result" (question): question is AnyTypedQuizQuestion => question.type !== null && question.type !== "result"
); );
console.log(questions) console.log(questions);
questions.forEach((question, index) => { questions.forEach((question, index) => {
updateQuestion(question.id, question => { updateQuestion(question.id, question => {
@ -163,6 +163,10 @@ export const updateQuestion = (
try { try {
const response = await questionApi.edit(questionToEditQuestionRequest(q)); const response = await questionApi.edit(questionToEditQuestionRequest(q));
//Если мы делаем листочек веточкой - удаляем созданный к нему результ
const questionResult = useQuestionsStore.getState().questions.find(questionResult => questionResult.type === "result" && questionResult.content.rule.parentId === q.content.id);
if (questionResult && q.content.rule.default.length !== 0) deleteQuestion(questionResult.quizId);
deleteQuestion;
setQuestionBackendId(questionId, response.updated); setQuestionBackendId(questionId, response.updated);
} catch (error) { } catch (error) {
if (isAxiosCanceledError(error)) return; if (isAxiosCanceledError(error)) return;
@ -307,13 +311,15 @@ export const createTypedQuestion = async (
if (!question) return; if (!question) return;
if (question.type !== null) throw new Error("Cannot upgrade already typed question"); if (question.type !== null) throw new Error("Cannot upgrade already typed question");
const untypedOrResultQuestionsLength = questions.filter(q => q.type === "result" || q.type === null).length;
try { try {
const createdQuestion = await questionApi.create({ const createdQuestion = await questionApi.create({
quiz_id: question.quizId, quiz_id: question.quizId,
type, type,
title: question.title, title: question.title,
description: question.description, description: question.description,
page: questions.length, page: questions.length - untypedOrResultQuestionsLength,
required: false, required: false,
content: JSON.stringify(defaultQuestionByType[type].content), content: JSON.stringify(defaultQuestionByType[type].content),
}); });
@ -335,11 +341,13 @@ export const createTypedQuestion = async (
} }
}); });
export const deleteQuestion = async (questionId: string, quizId: string) => requestQueue.enqueue(async () => { export const deleteQuestion = async (questionId: string) => requestQueue.enqueue(async () => {
const question = useQuestionsStore.getState().questions.find(q => q.id === questionId); const question = useQuestionsStore.getState().questions.find(q => q.id === questionId);
if (!question) return; if (!question) return;
if (question.type === null) { if (question.type === null) {
removeQuestion(questionId); removeQuestion(questionId);
return; return;
@ -347,46 +355,10 @@ export const deleteQuestion = async (questionId: string, quizId: string) => requ
try { try {
await questionApi.delete(question.backendId); await questionApi.delete(question.backendId);
if (question.content.rule.parentId === "root") { //удалить из стора root и очистить rule всем вопросам
updateRootContentId(quizId, "")
clearRuleForAll()
} 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 (!clearQuestions.includes(targetQuestion.content.id)) clearQuestions.push(targetQuestion.content.id)
getChildren(targetQuestion) //и ищем его потомков
}
})
}
getChildren(question)
//чистим потомков от инфы ветвления
clearQuestions.forEach((id) => {
updateQuestion(id, question => {
question.content.rule.parentId = ""
question.content.rule.main = []
question.content.rule.default = ""
})
})
//чистим rule родителя
const parentQuestion = getQuestionByContentId(question.content.rule.parentId)
const newRule = {}
newRule.main = parentQuestion.content.rule.main.filter((data) => data.next !== question.content.id) //удаляем условия перехода от родителя к этому вопросу
newRule.parentId = parentQuestion.content.rule.parentId
newRule.default = questions.filter((q) => {
return q.content.rule.parentId === question.content.rule.parentId && q.content.id !== question.content.id
})[0]?.content.id || ""
//Если этот вопрос был дефолтным у родителя - чистим дефолт
//Смотрим можем ли мы заменить id на один из main
updateQuestion(question.content.rule.parentId, (PQ) => {
PQ.content.rule = newRule
})
}
removeQuestion(questionId); removeQuestion(questionId);
updateQuestionOrders();
} catch (error) { } catch (error) {
devlog("Error deleting question", error); devlog("Error deleting question", error);
enqueueSnackbar("Не удалось удалить вопрос"); enqueueSnackbar("Не удалось удалить вопрос");
@ -400,8 +372,8 @@ export const copyQuestion = async (questionId: string, quizId: number) => reques
const frontId = nanoid(); const frontId = nanoid();
if (question.type === null) { if (question.type === null) {
const copiedQuestion = structuredClone(question); const copiedQuestion = structuredClone(question);
copiedQuestion.id = frontId copiedQuestion.id = frontId;
copiedQuestion.content.id = frontId copiedQuestion.content.id = frontId;
setProducedState(state => { setProducedState(state => {
state.questions.push(copiedQuestion); state.questions.push(copiedQuestion);
@ -421,7 +393,7 @@ export const copyQuestion = async (questionId: string, quizId: number) => reques
copiedQuestion.backendId = newQuestionId; copiedQuestion.backendId = newQuestionId;
copiedQuestion.id = frontId; copiedQuestion.id = frontId;
copiedQuestion.content.id = frontId; copiedQuestion.content.id = frontId;
copiedQuestion.content.rule = { main: [], parentId: "", default: "" }; copiedQuestion.content.rule = { main: [], parentId: "", default: "", children: [] };
setProducedState(state => { setProducedState(state => {
state.questions.push(copiedQuestion); state.questions.push(copiedQuestion);
@ -469,41 +441,41 @@ export const updateDragQuestionContentId = (contentId?: string) => {
}; };
export const clearRuleForAll = () => { export const clearRuleForAll = () => {
const { questions } = useQuestionsStore.getState() const { questions } = useQuestionsStore.getState();
questions.forEach(question => { 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)) {
updateQuestion(question.content.id, question => { updateQuestion(question.content.id, question => {
question.content.rule.parentId = "" question.content.rule.parentId = "";
question.content.rule.main = [] question.content.rule.main = [];
question.content.rule.default = "" question.content.rule.default = "";
}) });
} }
}); });
} };
export const updateOpenBranchingPanel = (value: boolean) => useQuestionsStore.setState({ openBranchingPanel: value }); export const updateOpenBranchingPanel = (value: boolean) => useQuestionsStore.setState({ openBranchingPanel: value });
let UDTOABM: ReturnType<typeof setTimeout>; let UDTOABM: ReturnType<typeof setTimeout>;
export const updateDesireToOpenABranchingModal = (contentId: string) => { export const updateDesireToOpenABranchingModal = (contentId: string) => {
useQuestionsStore.setState({ desireToOpenABranchingModal: contentId }) useQuestionsStore.setState({ desireToOpenABranchingModal: contentId });
clearTimeout(UDTOABM) clearTimeout(UDTOABM);
UDTOABM = setTimeout(() => { UDTOABM = setTimeout(() => {
useQuestionsStore.setState({ desireToOpenABranchingModal: null }) useQuestionsStore.setState({ desireToOpenABranchingModal: null });
}, 7000) }, 7000);
} };
export const clearDesireToOpenABranchingModal = () => { export const clearDesireToOpenABranchingModal = () => {
useQuestionsStore.setState({ desireToOpenABranchingModal: null }) useQuestionsStore.setState({ desireToOpenABranchingModal: null });
} };
export const updateEditSomeQuestion = (contentId?: string) => { export const updateEditSomeQuestion = (contentId?: string) => {
useQuestionsStore.setState({ editSomeQuestion: contentId === undefined ? null : contentId }) useQuestionsStore.setState({ editSomeQuestion: contentId === undefined ? null : contentId });
} };
export const createFrontResult = (quizId: number, parentContentId?: string) => setProducedState(state => { export const createFrontResult = (quizId: number, parentContentId?: string) => setProducedState(state => {
const frontId = nanoid() const frontId = nanoid();
const content = JSON.parse(JSON.stringify(defaultQuestionByType["result"].content)) const content = JSON.parse(JSON.stringify(defaultQuestionByType["result"].content));
content.id = frontId content.id = frontId;
if (parentContentId) content.rule.parentId = parentContentId if (parentContentId) content.rule.parentId = parentContentId;
state.questions.push({ state.questions.push({
id: frontId, id: frontId,
quizId, quizId,

@ -41,3 +41,12 @@ export const updateAnswer = (
useQuizViewStore.setState({ answers }); useQuizViewStore.setState({ answers });
}; };
export const deleteAnswer = (questionId: string) => {
const answers = [...useQuizViewStore.getState().answers];
const filteredItems = answers.filter(
(answer) => questionId !== answer.questionId
);
useQuizViewStore.setState({ answers: filteredItems });
};

@ -82,17 +82,15 @@ export default function QuizPreviewLayout() {
{quiz.config.startpage.description} {quiz.config.startpage.description}
</Typography> </Typography>
<Box> <Box>
{quiz.config.startpage.button && ( <Button
<Button variant="contained"
variant="contained" sx={{
sx={{ fontSize: "16px",
fontSize: "16px", padding: "10px 15px",
padding: "10px 15px", }}
}} >
> {quiz.config.startpage.button ? quiz.config.startpage.button : "Пройти тест"}
{quiz.config.startpage.button} </Button>
</Button>
)}
</Box> </Box>
</Box> </Box>
<Box> <Box>