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..b9e2d029
--- /dev/null
+++ b/src/components/Generation/match.sentences.edit.tsx
@@ -0,0 +1,130 @@
+import React from "react";
+import { MatchSentencesExercise } from "@/interfaces/exam";
+import Input from "@/components/Low/Input";
+import Select from "@/components/Low/Select";
+
+interface Props {
+ exercise: MatchSentencesExercise;
+ updateExercise: (data: any) => void;
+}
+const MatchSentencesEdit = (props: Props) => {
+ const { exercise, updateExercise } = props;
+
+ const selectOptions = exercise.options.map((option) => ({
+ value: option.id,
+ label: option.id,
+ }));
+
+ return (
+ <>
+
+ updateExercise({
+ prompt: value,
+ })
+ }
+ />
+ Solutions
+
+ {exercise.sentences.map((sentence, index) => (
+
+
+
+ updateExercise({
+ sentences: exercise.sentences.map((iSol) =>
+ iSol.id === sentence.id
+ ? {
+ ...iSol,
+ sentence: value,
+ }
+ : iSol
+ ),
+ })
+ }
+ className="px-2"
+ />
+
+
+
+
+ ))}
+
Options
+
+ {exercise.options.map((option, index) => (
+
+
+
+ updateExercise({
+ options: exercise.options.map((iSol) =>
+ iSol.id === option.id
+ ? {
+ ...iSol,
+ sentence: value,
+ }
+ : iSol
+ ),
+ })
+ }
+ className="px-2"
+ />
+
+
+
+
+ ))}
+
+
+ >
+ );
+};
+
+export default MatchSentencesEdit;
diff --git a/src/components/Generation/multiple.choice.edit.tsx b/src/components/Generation/multiple.choice.edit.tsx
new file mode 100644
index 00000000..98b12395
--- /dev/null
+++ b/src/components/Generation/multiple.choice.edit.tsx
@@ -0,0 +1,137 @@
+import React from "react";
+import Input from "@/components/Low/Input";
+import {
+ MultipleChoiceExercise,
+ MultipleChoiceQuestion,
+} from "@/interfaces/exam";
+import Select from "@/components/Low/Select";
+
+interface Props {
+ exercise: MultipleChoiceExercise;
+ updateExercise: (data: any) => void;
+}
+
+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
+ ),
+ })
+ }
+ />
+
+
+
+
+
+
+
+ {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/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
+ ),
+ })
+ }
+ />
+
+
+
+ ))}
+
+ >
+ );
+};
+
+export default TrueFalseEdit;
diff --git a/src/components/Generation/write.blanks.edit.tsx b/src/components/Generation/write.blanks.edit.tsx
new file mode 100644
index 00000000..d682743b
--- /dev/null
+++ b/src/components/Generation/write.blanks.edit.tsx
@@ -0,0 +1,94 @@
+import React from "react";
+import Input from "@/components/Low/Input";
+import { WriteBlanksExercise } from "@/interfaces/exam";
+
+interface Props {
+ exercise: WriteBlanksExercise;
+ updateExercise: (data: any) => void;
+}
+const WriteBlankEdits = (props: Props) => {
+ const { exercise, updateExercise } = props;
+
+ return (
+ <>
+
+ 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/components/Generation/writing.edit.tsx b/src/components/Generation/writing.edit.tsx
new file mode 100644
index 00000000..146f92bd
--- /dev/null
+++ b/src/components/Generation/writing.edit.tsx
@@ -0,0 +1,7 @@
+import React from 'react';
+
+const WritingEdit = () => {
+ return null;
+}
+
+export default WritingEdit;
\ No newline at end of file
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 (
- <>
-
-
-
-
-
-
-
-
- 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 && (
-
- )}
-
-
- >
- );
+ setGeneratedExam(undefined);
+ })
+ .catch((error) => {
+ console.log(error);
+ toast.error(
+ "Something went wrong while generating, please try again later."
+ );
+ })
+ .finally(() => setIsLoading(false));
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+ 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;
diff --git a/src/pages/(generation)/ListeningGeneration.tsx b/src/pages/(generation)/ListeningGeneration.tsx
index deda2732..fdbf36a7 100644
--- a/src/pages/(generation)/ListeningGeneration.tsx
+++ b/src/pages/(generation)/ListeningGeneration.tsx
@@ -1,345 +1,526 @@
+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";
+import WriteBlanksEdit from "@/components/Generation/write.blanks.edit";
const DIFFICULTIES: Difficulty[] = ["easy", "medium", "hard"];
+const MULTIPLE_CHOICE = { type: "multipleChoice", label: "Multiple Choice" };
+const WRITE_BLANKS_QUESTIONS = {
+ type: "writeBlanksQuestions",
+ label: "Write the Blanks: Questions",
+};
+const WRITE_BLANKS_FILL = {
+ type: "writeBlanksFill",
+ label: "Write the Blanks: Fill",
+};
+const WRITE_BLANKS_FORM = {
+ type: "writeBlanksForm",
+ label: "Write the Blanks: Form",
+};
+const MULTIPLE_CHOICE_3 = {
+ type: "multipleChoice3Options",
+ label: "Multiple Choice",
+};
+
const PartTab = ({
- part,
- types,
- difficulty,
- index,
- setPart,
+ part,
+ difficulty,
+ availableTypes,
+ index,
+ setPart,
+ updatePart,
}: {
- part?: ListeningPart;
- difficulty: Difficulty;
- types: string[];
- index: number;
- setPart: (part?: ListeningPart) => void;
+ part?: ListeningPart;
+ difficulty: Difficulty;
+ availableTypes: { type: string; label: 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 [types, setTypes] = useState([]);
- 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;
+ }
+
+ return part;
+ })
+ }
+ />
+ >
+ );
+ // TODO: This might be broken as they all returns the same
+ case "writeBlanks":
+ return (
+ <>
+ Exercise: Write Blanks
+ {
+ updatePart((part?: ListeningPart) => {
+ if (part) {
+ return {
+ ...part,
+ exercises: part.exercises.map((x) =>
+ x.id === exercise.id ? { ...x, ...data } : x
+ ),
+ } as ListeningPart;
+ }
+
+ return part;
+ });
+ }}
+ />
+ >
+ );
+ default:
+ return null;
+ }
+ });
+ };
+
+ const toggleType = (type: string) =>
+ setTypes((prev) =>
+ prev.includes(type)
+ ? [...prev.filter((x) => x !== type)]
+ : [...prev, type]
+ );
+
+ 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-listening/70 border-ielts-listening text-white"
+ )}
+ >
+ {x.label}
+
+ ))}
+
+
+
+
+
+
+ {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 [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 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 = () => {
+ 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 toggleType = (type: string) => setTypes((prev) => (prev.includes(type) ? [...prev.filter((x) => x !== type)] : [...prev, type]));
+ setIsLoading(true);
- 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!");
+ 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);
- setIsLoading(true);
+ setPart1(undefined);
+ setPart2(undefined);
+ setPart3(undefined);
+ setPart4(undefined);
+ setDifficulty(sample(DIFFICULTIES)!);
+ })
+ .catch((error) => {
+ console.log(error);
+ toast.error("Something went wrong!");
+ })
+ .finally(() => setIsLoading(false));
+ };
- 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);
+ 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",
+ }
+ );
- 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));
- };
+ return;
+ }
- 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",
- });
+ setExams([exam]);
+ setSelectedModules(["listening"]);
- return;
- }
+ router.push("/exercises");
+ };
- setExams([exam]);
- setSelectedModules(["listening"]);
-
- router.push("/exercises");
- };
-
- return (
- <>
-
-
-
- setMinTimer(parseInt(e) < 15 ? 15 : parseInt(e))}
- value={minTimer}
- className="max-w-[300px]"
- />
-
-
-
-
-
-
-
-
-
- {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}
-
- ))}
-
-
-
-
-
-
- clsx(
- "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-listening/70 flex gap-2 items-center justify-center",
- "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-listening 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-listening",
- )
- }>
- Section 1 {part1 && }
-
-
- clsx(
- "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-listening/70 flex gap-2 items-center justify-center",
- "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-listening 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-listening",
- )
- }>
- Section 2 {part2 && }
-
-
- clsx(
- "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-listening/70 flex gap-2 items-center justify-center",
- "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-listening 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-listening",
- )
- }>
- Section 3 {part3 && }
-
-
- clsx(
- "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-listening/70 flex gap-2 items-center justify-center",
- "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-listening 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-listening",
- )
- }>
- Section 4 {part4 && }
-
-
-
- {[
- {part: part1, setPart: setPart1},
- {part: part2, setPart: setPart2},
- {part: part3, setPart: setPart3},
- {part: part4, setPart: setPart4},
- ].map(({part, setPart}, index) => (
-
- ))}
-
-
-
- {resultingExam && (
-
- )}
-
-
- >
- );
+ return (
+ <>
+
+
+
+ setMinTimer(parseInt(e) < 15 ? 15 : parseInt(e))}
+ value={minTimer}
+ className="max-w-[300px]"
+ />
+
+
+
+
+
+
+
+
+ clsx(
+ "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-listening/70 flex gap-2 items-center justify-center",
+ "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-listening 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-listening"
+ )
+ }
+ >
+ Section 1 {part1 && }
+
+
+ clsx(
+ "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-listening/70 flex gap-2 items-center justify-center",
+ "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-listening 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-listening"
+ )
+ }
+ >
+ Section 2 {part2 && }
+
+
+ clsx(
+ "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-listening/70 flex gap-2 items-center justify-center",
+ "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-listening 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-listening"
+ )
+ }
+ >
+ Section 3 {part3 && }
+
+
+ clsx(
+ "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-listening/70 flex gap-2 items-center justify-center",
+ "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-listening 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-listening"
+ )
+ }
+ >
+ Section 4 {part4 && }
+
+
+
+ {[
+ {
+ 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) => (
+
+ ))}
+
+
+
+ {resultingExam && (
+
+ )}
+
+
+ >
+ );
};
export default ListeningGeneration;
diff --git a/src/pages/(generation)/ReadingGeneration.tsx b/src/pages/(generation)/ReadingGeneration.tsx
index a49d9676..9a7297c4 100644
--- a/src/pages/(generation)/ReadingGeneration.tsx
+++ b/src/pages/(generation)/ReadingGeneration.tsx
@@ -1,315 +1,498 @@
import Input from "@/components/Low/Input";
import Select from "@/components/Low/Select";
-import {Difficulty, ReadingExam, ReadingPart} from "@/interfaces/exam";
+import {
+ Difficulty,
+ Exercise,
+ ReadingExam,
+ ReadingPart,
+} 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 {v4} from "uuid";
+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";
+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";
+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,
+ part,
+ difficulty,
+ index,
+ setPart,
+ updatePart,
}: {
- part?: ReadingPart;
- types: string[];
- index: number;
- difficulty: Difficulty;
- setPart: (part?: ReadingPart) => void;
+ part?: ReadingPart;
+ index: number;
+ difficulty: Difficulty;
+ setPart: (part?: ReadingPart) => void;
+ updatePart: Dispatch>;
+ // updatePart: (updater: (part: ReadingPart) => ReadingPart) => void;
}) => {
- const [topic, setTopic] = useState("");
- const [isLoading, setIsLoading] = useState(false);
+ const [topic, setTopic] = useState("");
+ const [isLoading, setIsLoading] = useState(false);
+ const [types, setTypes] = useState([]);
- const generate = () => {
- const url = new URLSearchParams();
- url.append("difficulty", difficulty);
+ const toggleType = (type: string) =>
+ setTypes((prev) =>
+ prev.includes(type)
+ ? [...prev.filter((x) => x !== type)]
+ : [...prev, type]
+ );
- if (topic) url.append("topic", topic);
- if (types) types.forEach((t) => url.append("exercises", t));
+ const generate = () => {
+ const url = new URLSearchParams();
+ url.append("difficulty", difficulty);
- setPart(undefined);
- setIsLoading(true);
- axios
- .get(`/api/exam/reading/generate/reading_passage_${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));
- };
+ if (topic) url.append("topic", topic);
+ if (types) types.forEach((t) => url.append("exercises", t));
- return (
-
-
-
-
-
- {isLoading && (
-
-
- Generating...
-
- )}
- {part && (
-
-
- {part.exercises.map((x) => (
-
- {x.type && convertCamelCaseToReadable(x.type)}
-
- ))}
-
-
{part.text.title}
-
{part.text.content}
-
- )}
-
- );
+ setPart(undefined);
+ setIsLoading(true);
+ axios
+ .get(
+ `/api/exam/reading/generate/reading_passage_${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));
+ };
+
+ 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;
+ }
+
+ return part;
+ })
+ }
+ />
+ >
+ );
+ 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;
+ }
+
+ return part;
+ });
+ }}
+ />
+ >
+ );
+ 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;
+ }
+
+ return part;
+ });
+ }}
+ />
+ >
+ );
+ case "matchSentences":
+ return (
+ <>
+ Exercise: Match Sentences
+ {
+ updatePart((part?: ReadingPart) => {
+ if (part) {
+ return {
+ ...part,
+ exercises: part.exercises.map((x) =>
+ x.id === exercise.id ? { ...x, ...data } : x
+ ),
+ } as ReadingPart;
+ }
+
+ return part;
+ });
+ }}
+ />
+ >
+ );
+ default:
+ return null;
+ }
+ });
+ };
+
+ 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 [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 [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 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 toggleType = (type: string) => setTypes((prev) => (prev.includes(type) ? [...prev.filter((x) => x !== type)] : [...prev, type]));
+ return;
+ }
- 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",
- });
+ setExams([exam]);
+ setSelectedModules(["reading"]);
- return;
- }
+ router.push("/exercises");
+ };
- setExams([exam]);
- setSelectedModules(["reading"]);
+ 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;
+ }
- router.push("/exercises");
- };
+ setIsLoading(true);
+ const exam: ReadingExam = {
+ parts,
+ isDiagnostic: false,
+ minTimer,
+ module: "reading",
+ id: v4(),
+ type: "academic",
+ variant: parts.length === 3 ? "full" : "partial",
+ difficulty,
+ };
- 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;
- }
+ 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);
- setIsLoading(true);
- const exam: ReadingExam = {
- parts,
- isDiagnostic: false,
- minTimer,
- module: "reading",
- id: v4(),
- type: "academic",
- variant: parts.length === 3 ? "full" : "partial",
- difficulty,
- };
+ 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));
+ };
- 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));
- };
-
- return (
- <>
-
-
-
- setMinTimer(parseInt(e) < 15 ? 15 : parseInt(e))}
- value={minTimer}
- className="max-w-[300px]"
- />
-
-
-
-
-
-
-
-
-
- {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}
-
- ))}
-
-
-
-
-
-
- clsx(
- "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-reading/70 flex gap-2 items-center justify-center",
- "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-reading 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-reading",
- )
- }>
- Passage 1 {part1 && }
-
-
- clsx(
- "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-reading/70 flex gap-2 items-center justify-center",
- "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-reading 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-reading",
- )
- }>
- Passage 2 {part2 && }
-
-
- clsx(
- "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-reading/70 flex gap-2 items-center justify-center",
- "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-reading 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-reading",
- )
- }>
- Passage 3 {part3 && }
-
-
-
- {[
- {part: part1, setPart: setPart1},
- {part: part2, setPart: setPart2},
- {part: part3, setPart: setPart3},
- ].map(({part, setPart}, index) => (
-
- ))}
-
-
-
- {resultingExam && (
-
- )}
-
-
- >
- );
+ return (
+ <>
+
+
+
+ setMinTimer(parseInt(e) < 15 ? 15 : parseInt(e))}
+ value={minTimer}
+ className="max-w-[300px]"
+ />
+
+
+
+
+
+
+
+
+ clsx(
+ "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-reading/70 flex gap-2 items-center justify-center",
+ "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-reading 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-reading"
+ )
+ }
+ >
+ Passage 1 {part1 && }
+
+
+ clsx(
+ "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-reading/70 flex gap-2 items-center justify-center",
+ "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-reading 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-reading"
+ )
+ }
+ >
+ Passage 2 {part2 && }
+
+
+ clsx(
+ "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-reading/70 flex gap-2 items-center justify-center",
+ "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-reading 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-reading"
+ )
+ }
+ >
+ Passage 3 {part3 && }
+
+
+
+ {[
+ { part: part1, setPart: setPart1 },
+ { part: part2, setPart: setPart2 },
+ { part: part3, setPart: setPart3 },
+ ].map(({ part, setPart }, index) => (
+
+ ))}
+
+
+
+ {resultingExam && (
+
+ )}
+
+
+ >
+ );
};
export default ReadingGeneration;
diff --git a/src/pages/(generation)/SpeakingGeneration.tsx b/src/pages/(generation)/SpeakingGeneration.tsx
index e0d1134a..1c8d4bfc 100644
--- a/src/pages/(generation)/SpeakingGeneration.tsx
+++ b/src/pages/(generation)/SpeakingGeneration.tsx
@@ -1,378 +1,503 @@
import Input from "@/components/Low/Input";
import Select from "@/components/Low/Select";
-import {Difficulty, Exercise, InteractiveSpeakingExercise, SpeakingExam, SpeakingExercise} from "@/interfaces/exam";
-import {AVATARS} from "@/resources/speakingAvatars";
+import {
+ Difficulty,
+ Exercise,
+ InteractiveSpeakingExercise,
+ SpeakingExam,
+ SpeakingExercise,
+} from "@/interfaces/exam";
+import { AVATARS } from "@/resources/speakingAvatars";
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, uniq} from "lodash";
+import { capitalize, sample, uniq } from "lodash";
import moment from "moment";
-import {useRouter} from "next/router";
-import {useEffect, useState} from "react";
-import {BsArrowRepeat, BsCheck} from "react-icons/bs";
-import {toast} from "react-toastify";
-import {v4} from "uuid";
+import { useRouter } from "next/router";
+import { useEffect, useState, Dispatch, SetStateAction } from "react";
+import { BsArrowRepeat, BsCheck } from "react-icons/bs";
+import { toast } from "react-toastify";
+import { v4 } from "uuid";
const DIFFICULTIES: Difficulty[] = ["easy", "medium", "hard"];
const PartTab = ({
- part,
- index,
- difficulty,
- setPart,
+ part,
+ index,
+ difficulty,
+ setPart,
+ updatePart,
}: {
- part?: SpeakingPart;
- difficulty: Difficulty;
- index: number;
- setPart: (part?: SpeakingPart) => void;
+ part?: SpeakingPart;
+ difficulty: Difficulty;
+ index: number;
+ setPart: (part?: SpeakingPart) => void;
+ updatePart: Dispatch>;
}) => {
- const [gender, setGender] = useState<"male" | "female">("male");
- const [isLoading, setIsLoading] = useState(false);
+ const [gender, setGender] = useState<"male" | "female">("male");
+ const [isLoading, setIsLoading] = useState(false);
- const generate = () => {
- setPart(undefined);
- setIsLoading(true);
+ const generate = () => {
+ setPart(undefined);
+ setIsLoading(true);
- const url = new URLSearchParams();
- url.append("difficulty", difficulty);
+ const url = new URLSearchParams();
+ url.append("difficulty", difficulty);
- axios
- .get(`/api/exam/speaking/generate/speaking_task_${index}?${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.");
- console.log(result.data);
- setPart(result.data);
- })
- .catch((error) => {
- console.log(error);
- toast.error("Something went wrong!");
- })
- .finally(() => setIsLoading(false));
- };
+ axios
+ .get(
+ `/api/exam/speaking/generate/speaking_task_${index}?${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."
+ );
+ console.log(result.data);
+ setPart(result.data);
+ })
+ .catch((error) => {
+ console.log(error);
+ toast.error("Something went wrong!");
+ })
+ .finally(() => setIsLoading(false));
+ };
- const generateVideo = async () => {
- if (!part) return toast.error("Please generate the first part before generating the video!");
- toast.info("This will take quite a while, please do not leave this page or close the tab/window.");
+ const generateVideo = async () => {
+ if (!part)
+ return toast.error(
+ "Please generate the first part before generating the video!"
+ );
+ toast.info(
+ "This will take quite a while, please do not leave this page or close the tab/window."
+ );
- const avatar = sample(AVATARS.filter((x) => x.gender === gender));
+ const avatar = sample(AVATARS.filter((x) => x.gender === gender));
- setIsLoading(true);
- const initialTime = moment();
+ setIsLoading(true);
+ const initialTime = moment();
- axios
- .post(`/api/exam/speaking/generate/speaking/generate_video_${index}`, {...part, avatar: avatar?.id})
- .then((result) => {
- const isError = typeof result.data === "string" || moment().diff(initialTime, "seconds") < 60;
+ axios
+ .post(`/api/exam/speaking/generate/speaking/generate_video_${index}`, {
+ ...part,
+ avatar: avatar?.id,
+ })
+ .then((result) => {
+ const isError =
+ typeof result.data === "string" ||
+ moment().diff(initialTime, "seconds") < 60;
- playSound(isError ? "error" : "check");
- console.log(result.data);
- if (isError) return toast.error("Something went wrong, please try to generate the video again.");
- setPart({...part, result: {...result.data, topic: part?.topic}, gender, avatar});
- })
- .catch((e) => {
- toast.error("Something went wrong!");
- console.log(e);
- })
- .finally(() => setIsLoading(false));
- };
+ playSound(isError ? "error" : "check");
+ console.log(result.data);
+ if (isError)
+ return toast.error(
+ "Something went wrong, please try to generate the video again."
+ );
+ setPart({
+ ...part,
+ result: { ...result.data, topic: part?.topic },
+ gender,
+ avatar,
+ });
+ })
+ .catch((e) => {
+ toast.error("Something went wrong!");
+ console.log(e);
+ })
+ .finally(() => setIsLoading(false));
+ };
- return (
-
-
-
-
-
-
-
-
- {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 (
+
+
+
+
+
+
+
+
+ {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)}
+
+ )}
+ {part.questions?.map((question, index) => (
+
+ 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]"
- />
-
-
-
-
-
+ return (
+ <>
+
+
+
+ setMinTimer(parseInt(e) < 5 ? 5 : parseInt(e))}
+ value={minTimer}
+ className="max-w-[300px]"
+ />
+
+
+
+
+
-
-
-
- clsx(
- "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-speaking/70 flex gap-2 items-center justify-center",
- "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-speaking 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-speaking",
- )
- }>
- Exercise 1 {part1 && part1.result && }
-
-
- clsx(
- "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-speaking/70 flex gap-2 items-center justify-center",
- "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-speaking 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-speaking",
- )
- }>
- Exercise 2 {part2 && part2.result && }
-
-
- clsx(
- "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-speaking/70 flex gap-2 items-center justify-center",
- "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-speaking 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-speaking",
- )
- }>
- Interactive {part3 && part3.result && }
-
-
-
- {[
- {part: part1, setPart: setPart1},
- {part: part2, setPart: setPart2},
- {part: part3, setPart: setPart3},
- ].map(({part, setPart}, index) => (
-
- ))}
-
-
-
- {resultingExam && (
-
- )}
-
-
- >
- );
+
+
+
+ clsx(
+ "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-speaking/70 flex gap-2 items-center justify-center",
+ "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-speaking 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-speaking"
+ )
+ }
+ >
+ Exercise 1 {part1 && part1.result && }
+
+
+ clsx(
+ "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-speaking/70 flex gap-2 items-center justify-center",
+ "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-speaking 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-speaking"
+ )
+ }
+ >
+ Exercise 2 {part2 && part2.result && }
+
+
+ clsx(
+ "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-speaking/70 flex gap-2 items-center justify-center",
+ "ring-white ring-opacity-60 ring-offset-2 ring-offset-ielts-speaking 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-speaking"
+ )
+ }
+ >
+ Interactive {part3 && part3.result && }
+
+
+
+ {[
+ { part: part1, setPart: setPart1 },
+ { part: part2, setPart: setPart2 },
+ { part: part3, setPart: setPart3 },
+ ].map(({ part, setPart }, index) => (
+
+ ))}
+
+
+
+ {resultingExam && (
+
+ )}
+
+
+ >
+ );
};
export default SpeakingGeneration;