From c9daba17e115574e8c7b835833827f2274d25bb9 Mon Sep 17 00:00:00 2001 From: Joao Ramos Date: Wed, 3 Jul 2024 11:13:13 +0100 Subject: [PATCH 01/13] Added Fill Blanks Edits + True False Edit --- .../Generation/fill.blanks.edit.tsx | 84 +++ .../Generation/interactive.speaking.edit.tsx | 7 + .../Generation/match.sentences.edit.tsx | 7 + .../Generation/multiple.choice.edit.tsx | 7 + src/components/Generation/speaking.edit.tsx | 7 + src/components/Generation/true.false.edit.tsx | 71 ++ .../Generation/white.blanks.edit.tsx | 7 + src/components/Generation/writing.edit.tsx | 7 + src/pages/(generation)/ReadingGeneration.tsx | 691 +++++++++++------- 9 files changed, 610 insertions(+), 278 deletions(-) create mode 100644 src/components/Generation/fill.blanks.edit.tsx create mode 100644 src/components/Generation/interactive.speaking.edit.tsx create mode 100644 src/components/Generation/match.sentences.edit.tsx create mode 100644 src/components/Generation/multiple.choice.edit.tsx create mode 100644 src/components/Generation/speaking.edit.tsx create mode 100644 src/components/Generation/true.false.edit.tsx create mode 100644 src/components/Generation/white.blanks.edit.tsx create mode 100644 src/components/Generation/writing.edit.tsx diff --git a/src/components/Generation/fill.blanks.edit.tsx b/src/components/Generation/fill.blanks.edit.tsx new file mode 100644 index 00000000..c2210093 --- /dev/null +++ b/src/components/Generation/fill.blanks.edit.tsx @@ -0,0 +1,84 @@ +import { FillBlanksExercise } from "@/interfaces/exam"; +import React from "react"; +import Input from "@/components/Low/Input"; + +interface Props { + 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 + ), + }) + } + /> +
+ ))} +
+ + ); +}; + +export default FillBlanksEdit; diff --git a/src/components/Generation/interactive.speaking.edit.tsx b/src/components/Generation/interactive.speaking.edit.tsx new file mode 100644 index 00000000..351f8b4b --- /dev/null +++ b/src/components/Generation/interactive.speaking.edit.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const InteractiveSpeakingEdit = () => { + return null; +}; + +export default InteractiveSpeakingEdit; diff --git a/src/components/Generation/match.sentences.edit.tsx b/src/components/Generation/match.sentences.edit.tsx new file mode 100644 index 00000000..a4027a2e --- /dev/null +++ b/src/components/Generation/match.sentences.edit.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +const MatchSentencesEdit = () => { + return null; +} + +export default MatchSentencesEdit; \ No newline at end of file diff --git a/src/components/Generation/multiple.choice.edit.tsx b/src/components/Generation/multiple.choice.edit.tsx new file mode 100644 index 00000000..d943aed5 --- /dev/null +++ b/src/components/Generation/multiple.choice.edit.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +const MultipleChoiceEdit = () => { + return null; +} + +export default MultipleChoiceEdit; \ No newline at end of file diff --git a/src/components/Generation/speaking.edit.tsx b/src/components/Generation/speaking.edit.tsx new file mode 100644 index 00000000..a5649371 --- /dev/null +++ b/src/components/Generation/speaking.edit.tsx @@ -0,0 +1,7 @@ +import React from 'react'; + +const SpeakingEdit = () => { + return null; +} + +export default SpeakingEdit; \ No newline at end of file diff --git a/src/components/Generation/true.false.edit.tsx b/src/components/Generation/true.false.edit.tsx new file mode 100644 index 00000000..2a9281d2 --- /dev/null +++ b/src/components/Generation/true.false.edit.tsx @@ -0,0 +1,71 @@ +import React from "react"; +import { TrueFalseExercise } from "@/interfaces/exam"; +import Input from "@/components/Low/Input"; +import Select from "@/components/Low/Select"; +interface Props { + exercise: TrueFalseExercise; + updateExercise: (data: any) => void; +} + +const options = [ + { value: "true", label: "True" }, + { value: "false", label: "False" }, + { value: "not_given", label: "Not Given" }, +]; + +const TrueFalseEdit = (props: Props) => { + const { exercise, updateExercise } = props; + return ( + <> + + updateExercise({ + prompt: value, + }) + } + /> +

Questions

+
+ {exercise.questions.map((question, index) => ( +
+ + updateExercise({ + questions: exercise.questions.map((sol) => + sol.id === question.id ? { ...sol, prompt: value } : sol + ), + }) + } + /> +
+ - -
- {isLoading && ( -
- - Generating... -
- )} - {part && ( -
-
- {part.exercises.map((x) => ( - - {x.type && convertCamelCaseToReadable(x.type)} - - ))} -
-

{part.text.title}

- {part.text.content} -
- )} - - ); + const renderExercises = () => { + return part?.exercises.map((exercise) => { + switch (exercise.type) { + case "fillBlanks": + return ( + <> +

Exercise: Fill Blanks

+ + updatePart((part?: ReadingPart) => { + if (part) { + const exercises = part.exercises.map((x) => + x.id === exercise.id ? { ...x, ...data } : x + ) as Exercise[]; + const updatedPart = { ...part, exercises } as ReadingPart; + return updatedPart; + } + }) + } + /> + + ); + case "trueFalse": + return ( + <> +

Exercise: True or False

+ { + updatePart((part?: ReadingPart) => { + if (part) { + return { + ...part, + exercises: part.exercises.map((x) => + x.id === exercise.id ? { ...x, ...data } : x + ), + } as ReadingPart; + } + }); + }} + /> + + ); + default: + return null; + } + }); + }; + + return ( + +
+ + +
+ {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 [types, setTypes] = useState([]); - 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 [types, setTypes] = useState([]); + 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 availableTypes = [ - {type: "fillBlanks", label: "Fill the Blanks"}, - {type: "trueFalse", label: "True or False"}, - {type: "writeBlanks", label: "Write the Blanks"}, - {type: "matchSentences", label: "Match Sentences"}, - ]; + const availableTypes = [ + { type: "fillBlanks", label: "Fill the Blanks" }, + { type: "trueFalse", label: "True or False" }, + { type: "writeBlanks", label: "Write the Blanks" }, + { type: "matchSentences", label: "Match Sentences" }, + ]; - const toggleType = (type: string) => setTypes((prev) => (prev.includes(type) ? [...prev.filter((x) => x !== type)] : [...prev, type])); + const toggleType = (type: string) => + setTypes((prev) => + prev.includes(type) + ? [...prev.filter((x) => x !== type)] + : [...prev, type] + ); - 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); - setTypes([]); - }) - .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); + setTypes([]); + }) + .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]" + /> +
+
+ + + updateExercise({ + prompt: value, + }) + } + /> + + updateExercise({ + text: value, + }) + } + /> + + updateExercise({ + maxWords: Number(value), + }) + } + /> +

