frontAnswerer/lib/utils/hooks/useQuestionFlowControl.ts
nflnkr 979d0e7138 make quiz view store component store
move design list to separate file
2024-04-03 15:42:12 +03:00

207 lines
8.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<AnyTypedQuizQuestion>(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,
};
}