218 lines
6.9 KiB
TypeScript
218 lines
6.9 KiB
TypeScript
import { FillBlanksExercise, FillBlanksMCOption, ShuffleMap } from "@/interfaces/exam";
|
|
import clsx from "clsx";
|
|
import reactStringReplace from "react-string-replace";
|
|
import { CommonProps } from ".";
|
|
import { Fragment } from "react";
|
|
import Button from "../Low/Button";
|
|
import useExamStore from "@/stores/examStore";
|
|
|
|
export default function FillBlanksSolutions({
|
|
id,
|
|
type,
|
|
prompt,
|
|
solutions,
|
|
words,
|
|
text,
|
|
onNext,
|
|
onBack,
|
|
}: FillBlanksExercise & CommonProps) {
|
|
const storeUserSolutions = useExamStore((state) => state.userSolutions);
|
|
|
|
const correctUserSolutions = storeUserSolutions.find(
|
|
(solution) => solution.exercise === id
|
|
)?.solutions;
|
|
|
|
const shuffles = useExamStore((state) => state.shuffles);
|
|
|
|
const calculateScore = () => {
|
|
const total = text.match(/({{\d+}})/g)?.length || 0;
|
|
const correct = correctUserSolutions!.filter((x) => {
|
|
const solution = solutions.find((y) => x.id.toString() === y.id.toString())?.solution;
|
|
if (!solution) return false;
|
|
|
|
const option = words.find((w) => {
|
|
if (typeof w === "string") {
|
|
return w.toLowerCase() === x.solution.toLowerCase();
|
|
} else if ('letter' in w) {
|
|
return w.letter.toLowerCase() === x.solution.toLowerCase();
|
|
} else {
|
|
return w.id.toString() === x.id.toString();
|
|
}
|
|
});
|
|
if (!option) return false;
|
|
|
|
if (typeof option === "string") {
|
|
return solution.toLowerCase() === option.toLowerCase();
|
|
} else if ('letter' in option) {
|
|
return solution.toLowerCase() === option.word.toLowerCase();
|
|
} else if ('options' in option) {
|
|
return option.options[solution as keyof typeof option.options] == x.solution;
|
|
}
|
|
return false;
|
|
}).length;
|
|
const missing = total - correctUserSolutions!.filter((x) => solutions.find((y) => x.id.toString() === y.id.toString())).length;
|
|
return { total, correct, missing };
|
|
};
|
|
|
|
|
|
const typeCheckWordsMC = (words: any[]): words is FillBlanksMCOption[] => {
|
|
return Array.isArray(words) && words.every(
|
|
word => word && typeof word === 'object' && 'id' in word && 'options' in word
|
|
);
|
|
}
|
|
|
|
const renderLines = (line: string) => {
|
|
return (
|
|
<span>
|
|
{reactStringReplace(line, /({{\d+}})/g, (match) => {
|
|
const questionId = match.replaceAll(/[\{\}]/g, "");
|
|
const userSolution = correctUserSolutions!.find((x) => x.id.toString() === questionId.toString());
|
|
const answerSolution = solutions.find(sol => sol.id.toString() === questionId.toString())!.solution;
|
|
const questionShuffleMap = shuffles.find((x) => x.exerciseID == id)?.shuffles.find((y) => y.questionID == questionId);
|
|
const newAnswerSolution = questionShuffleMap ? questionShuffleMap.map[answerSolution].toLowerCase() : answerSolution.toLowerCase();
|
|
|
|
if (!userSolution) {
|
|
let answerText;
|
|
if (typeCheckWordsMC(words)) {
|
|
const options = words.find((x) => x.id.toString() === questionId.toString());
|
|
const correctKey = Object.keys(options!.options).find(key =>
|
|
key.toLowerCase() === newAnswerSolution
|
|
);
|
|
answerText = options!.options[correctKey as keyof typeof options];
|
|
} else {
|
|
answerText = answerSolution;
|
|
}
|
|
|
|
return (
|
|
<button
|
|
className={clsx(
|
|
"rounded-full hover:text-white hover:bg-mti-gray-davy transition duration-300 ease-in-out my-1 px-5 py-2 text-center text-white bg-mti-gray-davy",
|
|
)}>
|
|
{answerText}
|
|
</button>
|
|
);
|
|
}
|
|
|
|
const userSolutionWord = words.find((w) =>
|
|
typeof w === "string"
|
|
? w.toLowerCase() === userSolution.solution.toLowerCase()
|
|
: 'letter' in w
|
|
? w.letter.toLowerCase() === userSolution.solution.toLowerCase()
|
|
: 'options' in w
|
|
? w.id === userSolution.questionId
|
|
: false
|
|
);
|
|
|
|
const userSolutionText =
|
|
typeof userSolutionWord === "string"
|
|
? userSolutionWord
|
|
: userSolutionWord && 'letter' in userSolutionWord
|
|
? userSolutionWord.word
|
|
: userSolutionWord && 'options' in userSolutionWord
|
|
? userSolution.solution
|
|
: userSolution.solution;
|
|
|
|
let correct;
|
|
let solutionText;
|
|
if (typeCheckWordsMC(words)) {
|
|
const options = words.find((x) => x.id.toString() === questionId.toString());
|
|
if (options) {
|
|
const correctKey = Object.keys(options.options).find(key =>
|
|
key.toLowerCase() === newAnswerSolution
|
|
);
|
|
correct = userSolution.solution == options.options[correctKey as keyof typeof options.options];
|
|
solutionText = options.options[correctKey as keyof typeof options.options] || answerSolution;
|
|
} else {
|
|
correct = false;
|
|
solutionText = answerSolution;
|
|
}
|
|
|
|
} else {
|
|
correct = userSolutionText === answerSolution;
|
|
solutionText = answerSolution;
|
|
}
|
|
|
|
if (correct) {
|
|
return (
|
|
<button
|
|
className={clsx(
|
|
"rounded-full hover:text-white hover:bg-mti-purple transition duration-300 ease-in-out my-1",
|
|
userSolution && "px-5 py-2 text-center text-white bg-mti-purple-light",
|
|
)}>
|
|
{solutionText}
|
|
</button>
|
|
);
|
|
} else {
|
|
return (
|
|
<>
|
|
<button
|
|
className={clsx(
|
|
"rounded-full hover:text-white hover:bg-mti-rose transition duration-300 ease-in-out my-1 mr-1",
|
|
userSolution && "px-5 py-2 text-center text-white bg-mti-rose-light",
|
|
)}>
|
|
{userSolutionText}
|
|
</button>
|
|
|
|
<button
|
|
className={clsx(
|
|
"rounded-full hover:text-white hover:bg-mti-purple transition duration-300 ease-in-out my-1",
|
|
userSolution && "px-5 py-2 text-center text-white bg-mti-purple-light",
|
|
)}>
|
|
{solutionText}
|
|
</button>
|
|
</>
|
|
);
|
|
}
|
|
})}
|
|
</span>
|
|
);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<div className="flex flex-col gap-4 mt-4 h-full w-full mb-20">
|
|
<span className="bg-mti-gray-smoke rounded-xl px-5 py-6">
|
|
{correctUserSolutions &&
|
|
text.split("\\n").map((line, index) => (
|
|
<p key={index}>
|
|
{renderLines(line)}
|
|
<br />
|
|
</p>
|
|
))}
|
|
</span>
|
|
<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" />
|
|
Correct
|
|
</div>
|
|
<div className="flex gap-2 items-center">
|
|
<div className="w-4 h-4 rounded-full bg-mti-gray-davy" />
|
|
Unanswered
|
|
</div>
|
|
<div className="flex gap-2 items-center">
|
|
<div className="w-4 h-4 rounded-full bg-mti-rose" />
|
|
Wrong
|
|
</div>
|
|
</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={() => onBack({ exercise: id, solutions: correctUserSolutions!, score: calculateScore(), type })}
|
|
className="max-w-[200px] w-full">
|
|
Back
|
|
</Button>
|
|
|
|
<Button
|
|
color="purple"
|
|
onClick={() => onNext({ exercise: id, solutions: correctUserSolutions!, score: calculateScore(), type })}
|
|
className="max-w-[200px] self-end w-full">
|
|
Next
|
|
</Button>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|