276 lines
13 KiB
TypeScript
276 lines
13 KiB
TypeScript
import { Exercise, InteractiveSpeakingExercise, LevelExam, LevelPart, SpeakingExercise } from "@/interfaces/exam";
|
|
import SettingsEditor from ".";
|
|
import Option from "@/interfaces/option";
|
|
import Dropdown from "@/components/Dropdown";
|
|
import clsx from "clsx";
|
|
import ExercisePicker from "../ExercisePicker";
|
|
import useExamEditorStore from "@/stores/examEditor";
|
|
import useSettingsState from "../Hooks/useSettingsState";
|
|
import { LevelSectionSettings, SectionSettings } from "@/stores/examEditor/types";
|
|
import { toast } from "react-toastify";
|
|
import axios from "axios";
|
|
import { playSound } from "@/utils/sound";
|
|
import { useRouter } from "next/router";
|
|
import { usePersistentExamStore } from "@/stores/examStore";
|
|
import openDetachedTab from "@/utils/popout";
|
|
import ListeningComponents from "./listening/components";
|
|
import ReadingComponents from "./reading/components";
|
|
import WritingComponents from "./writing/components";
|
|
import SpeakingComponents from "./speaking/components";
|
|
import SectionPicker from "./Shared/SectionPicker";
|
|
import SettingsDropdown from "./Shared/SettingsDropdown";
|
|
|
|
|
|
const LevelSettings: React.FC = () => {
|
|
|
|
const router = useRouter();
|
|
|
|
const {
|
|
setExam,
|
|
setExerciseIndex,
|
|
setPartIndex,
|
|
setQuestionIndex,
|
|
setBgColor,
|
|
} = usePersistentExamStore();
|
|
|
|
const { currentModule, title } = useExamEditorStore();
|
|
const {
|
|
focusedSection,
|
|
difficulty,
|
|
sections,
|
|
minTimer,
|
|
isPrivate,
|
|
} = useExamEditorStore(state => state.modules[currentModule]);
|
|
|
|
const { localSettings, updateLocalAndScheduleGlobal } = useSettingsState<LevelSectionSettings>(
|
|
currentModule,
|
|
focusedSection
|
|
);
|
|
|
|
const section = sections.find((section) => section.sectionId == focusedSection);
|
|
const focusedExercise = section?.focusedExercise;
|
|
if (section === undefined) return <></>;
|
|
|
|
const currentSection = section.state as LevelPart;
|
|
const readingSection = section.readingSection;
|
|
const listeningSection = section.listeningSection;
|
|
|
|
const canPreview = currentSection.exercises.length > 0;
|
|
|
|
const submitLevel = () => {
|
|
if (title === "") {
|
|
toast.error("Enter a title for the exam!");
|
|
return;
|
|
}
|
|
const exam: LevelExam = {
|
|
parts: sections.map((s) => {
|
|
const part = s.state as LevelPart;
|
|
return {
|
|
...part,
|
|
intro: localSettings.currentIntro,
|
|
category: localSettings.category
|
|
};
|
|
}),
|
|
isDiagnostic: false,
|
|
minTimer,
|
|
module: "level",
|
|
id: title,
|
|
difficulty,
|
|
private: isPrivate,
|
|
};
|
|
|
|
axios.post(`/api/exam/level`, exam)
|
|
.then((result) => {
|
|
playSound("sent");
|
|
toast.success(`Submitted Exam ID: ${result.data.id}`);
|
|
})
|
|
.catch((error) => {
|
|
console.log(error);
|
|
toast.error(error.response.data.error || "Something went wrong while submitting, please try again later.");
|
|
})
|
|
}
|
|
|
|
const preview = () => {
|
|
setExam({
|
|
parts: sections.map((s) => {
|
|
const exercise = s.state as LevelPart;
|
|
return {
|
|
...exercise,
|
|
intro: s.settings.currentIntro,
|
|
category: s.settings.category
|
|
};
|
|
}),
|
|
minTimer,
|
|
module: "level",
|
|
id: title,
|
|
isDiagnostic: false,
|
|
variant: undefined,
|
|
difficulty,
|
|
private: isPrivate,
|
|
} as LevelExam);
|
|
setExerciseIndex(0);
|
|
setQuestionIndex(0);
|
|
setPartIndex(0);
|
|
openDetachedTab("popout?type=Exam&module=level", router)
|
|
}
|
|
|
|
const speakingExercise = focusedExercise === undefined ? undefined : currentSection.exercises[focusedExercise] as SpeakingExercise | InteractiveSpeakingExercise;
|
|
|
|
return (
|
|
<SettingsEditor
|
|
sectionLabel={`Part ${focusedSection}`}
|
|
sectionId={focusedSection}
|
|
module="level"
|
|
introPresets={[]}
|
|
preview={preview}
|
|
canPreview={canPreview}
|
|
canSubmit={canPreview}
|
|
submitModule={submitLevel}
|
|
>
|
|
<div>
|
|
<Dropdown title="Add Level Exercises" className={
|
|
clsx(
|
|
"w-full font-semibold flex justify-between items-center p-4 bg-gradient-to-r border",
|
|
"bg-ielts-level/70 border-ielts-level hover:bg-ielts-level",
|
|
"text-white shadow-md transition-all duration-300",
|
|
localSettings.isLevelDropdownOpen ? "rounded-t-lg" : "rounded-lg"
|
|
)
|
|
}
|
|
contentWrapperClassName={"pt-4 px-2 bg-white rounded-b-lg shadow-md transition-all duration-300 ease-in-out"}
|
|
open={localSettings.isLevelDropdownOpen}
|
|
setIsOpen={(isOpen: boolean) => updateLocalAndScheduleGlobal({ isLevelDropdownOpen: isOpen }, false)}
|
|
>
|
|
<ExercisePicker
|
|
module="level"
|
|
sectionId={focusedSection}
|
|
difficulty={difficulty}
|
|
/>
|
|
</Dropdown>
|
|
</div>
|
|
<div>
|
|
<Dropdown title="Add Reading Exercises" className={
|
|
clsx(
|
|
"w-full font-semibold flex justify-between items-center p-4 bg-gradient-to-r border",
|
|
"bg-ielts-reading/70 border-ielts-reading hover:bg-ielts-reading",
|
|
"text-white shadow-md transition-all duration-300",
|
|
localSettings.isReadingDropdownOpen ? "rounded-t-lg" : "rounded-lg"
|
|
)
|
|
}
|
|
contentWrapperClassName={"pt-4 px-2 bg-white rounded-b-lg shadow-md transition-all duration-300 ease-in-out"}
|
|
open={localSettings.isReadingDropdownOpen}
|
|
setIsOpen={(isOpen: boolean) => updateLocalAndScheduleGlobal({ isReadingDropdownOpen: isOpen }, false)}
|
|
>
|
|
<div className="space-y-2 px-2 pb-2">
|
|
<SectionPicker {...{ module: "reading", sectionId: focusedSection, localSettings, updateLocalAndScheduleGlobal }} />
|
|
<ReadingComponents
|
|
{...{ localSettings, updateLocalAndScheduleGlobal, currentSection, generatePassageDisabled: readingSection === undefined, levelId: readingSection, level: true }}
|
|
/>
|
|
</div>
|
|
</Dropdown>
|
|
</div>
|
|
<div>
|
|
<Dropdown title="Add Listening Exercises" className={
|
|
clsx(
|
|
"w-full font-semibold flex justify-between items-center p-4 bg-gradient-to-r border",
|
|
"bg-ielts-listening/70 border-ielts-listening hover:bg-ielts-listening",
|
|
"text-white shadow-md transition-all duration-300",
|
|
localSettings.isListeningDropdownOpen ? "rounded-t-lg" : "rounded-lg"
|
|
)
|
|
}
|
|
contentWrapperClassName={"pt-4 px-2 bg-white rounded-b-lg shadow-md transition-all duration-300 ease-in-out"}
|
|
open={localSettings.isListeningDropdownOpen}
|
|
setIsOpen={(isOpen: boolean) => updateLocalAndScheduleGlobal({ isListeningDropdownOpen: isOpen }, false)}
|
|
>
|
|
<div className="space-y-2 px-2 pb-2">
|
|
<SectionPicker {...{ module: "listening", sectionId: focusedSection, localSettings, updateLocalAndScheduleGlobal }} />
|
|
<ListeningComponents
|
|
{...{ localSettings, updateLocalAndScheduleGlobal, currentSection, audioContextDisabled: listeningSection === undefined, levelId: listeningSection, level: true }}
|
|
/>
|
|
</div>
|
|
</Dropdown>
|
|
</div>
|
|
<div>
|
|
<Dropdown title="Add Writing Exercises" className={
|
|
clsx(
|
|
"w-full font-semibold flex justify-between items-center p-4 bg-gradient-to-r border",
|
|
"bg-ielts-writing/70 border-ielts-writing hover:bg-ielts-writing",
|
|
"text-white shadow-md transition-all duration-300",
|
|
localSettings.isWritingDropdownOpen ? "rounded-t-lg" : "rounded-lg"
|
|
)
|
|
}
|
|
contentWrapperClassName={"pt-4 px-2 bg-white rounded-b-lg shadow-md transition-all duration-300 ease-in-out"}
|
|
open={localSettings.isWritingDropdownOpen}
|
|
setIsOpen={(isOpen: boolean) => updateLocalAndScheduleGlobal({ isWritingDropdownOpen: isOpen }, false)}
|
|
>
|
|
<ExercisePicker
|
|
module="writing"
|
|
sectionId={focusedSection}
|
|
difficulty={difficulty}
|
|
levelSectionId={focusedSection}
|
|
level
|
|
/>
|
|
</Dropdown>
|
|
</div >
|
|
<div>
|
|
<Dropdown title="Add Speaking Exercises" className={
|
|
clsx(
|
|
"w-full font-semibold flex justify-between items-center p-4 bg-gradient-to-r border",
|
|
"bg-ielts-speaking/70 border-ielts-speaking hover:bg-ielts-speaking",
|
|
"text-white shadow-md transition-all duration-300",
|
|
localSettings.isSpeakingDropdownOpen ? "rounded-t-lg" : "rounded-lg"
|
|
)
|
|
}
|
|
contentWrapperClassName={"pt-4 px-2 bg-white rounded-b-lg shadow-md transition-all duration-300 ease-in-out"}
|
|
open={localSettings.isSpeakingDropdownOpen}
|
|
setIsOpen={(isOpen: boolean) => updateLocalAndScheduleGlobal({ isSpeakingDropdownOpen: isOpen }, false)}
|
|
>
|
|
<Dropdown title="Exercises" className={
|
|
clsx(
|
|
"w-full font-semibold flex justify-between items-center p-4 bg-gradient-to-r border",
|
|
"bg-ielts-speaking/70 border-ielts-speaking hover:bg-ielts-speaking",
|
|
"text-white shadow-md transition-all duration-300",
|
|
localSettings.isSpeakingDropdownOpen ? "rounded-t-lg" : "rounded-lg"
|
|
)
|
|
}
|
|
contentWrapperClassName={"pt-4 px-2 bg-white rounded-b-lg shadow-md transition-all duration-300 ease-in-out border border-ielts-speaking"}
|
|
open={localSettings.isSpeakingDropdownOpen}
|
|
setIsOpen={(isOpen: boolean) => updateLocalAndScheduleGlobal({ isSpeakingDropdownOpen: isOpen }, false)}
|
|
>
|
|
<div className="space-y-2 px-2 pb-2">
|
|
<ExercisePicker
|
|
module="speaking"
|
|
sectionId={focusedSection}
|
|
difficulty={difficulty}
|
|
levelSectionId={focusedSection}
|
|
level
|
|
/>
|
|
</div>
|
|
</Dropdown>
|
|
|
|
{speakingExercise !== undefined &&
|
|
<Dropdown title="Configure Speaking Exercise" className={
|
|
clsx(
|
|
"w-full font-semibold flex justify-between items-center p-4 bg-gradient-to-r border",
|
|
"bg-ielts-speaking/70 border-ielts-speaking hover:bg-ielts-speaking",
|
|
"text-white shadow-md transition-all duration-300",
|
|
localSettings.isSpeakingDropdownOpen ? "rounded-t-lg" : "rounded-lg"
|
|
)
|
|
}
|
|
contentWrapperClassName={"pt-4 px-2 bg-white rounded-b-lg shadow-md transition-all duration-300 ease-in-out border border-ielts-speaking"}
|
|
open={localSettings.isSpeakingDropdownOpen}
|
|
setIsOpen={(isOpen: boolean) => updateLocalAndScheduleGlobal({ isSpeakingDropdownOpen: isOpen }, false)}
|
|
>
|
|
<SpeakingComponents
|
|
{...{ localSettings, updateLocalAndScheduleGlobal, section: speakingExercise }}
|
|
level
|
|
/>
|
|
</Dropdown>
|
|
}
|
|
</Dropdown>
|
|
</div>
|
|
</SettingsEditor >
|
|
);
|
|
};
|
|
|
|
export default LevelSettings;
|