110 lines
3.9 KiB
TypeScript
110 lines
3.9 KiB
TypeScript
import {errorButtonStyle, infoButtonStyle} from "@/constants/buttonStyles";
|
|
import {MatchSentencesExercise} from "@/interfaces/exam";
|
|
import {mdiArrowLeft, mdiArrowRight} from "@mdi/js";
|
|
import Icon from "@mdi/react";
|
|
import clsx from "clsx";
|
|
import {Fragment, useEffect, useState} from "react";
|
|
import LineTo from "react-lineto";
|
|
import {CommonProps} from ".";
|
|
import Button from "../Low/Button";
|
|
import Xarrow from "react-xarrows";
|
|
import useExamStore from "@/stores/examStore";
|
|
|
|
export default function MatchSentences({id, options, type, prompt, sentences, userSolutions, onNext, onBack}: MatchSentencesExercise & CommonProps) {
|
|
const [selectedQuestion, setSelectedQuestion] = useState<string>();
|
|
const [answers, setAnswers] = useState<{question: string; option: string}[]>(userSolutions);
|
|
|
|
const hasExamEnded = useExamStore((state) => state.hasExamEnded);
|
|
|
|
const calculateScore = () => {
|
|
const total = sentences.length;
|
|
const correct = answers.filter((x) => sentences.find((y) => y.id.toString() === x.question)?.solution === x.option || false).length;
|
|
const missing = total - answers.filter((x) => sentences.find((y) => y.id.toString() === x.question)).length;
|
|
|
|
return {total, correct, missing};
|
|
};
|
|
|
|
const selectOption = (option: string) => {
|
|
if (!selectedQuestion) return;
|
|
setAnswers((prev) => [...prev.filter((x) => x.question !== selectedQuestion), {question: selectedQuestion, option}]);
|
|
setSelectedQuestion(undefined);
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (hasExamEnded) onNext({exercise: id, solutions: answers, score: calculateScore(), type});
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [hasExamEnded]);
|
|
|
|
return (
|
|
<>
|
|
<div className="flex flex-col gap-4 mt-4 h-full mb-20">
|
|
<span className="text-sm w-full leading-6">
|
|
{prompt.split("\\n").map((line, index) => (
|
|
<Fragment key={index}>
|
|
{line}
|
|
<br />
|
|
</Fragment>
|
|
))}
|
|
</span>
|
|
<div className="flex gap-8 w-full items-center justify-between bg-mti-gray-smoke rounded-xl px-24 py-6">
|
|
<div className="flex flex-col gap-4">
|
|
{sentences.map(({sentence, id}) => (
|
|
<div key={`question_${id}`} className="flex items-center justify-end gap-2 cursor-pointer">
|
|
<span>{sentence} </span>
|
|
<button
|
|
id={id}
|
|
onClick={() => setSelectedQuestion((prev) => (prev === id ? undefined : id))}
|
|
className={clsx(
|
|
"bg-mti-purple-ultralight text-mti-purple hover:text-white hover:bg-mti-purple w-8 h-8 rounded-full z-10",
|
|
"transition duration-300 ease-in-out",
|
|
selectedQuestion === id && "!text-white !bg-mti-purple",
|
|
id,
|
|
)}>
|
|
{id}
|
|
</button>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div className="flex flex-col gap-4">
|
|
{options.map(({sentence, id}) => (
|
|
<div key={`answer_${id}`} className={clsx("flex items-center justify-start gap-2 cursor-pointer")}>
|
|
<button
|
|
id={id}
|
|
onClick={() => selectOption(id)}
|
|
className={clsx(
|
|
"bg-mti-purple-ultralight text-mti-purple hover:text-white hover:bg-mti-purple w-8 h-8 rounded-full z-10",
|
|
"transition duration-300 ease-in-out",
|
|
id,
|
|
)}>
|
|
{id}
|
|
</button>
|
|
<span>{sentence}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
{answers.map((solution, index) => (
|
|
<Xarrow key={index} start={solution.question} end={solution.option} lineColor="#7872BF" showHead={false} />
|
|
))}
|
|
</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: answers, score: calculateScore(), type})}
|
|
className="max-w-[200px] w-full">
|
|
Back
|
|
</Button>
|
|
|
|
<Button
|
|
color="purple"
|
|
onClick={() => onNext({exercise: id, solutions: answers, score: calculateScore(), type})}
|
|
className="max-w-[200px] self-end w-full">
|
|
Next
|
|
</Button>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|