import clsx from "clsx"; import SectionRenderer from "./SectionRenderer"; import Input from "../Low/Input"; import Select from "../Low/Select"; import { capitalize } from "lodash"; import { AccessType, ACCESSTYPE, Difficulty } from "@/interfaces/exam"; import { useCallback, useEffect, useMemo, useState } from "react"; import { toast } from "react-toastify"; import { ModuleState, SectionState } from "@/stores/examEditor/types"; import { Module } from "@/interfaces"; import useExamEditorStore from "@/stores/examEditor"; import WritingSettings from "./SettingsEditor/writing"; import ReadingSettings from "./SettingsEditor/reading"; import LevelSettings from "./SettingsEditor/level"; import ListeningSettings from "./SettingsEditor/listening"; import SpeakingSettings from "./SettingsEditor/speaking"; import ImportOrStartFromScratch from "./ImportExam/ImportOrFromScratch"; import { defaultSectionSettings } from "@/stores/examEditor/defaults"; import Button from "../Low/Button"; import ResetModule from "./Standalone/ResetModule"; import ListeningInstructions from "./Standalone/ListeningInstructions"; import { EntityWithRoles } from "@/interfaces/entity"; import Option from "../../interfaces/option"; const DIFFICULTIES: Option[] = [ { value: "A1", label: "A1" }, { value: "A2", label: "A2" }, { value: "B1", label: "B1" }, { value: "B2", label: "B2" }, { value: "C1", label: "C1" }, { value: "C2", label: "C2" }, ]; const ModuleSettings: Record = { reading: ReadingSettings, writing: WritingSettings, speaking: SpeakingSettings, listening: ListeningSettings, level: LevelSettings, }; const ExamEditor: React.FC<{ levelParts?: number; entitiesAllowEditPrivacy: EntityWithRoles[]; entitiesAllowConfExams: EntityWithRoles[]; entitiesAllowPublicExams: EntityWithRoles[]; }> = ({ levelParts = 0, entitiesAllowEditPrivacy = [], entitiesAllowConfExams = [], entitiesAllowPublicExams = [], }) => { const { currentModule, dispatch } = useExamEditorStore(); const { sections, minTimer, expandedSections, examLabel, access, difficulty, sectionLabels, importModule, } = useExamEditorStore((state) => state.modules[currentModule]); const [numberOfLevelParts, setNumberOfLevelParts] = useState( levelParts !== 0 ? levelParts : 1 ); const [isResetModuleOpen, setIsResetModuleOpen] = useState(false); // For exam edits useEffect(() => { if (levelParts !== 0) { setNumberOfLevelParts(levelParts); dispatch({ type: "UPDATE_MODULE", payload: { updates: { sectionLabels: Array.from({ length: levelParts }).map((_, i) => ({ id: i + 1, label: `Part ${i + 1}`, })), }, module: "level", }, }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [levelParts]); useEffect(() => { const currentSections = sections; const currentLabels = sectionLabels; let updatedSections: SectionState[]; let updatedLabels: any; if ( (currentModule === "level" && currentSections.length !== currentLabels.length) || numberOfLevelParts !== currentSections.length ) { const newSections = [...currentSections]; const newLabels = [...currentLabels]; for (let i = currentLabels.length; i < numberOfLevelParts; i++) { if (currentSections.length !== numberOfLevelParts) newSections.push(defaultSectionSettings(currentModule, i + 1)); newLabels.push({ id: i + 1, label: `Part ${i + 1}`, }); } updatedSections = newSections; updatedLabels = newLabels; } else if (numberOfLevelParts < currentSections.length) { updatedSections = currentSections.slice(0, numberOfLevelParts); updatedLabels = currentLabels.slice(0, numberOfLevelParts); } else { return; } const updatedExpandedSections = expandedSections.filter((sectionId) => updatedSections.some((section) => section.sectionId === sectionId) ); dispatch({ type: "UPDATE_MODULE", payload: { updates: { sections: updatedSections, sectionLabels: updatedLabels, expandedSections: updatedExpandedSections, }, }, }); // eslint-disable-next-line react-hooks/exhaustive-deps }, [numberOfLevelParts]); const sectionIds = useMemo( () => sections.map((section) => section.sectionId), [sections] ); const updateModule = useCallback( (updates: Partial) => { dispatch({ type: "UPDATE_MODULE", payload: { updates } }); }, [dispatch] ); const toggleSection = useCallback( (sectionId: number) => { if (expandedSections.length === 1 && sectionIds.includes(sectionId)) { toast.error("Include at least one section!"); return; } dispatch({ type: "TOGGLE_SECTION", payload: { sectionId } }); }, [dispatch, expandedSections, sectionIds] ); const Settings = useMemo( () => ModuleSettings[currentModule], [currentModule] ); const showImport = useMemo( () => importModule && ["reading", "listening", "level"].includes(currentModule), [importModule, currentModule] ); const accessTypeOptions = useMemo(() => { let options: Option[] = [{ value: "private", label: "Private" }]; if (entitiesAllowConfExams.length > 0) { options.push({ value: "confidential", label: "Confidential" }); } if (entitiesAllowPublicExams.length > 0) { options.push({ value: "public", label: "Public" }); } return options; }, [entitiesAllowConfExams.length, entitiesAllowPublicExams.length]); const updateLevelParts = useCallback((parts: number) => { setNumberOfLevelParts(parts); }, []); return ( <> {showImport ? ( ) : ( <> {isResetModuleOpen && ( )}
3 ? "-2xl:flex-col" : "-xl:flex-col" )} >
updateModule({ minTimer: parseInt(e) < 15 ? 15 : parseInt(e), }) } value={minTimer} className="max-w-[125px] min-w-[100px] w-min" />
setNumberOfLevelParts(parseInt(v))} value={numberOfLevelParts} />
)}
updateModule({ examLabel: text })} roundness="xl" value={examLabel} required />
{currentModule === "listening" && }
)} ); }; export default ExamEditor;