From 8f5b27e9ce5034e7fcbe044b2538b77edbf5e8f9 Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Sat, 27 Jul 2024 14:38:45 +0100 Subject: [PATCH] Updated the FillBlanks to the new format --- src/components/Exercises/FillBlanks.tsx | 77 +- .../Generation/fill.blanks.edit.tsx | 146 ++-- src/components/Solutions/FillBlanks.tsx | 39 +- src/interfaces/exam.ts | 14 +- src/pages/(generation)/LevelGeneration.tsx | 2 +- src/pages/(generation)/ReadingGeneration.tsx | 824 ++++++++---------- 6 files changed, 523 insertions(+), 579 deletions(-) diff --git a/src/components/Exercises/FillBlanks.tsx b/src/components/Exercises/FillBlanks.tsx index 92487c0a..d208687b 100644 --- a/src/components/Exercises/FillBlanks.tsx +++ b/src/components/Exercises/FillBlanks.tsx @@ -77,15 +77,8 @@ export default function FillBlanks({ onBack, }: FillBlanksExercise & CommonProps) { const [answers, setAnswers] = useState<{id: string; solution: string}[]>(userSolutions); - const [currentBlankId, setCurrentBlankId] = useState(); - const [isDrawerShowing, setIsDrawerShowing] = useState(false); const hasExamEnded = useExamStore((state) => state.hasExamEnded); - const allBlanks = Array.from(text.match(/({{\d+}})/g) || []).map((x) => x.replaceAll("{", "").replaceAll("}", "")); - - useEffect(() => { - setTimeout(() => setIsDrawerShowing(!!currentBlankId), 100); - }, [currentBlankId]); useEffect(() => { if (hasExamEnded) onNext({exercise: id, solutions: answers, score: calculateScore(), type}); @@ -94,9 +87,17 @@ export default function FillBlanks({ const calculateScore = () => { const total = text.match(/({{\d+}})/g)?.length || 0; - const correct = answers.filter( - (x) => solutions.find((y) => x.id.toString() === y.id.toString())?.solution === x.solution.toLowerCase() || false, - ).length; + const correct = answers.filter((x) => { + const solution = solutions.find((y) => x.id.toString() === y.id.toString())?.solution.toLowerCase(); + if (!solution) return false; + + const option = words.find((w) => + typeof w === "string" ? w.toLowerCase() === x.solution.toLowerCase() : w.letter.toLowerCase() === x.solution.toLowerCase(), + ); + if (!option) return false; + + return solution === (typeof option === "string" ? option.toLowerCase() : option.word.toLowerCase()); + }).length; const missing = total - answers.filter((x) => solutions.find((y) => x.id.toString() === y.id.toString())).length; return {total, correct, missing}; @@ -104,49 +105,29 @@ export default function FillBlanks({ const renderLines = (line: string) => { return ( - +
{reactStringReplace(line, /({{\d+}})/g, (match) => { const id = match.replaceAll(/[\{\}]/g, ""); const userSolution = answers.find((x) => x.id === id); return ( - + onChange={(e) => setAnswers((prev) => [...prev.filter((x) => x.id !== id), {id, solution: e.target.value}])} + value={userSolution?.solution}> ); })} - +
); }; return ( <>
- {(!!currentBlankId || isDrawerShowing) && ( - ({word, isDisabled: allowRepetition ? false : answers.map((x) => x.solution).includes(word)}))} - previouslySelectedWord={currentBlankId ? answers.find((x) => x.id === currentBlankId)?.solution : undefined} - isOpen={isDrawerShowing} - onCancel={() => setCurrentBlankId(undefined)} - onAnswer={(solution: string) => { - setAnswers((prev) => [...prev.filter((x) => x.id !== currentBlankId), {id: currentBlankId!, solution}]); - if (allBlanks.findIndex((x) => x === currentBlankId) + 1 < allBlanks.length) { - setCurrentBlankId(allBlanks[allBlanks.findIndex((x) => x === currentBlankId) + 1]); - return; - } - setCurrentBlankId(undefined); - }} - /> - )} {prompt.split("\\n").map((line, index) => ( @@ -163,6 +144,26 @@ export default function FillBlanks({

))}
+
+ Options +
+ {words.map((v) => { + const text = typeof v === "string" ? v : `${v.letter} - ${v.word}`; + + return ( + x.solution.toLowerCase() === (typeof v === "string" ? v : v.letter).toLowerCase()) && + "bg-mti-purple-dark text-white", + )} + key={text}> + {text} + + ); + })} +
+
diff --git a/src/components/Generation/fill.blanks.edit.tsx b/src/components/Generation/fill.blanks.edit.tsx index c2210093..4c82c68f 100644 --- a/src/components/Generation/fill.blanks.edit.tsx +++ b/src/components/Generation/fill.blanks.edit.tsx @@ -1,84 +1,82 @@ -import { FillBlanksExercise } from "@/interfaces/exam"; +import {FillBlanksExercise} from "@/interfaces/exam"; import React from "react"; import Input from "@/components/Low/Input"; interface Props { - exercise: FillBlanksExercise; - updateExercise: (data: any) => void; + exercise: FillBlanksExercise; + updateExercise: (data: any) => void; } const FillBlanksEdit = (props: Props) => { - const { exercise, updateExercise } = props; - return ( - <> - - updateExercise({ - prompt: value, - }) - } - /> - - updateExercise({ - text: value, - }) - } - /> -

Solutions

-
- {exercise.solutions.map((solution, index) => ( -
- - updateExercise({ - solutions: exercise.solutions.map((sol) => - sol.id === solution.id ? { ...sol, solution: value } : sol - ), - }) - } - /> -
- ))} -
-

Words

-
- {exercise.words.map((word, index) => ( -
- - updateExercise({ - words: exercise.words.map((sol, idx) => - index === idx ? value : sol - ), - }) - } - /> -
- ))} -
- - ); + const {exercise, updateExercise} = props; + return ( + <> + + updateExercise({ + prompt: value, + }) + } + /> + + updateExercise({ + text: value, + }) + } + /> +

Solutions

+
+ {exercise.solutions.map((solution, index) => ( +
+ + updateExercise({ + solutions: exercise.solutions.map((sol) => (sol.id === solution.id ? {...sol, solution: value} : sol)), + }) + } + /> +
+ ))} +
+

Words

+
+ {exercise.words.map((word, index) => ( +
+ + updateExercise({ + words: exercise.words.map((sol, idx) => + index === idx ? (typeof word === "string" ? value : {...word, word: value}) : sol, + ), + }) + } + /> +
+ ))} +
+ + ); }; export default FillBlanksEdit; diff --git a/src/components/Solutions/FillBlanks.tsx b/src/components/Solutions/FillBlanks.tsx index dde2820b..a08f5330 100644 --- a/src/components/Solutions/FillBlanks.tsx +++ b/src/components/Solutions/FillBlanks.tsx @@ -5,12 +5,30 @@ import {CommonProps} from "."; import {Fragment} from "react"; import Button from "../Low/Button"; -export default function FillBlanksSolutions({id, type, prompt, solutions, text, userSolutions, onNext, onBack}: FillBlanksExercise & CommonProps) { +export default function FillBlanksSolutions({ + id, + type, + prompt, + solutions, + words, + text, + userSolutions, + onNext, + onBack, +}: FillBlanksExercise & CommonProps) { const calculateScore = () => { const total = text.match(/({{\d+}})/g)?.length || 0; - const correct = userSolutions.filter( - (x) => solutions.find((y) => x.id.toString() === y.id.toString())?.solution === x.solution.toLowerCase() || false, - ).length; + const correct = userSolutions.filter((x) => { + const solution = solutions.find((y) => x.id.toString() === y.id.toString())?.solution.toLowerCase(); + if (!solution) return false; + + const option = words.find((w) => + typeof w === "string" ? w.toLowerCase() === x.solution.toLowerCase() : w.letter.toLowerCase() === x.solution.toLowerCase(), + ); + if (!option) return false; + + return solution === (typeof option === "string" ? option.toLowerCase() : option.word.toLowerCase()); + }).length; const missing = total - userSolutions.filter((x) => solutions.find((y) => x.id.toString() === y.id.toString())).length; return {total, correct, missing}; @@ -35,7 +53,14 @@ export default function FillBlanksSolutions({id, type, prompt, solutions, text, ); } - if (userSolution.solution === solution.solution) { + const userSolutionWord = words.find((w) => + typeof w === "string" + ? w.toLowerCase() === userSolution.solution.toLowerCase() + : w.letter.toLowerCase() === userSolution.solution.toLowerCase(), + ); + const userSolutionText = typeof userSolutionWord === "string" ? userSolutionWord : userSolutionWord?.word; + + if (userSolutionText === solution.solution) { return ( -
- {isLoading && ( -
- - - Generating... - -
- )} - {part && ( - <> -
-
- {part.exercises.map((x) => ( - - {x.type && convertCamelCaseToReadable(x.type)} - - ))} -
-

{part.text.title}

- {part.text.content} -
- {renderExercises()} - - )} - - ); + return ( + +
+ +
+ {availableTypes.map((x) => ( + toggleType(x.type)} + key={x.type} + className={clsx( + "px-6 py-4 w-64 flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer", + "transition duration-300 ease-in-out", + !types.includes(x.type) ? "bg-white border-mti-gray-platinum" : "bg-ielts-reading/70 border-ielts-reading text-white", + )}> + {x.label} + + ))} +
+
+
+ + +
+ {isLoading && ( +
+ + Generating... +
+ )} + {part && ( + <> +
+
+ {part.exercises.map((x) => ( + + {x.type && convertCamelCaseToReadable(x.type)} + + ))} +
+

{part.text.title}

+ {part.text.content} +
+ {renderExercises()} + + )} +
+ ); }; const ReadingGeneration = () => { - const [part1, setPart1] = useState(); - const [part2, setPart2] = useState(); - const [part3, setPart3] = useState(); - const [minTimer, setMinTimer] = useState(60); - const [isLoading, setIsLoading] = useState(false); - const [resultingExam, setResultingExam] = useState(); - const [difficulty, setDifficulty] = useState( - sample(DIFFICULTIES)! - ); + const [part1, setPart1] = useState(); + const [part2, setPart2] = useState(); + const [part3, setPart3] = useState(); + const [minTimer, setMinTimer] = useState(60); + const [isLoading, setIsLoading] = useState(false); + const [resultingExam, setResultingExam] = useState(); + const [difficulty, setDifficulty] = useState(sample(DIFFICULTIES)!); - useEffect(() => { - const parts = [part1, part2, part3].filter((x) => !!x); - setMinTimer(parts.length === 0 ? 60 : parts.length * 20); - }, [part1, part2, part3]); + useEffect(() => { + const parts = [part1, part2, part3].filter((x) => !!x); + setMinTimer(parts.length === 0 ? 60 : parts.length * 20); + }, [part1, part2, part3]); - const router = useRouter(); + const router = useRouter(); - const setExams = useExamStore((state) => state.setExams); - const setSelectedModules = useExamStore((state) => state.setSelectedModules); + const setExams = useExamStore((state) => state.setExams); + const setSelectedModules = useExamStore((state) => state.setSelectedModules); - const loadExam = async (examId: string) => { - const exam = await getExamById("reading", examId.trim()); - if (!exam) { - toast.error( - "Unknown Exam ID! Please make sure you selected the right module and entered the right exam ID", - { - toastId: "invalid-exam-id", - } - ); + const loadExam = async (examId: string) => { + const exam = await getExamById("reading", examId.trim()); + if (!exam) { + toast.error("Unknown Exam ID! Please make sure you selected the right module and entered the right exam ID", { + toastId: "invalid-exam-id", + }); - return; - } + return; + } - setExams([exam]); - setSelectedModules(["reading"]); + setExams([exam]); + setSelectedModules(["reading"]); - router.push("/exercises"); - }; + router.push("/exercises"); + }; - const submitExam = () => { - const parts = [part1, part2, part3].filter((x) => !!x) as ReadingPart[]; - if (parts.length === 0) { - toast.error("Please generate at least one passage before submitting"); - return; - } + const submitExam = () => { + const parts = [part1, part2, part3].filter((x) => !!x) as ReadingPart[]; + if (parts.length === 0) { + toast.error("Please generate at least one passage before submitting"); + return; + } - setIsLoading(true); - const exam: ReadingExam = { - parts, - isDiagnostic: false, - minTimer, - module: "reading", - id: v4(), - type: "academic", - variant: parts.length === 3 ? "full" : "partial", - difficulty, - }; + setIsLoading(true); + const exam: ReadingExam = { + parts, + isDiagnostic: false, + minTimer, + module: "reading", + id: v4(), + type: "academic", + variant: parts.length === 3 ? "full" : "partial", + difficulty, + }; - axios - .post(`/api/exam/reading`, exam) - .then((result) => { - playSound("sent"); - console.log(`Generated Exam ID: ${result.data.id}`); - toast.success( - "This new exam has been generated successfully! Check the ID in our browser's console." - ); - setResultingExam(result.data); + axios + .post(`/api/exam/reading`, exam) + .then((result) => { + playSound("sent"); + console.log(`Generated Exam ID: ${result.data.id}`); + toast.success("This new exam has been generated successfully! Check the ID in our browser's console."); + setResultingExam(result.data); - setPart1(undefined); - setPart2(undefined); - setPart3(undefined); - setDifficulty(sample(DIFFICULTIES)!); - setMinTimer(60); - }) - .catch((error) => { - console.log(error); - toast.error( - "Something went wrong while generating, please try again later." - ); - }) - .finally(() => setIsLoading(false)); - }; + setPart1(undefined); + setPart2(undefined); + setPart3(undefined); + setDifficulty(sample(DIFFICULTIES)!); + setMinTimer(60); + }) + .catch((error) => { + console.log(error); + toast.error("Something went wrong while generating, please try again later."); + }) + .finally(() => setIsLoading(false)); + }; - return ( - <> -
-
- - setMinTimer(parseInt(e) < 15 ? 15 : parseInt(e))} - value={minTimer} - className="max-w-[300px]" - /> -
-
- - setMinTimer(parseInt(e) < 15 ? 15 : parseInt(e))} + value={minTimer} + className="max-w-[300px]" + /> +
+
+ +