import { ExerciseOnlyExam, ModuleExam, PartExam } from "@/interfaces/exam"; import { useState, useEffect } from "react"; import scrollToTop from "../utils/scrollToTop"; import { Module } from "@/interfaces"; import { answeredEveryQuestion, answeredEveryQuestionInPart } 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; modalKwargs?: () => void; showBlankModal?: boolean; setShowBlankModal?: React.Dispatch>; showSolutions: boolean; preview: boolean; disableBetweenParts?: boolean; allPartExercisesRender?: boolean; modalBetweenParts?: boolean; }) => { showPartDivider: boolean; seenParts: Set; isBetweenParts: boolean; startNow: boolean; nextExercise: (isBetweenParts?: boolean) => void; previousExercise: (isBetweenParts?: boolean) => void; setShowPartDivider: React.Dispatch>; setSeenParts: React.Dispatch>>; setIsBetweenParts: React.Dispatch>; }; const useExamNavigation: UseExamNavigation = ({ exam, module, setShowBlankModal, modalKwargs, showSolutions, preview, disableBetweenParts = false, allPartExercisesRender = false, modalBetweenParts = 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(module === "reading" ? (partIndex !== 0 && exerciseIndex === 0 && !disableBetweenParts) : (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)); const [startNow, setStartNow] = useState(!showPartDivider && !showSolutions); // when navbar is used useEffect(()=> { if(startNow && !showPartDivider && partIndex !== 0) { setStartNow(false); } } , [partIndex, startNow, showPartDivider]) 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); if (startNow) { setStartNow(false); return; } if (isBetweenParts) { setIsBetweenParts(false); return; } if (allPartExercisesRender) { if (partIndex < partExam.parts.length - 1) { if (!disableBetweenParts) setIsBetweenParts(true); setPartIndex(partIndex + 1); return; } if (!answeredEveryQuestion(exam as PartExam, userSolutions) && !keepGoing && setShowBlankModal && !showSolutions && !preview) { if (modalKwargs) modalKwargs(); setShowBlankModal(true); return; } if (preview) { setPartIndex(0); } else if (!showSolutions) { dispatch({ type: "FINALIZE_MODULE", payload: { updateTimers: true } }); } else { dispatch({ type: "FINALIZE_MODULE_SOLUTIONS" }); } return; } const reachedFinalExercise = exerciseIndex + 1 === partExam.parts[partIndex].exercises.length; const currentExercise = partExam.parts[partIndex].exercises[exerciseIndex]; if (currentExercise.type === "multipleChoice") { const nextQuestionIndex = questionIndex + MC_PER_PAGE; if (nextQuestionIndex < currentExercise.questions!.length) { setQuestionIndex(nextQuestionIndex); return; } } if (!reachedFinalExercise) { setExerciseIndex(exerciseIndex + 1); setQuestionIndex(0); return; } if (modalBetweenParts && !seenParts.has(partIndex + 1) && !answeredEveryQuestionInPart(exam as PartExam, partIndex, userSolutions) && !keepGoing && setShowBlankModal && !showSolutions && !preview) { if (modalKwargs) modalKwargs(); setShowBlankModal(true); return; } if (partIndex < partExam.parts.length - 1) { if (!disableBetweenParts) setIsBetweenParts(true); setPartIndex(partIndex + 1); setExerciseIndex(0); setQuestionIndex(0); return; } if (!modalBetweenParts && !answeredEveryQuestion(exam as PartExam, userSolutions) && !keepGoing && setShowBlankModal && !showSolutions && !preview) { if (modalKwargs) modalKwargs(); setShowBlankModal(true); return; } if (preview) { setPartIndex(0); setExerciseIndex(0); setQuestionIndex(0); } else if (!showSolutions) { dispatch({ type: "FINALIZE_MODULE", payload: { updateTimers: true } }); } else { dispatch({ type: "FINALIZE_MODULE_SOLUTIONS" }); } }; const previousPartExam = () => { if (!showPartDivider && partIndex === 0 && exerciseIndex === 0 && questionIndex === 0 && !startNow) { setStartNow(true); return; } if (!disableBetweenParts && isBetweenParts) { setIsBetweenParts(false); } if (allPartExercisesRender) { if (isBetweenParts && partIndex !== 0) { setPartIndex(partIndex - 1); setIsBetweenParts(false); return; } if (disableBetweenParts) { if (partIndex !== 0) { setPartIndex(partIndex - 1); } } else { setIsBetweenParts(true); } return; } const currentExercise = (exam as PartExam).parts[partIndex].exercises[exerciseIndex]; if (currentExercise.type === "multipleChoice" && questionIndex > 0 && !allPartExercisesRender) { setQuestionIndex(Math.max(0, questionIndex - MC_PER_PAGE)); return; } if (exerciseIndex !== 0) { setExerciseIndex(exerciseIndex - 1); setQuestionIndex(0); } if (partIndex !== 0) { setPartIndex(partIndex - 1); setExerciseIndex((exam as PartExam).parts[partIndex].exercises.length - 1); if (isBetweenParts) setIsBetweenParts(false); return; } }; 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, startNow }; } export default useExamNavigation;