103 lines
3.7 KiB
TypeScript
103 lines
3.7 KiB
TypeScript
import {renderExercise} from "@/components/Exercises";
|
|
import ModuleTitle from "@/components/Medium/ModuleTitle";
|
|
import {renderSolution} from "@/components/Solutions";
|
|
import {infoButtonStyle} from "@/constants/buttonStyles";
|
|
import {UserSolution, SpeakingExam, SpeakingExercise, InteractiveSpeakingExercise} from "@/interfaces/exam";
|
|
import useExamStore from "@/stores/examStore";
|
|
import {defaultUserSolutions} from "@/utils/exams";
|
|
import {countExercises} from "@/utils/moduleUtils";
|
|
import {convertCamelCaseToReadable} from "@/utils/string";
|
|
import {mdiArrowRight} from "@mdi/js";
|
|
import Icon from "@mdi/react";
|
|
import clsx from "clsx";
|
|
import {Fragment, useEffect, useState} from "react";
|
|
import {toast} from "react-toastify";
|
|
|
|
interface Props {
|
|
exam: SpeakingExam;
|
|
showSolutions?: boolean;
|
|
onFinish: (userSolutions: UserSolution[]) => void;
|
|
}
|
|
|
|
export default function Speaking({exam, showSolutions = false, onFinish}: Props) {
|
|
const [speakingPromptsDone, setSpeakingPromptsDone] = useState<{id: string; amount: number}[]>([]);
|
|
|
|
const {userSolutions, setUserSolutions} = useExamStore((state) => state);
|
|
const {questionIndex, setQuestionIndex} = useExamStore((state) => state);
|
|
const {hasExamEnded, setHasExamEnded} = useExamStore((state) => state);
|
|
const {exerciseIndex, setExerciseIndex} = useExamStore((state) => state);
|
|
|
|
const scrollToTop = () => Array.from(document.getElementsByTagName("body")).forEach((body) => body.scrollTo(0, 0));
|
|
|
|
const nextExercise = (solution?: UserSolution) => {
|
|
scrollToTop();
|
|
if (solution) {
|
|
setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), {...solution, module: "speaking", exam: exam.id}]);
|
|
}
|
|
|
|
if (questionIndex > 0) {
|
|
const exercise = getExercise();
|
|
setSpeakingPromptsDone((prev) => [...prev.filter((x) => x.id !== exercise.id), {id: exercise.id, amount: questionIndex}]);
|
|
}
|
|
setQuestionIndex(0);
|
|
|
|
if (exerciseIndex + 1 < exam.exercises.length) {
|
|
setExerciseIndex(exerciseIndex + 1);
|
|
return;
|
|
}
|
|
|
|
if (exerciseIndex >= exam.exercises.length) return;
|
|
|
|
setHasExamEnded(false);
|
|
|
|
if (solution) {
|
|
onFinish([...userSolutions.filter((x) => x.exercise !== solution.exercise), {...solution, module: "speaking", exam: exam.id}]);
|
|
} else {
|
|
onFinish(userSolutions);
|
|
}
|
|
};
|
|
|
|
const previousExercise = (solution?: UserSolution) => {
|
|
scrollToTop();
|
|
if (solution) {
|
|
setUserSolutions([...userSolutions.filter((x) => x.exercise !== solution.exercise), {...solution, module: "speaking", exam: exam.id}]);
|
|
}
|
|
|
|
if (exerciseIndex > 0) {
|
|
setExerciseIndex(exerciseIndex - 1);
|
|
}
|
|
};
|
|
|
|
const getExercise = () => {
|
|
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;
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<div className="flex flex-col h-full w-full gap-8 items-center">
|
|
<ModuleTitle
|
|
label={convertCamelCaseToReadable(exam.exercises[exerciseIndex].type)}
|
|
minTimer={exam.minTimer}
|
|
exerciseIndex={exerciseIndex + 1 + questionIndex + speakingPromptsDone.reduce((acc, curr) => acc + curr.amount, 0)}
|
|
module="speaking"
|
|
totalExercises={countExercises(exam.exercises)}
|
|
disableTimer={showSolutions}
|
|
/>
|
|
{exerciseIndex > -1 &&
|
|
exerciseIndex < exam.exercises.length &&
|
|
!showSolutions &&
|
|
renderExercise(getExercise(), exam.id, nextExercise, previousExercise)}
|
|
{exerciseIndex > -1 &&
|
|
exerciseIndex < exam.exercises.length &&
|
|
showSolutions &&
|
|
renderSolution(exam.exercises[exerciseIndex], nextExercise, previousExercise)}
|
|
</div>
|
|
</>
|
|
);
|
|
}
|