Navigation rework, added prompt edit to components that were missing
This commit is contained in:
217
src/exams/Navigation/useExamNavigation.tsx
Normal file
217
src/exams/Navigation/useExamNavigation.tsx
Normal file
@@ -0,0 +1,217 @@
|
||||
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<React.SetStateAction<boolean>>;
|
||||
showSolutions: boolean;
|
||||
preview: boolean;
|
||||
disableBetweenParts?: boolean;
|
||||
}) => {
|
||||
showPartDivider: boolean;
|
||||
seenParts: Set<number>;
|
||||
isBetweenParts: boolean;
|
||||
nextExercise: (isBetweenParts?: boolean) => void;
|
||||
previousExercise: (isBetweenParts?: boolean) => void;
|
||||
setShowPartDivider: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setSeenParts: React.Dispatch<React.SetStateAction<Set<number>>>;
|
||||
setIsBetweenParts: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
};
|
||||
|
||||
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<Set<number>>(
|
||||
new Set(showSolutions ?
|
||||
(isPartExam ?
|
||||
(exam as PartExam).parts.map((_, index) => index) :
|
||||
(exam as ExerciseOnlyExam).exercises.map((_, index) => index)
|
||||
) :
|
||||
[]
|
||||
)
|
||||
);
|
||||
const [showPartDivider, setShowPartDivider] = useState<boolean>(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;
|
||||
Reference in New Issue
Block a user