From 7ae91d7bc1a1bfda742f76ab010b4b2875c7cb20 Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Thu, 7 Nov 2024 14:55:44 +0000 Subject: [PATCH] Updated some troubles related to the Level Exam --- .../Medium/ModuleTitle/MCQuestionGrid.tsx | 157 +++++++++--------- src/components/Medium/ModuleTitle/index.tsx | 22 ++- src/pages/official-exam.tsx | 8 +- 3 files changed, 92 insertions(+), 95 deletions(-) diff --git a/src/components/Medium/ModuleTitle/MCQuestionGrid.tsx b/src/components/Medium/ModuleTitle/MCQuestionGrid.tsx index ba0d67c6..11673641 100644 --- a/src/components/Medium/ModuleTitle/MCQuestionGrid.tsx +++ b/src/components/Medium/ModuleTitle/MCQuestionGrid.tsx @@ -3,105 +3,96 @@ import Modal from "@/components/Modal"; import { Exam, LevelExam, MultipleChoiceExercise, ShuffleMap } from "@/interfaces/exam"; import useExamStore from "@/stores/examStore"; import clsx from "clsx"; -import { useState } from "react"; +import { useMemo, useState } from "react"; import { BsFillGrid3X3GapFill } from "react-icons/bs"; interface Props { - showSolutions: boolean; - runOnClick: ((index: number) => void) | undefined; + exam: LevelExam + showSolutions: boolean; + runOnClick: ((index: number) => void) | undefined; } -const MCQuestionGrid: React.FC = ({showSolutions, runOnClick}) => { - const [isOpen, setIsOpen] = useState(false); +const MCQuestionGrid: React.FC = ({ exam, showSolutions, runOnClick }) => { + const [isOpen, setIsOpen] = useState(false); - const { + const { userSolutions, partIndex: sectionIndex, - exerciseIndex, - exam + exerciseIndex, } = useExamStore((state) => state); - const isMultipleChoiceLevelExercise = () => { - if (exam?.module === 'level' && typeof sectionIndex === "number" && sectionIndex > -1) { - const currentExercise = (exam as LevelExam).parts[sectionIndex].exercises[exerciseIndex]; - return currentExercise && currentExercise.type === 'multipleChoice'; - } - return false; - }; + const currentExercise = useMemo(() => (exam as LevelExam).parts[sectionIndex!].exercises[exerciseIndex] as MultipleChoiceExercise, [exam, exerciseIndex, sectionIndex]) + const userSolution = useMemo(() => userSolutions!.find((x) => x.exercise.toString() == currentExercise.id.toString())!, [currentExercise.id, userSolutions]) + const answeredQuestions = useMemo(() => new Set(userSolution.solutions.map(sol => sol.question.toString())), [userSolution.solutions]) + const exerciseOffset = useMemo(() => Number(currentExercise.questions[0].id), [currentExercise.questions]) + const lastExercise = useMemo(() => exerciseOffset + (currentExercise.questions.length - 1), + [currentExercise.questions.length, exerciseOffset]); - if (!isMultipleChoiceLevelExercise() && !userSolutions) return null; + const getQuestionColor = (questionId: string, solution: string, userQuestionSolution: string | undefined) => { + const questionShuffleMap = userSolutions.reduce((foundMap, userSolution) => { + if (foundMap) return foundMap; + return userSolution.shuffleMaps?.find(map => map.questionID.toString() === questionId.toString()) || null; + }, null as ShuffleMap | null); + const newSolution = questionShuffleMap ? questionShuffleMap?.map[solution] : solution; - const currentExercise = (exam as LevelExam).parts[sectionIndex!].exercises[exerciseIndex] as MultipleChoiceExercise; - const userSolution = userSolutions!.find((x) => x.exercise.toString() == currentExercise.id.toString())!; - const answeredQuestions = new Set(userSolution.solutions.map(sol => sol.question.toString())); - const exerciseOffset = Number(currentExercise.questions[0].id); - const lastExercise = exerciseOffset + (currentExercise.questions.length - 1); + if (!userSolutions) return ""; - const getQuestionColor = (questionId: string, solution: string, userQuestionSolution: string | undefined) => { - const questionShuffleMap = userSolutions.reduce((foundMap, userSolution) => { - if (foundMap) return foundMap; - return userSolution.shuffleMaps?.find(map => map.questionID.toString() === questionId.toString()) || null; - }, null as ShuffleMap | null); - const newSolution = questionShuffleMap ? questionShuffleMap?.map[solution] : solution; + if (!userQuestionSolution) { + return "!bg-mti-gray-davy !border--mti-gray-davy !text-mti-gray-davy !text-white hover:!bg-gray-700"; + } - if (!userSolutions) return ""; + return userQuestionSolution === newSolution ? + "!bg-mti-purple-light !text-mti-purple-light !text-white hover:!bg-mti-purple-dark" : + "!bg-mti-rose-light !border-mti-rose-light !text-mti-rose-light !text-white hover:!bg-mti-rose-dark"; + } - if (!userQuestionSolution) { - return "!bg-mti-gray-davy !border--mti-gray-davy !text-mti-gray-davy !text-white hover:!bg-gray-700"; - } + return ( + <> + + setIsOpen(false)} + className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white shadow-xl transition-all" + > + <> +

{`Part ${sectionIndex + 1} (Questions ${exerciseOffset} - ${lastExercise})`}

+
+ {currentExercise.questions.map((_, index) => { + const questionNumber = exerciseOffset + index; + const isAnswered = answeredQuestions.has(questionNumber.toString()); + const solution = currentExercise.questions.find((x) => x.id.toString() == questionNumber.toString())!.solution; - return userQuestionSolution === newSolution ? - "!bg-mti-purple-light !text-mti-purple-light !text-white hover:!bg-mti-purple-dark" : - "!bg-mti-rose-light !border-mti-rose-light !text-mti-rose-light !text-white hover:!bg-mti-rose-dark"; - } - - return ( - <> - - setIsOpen(false)} - className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white shadow-xl transition-all" - > - <> -

{`Part ${sectionIndex + 1} (Questions ${exerciseOffset} - ${lastExercise})`}

-
- {currentExercise.questions.map((_, index) => { - const questionNumber = exerciseOffset + index; - const isAnswered = answeredQuestions.has(questionNumber.toString()); - const solution = currentExercise.questions.find((x) => x.id.toString() == questionNumber.toString())!.solution; - - const userQuestionSolution = currentExercise.userSolutions?.find((x) => x.question.toString() == questionNumber.toString())?.option; - return ( - - ); - })} -
-

- Click a question number to jump to that question -

- -
- - ); + const userQuestionSolution = currentExercise.userSolutions?.find((x) => x.question.toString() == questionNumber.toString())?.option; + return ( + + ); + })} +
+

+ Click a question number to jump to that question +

+ +
+ + ); } export default MCQuestionGrid; diff --git a/src/components/Medium/ModuleTitle/index.tsx b/src/components/Medium/ModuleTitle/index.tsx index f529b6f9..c22ed521 100644 --- a/src/components/Medium/ModuleTitle/index.tsx +++ b/src/components/Medium/ModuleTitle/index.tsx @@ -1,11 +1,11 @@ import { Module } from "@/interfaces"; import { moduleLabels } from "@/utils/moduleUtils"; import clsx from "clsx"; -import { ReactNode, useState } from "react"; +import { ReactNode, useMemo, useState } from "react"; import { BsBook, BsClipboard, BsHeadphones, BsMegaphone, BsPen } from "react-icons/bs"; import ProgressBar from "../../Low/ProgressBar"; import Timer from "../Timer"; -import { Exercise } from "@/interfaces/exam"; +import { Exercise, LevelExam } from "@/interfaces/exam"; import useExamStore from "@/stores/examStore"; import React from "react"; import MCQuestionGrid from "./MCQuestionGrid"; @@ -38,9 +38,7 @@ export default function ModuleTitle({ showSolutions = false, runOnClick = undefined }: Props) { - const { - exam - } = useExamStore((state) => state); + const { exam, partIndex, exerciseIndex: examExerciseIndex, userSolutions } = useExamStore((state) => state); const moduleIcon: { [key in Module]: ReactNode } = { reading: , @@ -50,6 +48,14 @@ export default function ModuleTitle({ level: , }; + const showGrid = useMemo(() => + exam?.module === "level" + && partIndex > -1 + && exam.parts[partIndex].exercises[examExerciseIndex].type === "multipleChoice" + && !!userSolutions, + [exam, examExerciseIndex, partIndex, userSolutions] + ) + return ( <> {showTimer && } @@ -67,7 +73,7 @@ export default function ModuleTitle({ return (
{partInstructions.split("\\n").map((line, lineIndex) => ( - not correct')}}> + not correct') }}> ))}
); @@ -87,9 +93,9 @@ export default function ModuleTitle({ - {exam?.module === "level" && } + {showGrid && } ); -} \ No newline at end of file +} diff --git a/src/pages/official-exam.tsx b/src/pages/official-exam.tsx index 7613a935..241742a8 100644 --- a/src/pages/official-exam.tsx +++ b/src/pages/official-exam.tsx @@ -13,7 +13,7 @@ import { Assignment } from "@/interfaces/results"; import { Stat, User } from "@/interfaces/user"; import { sessionOptions } from "@/lib/session"; import useExamStore from "@/stores/examStore"; -import { findBy, mapBy, redirect, serialize } from "@/utils"; +import { filterBy, findBy, mapBy, redirect, serialize } from "@/utils"; import { requestUser } from "@/utils/api"; import { activeAssignmentFilter } from "@/utils/assignments"; import { getAssignmentsByAssignee } from "@/utils/assignments.be"; @@ -60,7 +60,7 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => { const examIDs = uniqBy( assignments.flatMap((a) => - a.exams.filter((e) => e.assignee === user.id).map((e) => ({ module: e.module, id: e.id, key: `${e.module}_${e.id}` })), + filterBy(a.exams, 'assignee', user.id).map((e) => ({ module: e.module, id: e.id, key: `${e.module}_${e.id}` })), ), "key", ); @@ -93,7 +93,7 @@ export default function OfficialExam({ user, entities, assignments, sessions, ex state.setSelectedModules(mapBy(assignmentExams.sort(sortByModule), 'module')); state.setAssignment(assignment); - router.push("/exam"); + router.push(`/exam?assignment=${assignment.id}`); } }; @@ -112,7 +112,7 @@ export default function OfficialExam({ user, entities, assignments, sessions, ex state.setShowSolutions(false); state.setQuestionIndex(session.questionIndex); - router.push("/exam"); + router.push(`/exam?assignment=${session.assignment?.id}`); }; const logout = async () => {