ENCOA-154: Increase the number of questions per page in all modules (Priority in Level Test)
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import { MultipleChoiceExercise, MultipleChoiceQuestion, ShuffleMap } from "@/interfaces/exam";
|
||||
import {MultipleChoiceExercise, MultipleChoiceQuestion, ShuffleMap} from "@/interfaces/exam";
|
||||
import useExamStore from "@/stores/examStore";
|
||||
import clsx from "clsx";
|
||||
import { useEffect, useState } from "react";
|
||||
import {useEffect, useState} from "react";
|
||||
import reactStringReplace from "react-string-replace";
|
||||
import { CommonProps } from ".";
|
||||
import {CommonProps} from ".";
|
||||
import Button from "../Low/Button";
|
||||
import { v4 } from "uuid";
|
||||
import {v4} from "uuid";
|
||||
|
||||
function Question({
|
||||
id,
|
||||
@@ -18,9 +18,8 @@ function Question({
|
||||
}: MultipleChoiceQuestion & {
|
||||
userSolution: string | undefined;
|
||||
onSelectOption?: (option: string) => void;
|
||||
showSolution?: boolean,
|
||||
showSolution?: boolean;
|
||||
}) {
|
||||
|
||||
const renderPrompt = (prompt: string) => {
|
||||
return reactStringReplace(prompt, /(<u>.*?<\/u>)/g, (match) => {
|
||||
const word = match.replaceAll("<u>", "").replaceAll("</u>", "");
|
||||
@@ -49,7 +48,9 @@ function Question({
|
||||
"flex flex-col items-center border border-mti-gray-platinum p-4 px-8 rounded-xl gap-4 cursor-pointer bg-white relative select-none",
|
||||
userSolution === option.id.toString() && "border-mti-purple-light",
|
||||
)}>
|
||||
<span key={v4()} className={clsx("text-sm", userSolution !== option.id.toString() && "opacity-50")}>{option.id.toString()}</span>
|
||||
<span key={v4()} className={clsx("text-sm", userSolution !== option.id.toString() && "opacity-50")}>
|
||||
{option.id.toString()}
|
||||
</span>
|
||||
<img src={option.src!} alt={`Option ${option.id.toString()}`} />
|
||||
</div>
|
||||
))}
|
||||
@@ -71,38 +72,30 @@ function Question({
|
||||
);
|
||||
}
|
||||
|
||||
export default function MultipleChoice({ id, prompt, type, questions, userSolutions, onNext, onBack }: MultipleChoiceExercise & CommonProps) {
|
||||
const [answers, setAnswers] = useState<{ question: string; option: string }[]>(userSolutions);
|
||||
export default function MultipleChoice({id, prompt, type, questions, userSolutions, onNext, onBack}: MultipleChoiceExercise & CommonProps) {
|
||||
const [answers, setAnswers] = useState<{question: string; option: string}[]>(userSolutions);
|
||||
|
||||
const {
|
||||
questionIndex,
|
||||
exerciseIndex,
|
||||
exam,
|
||||
shuffles,
|
||||
hasExamEnded,
|
||||
partIndex,
|
||||
setQuestionIndex,
|
||||
setCurrentSolution
|
||||
} = useExamStore((state) => state);
|
||||
const {questionIndex, exerciseIndex, exam, shuffles, hasExamEnded, partIndex, setQuestionIndex, setCurrentSolution} = useExamStore(
|
||||
(state) => state,
|
||||
);
|
||||
|
||||
const shuffleMaps = shuffles.find((x) => x.exerciseID == id)?.shuffles;
|
||||
|
||||
const scrollToTop = () => Array.from(document.getElementsByTagName("body")).forEach((body) => body.scrollTo(0, 0));
|
||||
|
||||
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
|
||||
}, [hasExamEnded]);
|
||||
|
||||
const onSelectOption = (option: string) => {
|
||||
const question = questions[questionIndex];
|
||||
setAnswers((prev) => [...prev.filter((x) => x.question !== question.id), { option, question: question.id }]);
|
||||
const onSelectOption = (option: string, question: MultipleChoiceQuestion) => {
|
||||
setAnswers((prev) => [...prev.filter((x) => x.question !== question.id), {option, question: question.id}]);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentSolution({ exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps });
|
||||
setCurrentSolution({exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [answers, setAnswers])
|
||||
}, [answers, setAnswers]);
|
||||
|
||||
const getShuffledSolution = (originalSolution: string, questionShuffleMap: ShuffleMap) => {
|
||||
for (const [newPosition, originalPosition] of Object.entries(questionShuffleMap.map)) {
|
||||
@@ -111,8 +104,7 @@ export default function MultipleChoice({ id, prompt, type, questions, userSoluti
|
||||
}
|
||||
}
|
||||
return originalSolution;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const calculateScore = () => {
|
||||
const total = questions.length;
|
||||
@@ -135,24 +127,24 @@ export default function MultipleChoice({ id, prompt, type, questions, userSoluti
|
||||
return isSolutionCorrect || false;
|
||||
}).length;
|
||||
const missing = total - answers!.filter((x) => questions.find((y) => x.question.toString() === y.id.toString())).length;
|
||||
return { total, correct, missing };
|
||||
return {total, correct, missing};
|
||||
};
|
||||
|
||||
const next = () => {
|
||||
if (questionIndex === questions.length - 1) {
|
||||
onNext({ exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps });
|
||||
if (questionIndex + 1 >= questions.length - 1) {
|
||||
onNext({exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps});
|
||||
} else {
|
||||
setQuestionIndex(questionIndex + 1);
|
||||
setQuestionIndex(questionIndex + 2);
|
||||
}
|
||||
scrollToTop();
|
||||
};
|
||||
|
||||
const back = () => {
|
||||
if (questionIndex === 0) {
|
||||
onBack({ exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps });
|
||||
onBack({exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps});
|
||||
} else {
|
||||
if (exam?.module === "level" && typeof exam.parts[0].intro !== "undefined" && questionIndex === 0) return;
|
||||
setQuestionIndex(questionIndex - 1);
|
||||
if (exam?.module === "level" && typeof exam.parts[0].intro !== "undefined" && questionIndex === 0) return;
|
||||
setQuestionIndex(questionIndex - 2);
|
||||
}
|
||||
|
||||
scrollToTop();
|
||||
@@ -160,35 +152,47 @@ export default function MultipleChoice({ id, prompt, type, questions, userSoluti
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-2 mt-4 h-fit w-full mb-20 bg-mti-gray-smoke rounded-xl px-16 py-8">
|
||||
{/*<span className="text-xl font-semibold mb-2">{"Select the appropriate option."}</span>*/}
|
||||
{questionIndex < questions.length && (
|
||||
<Question
|
||||
{...questions[questionIndex]}
|
||||
userSolution={answers.find((x) => questions[questionIndex].id === x.question)?.option}
|
||||
onSelectOption={onSelectOption}
|
||||
/>
|
||||
<div className="flex flex-col gap-4 mt-2 mb-20">
|
||||
<div className="flex flex-col gap-8 h-fit w-full bg-mti-gray-smoke rounded-xl px-16 py-8">
|
||||
{/*<span className="text-xl font-semibold mb-2">{"Select the appropriate option."}</span>*/}
|
||||
{questionIndex < questions.length && (
|
||||
<Question
|
||||
{...questions[questionIndex]}
|
||||
userSolution={answers.find((x) => questions[questionIndex].id === x.question)?.option}
|
||||
onSelectOption={(option) => onSelectOption(option, questions[questionIndex])}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{questionIndex + 1 < questions.length && (
|
||||
<div className="flex flex-col gap-8 h-fit w-full bg-mti-gray-smoke rounded-xl px-16 py-8">
|
||||
<Question
|
||||
{...questions[questionIndex + 1]}
|
||||
userSolution={answers.find((x) => questions[questionIndex + 1].id === x.question)?.option}
|
||||
onSelectOption={(option) => onSelectOption(option, questions[questionIndex + 1])}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="self-end flex justify-between w-full gap-8 absolute bottom-8 left-0 px-8">
|
||||
<Button color="purple" variant="outline" onClick={back} className="max-w-[200px] w-full"
|
||||
disabled={
|
||||
exam && exam.module === "level" &&
|
||||
partIndex === 0 &&
|
||||
questionIndex === 0
|
||||
}
|
||||
>
|
||||
<Button
|
||||
color="purple"
|
||||
variant="outline"
|
||||
onClick={back}
|
||||
className="max-w-[200px] w-full"
|
||||
disabled={exam && exam.module === "level" && partIndex === 0 && questionIndex === 0}>
|
||||
Back
|
||||
</Button>
|
||||
|
||||
<Button color="purple" onClick={next} className="max-w-[200px] self-end w-full">
|
||||
{
|
||||
exam && exam.module === "level" &&
|
||||
partIndex === exam.parts.length - 1 &&
|
||||
exerciseIndex === exam.parts[partIndex].exercises.length - 1 &&
|
||||
questionIndex === questions.length - 1
|
||||
? "Submit" : "Next"}
|
||||
{exam &&
|
||||
exam.module === "level" &&
|
||||
partIndex === exam.parts.length - 1 &&
|
||||
exerciseIndex === exam.parts[partIndex].exercises.length - 1 &&
|
||||
questionIndex + 1 >= questions.length - 1
|
||||
? "Submit"
|
||||
: "Next"}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -111,10 +111,10 @@ export default function MultipleChoice({id, type, prompt, questions, userSolutio
|
||||
};
|
||||
|
||||
const next = () => {
|
||||
if (questionIndex === questions.length - 1) {
|
||||
if (questionIndex + 1 >= questions.length - 1) {
|
||||
onNext({exercise: id, solutions: userSolutions, score: calculateScore(), type});
|
||||
} else {
|
||||
setQuestionIndex(questionIndex + 1);
|
||||
setQuestionIndex(questionIndex + 2);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -122,22 +122,34 @@ export default function MultipleChoice({id, type, prompt, questions, userSolutio
|
||||
if (questionIndex === 0) {
|
||||
onBack({exercise: id, solutions: userSolutions, score: calculateScore(), type});
|
||||
} else {
|
||||
setQuestionIndex(questionIndex - 1);
|
||||
setQuestionIndex(questionIndex - 2);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-4 w-full h-full mb-20">
|
||||
<div className="flex flex-col gap-2 mt-4 h-full bg-mti-gray-smoke rounded-xl px-16 py-8">
|
||||
{/*<span className="text-xl font-semibold">{prompt}</span>*/}
|
||||
{userSolutions && questionIndex < questions.length && (
|
||||
<Question
|
||||
{...questions[questionIndex]}
|
||||
userSolution={userSolutions.find((x) => questions[questionIndex].id === x.question)?.option}
|
||||
/>
|
||||
<div className="flex flex-col gap-4 mt-2">
|
||||
<div className="flex flex-col gap-8 h-fit w-full bg-mti-gray-smoke rounded-xl px-16 py-8">
|
||||
{/*<span className="text-xl font-semibold">{prompt}</span>*/}
|
||||
{userSolutions && questionIndex < questions.length && (
|
||||
<Question
|
||||
{...questions[questionIndex]}
|
||||
userSolution={userSolutions.find((x) => questions[questionIndex].id === x.question)?.option}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{userSolutions && questionIndex + 1 < questions.length && (
|
||||
<div className="flex flex-col gap-8 h-fit w-full bg-mti-gray-smoke rounded-xl px-16 py-8">
|
||||
<Question
|
||||
{...questions[questionIndex + 1]}
|
||||
userSolution={userSolutions.find((x) => questions[questionIndex + 1].id === x.question)?.option}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex gap-4 items-center">
|
||||
<div className="flex gap-2 items-center">
|
||||
<div className="w-4 h-4 rounded-full bg-mti-purple" />
|
||||
@@ -160,13 +172,7 @@ export default function MultipleChoice({id, type, prompt, questions, userSolutio
|
||||
variant="outline"
|
||||
onClick={back}
|
||||
className="max-w-[200px] w-full"
|
||||
disabled={
|
||||
exam &&
|
||||
typeof partIndex !== "undefined" &&
|
||||
exam.module === "level" &&
|
||||
questionIndex === 0 &&
|
||||
partIndex === 0
|
||||
}>
|
||||
disabled={exam && typeof partIndex !== "undefined" && exam.module === "level" && questionIndex === 0 && partIndex === 0}>
|
||||
Back
|
||||
</Button>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user