Part and MC question grid jump to, has a bug on next going to refactor the whole thing

This commit is contained in:
Carlos Mesquita
2024-08-23 21:17:32 +01:00
parent b4b078c8c9
commit f0f38b335f
9 changed files with 339 additions and 114 deletions

View File

@@ -1,13 +1,17 @@
import {Module} from "@/interfaces";
import useExamStore from "@/stores/examStore";
import {moduleLabels} from "@/utils/moduleUtils";
import { Module } from "@/interfaces";
import { moduleLabels } from "@/utils/moduleUtils";
import clsx from "clsx";
import {motion} from "framer-motion";
import {ReactNode, useEffect, useState} from "react";
import {BsBook, BsClipboard, BsHeadphones, BsMegaphone, BsPen, BsStopwatch} from "react-icons/bs";
import { Fragment, ReactNode, useCallback, useState } from "react";
import { BsBook, BsClipboard, BsHeadphones, BsMegaphone, BsPen, BsStopwatch } from "react-icons/bs";
import ProgressBar from "../Low/ProgressBar";
import TimerEndedModal from "../TimerEndedModal";
import Timer from "./Timer";
import { Exam, LevelExam, MultipleChoiceExercise, ShuffleMap, UserSolution } from "@/interfaces/exam";
import { BsFillGrid3X3GapFill } from "react-icons/bs";
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import Button from "../Low/Button";
import { Dialog, Transition } from "@headlessui/react";
import useExamStore from "@/stores/examStore";
import Modal from "../Modal";
interface Props {
minTimer: number;
@@ -18,13 +22,32 @@ interface Props {
disableTimer?: boolean;
partLabel?: string;
showTimer?: boolean;
showSolutions?: boolean;
runOnClick?: ((questionIndex: number) => void) | undefined;
}
export default function ModuleTitle({
minTimer, module, label, exerciseIndex, totalExercises, disableTimer = false, partLabel, showTimer = true
export default function ModuleTitle({
minTimer,
module,
label,
exerciseIndex,
totalExercises,
disableTimer = false,
partLabel,
showTimer = true,
showSolutions = false,
runOnClick = undefined
}: Props) {
const {
userSolutions,
partIndex,
exam
} = useExamStore((state) => state);
const examExerciseIndex = useExamStore((state) => state.exerciseIndex)
const moduleIcon: {[key in Module]: ReactNode} = {
const [isOpen, setIsOpen] = useState(false);
const moduleIcon: { [key in Module]: ReactNode } = {
reading: <BsBook className="text-ielts-reading w-6 h-6" />,
listening: <BsHeadphones className="text-ielts-listening w-6 h-6" />,
writing: <BsPen className="text-ielts-writing w-6 h-6" />,
@@ -32,6 +55,78 @@ export default function ModuleTitle({
level: <BsClipboard className="text-ielts-level w-6 h-6" />,
};
const isMultipleChoiceLevelExercise = () => {
if (exam?.module === 'level' && typeof partIndex === "number" && partIndex > -1) {
const currentExercise = (exam as LevelExam).parts[partIndex].exercises[examExerciseIndex];
return currentExercise && currentExercise.type === 'multipleChoice';
}
return false;
};
const renderMCQuestionGrid = () => {
if (!isMultipleChoiceLevelExercise() && !userSolutions) return null;
const currentExercise = (exam as LevelExam).parts[partIndex!].exercises[examExerciseIndex] as MultipleChoiceExercise;
const userSolution = userSolutions!.find((x) => x.exercise == currentExercise.id)!;
const answeredQuestions = new Set(userSolution.solutions.map(sol => sol.question));
const exerciseOffset = currentExercise.questions[0].id;
const lastExercise = exerciseOffset + (currentExercise.questions.length - 1);
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 (!userSolutions) return "";
if (!userQuestionSolution) {
return "!bg-mti-gray-davy !border--mti-gray-davy !text-mti-gray-davy !text-white hover:!bg-gray-700";
}
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 (
<>
<h3 className="text-xl font-semibold mb-4 text-center">{`Part ${partIndex + 1} (Questions ${exerciseOffset} - ${lastExercise})`}</h3>
<div className="grid grid-cols-5 gap-3 px-4 py-2">
{currentExercise.questions.map((_, index) => {
const questionNumber = exerciseOffset + index;
const isAnswered = answeredQuestions.has(questionNumber);
const solution = currentExercise.questions.find((x) => x.id == questionNumber)!.solution;
const userQuestionSolution = currentExercise.userSolutions?.find((x) => x.question == questionNumber)?.option;
return (
<Button
variant={showSolutions ? "solid" : (isAnswered ? "solid" : "outline")}
key={index}
className={clsx(
"w-12 h-12 flex items-center justify-center rounded-lg text-sm font-bold transition-all duration-200 ease-in-out",
(showSolutions ?
getQuestionColor(questionNumber.toString(), solution, userQuestionSolution) :
(isAnswered ?
"bg-mti-purple-light border-mti-purple-light text-white hover:bg-mti-purple-dark hover:border-mti-purple-dark":
"bg-white border-gray-400 hover:bg-gray-100 hover:text-gray-700"
)
)
)}
onClick={() => { if (typeof runOnClick !== "undefined") { runOnClick(index); } setIsOpen(false); }}
>
{questionNumber}
</Button>
);
})}
</div>
<p className="mt-4 text-sm text-gray-600 text-center">
Click a question number to jump to that question
</p>
</>
);
};
return (
<>
{showTimer && <Timer minTimer={minTimer} disableTimer={disableTimer} />}
@@ -67,8 +162,24 @@ export default function ModuleTitle({
</div>
<ProgressBar color={module} label="" percentage={(exerciseIndex * 100) / totalExercises} className="h-2 w-full" />
</div>
{isMultipleChoiceLevelExercise() && (
<>
<Button variant="outline" onClick={() => setIsOpen(true)} padding="p-2" className="rounded-lg">
<BsFillGrid3X3GapFill size={24} />
</Button>
<Modal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
className="w-full max-w-md transform overflow-hidden rounded-2xl bg-white shadow-xl transition-all"
>
<>
{renderMCQuestionGrid()}
</>
</Modal>
</>
)}
</div>
</div>
</>
);
}
}