Remove unused imports and changed and improved layout design and responsiveness in some components and fixed some bugs.
This commit is contained in:
@@ -95,7 +95,7 @@ const SettingsEditor: React.FC<SettingsEditorProps> = ({
|
||||
}, [updateLocalAndScheduleGlobal]);
|
||||
|
||||
return (
|
||||
<div className={`flex flex-col gap-8 border bg-ielts-${module}/20 rounded-3xl p-8 w-1/3 h-fit`}>
|
||||
<div className={`flex flex-col gap-8 border bg-ielts-${module}/20 rounded-3xl p-8 w-1/3 h-fit -2xl:w-full`}>
|
||||
<div className={`w-full flex justify-center text-ielts-${module} font-bold text-xl`}>{sectionLabel} Settings</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Dropdown
|
||||
|
||||
@@ -233,7 +233,7 @@ const ListeningComponents: React.FC<Props> = ({ currentSection, localSettings, u
|
||||
setIsOpen={(isOpen: boolean) => updateLocalAndScheduleGlobal({ isAudioContextOpen: isOpen }, false)}
|
||||
contentWrapperClassName={level ? `border border-ielts-listening` : ''}
|
||||
>
|
||||
<div className="flex flex-row gap-2 items-center px-2 pb-4">
|
||||
<div className="flex flex-row flex-wrap gap-2 items-center px-2 pb-4">
|
||||
<div className="flex flex-col flex-grow gap-4 px-2">
|
||||
<label className="font-normal text-base text-mti-gray-dim">Topic (Optional)</label>
|
||||
<Input
|
||||
|
||||
@@ -5,103 +5,140 @@ import ExercisePicker from "../../ExercisePicker";
|
||||
import { generate } from "../Shared/Generate";
|
||||
import GenerateBtn from "../Shared/GenerateBtn";
|
||||
import { LevelPart, ReadingPart } from "@/interfaces/exam";
|
||||
import { LevelSectionSettings, ReadingSectionSettings } from "@/stores/examEditor/types";
|
||||
import {
|
||||
LevelSectionSettings,
|
||||
ReadingSectionSettings,
|
||||
} from "@/stores/examEditor/types";
|
||||
import useExamEditorStore from "@/stores/examEditor";
|
||||
|
||||
interface Props {
|
||||
localSettings: ReadingSectionSettings | LevelSectionSettings;
|
||||
updateLocalAndScheduleGlobal: (updates: Partial<ReadingSectionSettings | LevelSectionSettings>, schedule?: boolean) => void;
|
||||
currentSection: ReadingPart | LevelPart;
|
||||
generatePassageDisabled?: boolean;
|
||||
levelId?: number;
|
||||
level?: boolean;
|
||||
localSettings: ReadingSectionSettings | LevelSectionSettings;
|
||||
updateLocalAndScheduleGlobal: (
|
||||
updates: Partial<ReadingSectionSettings | LevelSectionSettings>,
|
||||
schedule?: boolean
|
||||
) => void;
|
||||
currentSection: ReadingPart | LevelPart;
|
||||
generatePassageDisabled?: boolean;
|
||||
levelId?: number;
|
||||
level?: boolean;
|
||||
}
|
||||
|
||||
const ReadingComponents: React.FC<Props> = ({localSettings, updateLocalAndScheduleGlobal, currentSection, levelId, level = false, generatePassageDisabled = false}) => {
|
||||
const { currentModule } = useExamEditorStore();
|
||||
const {
|
||||
focusedSection,
|
||||
difficulty,
|
||||
} = useExamEditorStore(state => state.modules[currentModule]);
|
||||
const ReadingComponents: React.FC<Props> = ({
|
||||
localSettings,
|
||||
updateLocalAndScheduleGlobal,
|
||||
currentSection,
|
||||
levelId,
|
||||
level = false,
|
||||
generatePassageDisabled = false,
|
||||
}) => {
|
||||
const { currentModule } = useExamEditorStore();
|
||||
const { focusedSection, difficulty } = useExamEditorStore(
|
||||
(state) => state.modules[currentModule]
|
||||
);
|
||||
|
||||
const generatePassage = useCallback(() => {
|
||||
generate(
|
||||
levelId ? levelId : focusedSection,
|
||||
"reading",
|
||||
"passage",
|
||||
{
|
||||
method: 'GET',
|
||||
queryParams: {
|
||||
difficulty,
|
||||
...(localSettings.readingTopic && { topic: localSettings.readingTopic })
|
||||
}
|
||||
},
|
||||
(data: any) => [{
|
||||
title: data.title,
|
||||
text: data.text
|
||||
}],
|
||||
level ? focusedSection : undefined,
|
||||
level
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [localSettings.readingTopic, difficulty, focusedSection, levelId]);
|
||||
|
||||
const onTopicChange = useCallback((readingTopic: string) => {
|
||||
updateLocalAndScheduleGlobal({ readingTopic });
|
||||
}, [updateLocalAndScheduleGlobal]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dropdown
|
||||
title="Generate Passage"
|
||||
module="reading"
|
||||
open={localSettings.isPassageOpen}
|
||||
setIsOpen={(isOpen: boolean) => updateLocalAndScheduleGlobal({ isPassageOpen: isOpen }, false)}
|
||||
contentWrapperClassName={level ? `border border-ielts-reading`: ''}
|
||||
disabled={generatePassageDisabled}
|
||||
>
|
||||
<div className="flex flex-row gap-2 items-center px-2 pb-4">
|
||||
<div className="flex flex-col flex-grow gap-4 px-2">
|
||||
<label className="font-normal text-base text-mti-gray-dim">Topic (Optional)</label>
|
||||
<Input
|
||||
key={`section-${focusedSection}`}
|
||||
type="text"
|
||||
placeholder="Topic"
|
||||
name="category"
|
||||
onChange={onTopicChange}
|
||||
roundness="full"
|
||||
value={localSettings.readingTopic}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex self-end h-16 mb-1">
|
||||
<GenerateBtn
|
||||
module="reading"
|
||||
genType="passage"
|
||||
sectionId={focusedSection}
|
||||
generateFnc={generatePassage}
|
||||
level={level}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Dropdown>
|
||||
<Dropdown
|
||||
title="Add Exercises"
|
||||
module="reading"
|
||||
open={localSettings.isReadingTopicOpean}
|
||||
setIsOpen={(isOpen: boolean) => updateLocalAndScheduleGlobal({ isReadingTopicOpean: isOpen })}
|
||||
contentWrapperClassName={level ? `border border-ielts-reading`: ''}
|
||||
disabled={currentSection === undefined || currentSection.text === undefined || currentSection.text.content === "" || currentSection.text.title === ""}
|
||||
>
|
||||
<ExercisePicker
|
||||
module="reading"
|
||||
sectionId={levelId !== undefined ? levelId : focusedSection}
|
||||
extraArgs={{ text: currentSection === undefined || currentSection.text === undefined ? "" : currentSection.text.content }}
|
||||
levelSectionId={focusedSection}
|
||||
level={level}
|
||||
/>
|
||||
</Dropdown>
|
||||
</>
|
||||
const generatePassage = useCallback(() => {
|
||||
generate(
|
||||
levelId ? levelId : focusedSection,
|
||||
"reading",
|
||||
"passage",
|
||||
{
|
||||
method: "GET",
|
||||
queryParams: {
|
||||
difficulty,
|
||||
...(localSettings.readingTopic && {
|
||||
topic: localSettings.readingTopic,
|
||||
}),
|
||||
},
|
||||
},
|
||||
(data: any) => [
|
||||
{
|
||||
title: data.title,
|
||||
text: data.text,
|
||||
},
|
||||
],
|
||||
level ? focusedSection : undefined,
|
||||
level
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [localSettings.readingTopic, difficulty, focusedSection, levelId]);
|
||||
|
||||
const onTopicChange = useCallback(
|
||||
(readingTopic: string) => {
|
||||
updateLocalAndScheduleGlobal({ readingTopic });
|
||||
},
|
||||
[updateLocalAndScheduleGlobal]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Dropdown
|
||||
title="Generate Passage"
|
||||
module="reading"
|
||||
open={localSettings.isPassageOpen}
|
||||
setIsOpen={(isOpen: boolean) =>
|
||||
updateLocalAndScheduleGlobal({ isPassageOpen: isOpen }, false)
|
||||
}
|
||||
contentWrapperClassName={level ? `border border-ielts-reading` : ""}
|
||||
disabled={generatePassageDisabled}
|
||||
>
|
||||
<div
|
||||
className="flex flex-row flex-wrap gap-2 items-center px-2 pb-4 "
|
||||
>
|
||||
<div className="flex flex-col flex-grow gap-4 px-2">
|
||||
<label className="font-normal text-base text-mti-gray-dim">
|
||||
Topic (Optional)
|
||||
</label>
|
||||
<Input
|
||||
key={`section-${focusedSection}`}
|
||||
type="text"
|
||||
placeholder="Topic"
|
||||
name="category"
|
||||
onChange={onTopicChange}
|
||||
roundness="full"
|
||||
value={localSettings.readingTopic}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex self-end h-16 mb-1">
|
||||
<GenerateBtn
|
||||
module="reading"
|
||||
genType="passage"
|
||||
sectionId={focusedSection}
|
||||
generateFnc={generatePassage}
|
||||
level={level}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Dropdown>
|
||||
<Dropdown
|
||||
title="Add Exercises"
|
||||
module="reading"
|
||||
open={localSettings.isReadingTopicOpean}
|
||||
setIsOpen={(isOpen: boolean) =>
|
||||
updateLocalAndScheduleGlobal({ isReadingTopicOpean: isOpen })
|
||||
}
|
||||
contentWrapperClassName={level ? `border border-ielts-reading` : ""}
|
||||
disabled={
|
||||
currentSection === undefined ||
|
||||
currentSection.text === undefined ||
|
||||
currentSection.text.content === "" ||
|
||||
currentSection.text.title === ""
|
||||
}
|
||||
>
|
||||
<ExercisePicker
|
||||
module="reading"
|
||||
sectionId={levelId !== undefined ? levelId : focusedSection}
|
||||
extraArgs={{
|
||||
text:
|
||||
currentSection === undefined || currentSection.text === undefined
|
||||
? ""
|
||||
: currentSection.text.content,
|
||||
}}
|
||||
levelSectionId={focusedSection}
|
||||
level={level}
|
||||
/>
|
||||
</Dropdown>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReadingComponents;
|
||||
|
||||
@@ -19,8 +19,8 @@ const label = (type: string, firstId: string, lastId: string) => {
|
||||
const ExerciseLabel: React.FC<Props> = ({type, firstId, lastId, prompt}) => {
|
||||
return (
|
||||
<div className="flex w-full justify-between items-center mr-4">
|
||||
<span className="font-semibold">{label(type, firstId, lastId)}</span>
|
||||
<div className="text-sm font-light italic">{previewLabel(prompt)}</div>
|
||||
<span className="font-semibold ellipsis-2">{label(type, firstId, lastId)}</span>
|
||||
<div className="text-sm font-light italic ellipsis-2">{previewLabel(prompt)}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -24,216 +24,265 @@ import ListeningInstructions from "./Standalone/ListeningInstructions";
|
||||
const DIFFICULTIES: Difficulty[] = ["A1", "A2", "B1", "B2", "C1", "C2"];
|
||||
|
||||
const ExamEditor: React.FC<{ levelParts?: number }> = ({ levelParts = 0 }) => {
|
||||
const { currentModule, dispatch } = useExamEditorStore();
|
||||
const {
|
||||
sections,
|
||||
minTimer,
|
||||
expandedSections,
|
||||
examLabel,
|
||||
isPrivate,
|
||||
difficulty,
|
||||
sectionLabels,
|
||||
importModule
|
||||
} = useExamEditorStore(state => state.modules[currentModule]);
|
||||
const { currentModule, dispatch } = useExamEditorStore();
|
||||
const {
|
||||
sections,
|
||||
minTimer,
|
||||
expandedSections,
|
||||
examLabel,
|
||||
isPrivate,
|
||||
difficulty,
|
||||
sectionLabels,
|
||||
importModule,
|
||||
} = useExamEditorStore((state) => state.modules[currentModule]);
|
||||
|
||||
const [numberOfLevelParts, setNumberOfLevelParts] = useState(levelParts !== 0 ? levelParts : 1);
|
||||
const [isResetModuleOpen, setIsResetModuleOpen] = useState(false);
|
||||
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])
|
||||
// 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
|
||||
}
|
||||
}
|
||||
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}`,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [numberOfLevelParts]);
|
||||
|
||||
const sectionIds = sections.map((section) => section.sectionId)
|
||||
|
||||
const updateModule = useCallback((updates: Partial<ModuleState>) => {
|
||||
dispatch({ type: 'UPDATE_MODULE', payload: { updates } });
|
||||
}, [dispatch]);
|
||||
|
||||
const toggleSection = (sectionId: number) => {
|
||||
if (expandedSections.length === 1 && sectionIds.includes(sectionId)) {
|
||||
toast.error("Include at least one section!");
|
||||
return;
|
||||
}
|
||||
dispatch({ type: 'TOGGLE_SECTION', payload: { sectionId } });
|
||||
};
|
||||
|
||||
const ModuleSettings: Record<Module, React.ComponentType> = {
|
||||
reading: ReadingSettings,
|
||||
writing: WritingSettings,
|
||||
speaking: SpeakingSettings,
|
||||
listening: ListeningSettings,
|
||||
level: LevelSettings
|
||||
};
|
||||
|
||||
const Settings = ModuleSettings[currentModule];
|
||||
const showImport = importModule && ["reading", "listening", "level"].includes(currentModule);
|
||||
|
||||
const updateLevelParts = (parts: number) => {
|
||||
setNumberOfLevelParts(parts);
|
||||
}
|
||||
updatedSections = newSections;
|
||||
updatedLabels = newLabels;
|
||||
} else if (numberOfLevelParts < currentSections.length) {
|
||||
updatedSections = currentSections.slice(0, numberOfLevelParts);
|
||||
updatedLabels = currentLabels.slice(0, numberOfLevelParts);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{showImport ? <ImportOrStartFromScratch module={currentModule} setNumberOfLevelParts={updateLevelParts} /> : (
|
||||
<>
|
||||
{isResetModuleOpen && <ResetModule module={currentModule} isOpen={isResetModuleOpen} setIsOpen={setIsResetModuleOpen} setNumberOfLevelParts={setNumberOfLevelParts}/>}
|
||||
<div className="flex gap-4 w-full items-center">
|
||||
<div className="flex flex-col gap-3">
|
||||
<label className="font-normal text-base text-mti-gray-dim">Timer</label>
|
||||
<Input
|
||||
type="number"
|
||||
name="minTimer"
|
||||
onChange={(e) => updateModule({ minTimer: parseInt(e) < 15 ? 15 : parseInt(e) })}
|
||||
value={minTimer}
|
||||
className="max-w-[300px]"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 flex-grow">
|
||||
<label className="font-normal text-base text-mti-gray-dim">Difficulty</label>
|
||||
<Select
|
||||
isMulti={true}
|
||||
options={DIFFICULTIES.map((x) => ({
|
||||
value: x,
|
||||
label: capitalize(x)
|
||||
}))}
|
||||
onChange={(values) => {
|
||||
const selectedDifficulties = values ? values.map(v => v.value as Difficulty) : [];
|
||||
updateModule({ difficulty: selectedDifficulties });
|
||||
}}
|
||||
value={
|
||||
difficulty
|
||||
? difficulty.map(d => ({
|
||||
value: d,
|
||||
label: capitalize(d)
|
||||
}))
|
||||
: null
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{(sectionLabels.length != 0 && currentModule !== "level") ? (
|
||||
<div className="flex flex-col gap-3">
|
||||
<label className="font-normal text-base text-mti-gray-dim">{sectionLabels[0].label.split(" ")[0]}</label>
|
||||
<div className="flex flex-row gap-8">
|
||||
{sectionLabels.map(({ id, label }) => (
|
||||
<span
|
||||
key={id}
|
||||
className={clsx(
|
||||
"px-6 py-4 w-48 h-[72px] flex justify-center items-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer",
|
||||
"transition duration-300 ease-in-out",
|
||||
sectionIds.includes(id)
|
||||
? `bg-ielts-${currentModule}/70 border-ielts-${currentModule} text-white`
|
||||
: "bg-white border-mti-gray-platinum"
|
||||
)}
|
||||
onClick={() => toggleSection(id)}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
) : (
|
||||
<div className="flex flex-col gap-3 w-1/3">
|
||||
<label className="font-normal text-base text-mti-gray-dim">Number of Parts</label>
|
||||
<Input type="number" name="Number of Parts" min={1} onChange={(v) => setNumberOfLevelParts(parseInt(v))} value={numberOfLevelParts} />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col gap-3 w-fit h-fit">
|
||||
<div className="h-6" />
|
||||
<Checkbox isChecked={isPrivate} onChange={(checked) => updateModule({ isPrivate: checked })}>
|
||||
Privacy (Only available for Assignments)
|
||||
</Checkbox>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row gap-3 w-full">
|
||||
<div className="flex flex-col gap-3 flex-grow">
|
||||
<label className="font-normal text-base text-mti-gray-dim">Exam Label *</label>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Exam Label"
|
||||
name="label"
|
||||
onChange={(text) => updateModule({ examLabel: text })}
|
||||
roundness="xl"
|
||||
value={examLabel}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{currentModule === "listening" && <ListeningInstructions />}
|
||||
<Button
|
||||
onClick={() => setIsResetModuleOpen(true)}
|
||||
customColor={`bg-ielts-${currentModule}/70 hover:bg-ielts-${currentModule} border-ielts-${currentModule}`}
|
||||
className={`text-white self-end`}
|
||||
>
|
||||
Reset Module
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-row gap-8">
|
||||
<Settings />
|
||||
<div className="flex-grow max-w-[66%]">
|
||||
<SectionRenderer />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
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 = sections.map((section) => section.sectionId);
|
||||
|
||||
const updateModule = useCallback(
|
||||
(updates: Partial<ModuleState>) => {
|
||||
dispatch({ type: "UPDATE_MODULE", payload: { updates } });
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const toggleSection = (sectionId: number) => {
|
||||
if (expandedSections.length === 1 && sectionIds.includes(sectionId)) {
|
||||
toast.error("Include at least one section!");
|
||||
return;
|
||||
}
|
||||
dispatch({ type: "TOGGLE_SECTION", payload: { sectionId } });
|
||||
};
|
||||
|
||||
const ModuleSettings: Record<Module, React.ComponentType> = {
|
||||
reading: ReadingSettings,
|
||||
writing: WritingSettings,
|
||||
speaking: SpeakingSettings,
|
||||
listening: ListeningSettings,
|
||||
level: LevelSettings,
|
||||
};
|
||||
|
||||
const Settings = ModuleSettings[currentModule];
|
||||
const showImport =
|
||||
importModule && ["reading", "listening", "level"].includes(currentModule);
|
||||
|
||||
const updateLevelParts = (parts: number) => {
|
||||
setNumberOfLevelParts(parts);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{showImport ? (
|
||||
<ImportOrStartFromScratch
|
||||
module={currentModule}
|
||||
setNumberOfLevelParts={updateLevelParts}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
{isResetModuleOpen && (
|
||||
<ResetModule
|
||||
module={currentModule}
|
||||
isOpen={isResetModuleOpen}
|
||||
setIsOpen={setIsResetModuleOpen}
|
||||
setNumberOfLevelParts={setNumberOfLevelParts}
|
||||
/>
|
||||
)}
|
||||
<div className="flex gap-4 w-full items-center -xl:flex-col">
|
||||
<div className="flex flex-row gap-3 w-full">
|
||||
<div className="flex flex-col gap-3">
|
||||
<label className="font-normal text-base text-mti-gray-dim">
|
||||
Timer
|
||||
</label>
|
||||
<Input
|
||||
type="number"
|
||||
name="minTimer"
|
||||
onChange={(e) =>
|
||||
updateModule({
|
||||
minTimer: parseInt(e) < 15 ? 15 : parseInt(e),
|
||||
})
|
||||
}
|
||||
value={minTimer}
|
||||
className="max-w-[300px]"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 flex-grow">
|
||||
<label className="font-normal text-base text-mti-gray-dim">
|
||||
Difficulty
|
||||
</label>
|
||||
<Select
|
||||
isMulti={true}
|
||||
options={DIFFICULTIES.map((x) => ({
|
||||
value: x,
|
||||
label: capitalize(x),
|
||||
}))}
|
||||
onChange={(values) => {
|
||||
const selectedDifficulties = values
|
||||
? values.map((v) => v.value as Difficulty)
|
||||
: [];
|
||||
updateModule({ difficulty: selectedDifficulties });
|
||||
}}
|
||||
value={
|
||||
difficulty
|
||||
? difficulty.map((d) => ({
|
||||
value: d,
|
||||
label: capitalize(d),
|
||||
}))
|
||||
: null
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{sectionLabels.length != 0 && currentModule !== "level" ? (
|
||||
<div className="flex flex-col gap-3 -xl:w-full">
|
||||
<label className="font-normal text-base text-mti-gray-dim">
|
||||
{sectionLabels[0].label.split(" ")[0]}
|
||||
</label>
|
||||
<div className="flex flex-row gap-8">
|
||||
{sectionLabels.map(({ id, label }) => (
|
||||
<span
|
||||
key={id}
|
||||
className={clsx(
|
||||
"px-6 py-4 w-48 h-[72px] flex justify-center items-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer",
|
||||
"transition duration-300 ease-in-out",
|
||||
sectionIds.includes(id)
|
||||
? `bg-ielts-${currentModule}/70 border-ielts-${currentModule} text-white`
|
||||
: "bg-white border-mti-gray-platinum"
|
||||
)}
|
||||
onClick={() => toggleSection(id)}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col gap-3 w-1/3">
|
||||
<label className="font-normal text-base text-mti-gray-dim">
|
||||
Number of Parts
|
||||
</label>
|
||||
<Input
|
||||
type="number"
|
||||
name="Number of Parts"
|
||||
min={1}
|
||||
onChange={(v) => setNumberOfLevelParts(parseInt(v))}
|
||||
value={numberOfLevelParts}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-col gap-3 w-fit h-fit">
|
||||
<div className="h-6" />
|
||||
<Checkbox
|
||||
isChecked={isPrivate}
|
||||
onChange={(checked) => updateModule({ isPrivate: checked })}
|
||||
>
|
||||
Privacy (Only available for Assignments)
|
||||
</Checkbox>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-row gap-3 w-full">
|
||||
<div className="flex flex-col gap-3 flex-grow">
|
||||
<label className="font-normal text-base text-mti-gray-dim">
|
||||
Exam Label *
|
||||
</label>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Exam Label"
|
||||
name="label"
|
||||
onChange={(text) => updateModule({ examLabel: text })}
|
||||
roundness="xl"
|
||||
value={examLabel}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{currentModule === "listening" && <ListeningInstructions />}
|
||||
<Button
|
||||
onClick={() => setIsResetModuleOpen(true)}
|
||||
customColor={`bg-ielts-${currentModule}/70 hover:bg-ielts-${currentModule} border-ielts-${currentModule}`}
|
||||
className={`text-white self-end`}
|
||||
>
|
||||
Reset Module
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-row gap-8 -2xl:flex-col">
|
||||
<Settings />
|
||||
<div className="flex-grow max-w-[66%] -2xl:max-w-full">
|
||||
<SectionRenderer />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExamEditor;
|
||||
|
||||
Reference in New Issue
Block a user