ENCOA-260, ENCOA-259
This commit is contained in:
@@ -155,11 +155,11 @@ const Templates: React.FC<Props> = ({ module, state, setState }) => {
|
||||
</li>
|
||||
{["reading", "level"].includes(module) && (
|
||||
<li className="text-gray-700 list-disc">
|
||||
a part must only contain a reading passage and it must be between the part delineator (e.g. Part 1) and the part exercises.
|
||||
a part must only contain a single reading passage and it must be between the part delineator (e.g. Part 1) and the part exercises.
|
||||
</li>
|
||||
)}
|
||||
<li className="text-gray-700 list-disc">
|
||||
if solutions are going to be uploaded the exercise numbers/id's must match the ones in the solutions.
|
||||
if solutions are going to be uploaded, the exercise numbers/id's must match the ones in the solutions.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -186,7 +186,7 @@ const Templates: React.FC<Props> = ({ module, state, setState }) => {
|
||||
}
|
||||
<div className="bg-gray-50 rounded-lg p-4">
|
||||
<p className="text-gray-600">
|
||||
{`The downloadable template is an example of a file that can be imported. Your document doesn't need to be a carbon copy of the template - it can have different styling and formatting but it must adhere to the previous requirements${state.type === "exam" ? "and exercises of the same type should have the same formatting" : ""}.`}
|
||||
{`The downloadable template is an example of a file that can be imported. Your document doesn't need to be a carbon copy of the template - it can have different styling and formatting but it must adhere to the previous requirements${state.type === "exam" ? " and exercises of the same type should have the same formatting" : ""}.`}
|
||||
</p>
|
||||
</div>
|
||||
<div className="w-full flex justify-between mt-4 gap-8">
|
||||
|
||||
116
src/components/ExamEditor/ResetModule.tsx
Normal file
116
src/components/ExamEditor/ResetModule.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import Button from "@/components/Low/Button";
|
||||
import { Module } from "@/interfaces";
|
||||
import useExamEditorStore from "@/stores/examEditor";
|
||||
import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from "@headlessui/react";
|
||||
import { capitalize } from "lodash";
|
||||
import React, { Fragment, useCallback, useEffect, useState } from "react";
|
||||
|
||||
interface Props {
|
||||
module: Module;
|
||||
isOpen: boolean;
|
||||
setIsOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
setNumberOfLevelParts: React.Dispatch<React.SetStateAction<number>>;
|
||||
}
|
||||
|
||||
const ResetModule: React.FC<Props> = ({ module, isOpen, setIsOpen, setNumberOfLevelParts }) => {
|
||||
const [isClosing, setIsClosing] = useState(false);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
const { dispatch } = useExamEditorStore();
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setMounted(true);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen && mounted) {
|
||||
const timer = setTimeout(() => {
|
||||
setMounted(false);
|
||||
setIsClosing(false);
|
||||
}, 300);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [isOpen, mounted]);
|
||||
|
||||
const blockMultipleClicksClose = useCallback(() => {
|
||||
if (isClosing) return;
|
||||
setIsClosing(true);
|
||||
setIsOpen(!isOpen);
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
setIsClosing(false);
|
||||
}, 300);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [isClosing, setIsOpen, isOpen]);
|
||||
|
||||
if (!mounted && !isOpen) return null;
|
||||
|
||||
const handleResetModule = () => {
|
||||
dispatch({ type: 'RESET_MODULE', payload: { module } });
|
||||
setIsOpen(false);
|
||||
setNumberOfLevelParts(1);
|
||||
}
|
||||
|
||||
return (
|
||||
<Transition
|
||||
show={isOpen}
|
||||
as={Fragment}
|
||||
beforeEnter={() => setIsClosing(false)}
|
||||
beforeLeave={() => setIsClosing(true)}
|
||||
afterLeave={() => {
|
||||
setIsClosing(false);
|
||||
setMounted(false);
|
||||
}}
|
||||
>
|
||||
<Dialog onClose={() => blockMultipleClicksClose()} className="relative z-50">
|
||||
<TransitionChild
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0">
|
||||
<div className="fixed inset-0 bg-black/30" />
|
||||
</TransitionChild>
|
||||
|
||||
<TransitionChild
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95">
|
||||
<div className="fixed inset-0 flex items-center justify-center p-4">
|
||||
<DialogPanel className={`bg-ielts-${module}-light w-full max-w-xl h-fit p-8 rounded-xl flex flex-col gap-4`}>
|
||||
<DialogTitle className="flex font-bold text-xl justify-center text-gray-700"><span>Reset {capitalize(module)} Module</span></DialogTitle>
|
||||
<div className="flex flex-col w-full mt-4 gap-6">
|
||||
<div className="bg-gray-50 rounded-lg p-4">
|
||||
<p className="text-gray-600">
|
||||
Do you want to reset the {module} module?
|
||||
</p>
|
||||
<br/>
|
||||
<p className="text-gray-600 font-bold">This will reset all the current data in the {module} module and cannot be undone.</p>
|
||||
</div>
|
||||
<div className="w-full flex justify-between mt-4 gap-8">
|
||||
<Button color="purple" onClick={blockMultipleClicksClose} variant="outline" className="self-end w-full bg-white">
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<Button color="purple" onClick={handleResetModule} variant="solid" className="self-end w-full">
|
||||
Reset
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</DialogPanel>
|
||||
</div>
|
||||
</TransitionChild>
|
||||
</Dialog>
|
||||
</Transition>
|
||||
);
|
||||
}
|
||||
|
||||
export default ResetModule;
|
||||
@@ -34,7 +34,9 @@ const SettingsEditor: React.FC<SettingsEditorProps> = ({
|
||||
canPreview,
|
||||
canSubmit
|
||||
}) => {
|
||||
const { dispatch } = useExamEditorStore()
|
||||
const examLabel = useExamEditorStore((state) => state.modules[module].examLabel) || '';
|
||||
const type = useExamEditorStore((s) => s.modules[module].type);
|
||||
const { localSettings, updateLocalAndScheduleGlobal } = useSettingsState<SectionSettings>(
|
||||
module,
|
||||
sectionId
|
||||
@@ -50,6 +52,18 @@ const SettingsEditor: React.FC<SettingsEditorProps> = ({
|
||||
updateLocalAndScheduleGlobal({ category: text });
|
||||
}, [updateLocalAndScheduleGlobal]);
|
||||
|
||||
const typeOptions = [
|
||||
{ value: 'general', label: 'General' },
|
||||
{ value: 'academic', label: 'Academic' }
|
||||
];
|
||||
|
||||
const onTypeChange = useCallback((option: { value: string | null, label: string }) => {
|
||||
dispatch({
|
||||
type: 'UPDATE_MODULE',
|
||||
payload: { module, updates: { type: option.value as "academic" | "general" | undefined } }
|
||||
});
|
||||
}, [dispatch, module]);
|
||||
|
||||
const onIntroOptionChange = useCallback((option: { value: string | null, label: string }) => {
|
||||
let updates: Partial<SectionSettings> = { introOption: option };
|
||||
|
||||
@@ -79,7 +93,7 @@ const SettingsEditor: React.FC<SettingsEditorProps> = ({
|
||||
currentIntro: text
|
||||
});
|
||||
}, [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={`w-full flex justify-center text-ielts-${module} font-bold text-xl`}>{sectionLabel} Settings</div>
|
||||
@@ -100,6 +114,18 @@ const SettingsEditor: React.FC<SettingsEditorProps> = ({
|
||||
value={localSettings.category || ''}
|
||||
/>
|
||||
</Dropdown>
|
||||
{["reading", "writing"].includes(module) && <Dropdown
|
||||
title="Type"
|
||||
module={module}
|
||||
open={localSettings.isTypeDropdownOpen}
|
||||
setIsOpen={(isOpen: boolean) => updateLocalAndScheduleGlobal({ isTypeDropdownOpen: isOpen }, false)}
|
||||
>
|
||||
<Select
|
||||
options={typeOptions}
|
||||
onChange={(o) => onTypeChange({ value: o!.value, label: o!.label })}
|
||||
value={typeOptions.find(o => o.value === type)}
|
||||
/>
|
||||
</Dropdown>}
|
||||
<Dropdown
|
||||
title="Divider"
|
||||
module={module}
|
||||
|
||||
@@ -31,6 +31,7 @@ const ReadingSettings: React.FC = () => {
|
||||
sections,
|
||||
minTimer,
|
||||
isPrivate,
|
||||
type,
|
||||
} = useExamEditorStore(state => state.modules[currentModule]);
|
||||
|
||||
const { localSettings, updateLocalAndScheduleGlobal } = useSettingsState<ReadingSectionSettings>(
|
||||
@@ -78,10 +79,10 @@ const ReadingSettings: React.FC = () => {
|
||||
minTimer,
|
||||
module: "reading",
|
||||
id: title,
|
||||
type: "academic",
|
||||
variant: sections.length === 3 ? "full" : "partial",
|
||||
difficulty,
|
||||
private: isPrivate,
|
||||
type: type!
|
||||
};
|
||||
|
||||
axios.post(`/api/exam/reading`, exam)
|
||||
@@ -112,6 +113,7 @@ const ReadingSettings: React.FC = () => {
|
||||
variant: undefined,
|
||||
difficulty,
|
||||
private: isPrivate,
|
||||
type: type!
|
||||
} as ReadingExam);
|
||||
setExerciseIndex(0);
|
||||
setQuestionIndex(0);
|
||||
|
||||
@@ -25,6 +25,7 @@ const WritingSettings: React.FC = () => {
|
||||
isPrivate,
|
||||
sections,
|
||||
focusedSection,
|
||||
type
|
||||
} = useExamEditorStore((store) => store.modules["writing"]);
|
||||
|
||||
const states = sections.flatMap((s) => s.state) as WritingExercise[];
|
||||
@@ -72,6 +73,7 @@ const WritingSettings: React.FC = () => {
|
||||
variant: undefined,
|
||||
difficulty,
|
||||
private: isPrivate,
|
||||
type: type!
|
||||
});
|
||||
setExerciseIndex(0);
|
||||
openDetachedTab("popout?type=Exam&module=writing", router)
|
||||
@@ -94,6 +96,7 @@ const WritingSettings: React.FC = () => {
|
||||
variant: undefined,
|
||||
difficulty,
|
||||
private: isPrivate,
|
||||
type: type!
|
||||
};
|
||||
|
||||
axios
|
||||
|
||||
@@ -17,6 +17,8 @@ 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 "./ResetModule";
|
||||
|
||||
const DIFFICULTIES: Difficulty[] = ["easy", "medium", "hard"];
|
||||
|
||||
@@ -34,6 +36,7 @@ const ExamEditor: React.FC<{ levelParts?: number }> = ({ levelParts = 0 }) => {
|
||||
} = useExamEditorStore(state => state.modules[currentModule]);
|
||||
|
||||
const [numberOfLevelParts, setNumberOfLevelParts] = useState(levelParts !== 0 ? levelParts : 1);
|
||||
const [isResetModuleOpen, setIsResetModuleOpen] = useState(false);
|
||||
|
||||
// For exam edits
|
||||
useEffect(() => {
|
||||
@@ -129,6 +132,7 @@ const ExamEditor: React.FC<{ levelParts?: number }> = ({ levelParts = 0 }) => {
|
||||
<>
|
||||
{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>
|
||||
@@ -183,7 +187,8 @@ const ExamEditor: React.FC<{ levelParts?: number }> = ({ levelParts = 0 }) => {
|
||||
</Checkbox>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 w-full">
|
||||
<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"
|
||||
@@ -194,6 +199,10 @@ const ExamEditor: React.FC<{ levelParts?: number }> = ({ levelParts = 0 }) => {
|
||||
value={examLabel}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
{["reading", "listening", "level"].includes(currentModule) && <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 />
|
||||
|
||||
Reference in New Issue
Block a user