diff --git a/src/components/Medium/Timer.tsx b/src/components/Medium/Timer.tsx index b75b6c64..416846f3 100644 --- a/src/components/Medium/Timer.tsx +++ b/src/components/Medium/Timer.tsx @@ -1,9 +1,9 @@ import useExamStore from "@/stores/examStore"; -import {useEffect, useState} from "react"; -import {motion} from "framer-motion"; +import { useEffect, useState } from "react"; +import { motion } from "framer-motion"; import TimerEndedModal from "../TimerEndedModal"; import clsx from "clsx"; -import {BsStopwatch} from "react-icons/bs"; +import { BsStopwatch } from "react-icons/bs"; interface Props { minTimer: number; @@ -11,13 +11,13 @@ interface Props { standalone?: boolean; } -const Timer: React.FC = ({minTimer, disableTimer, standalone = false}) => { +const Timer: React.FC = ({ minTimer, disableTimer, standalone = false }) => { const [timer, setTimer] = useState(minTimer * 60); const [showModal, setShowModal] = useState(false); const [warningMode, setWarningMode] = useState(false); const setHasExamEnded = useExamStore((state) => state.setHasExamEnded); - const {timeSpent} = useExamStore((state) => state); + const { timeSpent } = useExamStore((state) => state); useEffect(() => setTimer((prev) => prev - timeSpent), [timeSpent]); @@ -54,9 +54,9 @@ const Timer: React.FC = ({minTimer, disableTimer, standalone = false}) => standalone ? "top-10" : "top-4", warningMode && !disableTimer && "bg-mti-red-light text-mti-gray-seasalt", )} - initial={{scale: warningMode && !disableTimer ? 0.8 : 1}} - animate={{scale: warningMode && !disableTimer ? 1.1 : 1}} - transition={{repeat: Infinity, repeatType: "reverse", duration: 0.5, ease: "easeInOut"}}> + initial={{ scale: warningMode && !disableTimer ? 0.8 : 1 }} + animate={{ scale: warningMode && !disableTimer ? 1.1 : 1 }} + transition={{ repeat: Infinity, repeatType: "reverse", duration: 0.5, ease: "easeInOut" }}> {timer > 0 && ( diff --git a/src/exams/Level/index.tsx b/src/exams/Level/index.tsx index 64a54f86..7c99121d 100644 --- a/src/exams/Level/index.tsx +++ b/src/exams/Level/index.tsx @@ -78,7 +78,7 @@ export default function Level({ exam, showSolutions = false, onFinish, preview = const [startNow, setStartNow] = useState(!showSolutions); - useEffect(() => { + useEffect(() => { if (!showSolutions && exam.parts[partIndex]?.intro !== undefined && exam.parts[partIndex]?.intro !== "" && !seenParts.has(partIndex)) { setShowPartDivider(true); setBgColor(levelBgColor); @@ -101,6 +101,11 @@ export default function Level({ exam, showSolutions = false, onFinish, preview = const [showSolutionsSave, setShowSolutionsSave] = useState(showSolutions ? userSolutions.filter((x) => x.module === "level") : undefined) + useEffect(() => { + if (hasExamEnded) onFinish(userSolutions) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [hasExamEnded]); + useEffect(() => { if (typeof currentSolution !== "undefined") { setUserSolutions([...userSolutions.filter((x) => x.exercise !== currentSolution.exercise), { ...currentSolution, module: "level" as Module, exam: exam.id, shuffleMaps: exam.shuffle ? [...shuffles.find((x) => x.exerciseID == currentExercise?.id)?.shuffles!] : [] }]); diff --git a/src/exams/Listening.tsx b/src/exams/Listening.tsx index 7288ca8e..f10ed967 100644 --- a/src/exams/Listening.tsx +++ b/src/exams/Listening.tsx @@ -145,10 +145,9 @@ export default function Listening({ exam, showSolutions = false, preview = false }, []); useEffect(() => { - if (hasExamEnded && exerciseIndex === -1) { - setExerciseIndex(exerciseIndex + 1); - } - }, [hasExamEnded, exerciseIndex, setExerciseIndex]); + if (hasExamEnded) onFinish(userSolutions) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [hasExamEnded]); const confirmFinishModule = (keepGoing?: boolean) => { if (!keepGoing) { diff --git a/src/exams/Reading.tsx b/src/exams/Reading.tsx index 4b50cf9e..c0034746 100644 --- a/src/exams/Reading.tsx +++ b/src/exams/Reading.tsx @@ -173,10 +173,9 @@ export default function Reading({ exam, showSolutions = false, preview = false, }, []); useEffect(() => { - if (hasExamEnded && exerciseIndex === -1) { - setExerciseIndex(exerciseIndex + 1); - } - }, [hasExamEnded, exerciseIndex, setExerciseIndex]); + if (hasExamEnded) onFinish(userSolutions) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [hasExamEnded]); const confirmFinishModule = (keepGoing?: boolean) => { if (!keepGoing) { diff --git a/src/exams/Speaking.tsx b/src/exams/Speaking.tsx index 6b124ae3..6c70bcf5 100644 --- a/src/exams/Speaking.tsx +++ b/src/exams/Speaking.tsx @@ -53,10 +53,9 @@ export default function Speaking({ exam, showSolutions = false, onFinish, previe }, [exerciseIndex]); useEffect(() => { - if (hasExamEnded && exerciseIndex === -1) { - setExerciseIndex(exerciseIndex + 1); - } - }, [hasExamEnded, exerciseIndex, setExerciseIndex]); + if (hasExamEnded) onFinish(userSolutions) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [hasExamEnded]); const scrollToTop = () => Array.from(document.getElementsByTagName("body")).forEach((body) => body.scrollTo(0, 0)); diff --git a/src/exams/Writing.tsx b/src/exams/Writing.tsx index acfea4fb..20fd8c66 100644 --- a/src/exams/Writing.tsx +++ b/src/exams/Writing.tsx @@ -34,10 +34,9 @@ export default function Writing({ exam, showSolutions = false, preview = false, const [showPartDivider, setShowPartDivider] = useState(typeof exam.exercises[0].intro === "string" && exam.exercises[0].intro !== ""); useEffect(() => { - if (hasExamEnded && exerciseIndex === -1) { - setExerciseIndex(exerciseIndex + 1); - } - }, [hasExamEnded, exerciseIndex, setExerciseIndex]); + if (hasExamEnded) onFinish(userSolutions) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [hasExamEnded]); const scrollToTop = () => Array.from(document.getElementsByTagName("body")).forEach((body) => body.scrollTo(0, 0)); @@ -100,7 +99,7 @@ export default function Writing({ exam, showSolutions = false, preview = false, defaultTitle="Writing exam" section={exam.exercises[exerciseIndex]} sectionIndex={exerciseIndex} - onNext={() => { setShowPartDivider(false); setBgColor("bg-white"); setSeenParts((prev) => new Set(prev).add(exerciseIndex))}} + onNext={() => { setShowPartDivider(false); setBgColor("bg-white"); setSeenParts((prev) => new Set(prev).add(exerciseIndex)) }} /> : (
(); -const ExamOwnerSelector = ({options, exam, onSave}: {options: User[]; exam: Exam; onSave: (owners: string[]) => void}) => { +const ExamOwnerSelector = ({ options, exam, onSave }: { options: User[]; exam: Exam; onSave: (owners: string[]) => void }) => { const [owners, setOwners] = useState(exam.owners || []); return ( @@ -57,12 +57,12 @@ const ExamOwnerSelector = ({options, exam, onSave}: {options: User[]; exam: Exam ); }; -export default function ExamList({user, entities}: {user: User; entities: EntityWithRoles[];}) { +export default function ExamList({ user, entities }: { user: User; entities: EntityWithRoles[]; }) { const [selectedExam, setSelectedExam] = useState(); - const {exams, reload} = useExams(); - const {users} = useUsers(); - const {groups} = useGroups({admin: user?.id, userType: user?.type}); + const { exams, reload } = useExams(); + const { users } = useUsers(); + const { groups } = useGroups({ admin: user?.id, userType: user?.type }); const filteredExams = useMemo(() => exams.filter((e) => { if (!e.private) return true @@ -90,7 +90,7 @@ export default function ExamList({user, entities}: {user: User; entities: Entity }); }, [filteredExams, users]); - const {rows: filteredRows, renderSearch} = useListSearch(searchFields, parsedExams); + const { rows: filteredRows, renderSearch } = useListSearch(searchFields, parsedExams); const setExams = useExamStore((state) => state.setExams); const setSelectedModules = useExamStore((state) => state.setSelectedModules); @@ -110,14 +110,14 @@ export default function ExamList({user, entities}: {user: User; entities: Entity setExams([exam]); setSelectedModules([module]); - router.push("/exercises"); + router.push("/exam"); }; const privatizeExam = async (exam: Exam) => { if (!confirm(`Are you sure you want to make this ${capitalize(exam.module)} exam ${exam.private ? "public" : "private"}?`)) return; axios - .patch(`/api/exam/${exam.module}/${exam.id}`, {private: !exam.private}) + .patch(`/api/exam/${exam.module}/${exam.id}`, { private: !exam.private }) .then(() => toast.success(`Updated the "${exam.id}" exam`)) .catch((reason) => { if (reason.response.status === 404) { @@ -227,7 +227,7 @@ export default function ExamList({user, entities}: {user: User; entities: Entity { header: "", id: "actions", - cell: ({row}: {row: {original: Exam}}) => { + cell: ({ row }: { row: { original: Exam } }) => { return (
{(row.original.owners?.includes(user.id) || checkAccess(user, ["admin", "developer"])) && ( @@ -273,7 +273,7 @@ export default function ExamList({user, entities}: {user: User; entities: Entity {renderSearch()} setSelectedExam(undefined)}> {!!selectedExam ? ( - updateExam(selectedExam, {owners})} /> + updateExam(selectedExam, { owners })} /> ) : (
)} @@ -304,4 +304,4 @@ export default function ExamList({user, entities}: {user: User; entities: Entity
); -} \ No newline at end of file +}