frontAnswerer/lib/utils/hooks/useQuestionFlowControl.ts

205 lines
8.1 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 { useCallback, useDebugValue, useMemo, useState } from "react";
import { enqueueSnackbar } from "notistack";
import moment from "moment";
import { isResultQuestionEmpty } from "@/components/ViewPublicationPage/tools/checkEmptyData";
import { useQuizSettings } from "@contexts/QuizDataContext";
import { useQuizViewStore } from "@stores/quizView";
import { useVkMetricsGoals } from "@/utils/hooks/metrics/useVkMetricsGoals";
import { useYandexMetricsGoals } from "@/utils/hooks/metrics/useYandexMetricsGoals";
export function useQuestionFlowControl() {
const { settings, questions } = useQuizSettings();
const sortedQuestions = useMemo(() => {
return [...questions].sort((a, b) => a.page - b.page);
}, [questions]);
const [currentQuestionId, setCurrentQuestionId] = useState<string | null>(getFirstQuestionId);
const answers = useQuizViewStore((state) => state.answers);
const pointsSum = useQuizViewStore((state) => state.pointsSum);
const setCurrentQuizStep = useQuizViewStore((state) => state.setCurrentQuizStep);
const vkMetrics = useVkMetricsGoals(settings.cfg.vkMetricsNumber);
const yandexMetrics = useYandexMetricsGoals(settings.cfg.yandexMetricsNumber);
const currentQuestion = sortedQuestions.find((question) => question.id === currentQuestionId) ?? sortedQuestions[0];
const linearQuestionIndex =
currentQuestion && sortedQuestions.every(({ content }) => content.rule.parentId !== "root") // null when branching enabled
? sortedQuestions.indexOf(currentQuestion)
: null;
function getFirstQuestionId() {
if (sortedQuestions.length === 0) return null;
if (settings.cfg.haveRoot) {
const nextQuestion = sortedQuestions.find(
(question) => question.id === settings.cfg.haveRoot || question.content.id === settings.cfg.haveRoot
);
if (!nextQuestion) return null;
return nextQuestion.id;
}
return sortedQuestions[0].id;
}
const nextQuestionIdPointsLogic = useCallback(() => {
return sortedQuestions.find((question) => question.type === "result" && question.content.rule.parentId === "line");
}, [sortedQuestions]);
const nextQuestionIdMainLogic = useCallback(() => {
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 sortedQuestions.find((q) => {
return q.type === "result" && q.content.rule.parentId === currentQuestion.content.id;
})?.id;
}, [answers, currentQuestion, sortedQuestions]);
const nextQuestionId = useMemo(() => {
if (settings.cfg.score) {
return nextQuestionIdPointsLogic();
}
return nextQuestionIdMainLogic();
}, [nextQuestionIdMainLogic, nextQuestionIdPointsLogic, settings.cfg.score]);
const prevQuestion =
linearQuestionIndex !== null
? sortedQuestions[linearQuestionIndex - 1]
: sortedQuestions.find(
(q) =>
q.id === currentQuestion?.content.rule.parentId || q.content.id === currentQuestion?.content.rule.parentId
);
const findResultPointsLogic = useCallback(() => {
const results = sortedQuestions.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);
return results[numbers.indexOf(indexOfNext)];
}, [pointsSum, sortedQuestions]);
const nextQuestion = useMemo(() => {
let next;
if (settings.cfg.score) {
if (linearQuestionIndex !== null) {
next = sortedQuestions[linearQuestionIndex + 1];
if (next?.type === "result" || next == undefined) next = findResultPointsLogic();
}
} else {
if (linearQuestionIndex !== null) {
next =
sortedQuestions[linearQuestionIndex + 1] ??
sortedQuestions.find((question) => question.type === "result" && question.content.rule.parentId === "line");
} else {
next = sortedQuestions.find((q) => q.id === nextQuestionId || q.content.id === nextQuestionId);
}
}
return next;
}, [nextQuestionId, findResultPointsLogic, linearQuestionIndex, sortedQuestions, settings.cfg.score]);
const showResult = useCallback(() => {
if (nextQuestion?.type !== "result") throw new Error("Current question is not result");
setCurrentQuestionId(nextQuestion.id);
if (settings.cfg.resultInfo.showResultForm === "after" || isResultQuestionEmpty(nextQuestion))
setCurrentQuizStep("contactform");
}, [nextQuestion, setCurrentQuizStep, 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, setCurrentQuizStep]);
const moveToPrevQuestion = useCallback(() => {
if (!prevQuestion) throw new Error("Previous question not found");
setCurrentQuestionId(prevQuestion.id);
}, [prevQuestion]);
const moveToNextQuestion = useCallback(() => {
if (!nextQuestion) throw new Error("Next question not found");
// Засчитываем переход с вопроса дальше
vkMetrics.questionPassed(currentQuestion.id);
yandexMetrics.questionPassed(currentQuestion.id);
if (nextQuestion.type === "result") return showResult();
setCurrentQuestionId(nextQuestion.id);
}, [currentQuestion.id, nextQuestion, showResult, vkMetrics, yandexMetrics]);
const setQuestion = useCallback(
(questionId: string) => {
const question = sortedQuestions.find((q) => q.id === questionId);
if (!question) return;
setCurrentQuestionId(question.id);
},
[sortedQuestions]
);
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, nextQuestion]);
useDebugValue({
linearQuestionIndex,
currentQuestion: currentQuestion,
prevQuestion: prevQuestion,
nextQuestion: nextQuestion,
});
return {
currentQuestion,
currentQuestionStepNumber: linearQuestionIndex === null ? null : linearQuestionIndex + 1,
isNextButtonEnabled,
isPreviousButtonEnabled,
moveToPrevQuestion,
moveToNextQuestion,
showResultAfterContactForm,
setQuestion,
};
}