Solutions

+
+ {exercise.solutions.map((solution) => ( +
+ Solution ID: {solution.id} + {/* TODO: Consider adding an add and delete button */} +
+ {solution.solution.map((sol, solIndex) => ( + + updateExercise({ + solutions: exercise.solutions.map((iSol) => + iSol.id === solution.id + ? { + ...iSol, + solution: iSol.solution.map((iiSol, iiIndex) => { + if (iiIndex === solIndex) { + return value; + } + + return iiSol; + }), + } + : iSol + ), + }) + } + className="sm:w-1/2 lg:w-1/4 px-2" + /> + ))} +
+
+ ))} +
+ + ); +}; + +export default WriteBlankEdits; diff --git a/src/pages/(generation)/ReadingGeneration.tsx b/src/pages/(generation)/ReadingGeneration.tsx index a4581a2e..67bb431f 100644 --- a/src/pages/(generation)/ReadingGeneration.tsx +++ b/src/pages/(generation)/ReadingGeneration.tsx @@ -21,6 +21,7 @@ import { toast } from "react-toastify"; import { v4 } from "uuid"; import FillBlanksEdit from "@/components/Generation/fill.blanks.edit"; import TrueFalseEdit from "@/components/Generation/true.false.edit"; +import WriteBlanksEdit from "@/components/Generation/write.blanks.edit"; const DIFFICULTIES: Difficulty[] = ["easy", "medium", "hard"]; @@ -119,6 +120,28 @@ const PartTab = ({ /> ); + case "writeBlanks": + return ( + <> +

Exercise: Write Blanks

+ { + updatePart((part?: ReadingPart) => { + if (part) { + return { + ...part, + exercises: part.exercises.map((x) => + x.id === exercise.id ? { ...x, ...data } : x + ), + } as ReadingPart; + } + }); + }} + /> + + ); default: return null; } From 0b3e686f3f88369f28c2a3508d61937b7e7e81cd Mon Sep 17 00:00:00 2001 From: Joao Ramos Date: Thu, 4 Jul 2024 00:25:04 +0100 Subject: [PATCH 03/13] Match sentence log --- src/pages/(generation)/ReadingGeneration.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/(generation)/ReadingGeneration.tsx b/src/pages/(generation)/ReadingGeneration.tsx index 67bb431f..705e76de 100644 --- a/src/pages/(generation)/ReadingGeneration.tsx +++ b/src/pages/(generation)/ReadingGeneration.tsx @@ -142,6 +142,7 @@ const PartTab = ({ /> ); + // TODO: case "matchSentences": There seems to be an issue with the API default: return null; } From e5e4e87752c85f7496de7031760b2770a3924b36 Mon Sep 17 00:00:00 2001 From: Joao Ramos Date: Thu, 4 Jul 2024 01:15:33 +0100 Subject: [PATCH 04/13] Added Listening Multiple Choice Edit --- .../Generation/multiple.choice.edit.tsx | 138 +++- .../(generation)/ListeningGeneration.tsx | 727 ++++++++++-------- 2 files changed, 557 insertions(+), 308 deletions(-) diff --git a/src/components/Generation/multiple.choice.edit.tsx b/src/components/Generation/multiple.choice.edit.tsx index d943aed5..98b12395 100644 --- a/src/components/Generation/multiple.choice.edit.tsx +++ b/src/components/Generation/multiple.choice.edit.tsx @@ -1,7 +1,137 @@ -import React from 'react'; +import React from "react"; +import Input from "@/components/Low/Input"; +import { + MultipleChoiceExercise, + MultipleChoiceQuestion, +} from "@/interfaces/exam"; +import Select from "@/components/Low/Select"; -const MultipleChoiceEdit = () => { - return null; +interface Props { + exercise: MultipleChoiceExercise; + updateExercise: (data: any) => void; } -export default MultipleChoiceEdit; \ No newline at end of file +const variantOptions = [ + { value: "text", label: "Text", key: "text" }, + { value: "image", label: "Image", key: "src" }, +]; + +const MultipleChoiceEdit = (props: Props) => { + const { exercise, updateExercise } = props; + + return ( + <> +

Questions

+
+ {exercise.questions.map((question: MultipleChoiceQuestion, index) => { + const variantValue = variantOptions.find( + (o) => o.value === question.variant + ); + + const solutionsOptions = question.options.map((option) => ({ + value: option.id, + label: option.id, + })); + + const solutionValue = solutionsOptions.find( + (o) => o.value === question.solution + ); + return ( +
+ Question ID: {question.id} + + + updateExercise({ + questions: exercise.questions.map((sol) => + sol.id === question.id ? { ...sol, prompt: value } : sol + ), + }) + } + /> +
+
+ { + updateExercise({ + questions: exercise.questions.map((sol) => + sol.id === question.id + ? { ...sol, variant: value?.value } + : sol + ), + }); + }} + /> +
+
+
+ {question.options.map((option) => ( +
+ + updateExercise({ + questions: exercise.questions.map((sol) => + sol.id === question.id + ? { + ...sol, + options: sol.options.map((opt) => { + if ( + opt.id === option.id && + variantValue?.key + ) { + return { + ...opt, + [variantValue.key]: value, + }; + } + + return opt; + }), + } + : sol + ), + }) + } + /> +
+ ))} +
+
+ ); + })} +
+ + ); +}; + +export default MultipleChoiceEdit; diff --git a/src/pages/(generation)/ListeningGeneration.tsx b/src/pages/(generation)/ListeningGeneration.tsx index deda2732..4bf4f37c 100644 --- a/src/pages/(generation)/ListeningGeneration.tsx +++ b/src/pages/(generation)/ListeningGeneration.tsx @@ -1,345 +1,464 @@ +import MultipleChoiceEdit from "@/components/Generation/multiple.choice.edit"; import Input from "@/components/Low/Input"; import Select from "@/components/Low/Select"; -import {Difficulty, Exercise, ListeningExam} from "@/interfaces/exam"; +import { Difficulty, Exercise, ListeningExam } from "@/interfaces/exam"; import useExamStore from "@/stores/examStore"; -import {getExamById} from "@/utils/exams"; -import {playSound} from "@/utils/sound"; -import {convertCamelCaseToReadable} from "@/utils/string"; -import {Tab} from "@headlessui/react"; +import { getExamById } from "@/utils/exams"; +import { playSound } from "@/utils/sound"; +import { convertCamelCaseToReadable } from "@/utils/string"; +import { Tab } from "@headlessui/react"; import axios from "axios"; import clsx from "clsx"; -import {capitalize, sample} from "lodash"; -import {useRouter} from "next/router"; -import {useEffect, useState} from "react"; -import {BsArrowRepeat, BsCheck} from "react-icons/bs"; -import {toast} from "react-toastify"; +import { capitalize, sample } from "lodash"; +import { useRouter } from "next/router"; +import { useEffect, useState, Dispatch, SetStateAction } from "react"; +import { BsArrowRepeat, BsCheck } from "react-icons/bs"; +import { toast } from "react-toastify"; const DIFFICULTIES: Difficulty[] = ["easy", "medium", "hard"]; const PartTab = ({ - part, - types, - difficulty, - index, - setPart, + part, + types, + difficulty, + index, + setPart, + updatePart, }: { - part?: ListeningPart; - difficulty: Difficulty; - types: string[]; - index: number; - setPart: (part?: ListeningPart) => void; + part?: ListeningPart; + difficulty: Difficulty; + types: string[]; + index: number; + setPart: (part?: ListeningPart) => void; + updatePart: Dispatch>; }) => { - const [topic, setTopic] = useState(""); - const [isLoading, setIsLoading] = useState(false); + const [topic, setTopic] = useState(""); + const [isLoading, setIsLoading] = useState(false); - const generate = () => { - const url = new URLSearchParams(); - url.append("difficulty", difficulty); + const generate = () => { + const url = new URLSearchParams(); + url.append("difficulty", difficulty); - if (topic) url.append("topic", topic); - if (types) types.forEach((t) => url.append("exercises", t)); + if (topic) url.append("topic", topic); + if (types) types.forEach((t) => url.append("exercises", t)); - setPart(undefined); - setIsLoading(true); - axios - .get(`/api/exam/listening/generate/listening_section_${index}${topic || types ? `?${url.toString()}` : ""}`) - .then((result) => { - playSound(typeof result.data === "string" ? "error" : "check"); - if (typeof result.data === "string") return toast.error("Something went wrong, please try to generate again."); - setPart(result.data); - }) - .catch((error) => { - console.log(error); - toast.error("Something went wrong!"); - }) - .finally(() => setIsLoading(false)); - }; + setPart(undefined); + setIsLoading(true); + axios + .get( + `/api/exam/listening/generate/listening_section_${index}${ + topic || types ? `?${url.toString()}` : "" + }` + ) + .then((result) => { + playSound(typeof result.data === "string" ? "error" : "check"); + if (typeof result.data === "string") + return toast.error( + "Something went wrong, please try to generate again." + ); + setPart(result.data); + }) + .catch((error) => { + console.log(error); + toast.error("Something went wrong!"); + }) + .finally(() => setIsLoading(false)); + }; - return ( - -
- - -
- {isLoading && ( -
- - Generating... -
- )} - {part && ( -
-
- {part.exercises.map((x) => ( - - {x.type && convertCamelCaseToReadable(x.type)} - - ))} -
- {typeof part.text === "string" && {part.text.replaceAll("\n\n", " ")}} - {typeof part.text !== "string" && ( -
- {part.text.conversation.map((x, index) => ( - - {x.name}: - {x.text.replaceAll("\n\n", " ")} - - ))} -
- )} -
- )} -
- ); + const renderExercises = () => { + return part?.exercises.map((exercise) => { + switch (exercise.type) { + case "multipleChoice": + return ( + <> +

Exercise: Multiple Choice

+ + updatePart((part?: ListeningPart) => { + if (part) { + const exercises = part.exercises.map((x) => + x.id === exercise.id ? { ...x, ...data } : x + ) as Exercise[]; + const updatedPart = { + ...part, + exercises, + } as ListeningPart; + return updatedPart; + } + }) + } + /> + + ); + default: + return null; + } + }); + }; + + return ( + +
+ + +
+ {isLoading && ( +
+ + + Generating... + +
+ )} + {part && ( + <> +
+
+ {part.exercises.map((x) => ( + + {x.type && convertCamelCaseToReadable(x.type)} + + ))} +
+ {typeof part.text === "string" && ( + + {part.text.replaceAll("\n\n", " ")} + + )} + {typeof part.text !== "string" && ( +
+ {part.text.conversation.map((x, index) => ( + + {x.name}: + {x.text.replaceAll("\n\n", " ")} + + ))} +
+ )} +
+ {renderExercises()} + + )} +
+ ); }; interface ListeningPart { - exercises: Exercise[]; - text: - | { - conversation: { - gender: string; - name: string; - text: string; - voice: string; - }[]; - } - | string; + exercises: Exercise[]; + text: + | { + conversation: { + gender: string; + name: string; + text: string; + voice: string; + }[]; + } + | string; } const ListeningGeneration = () => { - const [part1, setPart1] = useState(); - const [part2, setPart2] = useState(); - const [part3, setPart3] = useState(); - const [part4, setPart4] = useState(); - const [minTimer, setMinTimer] = useState(30); - const [isLoading, setIsLoading] = useState(false); - const [resultingExam, setResultingExam] = useState(); - const [types, setTypes] = useState([]); - const [difficulty, setDifficulty] = useState(sample(DIFFICULTIES)!); + const [part1, setPart1] = useState(); + const [part2, setPart2] = useState(); + const [part3, setPart3] = useState(); + const [part4, setPart4] = useState(); + const [minTimer, setMinTimer] = useState(30); + const [isLoading, setIsLoading] = useState(false); + const [resultingExam, setResultingExam] = useState(); + const [types, setTypes] = useState([]); + const [difficulty, setDifficulty] = useState( + sample(DIFFICULTIES)! + ); - useEffect(() => { - const part1Timer = part1 ? 5 : 0; - const part2Timer = part2 ? 8 : 0; - const part3Timer = part3 ? 8 : 0; - const part4Timer = part4 ? 9 : 0; + useEffect(() => { + const part1Timer = part1 ? 5 : 0; + const part2Timer = part2 ? 8 : 0; + const part3Timer = part3 ? 8 : 0; + const part4Timer = part4 ? 9 : 0; - const sum = part1Timer + part2Timer + part3Timer + part4Timer; - setMinTimer(sum > 0 ? sum : 5); - }, [part1, part2, part3, part4]); + const sum = part1Timer + part2Timer + part3Timer + part4Timer; + setMinTimer(sum > 0 ? sum : 5); + }, [part1, part2, part3, part4]); - const availableTypes = [ - {type: "multipleChoice", label: "Multiple Choice"}, - {type: "writeBlanksQuestions", label: "Write the Blanks: Questions"}, - {type: "writeBlanksFill", label: "Write the Blanks: Fill"}, - {type: "writeBlanksForm", label: "Write the Blanks: Form"}, - ]; + const availableTypes = [ + { type: "multipleChoice", label: "Multiple Choice" }, + { type: "writeBlanksQuestions", label: "Write the Blanks: Questions" }, + { type: "writeBlanksFill", label: "Write the Blanks: Fill" }, + { type: "writeBlanksForm", label: "Write the Blanks: Form" }, + ]; - 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 toggleType = (type: string) => setTypes((prev) => (prev.includes(type) ? [...prev.filter((x) => x !== type)] : [...prev, type])); + const toggleType = (type: string) => + setTypes((prev) => + prev.includes(type) + ? [...prev.filter((x) => x !== type)] + : [...prev, type] + ); - const submitExam = () => { - const parts = [part1, part2, part3, part4].filter((x) => !!x); - console.log({parts}); - if (parts.length === 0) return toast.error("Please generate at least one section!"); + const submitExam = () => { + const parts = [part1, part2, part3, part4].filter((x) => !!x); + console.log({ parts }); + if (parts.length === 0) + return toast.error("Please generate at least one section!"); - setIsLoading(true); + setIsLoading(true); - axios - .post(`/api/exam/listening/generate/listening`, {parts, minTimer, difficulty}) - .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/listening/generate/listening`, { + parts, + minTimer, + difficulty, + }) + .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); - setPart4(undefined); - setDifficulty(sample(DIFFICULTIES)!); - setTypes([]); - }) - .catch((error) => { - console.log(error); - toast.error("Something went wrong!"); - }) - .finally(() => setIsLoading(false)); - }; + setPart1(undefined); + setPart2(undefined); + setPart3(undefined); + setPart4(undefined); + setDifficulty(sample(DIFFICULTIES)!); + setTypes([]); + }) + .catch((error) => { + console.log(error); + toast.error("Something went wrong!"); + }) + .finally(() => setIsLoading(false)); + }; - const loadExam = async (examId: string) => { - const exam = await getExamById("listening", 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("listening", 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(["listening"]); + setExams([exam]); + setSelectedModules(["listening"]); - router.push("/exercises"); - }; + router.push("/exercises"); + }; - 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]" + /> +
+
+ + (value ? setGender(value.value as typeof gender) : null)} - disabled={isLoading} - /> -
-
- - -
- {isLoading && ( -
- - Generating... -
- )} - {part && !isLoading && ( -
-

- {!!part.first_topic && !!part.second_topic ? `${part.first_topic} & ${part.second_topic}` : part.topic} -

- {part.question && {part.question}} - {part.questions && ( -
- {part.questions.map((question, index) => ( - - - {question} - - ))} -
- )} - {part.prompts && ( -
- You should talk about the following things: - {part.prompts.map((prompt, index) => ( - - - {prompt} - - ))} -
- )} - {part.result && Video Generated: ✅} - {part.avatar && part.gender && ( - - Instructor: {part.avatar.name} - {capitalize(part.avatar.gender)} - - )} -
- )} - - ); + return ( + +
+ + + updatePart((part?: SpeakingPart) => { + if (part) { + return { + ...part, + questions: part.questions?.map((x, xIndex) => + xIndex === index ? value : x + ), + } as SpeakingPart; + } + + return part; + }) + } + /> + ))} +
+ )} +
+ ); }; interface SpeakingPart { - prompts?: string[]; - question?: string; - questions?: string[]; - topic: string; - first_topic?: string; - second_topic?: string; - result?: SpeakingExercise | InteractiveSpeakingExercise; - gender?: "male" | "female"; - avatar?: (typeof AVATARS)[number]; + prompts?: string[]; + question?: string; + questions?: string[]; + topic: string; + first_topic?: string; + second_topic?: string; + result?: SpeakingExercise | InteractiveSpeakingExercise; + gender?: "male" | "female"; + avatar?: (typeof AVATARS)[number]; } const SpeakingGeneration = () => { - const [part1, setPart1] = useState(); - const [part2, setPart2] = useState(); - const [part3, setPart3] = useState(); - const [minTimer, setMinTimer] = useState(14); - 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(14); + 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 ? 5 : parts.length * 5); - }, [part1, part2, part3]); + useEffect(() => { + const parts = [part1, part2, part3].filter((x) => !!x); + setMinTimer(parts.length === 0 ? 5 : parts.length * 5); + }, [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 submitExam = () => { - if (!part1?.result && !part2?.result && !part3?.result) return toast.error("Please generate at least one task!"); + const submitExam = () => { + if (!part1?.result && !part2?.result && !part3?.result) + return toast.error("Please generate at least one task!"); - setIsLoading(true); + setIsLoading(true); - const genders = [part1?.gender, part2?.gender, part3?.gender].filter((x) => !!x); + const genders = [part1?.gender, part2?.gender, part3?.gender].filter( + (x) => !!x + ); - const exercises = [part1?.result, part2?.result, part3?.result] - .filter((x) => !!x) - .map((x) => ({ - ...x, - first_title: x?.type === "interactiveSpeaking" ? x.first_topic : undefined, - second_title: x?.type === "interactiveSpeaking" ? x.second_topic : undefined, - })); + const exercises = [part1?.result, part2?.result, part3?.result] + .filter((x) => !!x) + .map((x) => ({ + ...x, + first_title: + x?.type === "interactiveSpeaking" ? x.first_topic : undefined, + second_title: + x?.type === "interactiveSpeaking" ? x.second_topic : undefined, + })); - const exam: SpeakingExam = { - id: v4(), - isDiagnostic: false, - exercises: exercises as (SpeakingExercise | InteractiveSpeakingExercise)[], - minTimer, - variant: minTimer >= 14 ? "full" : "partial", - module: "speaking", - instructorGender: genders.every((x) => x === "male") ? "male" : genders.every((x) => x === "female") ? "female" : "varied", - }; + const exam: SpeakingExam = { + id: v4(), + isDiagnostic: false, + exercises: exercises as ( + | SpeakingExercise + | InteractiveSpeakingExercise + )[], + minTimer, + variant: minTimer >= 14 ? "full" : "partial", + module: "speaking", + instructorGender: genders.every((x) => x === "male") + ? "male" + : genders.every((x) => x === "female") + ? "female" + : "varied", + }; - axios - .post(`/api/exam/speaking`, 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/speaking`, 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(14); - }) - .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(14); + }) + .catch((error) => { + console.log(error); + toast.error( + "Something went wrong while generating, please try again later." + ); + }) + .finally(() => setIsLoading(false)); + }; - const loadExam = async (examId: string) => { - const exam = await getExamById("speaking", 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("speaking", 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(["speaking"]); + setExams([exam]); + setSelectedModules(["speaking"]); - router.push("/exercises"); - }; + router.push("/exercises"); + }; - return ( - <> -
-
- - setMinTimer(parseInt(e) < 5 ? 5 : parseInt(e))} - value={minTimer} - className="max-w-[300px]" - /> -
-
- - setMinTimer(parseInt(e) < 5 ? 5 : parseInt(e))} + value={minTimer} + className="max-w-[300px]" + /> +
+
+ + + updateExercise({ + prompt: value, + }) + } + /> +

