import { FillBlanksExercise, FillBlanksMCOption } from "@/interfaces/exam"; import useExamStore from "@/stores/examStore"; import clsx from "clsx"; import { Fragment, useEffect, useState } from "react"; import reactStringReplace from "react-string-replace"; import { CommonProps } from ".."; import Button from "../../Low/Button"; import { v4 } from "uuid"; const FillBlanks: React.FC = ({ id, type, prompt, solutions, text, words, userSolutions, variant, onNext, onBack, }) => { const { shuffles, exam, partIndex, questionIndex, exerciseIndex } = useExamStore((state) => state); const [answers, setAnswers] = useState<{ id: string; solution: string }[]>(userSolutions); const hasExamEnded = useExamStore((state) => state.hasExamEnded); const setCurrentSolution = useExamStore((state) => state.setCurrentSolution); const shuffleMaps = shuffles.find((x) => x.exerciseID == id)?.shuffles; const [currentMCSelection, setCurrentMCSelection] = useState<{ id: string, selection: FillBlanksMCOption }>(); 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 excludeWordMCType = (x: any) => { return typeof x === "string" ? x : x as { letter: string; word: string }; } useEffect(() => { if (hasExamEnded) onNext({ exercise: id, solutions: answers, score: calculateScore(), type }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [hasExamEnded]); let correctWords: any; if (exam && exam.module === "level" && exam.parts[partIndex].exercises[exerciseIndex].type === "fillBlanks") { correctWords = (exam.parts[partIndex].exercises[exerciseIndex] as FillBlanksExercise).words; } const calculateScore = () => { const total = text.match(/({{\d+}})/g)?.length || 0; const correct = answers!.filter((x) => { const solution = solutions.find((y) => x.id.toString() === y.id.toString())?.solution; if (!solution) return false; const option = correctWords!.find((w: any) => { if (typeof w === "string") { return w.toLowerCase() === x.solution.toLowerCase(); } else if ('letter' in w) { return w.word.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 - answers!.filter((x) => solutions.find((y) => x.id.toString() === y.id.toString())).length; return { total, correct, missing }; }; const renderLines = (line: string) => { return (
{reactStringReplace(line, /({{\d+}})/g, (match) => { const id = match.replaceAll(/[\{\}]/g, ""); const userSolution = answers.find((x) => x.id === id); const styles = clsx( "rounded-full hover:text-white focus:ring-0 focus:outline-none focus:!text-white focus:bg-mti-purple transition duration-300 ease-in-out my-1 px-5 py-2 text-center", !userSolution && "text-center text-mti-purple-light bg-mti-purple-ultralight", userSolution && "text-center text-mti-purple-dark bg-mti-purple-ultralight", ) return ( variant === "mc" ? ( <> {/*{`(${id})`}*/} ) : ( setAnswers((prev) => [...prev.filter((x) => x.id !== id), { id, solution: e.target.value }])} value={userSolution?.solution} /> ) ); })}
); }; const onSelection = (questionID: string, value: string) => { setAnswers((prev) => [...prev.filter((x) => x.id !== questionID), { id: questionID, solution: value }]); } useEffect(() => { setCurrentSolution({ exercise: id, solutions: answers, score: calculateScore(), type, shuffleMaps: shuffleMaps }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [answers]) return ( <>
{false && {prompt.split("\\n").map((line, index) => ( {line}
))}
} {text.split("\\n").map((line, index) => (

{renderLines(line)}

))}
{variant === "mc" && typeCheckWordsMC(words) ? ( <> {currentMCSelection && (
{`${currentMCSelection.id} - Select the appropriate word.`}
{currentMCSelection.selection?.options && Object.entries(currentMCSelection.selection.options).sort((a, b) => a[0].localeCompare(b[0])).map(([key, value]) => { return
onSelection(currentMCSelection.id, value)} className={clsx( "flex border p-4 rounded-xl gap-2 cursor-pointer bg-white text-base", !!answers.find((x) => x.solution.toLocaleLowerCase() === value.toLocaleLowerCase() && x.id === currentMCSelection.id) && "border-mti-purple-light", )}> {key}. {value}
/*;*/ })}
)} ) : (
Options
{words.map((v) => { v = excludeWordMCType(v); const text = typeof v === "string" ? v : `${v.letter} - ${v.word}`; return ( x.solution.toLowerCase() === (typeof v === "string" ? v : ("letter" in v ? v.letter : "")).toLowerCase()) && "bg-mti-purple-dark text-white", )} key={v4()} > {text} ) })}
)}
); } export default FillBlanks;