import { renderExercise } from "@/components/Exercises"; import ModuleTitle from "@/components/Medium/ModuleTitle"; import { renderSolution } from "@/components/Solutions"; import { UserSolution, SpeakingExam, SpeakingExercise, InteractiveSpeakingExercise, Exercise } from "@/interfaces/exam"; import useExamStore, { usePersistentExamStore } from "@/stores/exam"; import { countExercises } from "@/utils/moduleUtils"; import { convertCamelCaseToReadable } from "@/utils/string"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import PartDivider from "./Navigation/SectionDivider"; import { ExamProps } from "./types"; import useExamTimer from "@/hooks/useExamTimer"; import useExamNavigation from "./Navigation/useExamNavigation"; import ProgressButtons from "./components/ProgressButtons"; import { calculateExerciseIndexSpeaking } from "./utils/calculateExerciseIndex"; import SectionNavbar from "./Navigation/SectionNavbar"; const Speaking: React.FC> = ({ exam, showSolutions = false, preview = false }) => { const updateTimers = useExamTimer(exam.module, preview || showSolutions); const userSolutionRef = useRef<(() => UserSolution) | null>(null); const [solutionWasUpdated, setSolutionWasUpdated] = useState(false); const examState = useExamStore((state) => state); const persistentExamState = usePersistentExamStore((state) => state); const { exerciseIndex, userSolutions, flags, timeSpentCurrentModule, questionIndex, setBgColor, setUserSolutions, setTimeIsUp, dispatch, } = !preview ? examState : persistentExamState; const { finalizeModule, timeIsUp } = flags; const timer = useRef(exam.minTimer - timeSpentCurrentModule / 60); const { nextExercise, previousExercise, showPartDivider, setShowPartDivider, setSeenParts, seenParts, setIsBetweenParts } = useExamNavigation({ exam, module: "speaking", showSolutions, preview, disableBetweenParts: true }); useEffect(() => { if (finalizeModule || timeIsUp) { updateTimers(); if (timeIsUp) { setTimeIsUp(false); } dispatch({ type: "FINALIZE_MODULE", payload: { updateTimers: false } }) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [finalizeModule, timeIsUp]) const registerSolution = useCallback((updateSolution: () => UserSolution) => { userSolutionRef.current = updateSolution; setSolutionWasUpdated(true); }, []); useEffect(() => { if (solutionWasUpdated && userSolutionRef.current) { const solution = userSolutionRef.current(); setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), { ...solution, module: "speaking", exam: exam.id }]); setSolutionWasUpdated(false); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [solutionWasUpdated]) const currentExercise = useMemo(() => { const exercise = exam.exercises[exerciseIndex]; return { ...exercise, variant: exerciseIndex < 2 && exercise.type === "interactiveSpeaking" ? "initial" : undefined, userSolutions: userSolutions.find((x) => x.exercise === exercise.id)?.solutions || [], } as SpeakingExercise | InteractiveSpeakingExercise; // eslint-disable-next-line react-hooks/exhaustive-deps }, [exerciseIndex]); const progressButtons = useMemo(() => // Do not remove the ()=> in handle next nextExercise()} /> , [nextExercise, previousExercise]); const handlePartDividerClick = () => { setShowPartDivider(false); setBgColor("bg-white"); setSeenParts((prev) => new Set(prev).add(exerciseIndex)); } const memoizedExerciseIndex = useMemo(() => calculateExerciseIndexSpeaking(exam, exerciseIndex, questionIndex) // eslint-disable-next-line react-hooks/exhaustive-deps , [exerciseIndex, questionIndex] ); return ( <> {(showPartDivider) ? : ( <> {exam.exercises.length > 1 && }
{!showPartDivider && !showSolutions && renderExercise(currentExercise, exam.id, registerSolution, preview, progressButtons, progressButtons)} {showSolutions && renderSolution(currentExercise, progressButtons, progressButtons)}
)} ); } export default Speaking;