Solutions

+
+ {exercise.sentences.map((sentence, index) => ( +
+
+ + updateExercise({ + sentences: exercise.sentences.map((iSol) => + iSol.id === sentence.id + ? { + ...iSol, + sentence: { + ...iSol.sentence, + heading: value, + }, + } + : iSol + ), + }) + } + className="px-2" + /> +
+ + updateExercise({ + options: exercise.options.map((iSol) => + iSol.id === option.id + ? { + ...iSol, + sentence: value, + } + : iSol + ), + }) + } + className="px-2" + /> +
+ { const [minTimer, setMinTimer] = useState(30); const [isLoading, setIsLoading] = useState(false); const [resultingExam, setResultingExam] = useState(); - const [types, setTypes] = useState([]); const [difficulty, setDifficulty] = useState( sample(DIFFICULTIES)! ); @@ -243,25 +290,11 @@ const ListeningGeneration = () => { setMinTimer(sum > 0 ? sum : 5); }, [part1, part2, part3, part4]); - const availableTypes = [ - { type: "multipleChoice", label: "Multiple Choice" }, - { type: "writeBlanksQuestions", label: "Write the Blanks: Questions" }, - { type: "writeBlanksFill", label: "Write the Blanks: Fill" }, - { type: "writeBlanksForm", label: "Write the Blanks: Form" }, - ]; - const router = useRouter(); const setExams = useExamStore((state) => state.setExams); const setSelectedModules = useExamStore((state) => state.setSelectedModules); - const toggleType = (type: string) => - setTypes((prev) => - prev.includes(type) - ? [...prev.filter((x) => x !== type)] - : [...prev, type] - ); - const submitExam = () => { const parts = [part1, part2, part3, part4].filter((x) => !!x); console.log({ parts }); @@ -289,7 +322,6 @@ const ListeningGeneration = () => { setPart3(undefined); setPart4(undefined); setDifficulty(sample(DIFFICULTIES)!); - setTypes([]); }) .catch((error) => { console.log(error); @@ -349,30 +381,6 @@ const ListeningGeneration = () => { />
- -
- -
- {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-listening/70 border-ielts-listening text-white" - )} - > - {x.label} - - ))} -
-
- { {[ - { part: part1, setPart: setPart1 }, - { part: part2, setPart: setPart2 }, - { part: part3, setPart: setPart3 }, - { part: part4, setPart: setPart4 }, - ].map(({ part, setPart }, index) => ( + { + part: part1, + setPart: setPart1, + types: [ + MULTIPLE_CHOICE, + WRITE_BLANKS_QUESTIONS, + WRITE_BLANKS_FILL, + WRITE_BLANKS_FORM, + ], + }, + { + part: part2, + setPart: setPart2, + types: [MULTIPLE_CHOICE, WRITE_BLANKS_QUESTIONS], + }, + { + part: part3, + setPart: setPart3, + types: [MULTIPLE_CHOICE_3, WRITE_BLANKS_QUESTIONS], + }, + { + part: part4, + setPart: setPart4, + types: [ + MULTIPLE_CHOICE, + WRITE_BLANKS_QUESTIONS, + WRITE_BLANKS_FILL, + WRITE_BLANKS_FORM, + ], + }, + ].map(({ part, setPart, types }, index) => ( Date: Tue, 9 Jul 2024 23:42:16 +0100 Subject: [PATCH 10/13] Changed approach on available type selection --- src/pages/(generation)/ReadingGeneration.tsx | 80 +++++++++----------- 1 file changed, 37 insertions(+), 43 deletions(-) diff --git a/src/pages/(generation)/ReadingGeneration.tsx b/src/pages/(generation)/ReadingGeneration.tsx index 2e919822..9a7297c4 100644 --- a/src/pages/(generation)/ReadingGeneration.tsx +++ b/src/pages/(generation)/ReadingGeneration.tsx @@ -26,16 +26,21 @@ import MatchSentencesEdit from "@/components/Generation/match.sentences.edit"; const DIFFICULTIES: Difficulty[] = ["easy", "medium", "hard"]; +const availableTypes = [ + { type: "fillBlanks", label: "Fill the Blanks" }, + { type: "trueFalse", label: "True or False" }, + { type: "writeBlanks", label: "Write the Blanks" }, + { type: "paragraphMatch", label: "Match Sentences" }, +]; + const PartTab = ({ part, - types, difficulty, index, setPart, updatePart, }: { part?: ReadingPart; - types: string[]; index: number; difficulty: Difficulty; setPart: (part?: ReadingPart) => void; @@ -44,6 +49,14 @@ const PartTab = ({ }) => { const [topic, setTopic] = useState(""); const [isLoading, setIsLoading] = useState(false); + const [types, setTypes] = useState([]); + + const toggleType = (type: string) => + setTypes((prev) => + prev.includes(type) + ? [...prev.filter((x) => x !== type)] + : [...prev, type] + ); const generate = () => { const url = new URLSearchParams(); @@ -181,6 +194,28 @@ const PartTab = ({ 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} + + ))} +
+
{ const [part2, setPart2] = useState(); const [part3, setPart3] = useState(); const [minTimer, setMinTimer] = useState(60); - const [types, setTypes] = useState([]); const [isLoading, setIsLoading] = useState(false); const [resultingExam, setResultingExam] = useState(); const [difficulty, setDifficulty] = useState( @@ -266,20 +300,6 @@ const ReadingGeneration = () => { const setExams = useExamStore((state) => state.setExams); const setSelectedModules = useExamStore((state) => state.setSelectedModules); - const availableTypes = [ - { type: "fillBlanks", label: "Fill the Blanks" }, - { type: "trueFalse", label: "True or False" }, - { type: "writeBlanks", label: "Write the Blanks" }, - { type: "paragraphMatch", label: "Match Sentences" }, - ]; - - const toggleType = (type: string) => - setTypes((prev) => - prev.includes(type) - ? [...prev.filter((x) => x !== type)] - : [...prev, type] - ); - const loadExam = async (examId: string) => { const exam = await getExamById("reading", examId.trim()); if (!exam) { @@ -333,7 +353,6 @@ const ReadingGeneration = () => { setPart3(undefined); setDifficulty(sample(DIFFICULTIES)!); setMinTimer(60); - setTypes([]); }) .catch((error) => { console.log(error); @@ -376,30 +395,6 @@ const ReadingGeneration = () => { />
- -
- -
- {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} - - ))} -
-
- { ].map(({ part, setPart }, index) => ( Date: Wed, 17 Jul 2024 23:00:16 +0100 Subject: [PATCH 11/13] Fixed sentence header mapping --- src/components/Generation/match.sentences.edit.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/components/Generation/match.sentences.edit.tsx b/src/components/Generation/match.sentences.edit.tsx index 78ab3a95..b9e2d029 100644 --- a/src/components/Generation/match.sentences.edit.tsx +++ b/src/components/Generation/match.sentences.edit.tsx @@ -39,17 +39,14 @@ const MatchSentencesEdit = (props: Props) => { label={`Sentence ${index + 1}`} name="sentence" required - value={sentence.sentence.heading} + value={sentence.sentence} onChange={(value) => updateExercise({ sentences: exercise.sentences.map((iSol) => iSol.id === sentence.id ? { ...iSol, - sentence: { - ...iSol.sentence, - heading: value, - }, + sentence: value, } : iSol ), From 13fd7e1ee526b35fc94d688f49c95eb431634166 Mon Sep 17 00:00:00 2001 From: Joao Ramos Date: Wed, 17 Jul 2024 23:44:53 +0100 Subject: [PATCH 12/13] Updated Level Generation --- src/pages/(generation)/LevelGeneration.tsx | 603 ++++++++++++--------- 1 file changed, 342 insertions(+), 261 deletions(-) diff --git a/src/pages/(generation)/LevelGeneration.tsx b/src/pages/(generation)/LevelGeneration.tsx index 8b4d2207..80ca0a94 100644 --- a/src/pages/(generation)/LevelGeneration.tsx +++ b/src/pages/(generation)/LevelGeneration.tsx @@ -1,294 +1,375 @@ import Select from "@/components/Low/Select"; -import {Difficulty, LevelExam, MultipleChoiceExercise, MultipleChoiceQuestion} from "@/interfaces/exam"; +import { + Difficulty, + LevelExam, + MultipleChoiceExercise, + MultipleChoiceQuestion, + LevelPart, +} from "@/interfaces/exam"; import useExamStore from "@/stores/examStore"; -import {getExamById} from "@/utils/exams"; -import {playSound} from "@/utils/sound"; -import {Tab} from "@headlessui/react"; +import { getExamById } from "@/utils/exams"; +import { playSound } from "@/utils/sound"; +import { Tab } from "@headlessui/react"; import axios from "axios"; import clsx from "clsx"; -import {capitalize, sample} from "lodash"; -import {useRouter} from "next/router"; -import {useState} from "react"; -import {BsArrowRepeat, BsCheck, BsPencilSquare, BsX} from "react-icons/bs"; -import {toast} from "react-toastify"; -import {v4} from "uuid"; +import { capitalize, sample } from "lodash"; +import { useRouter } from "next/router"; +import { useState } from "react"; +import { BsArrowRepeat, BsCheck, BsPencilSquare, BsX } from "react-icons/bs"; +import { toast } from "react-toastify"; +import { v4 } from "uuid"; const DIFFICULTIES: Difficulty[] = ["easy", "medium", "hard"]; -const QuestionDisplay = ({question, onUpdate}: {question: MultipleChoiceQuestion; onUpdate: (question: MultipleChoiceQuestion) => void}) => { - const [isEditing, setIsEditing] = useState(false); - const [options, setOptions] = useState(question.options); +const QuestionDisplay = ({ + question, + onUpdate, +}: { + question: MultipleChoiceQuestion; + onUpdate: (question: MultipleChoiceQuestion) => void; +}) => { + const [isEditing, setIsEditing] = useState(false); + const [options, setOptions] = useState(question.options); + const [answer, setAnswer] = useState(question.solution); - return ( -
- - {question.id}. {question.prompt}{" "} - -
- {question.options.map((option, index) => ( - - - ({option.id}) - {" "} - {isEditing ? ( - setOptions((prev) => prev.map((x, idx) => (idx === index ? {...x, text: e.target.value} : x)))} - /> - ) : ( - {option.text} - )} - - ))} -
-
- {!isEditing && ( - - )} - {isEditing && ( - <> - - - - )} -
-
- ); + return ( +
+ + {question.id}. {question.prompt}{" "} + +
+ {question.options.map((option, index) => ( + + setAnswer(option.id)} + > + ({option.id}) + {" "} + {isEditing ? ( + + setOptions((prev) => + prev.map((x, idx) => + idx === index ? { ...x, text: e.target.value } : x + ) + ) + } + /> + ) : ( + {option.text} + )} + + ))} +
+
+ {!isEditing && ( + + )} + {isEditing && ( + <> + + + + )} +
+
+ ); }; -const TaskTab = ({exam, difficulty, setExam}: {exam?: LevelExam; difficulty: Difficulty; setExam: (exam: LevelExam) => void}) => { - const [isLoading, setIsLoading] = useState(false); +const TaskTab = ({ + exam, + difficulty, + setExam, +}: { + exam?: LevelPart; + difficulty: Difficulty; + setExam: (exam: LevelPart) => void; +}) => { + const [isLoading, setIsLoading] = useState(false); - const generate = () => { - const url = new URLSearchParams(); - url.append("difficulty", difficulty); + const generate = () => { + const url = new URLSearchParams(); + url.append("difficulty", difficulty); - setIsLoading(true); - axios - .get(`/api/exam/level/generate/level?${url.toString()}`) - .then((result) => { - playSound(typeof result.data === "string" ? "error" : "check"); - if (typeof result.data === "string") return toast.error("Something went wrong, please try to generate again."); - setExam(result.data); - }) - .catch((error) => { - console.log(error); - toast.error("Something went wrong!"); - }) - .finally(() => setIsLoading(false)); - }; + setIsLoading(true); + axios + .get(`/api/exam/level/generate/level?${url.toString()}`) + .then((result) => { + playSound(typeof result.data === "string" ? "error" : "check"); + if (typeof result.data === "string") + return toast.error( + "Something went wrong, please try to generate again." + ); + setExam(result.data); + }) + .catch((error) => { + console.log(error); + toast.error("Something went wrong!"); + }) + .finally(() => setIsLoading(false)); + }; - const onUpdate = (question: MultipleChoiceQuestion) => { - if (!exam) return; + const onUpdate = (question: MultipleChoiceQuestion) => { + if (!exam) return; - const updatedExam = { - ...exam, - parts: exam.parts.map((p) => - p.exercises.map((x) => ({ - ...x, - questions: (x as MultipleChoiceExercise).questions.map((q) => (q.id === question.id ? question : q)), - })), - ), - }; - console.log(updatedExam); - setExam(updatedExam as any); - }; + const updatedExam = { + ...exam, + exercises: exam.exercises.map((x) => ({ + ...x, + questions: (x as MultipleChoiceExercise).questions.map((q) => + q.id === question.id ? question : q + ), + }), + ), + }; + console.log(updatedExam); + setExam(updatedExam as any); + }; - return ( - -
- -
- {isLoading && ( -
- - Generating... -
- )} - {exam && ( -
- {exam.parts - .flatMap((x) => x.exercises) - .filter((x) => x.type === "multipleChoice") - .map((ex) => { - const exercise = ex as MultipleChoiceExercise; + return ( + +
+ +
+ {isLoading && ( +
+ + + Generating... + +
+ )} + {exam && ( +
+ {exam.exercises + .filter((x) => x.type === "multipleChoice") + .map((ex) => { + const exercise = ex as MultipleChoiceExercise; - return ( -
-
- Multiple Choice - - {exercise.questions.length} questions - -
-
- {exercise.questions.map((question) => ( - - ))} -
-
- ); - })} -
- )} -
- ); + return ( +
+
+ + Multiple Choice + + + {exercise.questions.length} questions + +
+
+ {exercise.questions.map((question) => ( + + ))} +
+
+ ); + })} +
+ )} +
+ ); }; const LevelGeneration = () => { - const [generatedExam, setGeneratedExam] = useState(); - const [isLoading, setIsLoading] = useState(false); - const [resultingExam, setResultingExam] = useState(); - const [difficulty, setDifficulty] = useState(sample(DIFFICULTIES)!); + const [generatedExam, setGeneratedExam] = useState(); + const [isLoading, setIsLoading] = useState(false); + const [resultingExam, setResultingExam] = useState(); + const [difficulty, setDifficulty] = useState( + sample(DIFFICULTIES)! + ); - 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("level", 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("level", 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(["level"]); + setExams([exam]); + setSelectedModules(["level"]); - router.push("/exercises"); - }; + router.push("/exercises"); + }; - const submitExam = () => { - if (!generatedExam) { - toast.error("Please generate all tasks before submitting"); - return; - } + const submitExam = () => { + if (!generatedExam) { + toast.error("Please generate all tasks before submitting"); + return; + } - setIsLoading(true); - const exam: LevelExam = { - ...generatedExam, - isDiagnostic: false, - minTimer: 25, - module: "level", - id: v4(), - }; + setIsLoading(true); - axios - .post(`/api/exam/level`, 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); + const exam: LevelExam = { + isDiagnostic: false, + minTimer: 25, + module: "level", + id: v4(), + parts: [generatedExam], + }; - setGeneratedExam(undefined); - }) - .catch((error) => { - console.log(error); - toast.error("Something went wrong while generating, please try again later."); - }) - .finally(() => setIsLoading(false)); - }; + axios + .post(`/api/exam/level`, 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); - return ( - <> -
-
- - ({ + value: x, + label: capitalize(x), + }))} + onChange={(value) => + value ? setDifficulty(value.value as Difficulty) : null + } + value={{ value: difficulty, label: capitalize(difficulty) }} + /> +
+
+ + + + clsx( + "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-level/70", + "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-level focus:outline-none focus:ring-2", + "transition duration-300 ease-in-out", + selected + ? "bg-white shadow" + : "text-blue-100 hover:bg-white/[0.12] hover:text-ielts-level" + ) + } + > + Exam + + + + + + +
+ {resultingExam && ( + + )} + +
+ + ); }; export default LevelGeneration; From 41873f80d72a04c34c7087ff2cc2b4587e9763f7 Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Thu, 18 Jul 2024 15:45:28 +0100 Subject: [PATCH 13/13] ENCOA-66: Payment record not sorting --- src/pages/payment-record.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/payment-record.tsx b/src/pages/payment-record.tsx index 16b16b3c..51801211 100644 --- a/src/pages/payment-record.tsx +++ b/src/pages/payment-record.tsx @@ -788,10 +788,11 @@ export default function PaymentRecord() { const {rows: filteredRows, renderSearch} = useListSearch(paypalFilterRows, updatedPaypalPayments); const paypalTable = useReactTable({ - data: filteredRows, + data: filteredRows.sort((a, b) => moment(b.createdAt).diff(moment(a.createdAt), "second")), columns: paypalColumns, getCoreRowModel: getCoreRowModel(), }); + const getUserModal = () => { if (user) { if (selectedCorporateUser) { @@ -888,7 +889,7 @@ export default function PaymentRecord() { const {rows: csvRows, columns: csvColumns} = getCSVData(); const renderTable = (table: Table) => ( - +
{table.getHeaderGroups().map((headerGroup) => (