diff --git a/src/components/Exercises/MultipleChoice.tsx b/src/components/Exercises/MultipleChoice.tsx index 3d093f3d..b16063c5 100644 --- a/src/components/Exercises/MultipleChoice.tsx +++ b/src/components/Exercises/MultipleChoice.tsx @@ -48,7 +48,16 @@ function Question({ ); } -export default function MultipleChoice({id, prompt, type, questions, userSolutions, onNext, onBack}: MultipleChoiceExercise & CommonProps) { +export default function MultipleChoice({ + id, + prompt, + type, + questions, + userSolutions, + updateIndex, + onNext, + onBack, +}: MultipleChoiceExercise & CommonProps) { const [answers, setAnswers] = useState<{question: string; option: string}[]>(userSolutions); const [questionIndex, setQuestionIndex] = useState(0); @@ -59,6 +68,10 @@ export default function MultipleChoice({id, prompt, type, questions, userSolutio // eslint-disable-next-line react-hooks/exhaustive-deps }, [hasExamEnded]); + useEffect(() => { + if (updateIndex) updateIndex(questionIndex); + }, [questionIndex, updateIndex]); + const onSelectOption = (option: string) => { const question = questions[questionIndex]; setAnswers((prev) => [...prev.filter((x) => x.question !== question.id), {option, question: question.id}]); diff --git a/src/components/Exercises/index.tsx b/src/components/Exercises/index.tsx index 89cc70af..7c66f005 100644 --- a/src/components/Exercises/index.tsx +++ b/src/components/Exercises/index.tsx @@ -22,11 +22,17 @@ import InteractiveSpeaking from "./InteractiveSpeaking"; const MatchSentences = dynamic(() => import("@/components/Exercises/MatchSentences"), {ssr: false}); export interface CommonProps { + updateIndex?: (internalIndex: number) => void; onNext: (userSolutions: UserSolution) => void; onBack: (userSolutions: UserSolution) => void; } -export const renderExercise = (exercise: Exercise, onNext: (userSolutions: UserSolution) => void, onBack: (userSolutions: UserSolution) => void) => { +export const renderExercise = ( + exercise: Exercise, + onNext: (userSolutions: UserSolution) => void, + onBack: (userSolutions: UserSolution) => void, + updateIndex?: (internalIndex: number) => void, +) => { switch (exercise.type) { case "fillBlanks": return ; @@ -35,7 +41,15 @@ export const renderExercise = (exercise: Exercise, onNext: (userSolutions: UserS case "matchSentences": return ; case "multipleChoice": - return ; + return ( + + ); case "writeBlanks": return ; case "writing": diff --git a/src/components/Solutions/MultipleChoice.tsx b/src/components/Solutions/MultipleChoice.tsx index 5ef32351..7a81ad16 100644 --- a/src/components/Solutions/MultipleChoice.tsx +++ b/src/components/Solutions/MultipleChoice.tsx @@ -1,7 +1,7 @@ /* eslint-disable @next/next/no-img-element */ import {MultipleChoiceExercise, MultipleChoiceQuestion} from "@/interfaces/exam"; import clsx from "clsx"; -import {useState} from "react"; +import {useEffect, useState} from "react"; import {CommonProps} from "."; import Button from "../Low/Button"; @@ -54,7 +54,16 @@ function Question({ ); } -export default function MultipleChoice({id, type, prompt, questions, userSolutions, onNext, onBack}: MultipleChoiceExercise & CommonProps) { +export default function MultipleChoice({ + id, + type, + prompt, + questions, + userSolutions, + updateIndex, + onNext, + onBack, +}: MultipleChoiceExercise & CommonProps) { const [questionIndex, setQuestionIndex] = useState(0); const calculateScore = () => { @@ -67,6 +76,10 @@ export default function MultipleChoice({id, type, prompt, questions, userSolutio return {total, correct, missing}; }; + useEffect(() => { + if (updateIndex) updateIndex(questionIndex); + }, [questionIndex, updateIndex]); + const next = () => { if (questionIndex === questions.length - 1) { onNext({exercise: id, solutions: userSolutions, score: calculateScore(), type}); diff --git a/src/components/Solutions/index.tsx b/src/components/Solutions/index.tsx index a7d39101..d61dc449 100644 --- a/src/components/Solutions/index.tsx +++ b/src/components/Solutions/index.tsx @@ -22,11 +22,12 @@ import Writing from "./Writing"; const MatchSentences = dynamic(() => import("@/components/Solutions/MatchSentences"), {ssr: false}); export interface CommonProps { + updateIndex?: (internalIndex: number) => void; onNext: (userSolutions: UserSolution) => void; onBack: (userSolutions: UserSolution) => void; } -export const renderSolution = (exercise: Exercise, onNext: () => void, onBack: () => void) => { +export const renderSolution = (exercise: Exercise, onNext: () => void, onBack: () => void, updateIndex?: (internalIndex: number) => void) => { switch (exercise.type) { case "fillBlanks": return ; @@ -35,7 +36,7 @@ export const renderSolution = (exercise: Exercise, onNext: () => void, onBack: ( case "matchSentences": return ; case "multipleChoice": - return ; + return ; case "writeBlanks": return ; case "writing": diff --git a/src/exams/Level.tsx b/src/exams/Level.tsx index 5ea79547..f38e1409 100644 --- a/src/exams/Level.tsx +++ b/src/exams/Level.tsx @@ -19,15 +19,28 @@ interface Props { } export default function Level({exam, showSolutions = false, onFinish}: Props) { + const [questionIndex, setQuestionIndex] = useState(0); + const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0); const [exerciseIndex, setExerciseIndex] = useState(0); const [userSolutions, setUserSolutions] = useState(exam.exercises.map((x) => defaultUserSolutions(x, exam))); const [hasExamEnded, setHasExamEnded] = useExamStore((state) => [state.hasExamEnded, state.setHasExamEnded]); + useEffect(() => { + setCurrentQuestionIndex(0); + }, [questionIndex]); + + useEffect(() => { + if (hasExamEnded && exerciseIndex === -1) { + setExerciseIndex((prev) => prev + 1); + } + }, [hasExamEnded, exerciseIndex]); + const nextExercise = (solution?: UserSolution) => { if (solution) { setUserSolutions((prev) => [...prev.filter((x) => x.exercise !== solution.exercise), solution]); } + setQuestionIndex((prev) => prev + currentQuestionIndex); if (exerciseIndex + 1 < exam.exercises.length) { setExerciseIndex((prev) => prev + 1); @@ -70,7 +83,7 @@ export default function Level({exam, showSolutions = false, onFinish}: Props) {
-1 && exerciseIndex < exam.exercises.length && !showSolutions && - renderExercise(getExercise(), nextExercise, previousExercise)} + renderExercise(getExercise(), nextExercise, previousExercise, setCurrentQuestionIndex)} {exerciseIndex > -1 && exerciseIndex < exam.exercises.length && showSolutions && - renderSolution(exam.exercises[exerciseIndex], nextExercise, previousExercise)} + renderSolution(exam.exercises[exerciseIndex], nextExercise, previousExercise, setCurrentQuestionIndex)}
); diff --git a/src/exams/Listening.tsx b/src/exams/Listening.tsx index e0b428ba..5534d09a 100644 --- a/src/exams/Listening.tsx +++ b/src/exams/Listening.tsx @@ -17,6 +17,8 @@ interface Props { } export default function Listening({exam, showSolutions = false, onFinish}: Props) { + const [questionIndex, setQuestionIndex] = useState(0); + const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0); const [exerciseIndex, setExerciseIndex] = useState(showSolutions ? 0 : -1); const [partIndex, setPartIndex] = useState(0); const [timesListened, setTimesListened] = useState(0); @@ -33,6 +35,10 @@ export default function Listening({exam, showSolutions = false, onFinish}: Props } }, [hasExamEnded, exerciseIndex]); + useEffect(() => { + setCurrentQuestionIndex(0); + }, [questionIndex]); + const confirmFinishModule = (keepGoing?: boolean) => { if (!keepGoing) { setShowBlankModal(false); @@ -46,6 +52,7 @@ export default function Listening({exam, showSolutions = false, onFinish}: Props if (solution) { setUserSolutions((prev) => [...prev.filter((x) => x.exercise !== solution.exercise), solution]); } + setQuestionIndex((prev) => prev + currentQuestionIndex); if (exerciseIndex + 1 < exam.parts[partIndex].exercises.length && !hasExamEnded) { setExerciseIndex((prev) => prev + 1); @@ -130,7 +137,10 @@ export default function Listening({exam, showSolutions = false, onFinish}: Props .flatMap((x) => x.exercises) .findIndex( (x) => x.id === exam.parts[partIndex].exercises[exerciseIndex === -1 ? exerciseIndex + 1 : exerciseIndex]?.id, - ) || 0) + (exerciseIndex === -1 ? 0 : 1) + ) || 0) + + (exerciseIndex === -1 ? 0 : 1) + + questionIndex + + currentQuestionIndex } minTimer={exam.minTimer} module="listening" @@ -141,11 +151,11 @@ export default function Listening({exam, showSolutions = false, onFinish}: Props {exerciseIndex > -1 && exerciseIndex < exam.parts[partIndex].exercises.length && !showSolutions && - renderExercise(getExercise(), nextExercise, previousExercise)} + renderExercise(getExercise(), nextExercise, previousExercise, setCurrentQuestionIndex)} {exerciseIndex > -1 && exerciseIndex < exam.parts[partIndex].exercises.length && showSolutions && - renderSolution(exam.parts[partIndex].exercises[exerciseIndex], nextExercise, previousExercise)} + renderSolution(exam.parts[partIndex].exercises[exerciseIndex], nextExercise, previousExercise, setCurrentQuestionIndex)} {exerciseIndex === -1 && partIndex > 0 && (
diff --git a/src/exams/Reading.tsx b/src/exams/Reading.tsx index 5c5e8919..7d0edff9 100644 --- a/src/exams/Reading.tsx +++ b/src/exams/Reading.tsx @@ -81,6 +81,8 @@ function TextModal({isOpen, title, content, onClose}: {isOpen: boolean; title: s } export default function Reading({exam, showSolutions = false, onFinish}: Props) { + const [questionIndex, setQuestionIndex] = useState(0); + const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0); const [exerciseIndex, setExerciseIndex] = useState(showSolutions ? 0 : -1); const [partIndex, setPartIndex] = useState(0); const [showTextModal, setShowTextModal] = useState(false); @@ -105,6 +107,10 @@ export default function Reading({exam, showSolutions = false, onFinish}: Props) }; }, []); + useEffect(() => { + setCurrentQuestionIndex(0); + }, [questionIndex]); + useEffect(() => { if (hasExamEnded && exerciseIndex === -1) { setExerciseIndex((prev) => prev + 1); @@ -124,6 +130,7 @@ export default function Reading({exam, showSolutions = false, onFinish}: Props) if (solution) { setUserSolutions((prev) => [...prev.filter((x) => x.exercise !== solution.exercise), solution]); } + setQuestionIndex((prev) => prev + currentQuestionIndex); if (exerciseIndex + 1 < exam.parts[partIndex].exercises.length && !hasExamEnded) { setExerciseIndex((prev) => prev + 1); @@ -207,7 +214,10 @@ export default function Reading({exam, showSolutions = false, onFinish}: Props) .flatMap((x) => x.exercises) .findIndex( (x) => x.id === exam.parts[partIndex].exercises[exerciseIndex === -1 ? exerciseIndex + 1 : exerciseIndex]?.id, - ) || 0) + (exerciseIndex === -1 ? 0 : 1) + ) || 0) + + (exerciseIndex === -1 ? 0 : 1) + + questionIndex + + currentQuestionIndex } module="reading" totalExercises={countExercises(exam.parts.flatMap((x) => x.exercises))} @@ -219,11 +229,11 @@ export default function Reading({exam, showSolutions = false, onFinish}: Props) {exerciseIndex > -1 && exerciseIndex < exam.parts[partIndex].exercises.length && !showSolutions && - renderExercise(getExercise(), nextExercise, previousExercise)} + renderExercise(getExercise(), nextExercise, previousExercise, setCurrentQuestionIndex)} {exerciseIndex > -1 && exerciseIndex < exam.parts[partIndex].exercises.length && showSolutions && - renderSolution(exam.parts[partIndex].exercises[exerciseIndex], nextExercise, previousExercise)} + renderSolution(exam.parts[partIndex].exercises[exerciseIndex], nextExercise, previousExercise, setCurrentQuestionIndex)}
{exerciseIndex > -1 && exerciseIndex < exam.parts[partIndex].exercises.length && (