Updated the eval calls to the backend, passed the navigation logic of level to useExamNavigation hook
This commit is contained in:
@@ -19,18 +19,18 @@ import { typeCheckWordsMC } from "@/utils/type.check";
|
||||
import SectionNavbar from "../Navigation/SectionNavbar";
|
||||
import AudioPlayer from "@/components/Low/AudioPlayer";
|
||||
import { ExamProps } from "../types";
|
||||
import {answeredEveryQuestionInPart} from "../utils/answeredEveryQuestion";
|
||||
import { answeredEveryQuestionInPart } from "../utils/answeredEveryQuestion";
|
||||
import useExamTimer from "@/hooks/useExamTimer";
|
||||
import ProgressButtons from "../components/ProgressButtons";
|
||||
import useExamNavigation from "../Navigation/useExamNavigation";
|
||||
import { calculateExerciseIndex } from "../utils/calculateExerciseIndex";
|
||||
|
||||
|
||||
const Level: React.FC<ExamProps<LevelExam>> = ({ exam, showSolutions = false, preview = false }) => {
|
||||
const levelBgColor = "bg-ielts-level-light";
|
||||
|
||||
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);
|
||||
|
||||
@@ -55,6 +55,7 @@ const Level: React.FC<ExamProps<LevelExam>> = ({ exam, showSolutions = false, pr
|
||||
const { finalizeModule, timeIsUp } = flags;
|
||||
|
||||
const timer = useRef(exam.minTimer - timeSpentCurrentModule / 60);
|
||||
const [isFirstTimeRender, setIsFirstTimeRender] = useState(partIndex === 0 && exerciseIndex == 0 && !showSolutions);
|
||||
|
||||
// In case client want to switch back
|
||||
const textRenderDisabled = true;
|
||||
@@ -66,7 +67,6 @@ const Level: React.FC<ExamProps<LevelExam>> = ({ exam, showSolutions = false, pr
|
||||
const [textRender, setTextRender] = useState(false);
|
||||
const [changedPrompt, setChangedPrompt] = useState(false);
|
||||
|
||||
const [seenParts, setSeenParts] = useState<Set<number>>(new Set(showSolutions ? exam.parts.map((_, index) => index) : [0]));
|
||||
|
||||
const [questionModalKwargs, setQuestionModalKwargs] = useState<{
|
||||
type?: "module" | "blankQuestions" | "submit"; unanswered?: boolean | undefined; onClose: (next?: boolean) => void | undefined;
|
||||
@@ -74,25 +74,39 @@ const Level: React.FC<ExamProps<LevelExam>> = ({ exam, showSolutions = false, pr
|
||||
type: "blankQuestions",
|
||||
onClose: function (x: boolean | undefined) { if (x) { setShowQuestionsModal(false); nextExercise(); } else { setShowQuestionsModal(false) } }
|
||||
});
|
||||
const [showPartDivider, setShowPartDivider] = useState<boolean>(typeof exam.parts[0].intro === "string" && !showSolutions);
|
||||
const [startNow, setStartNow] = useState<boolean>(!showSolutions);
|
||||
|
||||
const modalKwargs = () => {
|
||||
const kwargs: { type: "module" | "blankQuestions" | "submit", unanswered: boolean, onClose: (next?: boolean) => void; } = {
|
||||
type: "blankQuestions",
|
||||
unanswered: false,
|
||||
onClose: function (x: boolean | undefined) { if (x) { setContinueAnyways(true); setShowQuestionsModal(false); } else { setShowQuestionsModal(false) } }
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!showSolutions && exam.parts[partIndex]?.intro !== undefined && exam.parts[partIndex]?.intro !== "" && !seenParts.has(partIndex)) {
|
||||
setShowPartDivider(true);
|
||||
setBgColor(levelBgColor);
|
||||
if (partIndex === exam.parts.length - 1) {
|
||||
kwargs.type = "submit"
|
||||
kwargs.unanswered = !exam.parts.every((_, partIndex) => answeredEveryQuestionInPart(exam, partIndex, userSolutions));
|
||||
kwargs.onClose = function (x: boolean | undefined) { if (x) { setShowSubmissionModal(true); setShowQuestionsModal(false); } else { setShowQuestionsModal(false) } };
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [exerciseIndex]);
|
||||
setQuestionModalKwargs(kwargs);
|
||||
}
|
||||
|
||||
const {
|
||||
nextExercise, previousExercise,
|
||||
showPartDivider, setShowPartDivider,
|
||||
seenParts, setSeenParts,
|
||||
} = useExamNavigation(
|
||||
{
|
||||
exam, module: "level", showBlankModal: showQuestionsModal,
|
||||
setShowBlankModal: setShowQuestionsModal, showSolutions,
|
||||
preview, disableBetweenParts: true, modalKwargs
|
||||
});
|
||||
|
||||
|
||||
const registerSolution = useCallback((updateSolution: () => UserSolution) => {
|
||||
userSolutionRef.current = updateSolution;
|
||||
setSolutionWasUpdated(true);
|
||||
}, []);
|
||||
|
||||
const scrollToTop = () => Array.from(document.getElementsByTagName("body")).forEach((body) => body.scrollTo(0, 0));
|
||||
|
||||
const [contextWords, setContextWords] = useState<{ match: string, originalLine: string }[] | undefined>(undefined);
|
||||
const [contextWordLines, setContextWordLines] = useState<number[] | undefined>(undefined);
|
||||
const [totalLines, setTotalLines] = useState<number>(0);
|
||||
@@ -139,104 +153,6 @@ const Level: React.FC<ExamProps<LevelExam>> = ({ exam, showSolutions = false, pr
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [finalizeModule, timeIsUp])
|
||||
|
||||
const nextExercise = () => {
|
||||
scrollToTop();
|
||||
|
||||
if (exerciseIndex + 1 < exam.parts[partIndex].exercises.length) {
|
||||
setExerciseIndex(exerciseIndex + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (partIndex + 1 === exam.parts.length && !showQuestionsModal && !showSolutions && !continueAnyways) {
|
||||
modalKwargs();
|
||||
setShowQuestionsModal(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (partIndex + 1 < exam.parts.length) {
|
||||
if (!answeredEveryQuestionInPart(exam, partIndex, userSolutions) && !continueAnyways && !showSolutions && !seenParts.has(partIndex + 1)) {
|
||||
modalKwargs();
|
||||
setShowQuestionsModal(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!showSolutions && exam.parts[0].intro && !seenParts.has(partIndex + 1)) {
|
||||
setShowPartDivider(true);
|
||||
setBgColor(levelBgColor);
|
||||
}
|
||||
|
||||
if (partIndex < exam.parts.length - 1 && exam.parts[partIndex + 1].context && !textRenderDisabled) {
|
||||
setTextRender(true);
|
||||
}
|
||||
|
||||
setTimesListened(0);
|
||||
setPartIndex(partIndex + 1);
|
||||
setExerciseIndex(0);
|
||||
setQuestionIndex(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (partIndex + 1 === exam.parts.length && exerciseIndex === exam.parts[partIndex].exercises.length - 1 && !continueAnyways && !showSolutions) {
|
||||
modalKwargs();
|
||||
setShowQuestionsModal(true);
|
||||
}
|
||||
|
||||
if (!showSolutions) {
|
||||
dispatch({ type: "FINALIZE_MODULE", payload: { updateTimers: true } })
|
||||
} else {
|
||||
dispatch({ type: "FINALIZE_MODULE_SOLUTIONS"})
|
||||
}
|
||||
}
|
||||
|
||||
const previousExercise = () => {
|
||||
scrollToTop();
|
||||
|
||||
if (exam.parts[partIndex].context && questionIndex === 0 && !textRender && !textRenderDisabled) {
|
||||
setTextRender(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (questionIndex == 0) {
|
||||
setPartIndex(partIndex - 1);
|
||||
if (!seenParts.has(partIndex - 1)) {
|
||||
setBgColor(levelBgColor);
|
||||
setShowPartDivider(true);
|
||||
setQuestionIndex(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const lastExerciseIndex = exam.parts[partIndex - 1].exercises.length - 1;
|
||||
const lastExercise = exam.parts[partIndex - 1].exercises[lastExerciseIndex];
|
||||
setExerciseIndex(lastExerciseIndex);
|
||||
|
||||
if (lastExercise.type === "multipleChoice") {
|
||||
setQuestionIndex(lastExercise.questions.length - 1)
|
||||
} else {
|
||||
setQuestionIndex(0)
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setExerciseIndex(exerciseIndex - 1);
|
||||
if (exerciseIndex - 1 === -1) {
|
||||
setPartIndex(partIndex - 1);
|
||||
const lastPartExerciseIndex = exam.parts[partIndex - 1].exercises.length - 1;
|
||||
const previousExercise = exam.parts[partIndex - 1].exercises[lastPartExerciseIndex];
|
||||
if (previousExercise.type === "multipleChoice") {
|
||||
setQuestionIndex(previousExercise.questions.length - 1)
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const calculateExerciseIndex = () => {
|
||||
return exam.parts.reduce((acc, curr, index) => {
|
||||
if (index < partIndex) {
|
||||
return acc + countExercises(curr.exercises)
|
||||
}
|
||||
return acc;
|
||||
}, 0) + (questionIndex + 1);
|
||||
};
|
||||
|
||||
const renderAudioPlayer = () => (
|
||||
<div className="flex flex-col gap-8 w-full bg-mti-gray-seasalt rounded-xl py-8 px-16">
|
||||
@@ -393,26 +309,11 @@ const Level: React.FC<ExamProps<LevelExam>> = ({ exam, showSolutions = false, pr
|
||||
useEffect(() => {
|
||||
if (continueAnyways) {
|
||||
setContinueAnyways(false);
|
||||
nextExercise();
|
||||
nextExercise(true);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [continueAnyways]);
|
||||
|
||||
const modalKwargs = () => {
|
||||
const kwargs: { type: "module" | "blankQuestions" | "submit", unanswered: boolean, onClose: (next?: boolean) => void; } = {
|
||||
type: "blankQuestions",
|
||||
unanswered: false,
|
||||
onClose: function (x: boolean | undefined) { if (x) { setContinueAnyways(true); setShowQuestionsModal(false); } else { setShowQuestionsModal(false) } }
|
||||
};
|
||||
|
||||
if (partIndex === exam.parts.length - 1) {
|
||||
kwargs.type = "submit"
|
||||
kwargs.unanswered = !exam.parts.every((_, partIndex) => answeredEveryQuestionInPart(exam, partIndex, userSolutions));
|
||||
kwargs.onClose = function (x: boolean | undefined) { if (x) { setShowSubmissionModal(true); setShowQuestionsModal(false); } else { setShowQuestionsModal(false) } };
|
||||
}
|
||||
setQuestionModalKwargs(kwargs);
|
||||
}
|
||||
|
||||
const mcNavKwargs = {
|
||||
userSolutions: userSolutions,
|
||||
exam: exam,
|
||||
@@ -423,27 +324,10 @@ const Level: React.FC<ExamProps<LevelExam>> = ({ exam, showSolutions = false, pr
|
||||
runOnClick: setQuestionIndex
|
||||
}
|
||||
|
||||
const progressButtons = <ProgressButtons handlePrevious={previousExercise} handleNext={nextExercise} />;
|
||||
|
||||
const memoizedRender = useMemo(() => {
|
||||
setChangedPrompt(false);
|
||||
return (
|
||||
<>
|
||||
{textRender && !textRenderDisabled ?
|
||||
renderText() :
|
||||
<>
|
||||
{exam.parts[partIndex]?.context && renderText()}
|
||||
{exam.parts[partIndex]?.audio && renderAudioPlayer()}
|
||||
{(showSolutions) ?
|
||||
currentExercise && renderSolution(currentExercise, progressButtons, progressButtons) :
|
||||
currentExercise && renderExercise(currentExercise, exam.id, registerSolution, preview, progressButtons, progressButtons)
|
||||
}
|
||||
</>
|
||||
}
|
||||
</>
|
||||
)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [textRender, currentExercise, changedPrompt]);
|
||||
const progressButtons = useMemo(() =>
|
||||
// Do not remove the ()=> in handle next
|
||||
<ProgressButtons handlePrevious={previousExercise} handleNext={() => nextExercise()} />
|
||||
, [nextExercise, previousExercise]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -469,17 +353,17 @@ const Level: React.FC<ExamProps<LevelExam>> = ({ exam, showSolutions = false, pr
|
||||
</Modal>
|
||||
<QuestionsModal isOpen={showQuestionsModal} {...questionModalKwargs} />
|
||||
{
|
||||
!(partIndex === 0 && questionIndex === 0 && (showPartDivider || startNow)) &&
|
||||
!(partIndex === 0 && questionIndex === 0 && (showPartDivider || isFirstTimeRender)) &&
|
||||
<Timer minTimer={exam.minTimer} disableTimer={showSolutions || preview} standalone={true} />
|
||||
}
|
||||
{(showPartDivider || startNow) ?
|
||||
{(showPartDivider || isFirstTimeRender) ?
|
||||
<PartDivider
|
||||
module="level"
|
||||
sectionLabel="Part"
|
||||
defaultTitle="Placement Test"
|
||||
section={exam.parts[partIndex]}
|
||||
sectionIndex={partIndex}
|
||||
onNext={() => { setShowPartDivider(false); setStartNow(false); setBgColor("bg-white"); setSeenParts(prev => new Set(prev).add(partIndex)); }}
|
||||
onNext={() => { setShowPartDivider(false); setIsFirstTimeRender(false); setBgColor("bg-white"); setSeenParts(prev => new Set(prev).add(partIndex)); }}
|
||||
/> : (
|
||||
<>
|
||||
<SectionNavbar
|
||||
@@ -494,7 +378,7 @@ const Level: React.FC<ExamProps<LevelExam>> = ({ exam, showSolutions = false, pr
|
||||
examLabel={exam.label}
|
||||
partLabel={partLabel()}
|
||||
minTimer={timer.current}
|
||||
exerciseIndex={calculateExerciseIndex()}
|
||||
exerciseIndex={calculateExerciseIndex(exam, partIndex, exerciseIndex, questionIndex)}
|
||||
module="level"
|
||||
totalExercises={countExercises(exam.parts.flatMap((x) => x.exercises))}
|
||||
disableTimer={showSolutions}
|
||||
@@ -507,7 +391,17 @@ const Level: React.FC<ExamProps<LevelExam>> = ({ exam, showSolutions = false, pr
|
||||
"mb-20 w-full",
|
||||
!!exam.parts[partIndex].context && !textRender && "grid grid-cols-2 gap-4",
|
||||
)}>
|
||||
{memoizedRender}
|
||||
{textRender && !textRenderDisabled ?
|
||||
renderText() :
|
||||
<>
|
||||
{exam.parts[partIndex]?.context && renderText()}
|
||||
{exam.parts[partIndex]?.audio && renderAudioPlayer()}
|
||||
{(showSolutions) ?
|
||||
currentExercise && renderSolution(currentExercise, progressButtons, progressButtons) :
|
||||
currentExercise && renderExercise(currentExercise, exam.id, registerSolution, preview, progressButtons, progressButtons)
|
||||
}
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -11,6 +11,7 @@ const MC_PER_PAGE = 2;
|
||||
type UseExamNavigation = (props: {
|
||||
exam: ModuleExam;
|
||||
module: Module;
|
||||
modalKwargs?: () => void;
|
||||
showBlankModal?: boolean;
|
||||
setShowBlankModal?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
showSolutions: boolean;
|
||||
@@ -31,14 +32,15 @@ const useExamNavigation: UseExamNavigation = ({
|
||||
exam,
|
||||
module,
|
||||
setShowBlankModal,
|
||||
modalKwargs,
|
||||
showSolutions,
|
||||
preview,
|
||||
disableBetweenParts = false,
|
||||
}) => {
|
||||
|
||||
const examState = useExamStore((state) => state);
|
||||
const persistentExamState = usePersistentExamStore((state) => state);
|
||||
|
||||
const persistentExamState = usePersistentExamStore((state) => state);
|
||||
|
||||
const {
|
||||
exerciseIndex, setExerciseIndex,
|
||||
partIndex, setPartIndex,
|
||||
@@ -101,11 +103,13 @@ const useExamNavigation: UseExamNavigation = ({
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentExercise.type === "multipleChoice" && questionIndex < currentExercise.questions.length - 1) {
|
||||
setQuestionIndex(questionIndex + MC_PER_PAGE);
|
||||
return;
|
||||
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);
|
||||
@@ -121,6 +125,7 @@ const useExamNavigation: UseExamNavigation = ({
|
||||
}
|
||||
|
||||
if (!answeredEveryQuestion(exam as PartExam, userSolutions) && !keepGoing && setShowBlankModal && !showSolutions && !preview) {
|
||||
if (modalKwargs) modalKwargs()
|
||||
setShowBlankModal(true);
|
||||
return;
|
||||
}
|
||||
@@ -134,20 +139,17 @@ const useExamNavigation: UseExamNavigation = ({
|
||||
if (!showSolutions) {
|
||||
dispatch({ type: "FINALIZE_MODULE", payload: { updateTimers: true } });
|
||||
} else {
|
||||
dispatch({ type: "FINALIZE_MODULE_SOLUTIONS"});
|
||||
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);
|
||||
const currentExercise = (exam as PartExam).parts[partIndex].exercises[exerciseIndex];
|
||||
if (currentExercise.type === "multipleChoice" && questionIndex > 0) {
|
||||
setQuestionIndex(Math.max(0, questionIndex - MC_PER_PAGE));
|
||||
return;
|
||||
}
|
||||
|
||||
setQuestionIndex(0);
|
||||
|
||||
if (exerciseIndex === 0 && !disableBetweenParts) {
|
||||
setIsBetweenParts(true);
|
||||
return;
|
||||
@@ -155,7 +157,16 @@ const useExamNavigation: UseExamNavigation = ({
|
||||
|
||||
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 = () => {
|
||||
@@ -166,7 +177,7 @@ const useExamNavigation: UseExamNavigation = ({
|
||||
if (currentExercise.type === "interactiveSpeaking" && questionIndex < currentExercise.prompts.length - 1) {
|
||||
setQuestionIndex(questionIndex + 1)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!reachedFinalExercise) {
|
||||
setQuestionIndex(0);
|
||||
@@ -183,7 +194,7 @@ const useExamNavigation: UseExamNavigation = ({
|
||||
if (!showSolutions) {
|
||||
dispatch({ type: "FINALIZE_MODULE", payload: { updateTimers: true } });
|
||||
} else {
|
||||
dispatch({ type: "FINALIZE_MODULE_SOLUTIONS"});
|
||||
dispatch({ type: "FINALIZE_MODULE_SOLUTIONS" });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +205,7 @@ const useExamNavigation: UseExamNavigation = ({
|
||||
if (currentExercise.type === "interactiveSpeaking" && questionIndex !== 0) {
|
||||
setQuestionIndex(questionIndex - 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (exerciseIndex !== 0) {
|
||||
setExerciseIndex(exerciseIndex - 1);
|
||||
|
||||
@@ -15,7 +15,7 @@ import { calculateExerciseIndexSpeaking } from "./utils/calculateExerciseIndex";
|
||||
|
||||
|
||||
const Speaking: React.FC<ExamProps<SpeakingExam>> = ({ exam, showSolutions = false, preview = false }) => {
|
||||
const updateTimers = useExamTimer(exam.module, preview);
|
||||
const updateTimers = useExamTimer(exam.module, preview || showSolutions);
|
||||
const userSolutionRef = useRef<(() => UserSolution) | null>(null);
|
||||
const [solutionWasUpdated, setSolutionWasUpdated] = useState(false);
|
||||
|
||||
@@ -90,11 +90,8 @@ const Speaking: React.FC<ExamProps<SpeakingExam>> = ({ exam, showSolutions = fal
|
||||
setSeenParts((prev) => new Set(prev).add(exerciseIndex));
|
||||
}
|
||||
|
||||
const memoizedExerciseIndex = useMemo(() => {
|
||||
const bruh = calculateExerciseIndexSpeaking(exam, exerciseIndex, questionIndex)
|
||||
console.log(bruh);
|
||||
return bruh;
|
||||
}
|
||||
const memoizedExerciseIndex = useMemo(() =>
|
||||
calculateExerciseIndexSpeaking(exam, exerciseIndex, questionIndex)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
, [exerciseIndex, questionIndex]
|
||||
);
|
||||
|
||||
@@ -13,7 +13,7 @@ import SectionNavbar from "./Navigation/SectionNavbar";
|
||||
import ProgressButtons from "./components/ProgressButtons";
|
||||
|
||||
const Writing: React.FC<ExamProps<WritingExam>> = ({ exam, showSolutions = false, preview = false }) => {
|
||||
const updateTimers = useExamTimer(exam.module, preview);
|
||||
const updateTimers = useExamTimer(exam.module, preview || showSolutions);
|
||||
const userSolutionRef = useRef<(() => UserSolution) | null>(null);
|
||||
const [solutionWasUpdated, setSolutionWasUpdated] = useState(false);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user