import { ExerciseOnlyExam, ModuleExam, PartExam } from "@/interfaces/exam"; import { useState, useEffect } from "react"; import scrollToTop from "../utils/scrollToTop"; import { Module } from "@/interfaces"; import { answeredEveryQuestion } from "../utils/answeredEveryQuestion"; import useExamStore, { usePersistentExamStore } from "@/stores/exam"; import hasDivider from "../utils/hasDivider"; const MC_PER_PAGE = 2; type UseExamNavigation = (props: { exam: ModuleExam; module: Module; showBlankModal?: boolean; setShowBlankModal?: React.Dispatch>; showSolutions: boolean; preview: boolean; disableBetweenParts?: boolean; }) => { showPartDivider: boolean; seenParts: Set; isBetweenParts: boolean; nextExercise: (isBetweenParts?: boolean) => void; previousExercise: (isBetweenParts?: boolean) => void; setShowPartDivider: React.Dispatch>; setSeenParts: React.Dispatch>>; setIsBetweenParts: React.Dispatch>; }; const useExamNavigation: UseExamNavigation = ({ exam, module, setShowBlankModal, showSolutions, preview, disableBetweenParts = false, }) => { const examState = useExamStore((state) => state); const persistentExamState = usePersistentExamStore((state) => state); const { exerciseIndex, setExerciseIndex, partIndex, setPartIndex, questionIndex, setQuestionIndex, userSolutions, setModuleIndex, setBgColor, dispatch, } = !preview ? examState : persistentExamState; const [isBetweenParts, setIsBetweenParts] = useState(partIndex !== 0 && exerciseIndex == 0 && !disableBetweenParts); const isPartExam = ["reading", "listening", "level"].includes(exam.module); const [seenParts, setSeenParts] = useState>( new Set(showSolutions ? (isPartExam ? (exam as PartExam).parts.map((_, index) => index) : (exam as ExerciseOnlyExam).exercises.map((_, index) => index) ) : [] ) ); const [showPartDivider, setShowPartDivider] = useState(hasDivider(exam, 0)); useEffect(() => { if (!showSolutions && hasDivider(exam, isPartExam ? partIndex : exerciseIndex) && !seenParts.has(partIndex)) { setShowPartDivider(true); setBgColor(`bg-ielts-${module}-light`); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [partIndex]); const nextExercise = (keepGoing: boolean = false) => { scrollToTop(); if (isPartExam) { nextPartExam(keepGoing); } else { nextExerciseOnlyExam(); } }; const previousExercise = () => { scrollToTop(); if (isPartExam) { previousPartExam(); } else { previousExerciseOnlyExam(); } }; const nextPartExam = (keepGoing: boolean) => { const partExam = (exam as PartExam); const reachedFinalExercise = exerciseIndex + 1 === partExam.parts[partIndex].exercises.length; const currentExercise = partExam.parts[partIndex].exercises[exerciseIndex]; if (isBetweenParts) { setIsBetweenParts(false); return; } if (currentExercise.type === "multipleChoice" && questionIndex < currentExercise.questions.length - 1) { setQuestionIndex(questionIndex + MC_PER_PAGE); return; } if (!reachedFinalExercise) { setExerciseIndex(exerciseIndex + 1); setQuestionIndex(0); return; } if (partIndex < partExam.parts.length - 1) { if (!disableBetweenParts) setIsBetweenParts(true); setPartIndex(partIndex + 1); setExerciseIndex(0); setQuestionIndex(0); return; } if (!answeredEveryQuestion(exam as PartExam, userSolutions) && !keepGoing && setShowBlankModal && !showSolutions && !preview) { setShowBlankModal(true); return; } if (preview) { setPartIndex(0); setExerciseIndex(0); setQuestionIndex(0); } if (!showSolutions) { dispatch({ type: "FINALIZE_MODULE", payload: { updateTimers: true } }); } else { dispatch({ type: "FINALIZE_MODULE_SOLUTIONS"}); } } const previousPartExam = () => { if (partIndex !== 0) { setPartIndex(partIndex - 1); setExerciseIndex((exam as PartExam).parts[partIndex].exercises.length - 1); if (isBetweenParts) setIsBetweenParts(false); return; } setQuestionIndex(0); if (exerciseIndex === 0 && !disableBetweenParts) { setIsBetweenParts(true); return; } if (exerciseIndex !== 0) { setExerciseIndex(exerciseIndex - 1); } }; const nextExerciseOnlyExam = () => { const exerciseOnlyExam = (exam as ExerciseOnlyExam); const reachedFinalExercise = exerciseIndex + 1 === exerciseOnlyExam.exercises.length; const currentExercise = exerciseOnlyExam.exercises[exerciseIndex]; if (currentExercise.type === "interactiveSpeaking" && questionIndex < currentExercise.prompts.length - 1) { setQuestionIndex(questionIndex + 1) return; } if (!reachedFinalExercise) { setQuestionIndex(0); setExerciseIndex(exerciseIndex + 1); return; } if (preview) { setPartIndex(0); setExerciseIndex(0); setQuestionIndex(0); } if (!showSolutions) { dispatch({ type: "FINALIZE_MODULE", payload: { updateTimers: true } }); } else { dispatch({ type: "FINALIZE_MODULE_SOLUTIONS"}); } } const previousExerciseOnlyExam = () => { const currentExercise = (exam as ExerciseOnlyExam).exercises[exerciseIndex]; if (currentExercise.type === "interactiveSpeaking" && questionIndex !== 0) { setQuestionIndex(questionIndex - 1); return; } if (exerciseIndex !== 0) { setExerciseIndex(exerciseIndex - 1); return; } }; return { seenParts, showPartDivider, nextExercise, previousExercise, setShowPartDivider, setSeenParts, isBetweenParts, setIsBetweenParts, }; } export default useExamNavigation;