Added the capability for users to resume their previously stopped sessions
This commit is contained in:
@@ -59,12 +59,18 @@ export default function MultipleChoice({
|
|||||||
onBack,
|
onBack,
|
||||||
}: MultipleChoiceExercise & CommonProps) {
|
}: MultipleChoiceExercise & CommonProps) {
|
||||||
const [answers, setAnswers] = useState<{question: string; option: string}[]>(userSolutions);
|
const [answers, setAnswers] = useState<{question: string; option: string}[]>(userSolutions);
|
||||||
const [questionIndex, setQuestionIndex] = useState(0);
|
|
||||||
|
|
||||||
|
const {questionIndex, setQuestionIndex} = useExamStore((state) => state);
|
||||||
|
const {userSolutions: storeUserSolutions, setUserSolutions} = useExamStore((state) => state);
|
||||||
const hasExamEnded = useExamStore((state) => state.hasExamEnded);
|
const hasExamEnded = useExamStore((state) => state.hasExamEnded);
|
||||||
|
|
||||||
const scrollToTop = () => Array.from(document.getElementsByTagName("body")).forEach((body) => body.scrollTo(0, 0));
|
const scrollToTop = () => Array.from(document.getElementsByTagName("body")).forEach((body) => body.scrollTo(0, 0));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setUserSolutions([...storeUserSolutions.filter((x) => x.exercise !== id), {exercise: id, solutions: answers, score: calculateScore(), type}]);
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [answers]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hasExamEnded) onNext({exercise: id, solutions: answers, score: calculateScore(), type});
|
if (hasExamEnded) onNext({exercise: id, solutions: answers, score: calculateScore(), type});
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@@ -93,7 +99,7 @@ export default function MultipleChoice({
|
|||||||
if (questionIndex === questions.length - 1) {
|
if (questionIndex === questions.length - 1) {
|
||||||
onNext({exercise: id, solutions: answers, score: calculateScore(), type});
|
onNext({exercise: id, solutions: answers, score: calculateScore(), type});
|
||||||
} else {
|
} else {
|
||||||
setQuestionIndex((prev) => prev + 1);
|
setQuestionIndex(questionIndex + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollToTop();
|
scrollToTop();
|
||||||
@@ -103,7 +109,7 @@ export default function MultipleChoice({
|
|||||||
if (questionIndex === 0) {
|
if (questionIndex === 0) {
|
||||||
onBack({exercise: id, solutions: answers, score: calculateScore(), type});
|
onBack({exercise: id, solutions: answers, score: calculateScore(), type});
|
||||||
} else {
|
} else {
|
||||||
setQuestionIndex((prev) => prev - 1);
|
setQuestionIndex(questionIndex - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
scrollToTop();
|
scrollToTop();
|
||||||
|
|||||||
101
src/components/Medium/SessionCard.tsx
Normal file
101
src/components/Medium/SessionCard.tsx
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import {Session} from "@/hooks/useSessions";
|
||||||
|
import useExamStore from "@/stores/examStore";
|
||||||
|
import {sortByModuleName} from "@/utils/moduleUtils";
|
||||||
|
import axios from "axios";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import {capitalize} from "lodash";
|
||||||
|
import moment from "moment";
|
||||||
|
import {useState} from "react";
|
||||||
|
import {BsArrowRepeat, BsBook, BsClipboard, BsHeadphones, BsMegaphone, BsPen} from "react-icons/bs";
|
||||||
|
import {toast} from "react-toastify";
|
||||||
|
|
||||||
|
export default function SessionCard({
|
||||||
|
session,
|
||||||
|
reload,
|
||||||
|
loadSession,
|
||||||
|
}: {
|
||||||
|
session: Session;
|
||||||
|
reload: () => void;
|
||||||
|
loadSession: (session: Session) => Promise<void>;
|
||||||
|
}) {
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const deleteSession = async () => {
|
||||||
|
if (!confirm("Are you sure you want to delete this session?")) return;
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
await axios
|
||||||
|
.delete(`/api/sessions/${session.sessionId}`)
|
||||||
|
.then(() => {
|
||||||
|
toast.success(`Successfully delete session "${session.sessionId}"`);
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
toast.error("Something went wrong, please try again later");
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
reload();
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="border-mti-gray-anti-flash flex w-64 flex-col gap-3 rounded-xl border p-4 text-black">
|
||||||
|
<span className="flex gap-1">
|
||||||
|
<b>ID:</b>
|
||||||
|
{session.sessionId}
|
||||||
|
</span>
|
||||||
|
<span className="flex gap-1">
|
||||||
|
<b>Date:</b>
|
||||||
|
{moment(session.date).format("DD/MM/YYYY - HH:mm")}
|
||||||
|
</span>
|
||||||
|
<div className="flex w-full items-center justify-between">
|
||||||
|
<div className="-md:mt-2 grid w-full grid-cols-4 place-items-center justify-center gap-2">
|
||||||
|
{session.selectedModules.sort(sortByModuleName).map((module) => (
|
||||||
|
<div
|
||||||
|
key={module}
|
||||||
|
data-tip={capitalize(module)}
|
||||||
|
className={clsx(
|
||||||
|
"-md:px-4 tooltip flex w-fit items-center gap-2 rounded-xl py-2 text-white md:px-2 xl:px-4",
|
||||||
|
module === "reading" && "bg-ielts-reading",
|
||||||
|
module === "listening" && "bg-ielts-listening",
|
||||||
|
module === "writing" && "bg-ielts-writing",
|
||||||
|
module === "speaking" && "bg-ielts-speaking",
|
||||||
|
module === "level" && "bg-ielts-level",
|
||||||
|
)}>
|
||||||
|
{module === "reading" && <BsBook className="h-4 w-4" />}
|
||||||
|
{module === "listening" && <BsHeadphones className="h-4 w-4" />}
|
||||||
|
{module === "writing" && <BsPen className="h-4 w-4" />}
|
||||||
|
{module === "speaking" && <BsMegaphone className="h-4 w-4" />}
|
||||||
|
{module === "level" && <BsClipboard className="h-4 w-4" />}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 w-full">
|
||||||
|
<button
|
||||||
|
onClick={async () => await loadSession(session)}
|
||||||
|
disabled={isLoading}
|
||||||
|
className="bg-mti-green-ultralight w-full hover:bg-mti-green-light rounded-lg p-2 px-4 transition duration-300 ease-in-out hover:text-white disabled:cursor-not-allowed">
|
||||||
|
{!isLoading && "Resume"}
|
||||||
|
{isLoading && (
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<BsArrowRepeat className="animate-spin text-white" size={25} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={deleteSession}
|
||||||
|
disabled={isLoading}
|
||||||
|
className="bg-mti-red-ultralight w-full hover:bg-mti-red-light rounded-lg p-2 px-4 transition duration-300 ease-in-out hover:text-white disabled:cursor-not-allowed">
|
||||||
|
{!isLoading && "Delete"}
|
||||||
|
{isLoading && (
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<BsArrowRepeat className="animate-spin text-white" size={25} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -32,12 +32,12 @@ export default function Listening({exam, showSolutions = false, onFinish}: Props
|
|||||||
const scrollToTop = () => Array.from(document.getElementsByTagName("body")).forEach((body) => body.scrollTo(0, 0));
|
const scrollToTop = () => Array.from(document.getElementsByTagName("body")).forEach((body) => body.scrollTo(0, 0));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showSolutions) setExerciseIndex(-1);
|
if (showSolutions) return setExerciseIndex(-1);
|
||||||
}, [setExerciseIndex, showSolutions]);
|
}, [setExerciseIndex, showSolutions]);
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
if (exam.variant !== "partial") setPartIndex(-1);
|
// if (exam.variant !== "partial") setPartIndex(-1);
|
||||||
}, [exam.variant, setPartIndex]);
|
// }, [exam.variant, setPartIndex]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hasExamEnded && exerciseIndex === -1) {
|
if (hasExamEnded && exerciseIndex === -1) {
|
||||||
@@ -214,7 +214,7 @@ export default function Listening({exam, showSolutions = false, onFinish}: Props
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{exerciseIndex === -1 && partIndex === -1 && exam.variant !== "partial" && (
|
{partIndex === -1 && exam.variant !== "partial" && (
|
||||||
<Button color="purple" onClick={() => setPartIndex(0)} className="max-w-[200px] self-end w-full justify-self-end">
|
<Button color="purple" onClick={() => setPartIndex(0)} className="max-w-[200px] self-end w-full justify-self-end">
|
||||||
Start now
|
Start now
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {Module} from "@/interfaces";
|
|||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import {User} from "@/interfaces/user";
|
import {User} from "@/interfaces/user";
|
||||||
import ProgressBar from "@/components/Low/ProgressBar";
|
import ProgressBar from "@/components/Low/ProgressBar";
|
||||||
import {BsBook, BsCheck, BsCheckCircle, BsClipboard, BsHeadphones, BsMegaphone, BsPen, BsXCircle} from "react-icons/bs";
|
import {BsArrowRepeat, BsBook, BsCheck, BsCheckCircle, BsClipboard, BsHeadphones, BsMegaphone, BsPen, BsXCircle} from "react-icons/bs";
|
||||||
import {totalExamsByModule} from "@/utils/stats";
|
import {totalExamsByModule} from "@/utils/stats";
|
||||||
import useStats from "@/hooks/useStats";
|
import useStats from "@/hooks/useStats";
|
||||||
import Button from "@/components/Low/Button";
|
import Button from "@/components/Low/Button";
|
||||||
@@ -13,6 +13,10 @@ import {sortByModuleName} from "@/utils/moduleUtils";
|
|||||||
import {capitalize} from "lodash";
|
import {capitalize} from "lodash";
|
||||||
import ProfileSummary from "@/components/ProfileSummary";
|
import ProfileSummary from "@/components/ProfileSummary";
|
||||||
import {Variant} from "@/interfaces/exam";
|
import {Variant} from "@/interfaces/exam";
|
||||||
|
import useSessions, {Session} from "@/hooks/useSessions";
|
||||||
|
import SessionCard from "@/components/Medium/SessionCard";
|
||||||
|
import useExamStore from "@/stores/examStore";
|
||||||
|
import moment from "moment";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: User;
|
user: User;
|
||||||
@@ -25,13 +29,32 @@ export default function Selection({user, page, onStart, disableSelection = false
|
|||||||
const [selectedModules, setSelectedModules] = useState<Module[]>([]);
|
const [selectedModules, setSelectedModules] = useState<Module[]>([]);
|
||||||
const [avoidRepeatedExams, setAvoidRepeatedExams] = useState(true);
|
const [avoidRepeatedExams, setAvoidRepeatedExams] = useState(true);
|
||||||
const [variant, setVariant] = useState<Variant>("full");
|
const [variant, setVariant] = useState<Variant>("full");
|
||||||
|
|
||||||
const {stats} = useStats(user?.id);
|
const {stats} = useStats(user?.id);
|
||||||
|
const {sessions, isLoading, reload} = useSessions(user.id);
|
||||||
|
|
||||||
|
const state = useExamStore((state) => state);
|
||||||
|
|
||||||
const toggleModule = (module: Module) => {
|
const toggleModule = (module: Module) => {
|
||||||
const modules = selectedModules.filter((x) => x !== module);
|
const modules = selectedModules.filter((x) => x !== module);
|
||||||
setSelectedModules((prev) => (prev.includes(module) ? modules : [...modules, module]));
|
setSelectedModules((prev) => (prev.includes(module) ? modules : [...modules, module]));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const loadSession = async (session: Session) => {
|
||||||
|
state.setSelectedModules(session.selectedModules);
|
||||||
|
state.setExam(session.exam);
|
||||||
|
state.setExams(session.exams);
|
||||||
|
state.setSessionId(session.sessionId);
|
||||||
|
state.setAssignment(session.assignment);
|
||||||
|
state.setExerciseIndex(session.exerciseIndex);
|
||||||
|
state.setPartIndex(session.partIndex);
|
||||||
|
state.setModuleIndex(session.moduleIndex);
|
||||||
|
state.setTimeSpent(session.timeSpent);
|
||||||
|
state.setUserSolutions(session.userSolutions);
|
||||||
|
state.setShowSolutions(false);
|
||||||
|
state.setQuestionIndex(session.questionIndex);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="relative flex h-full w-full flex-col gap-8 md:gap-16">
|
<div className="relative flex h-full w-full flex-col gap-8 md:gap-16">
|
||||||
@@ -94,7 +117,28 @@ export default function Selection({user, page, onStart, disableSelection = false
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</section>
|
</section>
|
||||||
<section className="-lg:flex-col -lg:items-center -lg:gap-12 mt-8 flex w-full justify-between gap-8">
|
|
||||||
|
{sessions.length > 0 && (
|
||||||
|
<section className="flex flex-col gap-3 md:gap-3">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<div
|
||||||
|
onClick={reload}
|
||||||
|
className="text-mti-purple-light hover:text-mti-purple-dark flex cursor-pointer items-center gap-2 transition duration-300 ease-in-out">
|
||||||
|
<span className="text-mti-black text-lg font-bold">Unfinished Sessions</span>
|
||||||
|
<BsArrowRepeat className={clsx("text-xl", isLoading && "animate-spin")} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span className="text-mti-gray-taupe scrollbar-hide flex gap-8 overflow-x-scroll">
|
||||||
|
{sessions
|
||||||
|
.sort((a, b) => moment(b.date).diff(moment(a.date)))
|
||||||
|
.map((session) => (
|
||||||
|
<SessionCard session={session} key={session.sessionId} reload={reload} loadSession={loadSession} />
|
||||||
|
))}
|
||||||
|
</span>
|
||||||
|
</section>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<section className="-lg:flex-col -lg:items-center -lg:gap-12 mt-4 flex w-full justify-between gap-8">
|
||||||
<div
|
<div
|
||||||
onClick={!disableSelection && !selectedModules.includes("level") ? () => toggleModule("reading") : undefined}
|
onClick={!disableSelection && !selectedModules.includes("level") ? () => toggleModule("reading") : undefined}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {ExamState} from "@/stores/examStore";
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
|
|
||||||
export type Session = ExamState & {user: string};
|
export type Session = ExamState & {user: string; id: string; date: string};
|
||||||
|
|
||||||
export default function useSessions(user?: string) {
|
export default function useSessions(user?: string) {
|
||||||
const [sessions, setSessions] = useState<Session[]>([]);
|
const [sessions, setSessions] = useState<Session[]>([]);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {useRouter} from "next/router";
|
|||||||
import {toast, ToastContainer} from "react-toastify";
|
import {toast, ToastContainer} from "react-toastify";
|
||||||
import {v4 as uuidv4} from "uuid";
|
import {v4 as uuidv4} from "uuid";
|
||||||
import useSessions from "@/hooks/useSessions";
|
import useSessions from "@/hooks/useSessions";
|
||||||
|
import ShortUniqueId from "short-unique-id";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
page: "exams" | "exercises";
|
page: "exams" | "exercises";
|
||||||
@@ -36,15 +37,17 @@ export default function ExamPage({page}: Props) {
|
|||||||
const [statsAwaitingEvaluation, setStatsAwaitingEvaluation] = useState<string[]>([]);
|
const [statsAwaitingEvaluation, setStatsAwaitingEvaluation] = useState<string[]>([]);
|
||||||
const [timeSpent, setTimeSpent] = useState(0);
|
const [timeSpent, setTimeSpent] = useState(0);
|
||||||
|
|
||||||
const partIndex = useExamStore((state) => state.partIndex);
|
const resetStore = useExamStore((state) => state.reset);
|
||||||
const assignment = useExamStore((state) => state.assignment);
|
const assignment = useExamStore((state) => state.assignment);
|
||||||
const initialTimeSpent = useExamStore((state) => state.timeSpent);
|
const initialTimeSpent = useExamStore((state) => state.timeSpent);
|
||||||
const exerciseIndex = useExamStore((state) => state.exerciseIndex);
|
|
||||||
|
|
||||||
const {exam, setExam} = useExamStore((state) => state);
|
const {exam, setExam} = useExamStore((state) => state);
|
||||||
const {exams, setExams} = useExamStore((state) => state);
|
const {exams, setExams} = useExamStore((state) => state);
|
||||||
const {sessionId, setSessionId} = useExamStore((state) => state);
|
const {sessionId, setSessionId} = useExamStore((state) => state);
|
||||||
|
const {partIndex, setPartIndex} = useExamStore((state) => state);
|
||||||
const {moduleIndex, setModuleIndex} = useExamStore((state) => state);
|
const {moduleIndex, setModuleIndex} = useExamStore((state) => state);
|
||||||
|
const {questionIndex, setQuestionIndex} = useExamStore((state) => state);
|
||||||
|
const {exerciseIndex, setExerciseIndex} = useExamStore((state) => state);
|
||||||
const {userSolutions, setUserSolutions} = useExamStore((state) => state);
|
const {userSolutions, setUserSolutions} = useExamStore((state) => state);
|
||||||
const {showSolutions, setShowSolutions} = useExamStore((state) => state);
|
const {showSolutions, setShowSolutions} = useExamStore((state) => state);
|
||||||
const {selectedModules, setSelectedModules} = useExamStore((state) => state);
|
const {selectedModules, setSelectedModules} = useExamStore((state) => state);
|
||||||
@@ -52,10 +55,23 @@ export default function ExamPage({page}: Props) {
|
|||||||
const {user} = useUser({redirectTo: "/login"});
|
const {user} = useUser({redirectTo: "/login"});
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
resetStore();
|
||||||
|
setVariant("full");
|
||||||
|
setAvoidRepeated(false);
|
||||||
|
setHasBeenUploaded(false);
|
||||||
|
setShowAbandonPopup(false);
|
||||||
|
setIsEvaluationLoading(false);
|
||||||
|
setStatsAwaitingEvaluation([]);
|
||||||
|
setTimeSpent(0);
|
||||||
|
};
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
const saveSession = async () => {
|
const saveSession = async () => {
|
||||||
await axios.post("/api/sessions", {
|
await axios.post("/api/sessions", {
|
||||||
id: sessionId,
|
id: sessionId,
|
||||||
|
sessionId,
|
||||||
|
date: new Date().toISOString(),
|
||||||
userSolutions,
|
userSolutions,
|
||||||
moduleIndex,
|
moduleIndex,
|
||||||
selectedModules,
|
selectedModules,
|
||||||
@@ -65,6 +81,7 @@ export default function ExamPage({page}: Props) {
|
|||||||
exam,
|
exam,
|
||||||
partIndex,
|
partIndex,
|
||||||
exerciseIndex,
|
exerciseIndex,
|
||||||
|
questionIndex,
|
||||||
user: user?.id,
|
user: user?.id,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -82,7 +99,7 @@ export default function ExamPage({page}: Props) {
|
|||||||
if (sessionId.length > 0 && userSolutions.length > 0 && selectedModules.length > 0 && exams.length > 0 && !!exam && timeSpent > 0)
|
if (sessionId.length > 0 && userSolutions.length > 0 && selectedModules.length > 0 && exams.length > 0 && !!exam && timeSpent > 0)
|
||||||
saveSession();
|
saveSession();
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [assignment, exam, exams, moduleIndex, selectedModules, sessionId, userSolutions, user, exerciseIndex, partIndex]);
|
}, [assignment, exam, exams, moduleIndex, selectedModules, sessionId, userSolutions, user, exerciseIndex, partIndex, questionIndex]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (timeSpent % 20 === 0 && timeSpent > 0) saveSession();
|
if (timeSpent % 20 === 0 && timeSpent > 0) saveSession();
|
||||||
@@ -90,10 +107,11 @@ export default function ExamPage({page}: Props) {
|
|||||||
}, [timeSpent]);
|
}, [timeSpent]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedModules.length > 0) {
|
if (selectedModules.length > 0 && sessionId.length === 0) {
|
||||||
setSessionId(uuidv4());
|
const shortUID = new ShortUniqueId();
|
||||||
|
setSessionId(shortUID.randomUUID(8));
|
||||||
}
|
}
|
||||||
}, [setSessionId, selectedModules]);
|
}, [setSessionId, selectedModules, sessionId]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (user?.type === "developer") console.log(exam);
|
if (user?.type === "developer") console.log(exam);
|
||||||
}, [exam, user]);
|
}, [exam, user]);
|
||||||
@@ -258,6 +276,10 @@ export default function ExamPage({page}: Props) {
|
|||||||
|
|
||||||
setUserSolutions([...userSolutions.filter((x) => !solutionIds.includes(x.exercise)), ...solutions]);
|
setUserSolutions([...userSolutions.filter((x) => !solutionIds.includes(x.exercise)), ...solutions]);
|
||||||
setModuleIndex(moduleIndex + 1);
|
setModuleIndex(moduleIndex + 1);
|
||||||
|
|
||||||
|
setPartIndex(0);
|
||||||
|
setExerciseIndex(-1);
|
||||||
|
setQuestionIndex(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
const aggregateScoresByModule = (answers: UserSolution[]): {module: Module; total: number; missing: number; correct: number}[] => {
|
const aggregateScoresByModule = (answers: UserSolution[]): {module: Module; total: number; missing: number; correct: number}[] => {
|
||||||
@@ -377,7 +399,9 @@ export default function ExamPage({page}: Props) {
|
|||||||
abandonPopupTitle="Leave Exercise"
|
abandonPopupTitle="Leave Exercise"
|
||||||
abandonPopupDescription="Are you sure you want to leave the exercise? You will lose all your progress."
|
abandonPopupDescription="Are you sure you want to leave the exercise? You will lose all your progress."
|
||||||
abandonConfirmButtonText="Confirm"
|
abandonConfirmButtonText="Confirm"
|
||||||
onAbandon={() => router.reload()}
|
onAbandon={() => {
|
||||||
|
reset();
|
||||||
|
}}
|
||||||
onCancel={() => setShowAbandonPopup(false)}
|
onCancel={() => setShowAbandonPopup(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export interface ExamState {
|
|||||||
exam?: Exam;
|
exam?: Exam;
|
||||||
partIndex: number;
|
partIndex: number;
|
||||||
exerciseIndex: number;
|
exerciseIndex: number;
|
||||||
|
questionIndex: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExamFunctions {
|
export interface ExamFunctions {
|
||||||
@@ -24,13 +25,14 @@ export interface ExamFunctions {
|
|||||||
setShowSolutions: (showSolutions: boolean) => void;
|
setShowSolutions: (showSolutions: boolean) => void;
|
||||||
setHasExamEnded: (hasExamEnded: boolean) => void;
|
setHasExamEnded: (hasExamEnded: boolean) => void;
|
||||||
setSelectedModules: (modules: Module[]) => void;
|
setSelectedModules: (modules: Module[]) => void;
|
||||||
setAssignment: (assignment: Assignment) => void;
|
setAssignment: (assignment?: Assignment) => void;
|
||||||
setTimeSpent: (timeSpent: number) => void;
|
setTimeSpent: (timeSpent: number) => void;
|
||||||
setSessionId: (sessionId: string) => void;
|
setSessionId: (sessionId: string) => void;
|
||||||
setModuleIndex: (moduleIndex: number) => void;
|
setModuleIndex: (moduleIndex: number) => void;
|
||||||
setExam: (exam?: Exam) => void;
|
setExam: (exam?: Exam) => void;
|
||||||
setPartIndex: (partIndex: number) => void;
|
setPartIndex: (partIndex: number) => void;
|
||||||
setExerciseIndex: (exerciseIndex: number) => void;
|
setExerciseIndex: (exerciseIndex: number) => void;
|
||||||
|
setQuestionIndex: (questionIndex: number) => void;
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,7 +48,8 @@ export const initialState: ExamState = {
|
|||||||
exam: undefined,
|
exam: undefined,
|
||||||
moduleIndex: 0,
|
moduleIndex: 0,
|
||||||
partIndex: 0,
|
partIndex: 0,
|
||||||
exerciseIndex: 0,
|
exerciseIndex: -1,
|
||||||
|
questionIndex: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const useExamStore = create<ExamState & ExamFunctions>((set) => ({
|
const useExamStore = create<ExamState & ExamFunctions>((set) => ({
|
||||||
@@ -57,13 +60,14 @@ const useExamStore = create<ExamState & ExamFunctions>((set) => ({
|
|||||||
setShowSolutions: (showSolutions: boolean) => set(() => ({showSolutions})),
|
setShowSolutions: (showSolutions: boolean) => set(() => ({showSolutions})),
|
||||||
setSelectedModules: (modules: Module[]) => set(() => ({selectedModules: modules})),
|
setSelectedModules: (modules: Module[]) => set(() => ({selectedModules: modules})),
|
||||||
setHasExamEnded: (hasExamEnded: boolean) => set(() => ({hasExamEnded})),
|
setHasExamEnded: (hasExamEnded: boolean) => set(() => ({hasExamEnded})),
|
||||||
setAssignment: (assignment: Assignment) => set(() => ({assignment})),
|
setAssignment: (assignment?: Assignment) => set(() => ({assignment})),
|
||||||
setTimeSpent: (timeSpent) => set(() => ({timeSpent})),
|
setTimeSpent: (timeSpent) => set(() => ({timeSpent})),
|
||||||
setSessionId: (sessionId: string) => set(() => ({sessionId})),
|
setSessionId: (sessionId: string) => set(() => ({sessionId})),
|
||||||
setExam: (exam?: Exam) => set(() => ({exam})),
|
setExam: (exam?: Exam) => set(() => ({exam})),
|
||||||
setModuleIndex: (moduleIndex: number) => set(() => ({moduleIndex})),
|
setModuleIndex: (moduleIndex: number) => set(() => ({moduleIndex})),
|
||||||
setPartIndex: (partIndex: number) => set(() => ({partIndex})),
|
setPartIndex: (partIndex: number) => set(() => ({partIndex})),
|
||||||
setExerciseIndex: (exerciseIndex: number) => set(() => ({exerciseIndex})),
|
setExerciseIndex: (exerciseIndex: number) => set(() => ({exerciseIndex})),
|
||||||
|
setQuestionIndex: (questionIndex: number) => set(() => ({questionIndex})),
|
||||||
|
|
||||||
reset: () => set(() => initialState),
|
reset: () => set(() => initialState),
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user