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]" + /> +
+
+ +