import { AnyTypedQuizQuestion } from "@model/questionTypes/shared"; import { useQuizViewStore } from "@stores/quizView"; import { useCallback, useDebugValue, useMemo, useState } from "react"; import { isResultQuestionEmpty } from "../../components/ViewPublicationPage/tools/checkEmptyData"; import moment from "moment"; import { useQuizData } from "@contexts/QuizDataContext"; import { enqueueSnackbar } from "notistack"; export function useQuestionFlowControl() { const { settings, questions } = useQuizData(); const [currentQuestion, setCurrentQuestion] = useState(getFirstQuestion); const answers = useQuizViewStore(state => state.answers); const pointsSum = useQuizViewStore(state => state.pointsSum); const setCurrentQuizStep = useQuizViewStore(state => state.setCurrentQuizStep); const linearQuestionIndex = questions.every(({ content }) => content.rule.parentId !== "root") // null when branching enabled ? questions.indexOf(currentQuestion) : null; function getFirstQuestion() { if (questions.length === 0) throw new Error("No questions found"); if (settings.cfg.haveRoot) { const nextQuestion = questions.find( question => question.id === settings.cfg.haveRoot || question.content.id === settings.cfg.haveRoot ); if (!nextQuestion) throw new Error("Root question not found"); return nextQuestion; } return questions[0]; } const nextQuestionIdPointsLogic = () => { return questions.find(question => question.type === "result" && question.content.rule.parentId === "line" ); }; const nextQuestionIdMainLogic = () => { const questionAnswer = answers.find(({ questionId }) => questionId === currentQuestion.id); //Если ответ существует и не является объектом момента if (questionAnswer && !moment.isMoment(questionAnswer.answer)) { //Ответы должны храниться в массиве const userAnswers = Array.isArray(questionAnswer.answer) ? questionAnswer.answer : [questionAnswer.answer]; //Сравниваем список условий ветвления и выбираем подходящее for (const branchingRule of currentQuestion.content.rule.main) { if (userAnswers.some(answer => branchingRule.rules[0].answers.includes(answer))) { return branchingRule.next; } } } if (!currentQuestion.required) {//вопрос не обязателен и не нашли совпадений между ответами и условиями ветвления const defaultNextQuestionId = currentQuestion.content.rule.default; if (defaultNextQuestionId.length > 1 && defaultNextQuestionId !== " ") return defaultNextQuestionId; //Вопросы типа страница, ползунок, своё поле для ввода и дата не могут иметь больше 1 ребёнка. Пользователь не может настроить там дефолт //Кинуть на ребёнка надо даже если там нет дефолта if ( ["date", "page", "text", "number"].includes(currentQuestion.type) && currentQuestion.content.rule.children.length === 1 ) return currentQuestion.content.rule.children[0]; } //ничё не нашли, ищем резулт return questions.find(q => { return q.type === "result" && q.content.rule.parentId === currentQuestion.content.id; })?.id; }; const calculateNextQuestionId = useMemo(() => { if (settings.cfg.score) { return nextQuestionIdPointsLogic(); } return nextQuestionIdMainLogic(); }, [answers, currentQuestion, questions]); const prevQuestion = linearQuestionIndex !== null ? questions[linearQuestionIndex - 1] : questions.find(q => q.id === currentQuestion.content.rule.parentId || q.content.id === currentQuestion.content.rule.parentId ); const findResultPointsLogic = () => { const results = questions .filter(e => e.type === "result" && e.content.rule.minScore !== undefined && e.content.rule.minScore <= pointsSum); const numbers = results.map(e => e.type === "result" && e.content.rule.minScore !== undefined ? e.content.rule.minScore : 0); const indexOfNext = Math.max(...numbers); console.log(results); console.log(numbers); console.log(indexOfNext); return results[numbers.indexOf(indexOfNext)]; }; const getNextQuestion = () => { let next; console.log(11111111111); //Искать можно двумя логиками. Основной и балловой if (settings.cfg.score) { //Балловая console.log(222222222); //Ищем линейно if (linearQuestionIndex !== null) { console.log(33333333333); next = questions[linearQuestionIndex + 1]; console.log(4444444); console.log("перед ифом", next); if (next?.type === "result" || next == undefined) next = findResultPointsLogic(); console.log(5555555555); } } else { console.log(6666666); //Основная if (linearQuestionIndex !== null) { console.log(777777777); //Ищем линейно next = questions[linearQuestionIndex + 1] ?? questions.find(question => question.type === "result" && question.content.rule.parentId === "line" ); } else { console.log(88888888888888); //Ищем ветвлением next = questions.find(q => q.id === calculateNextQuestionId || q.content.id === calculateNextQuestionId); } } console.log("next", next); if (!next && currentQuestion.type !== "result") throw new Error("Не найден следующий вопрос"); return next; }; const nextQuestion = getNextQuestion(); const showResult = useCallback(() => { if (nextQuestion?.type !== "result") throw new Error("Current question is not result"); setCurrentQuestion(nextQuestion); if ( settings.cfg.resultInfo.showResultForm === "after" || isResultQuestionEmpty(nextQuestion) ) setCurrentQuizStep("contactform"); }, [nextQuestion, settings.cfg.resultInfo.showResultForm]); const showResultAfterContactForm = useCallback(() => { if (currentQuestion.type !== "result") throw new Error("Current question is not result"); if (isResultQuestionEmpty(currentQuestion)) { enqueueSnackbar("Данные отправлены"); return; } setCurrentQuizStep("question"); }, [currentQuestion]); const moveToPrevQuestion = useCallback(() => { if (!prevQuestion) throw new Error("Previous question not found"); setCurrentQuestion(prevQuestion); }, [prevQuestion]); const moveToNextQuestion = useCallback(() => { if (!nextQuestion) throw new Error("Next question not found"); if (nextQuestion.type === "result") return showResult(); setCurrentQuestion(nextQuestion); }, [nextQuestion, showResult]); const isPreviousButtonEnabled = Boolean(prevQuestion); const isNextButtonEnabled = useMemo(() => { const hasAnswer = answers.some(({ questionId }) => questionId === currentQuestion.id); if ("required" in currentQuestion.content && currentQuestion.content.required) { return hasAnswer; } return Boolean(nextQuestion); }, [answers, currentQuestion.content, currentQuestion.id, nextQuestion]); useDebugValue({ linearQuestionIndex, currentQuestion: currentQuestion, prevQuestion: prevQuestion, nextQuestion: nextQuestion, }); return { currentQuestion, currentQuestionStepNumber: linearQuestionIndex === null ? null : linearQuestionIndex + 1, isNextButtonEnabled, isPreviousButtonEnabled, moveToPrevQuestion, moveToNextQuestion, showResultAfterContactForm, }; }