183 lines
7.6 KiB
TypeScript
183 lines
7.6 KiB
TypeScript
import React, { ReactNode, useCallback, useEffect, useMemo, useState, useRef } from "react";
|
|
import { FaEye, FaFileUpload } from "react-icons/fa";
|
|
import clsx from "clsx";
|
|
import Select from "@/components/Low/Select";
|
|
import Input from "@/components/Low/Input";
|
|
import AutoExpandingTextArea from "@/components/Low/AutoExpandingTextarea";
|
|
import Option from '@/interfaces/option'
|
|
import Dropdown from "./Shared/SettingsDropdown";
|
|
import useSettingsState from "../Hooks/useSettingsState";
|
|
import { Module } from "@/interfaces";
|
|
import { SectionSettings } from "@/stores/examEditor/types";
|
|
import useExamEditorStore from "@/stores/examEditor";
|
|
|
|
interface SettingsEditorProps {
|
|
sectionId: number,
|
|
sectionLabel: string;
|
|
module: Module,
|
|
introPresets: Option[];
|
|
children?: ReactNode;
|
|
canPreview: boolean;
|
|
canSubmit: boolean;
|
|
submitModule: () => void;
|
|
preview: () => void;
|
|
}
|
|
|
|
const SettingsEditor: React.FC<SettingsEditorProps> = ({
|
|
sectionId,
|
|
sectionLabel,
|
|
module,
|
|
introPresets,
|
|
children,
|
|
preview,
|
|
submitModule,
|
|
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
|
|
);
|
|
|
|
const options = useMemo(() => [
|
|
{ value: 'None', label: 'None' },
|
|
...introPresets,
|
|
{ value: 'Custom', label: 'Custom' }
|
|
], [introPresets]);
|
|
|
|
const onCategoryChange = useCallback((text: string) => {
|
|
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 };
|
|
|
|
switch (option.label) {
|
|
case 'None':
|
|
updates.currentIntro = undefined;
|
|
break;
|
|
case 'Custom':
|
|
updates.currentIntro = localSettings.customIntro;
|
|
break;
|
|
default:
|
|
const selectedPreset = introPresets.find(preset => preset.label === option.label);
|
|
if (selectedPreset) {
|
|
updates.currentIntro = selectedPreset.value!
|
|
.replace('{part}', sectionLabel)
|
|
.replace('{label}', examLabel);
|
|
}
|
|
}
|
|
|
|
updateLocalAndScheduleGlobal(updates);
|
|
}, [updateLocalAndScheduleGlobal, localSettings.customIntro, introPresets, sectionLabel, examLabel]);
|
|
|
|
const onCustomIntroChange = useCallback((text: string) => {
|
|
updateLocalAndScheduleGlobal({
|
|
introOption: { value: 'Custom', label: 'Custom' },
|
|
customIntro: text,
|
|
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>
|
|
<div className="flex flex-col gap-4">
|
|
<Dropdown
|
|
title="Category"
|
|
module={module}
|
|
open={localSettings.isCategoryDropdownOpen}
|
|
setIsOpen={(isOpen: boolean) => updateLocalAndScheduleGlobal({ isCategoryDropdownOpen: isOpen }, false)}
|
|
>
|
|
<Input
|
|
key={`section-${sectionId}`}
|
|
type="text"
|
|
placeholder="Category"
|
|
name="category"
|
|
onChange={onCategoryChange}
|
|
roundness="full"
|
|
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}
|
|
open={localSettings.isIntroDropdownOpen}
|
|
setIsOpen={(isOpen: boolean) => updateLocalAndScheduleGlobal({ isIntroDropdownOpen: isOpen }, false)}
|
|
>
|
|
<div className="flex flex-col gap-3 w-full">
|
|
<Select
|
|
options={options}
|
|
onChange={(o) => onIntroOptionChange({ value: o!.value, label: o!.label })}
|
|
value={localSettings.introOption}
|
|
/>
|
|
{localSettings.introOption && localSettings.introOption.value !== "None" && (
|
|
<AutoExpandingTextArea
|
|
key={`section-${sectionId}`}
|
|
value={localSettings.currentIntro || ''}
|
|
onChange={onCustomIntroChange}
|
|
/>
|
|
)}
|
|
</div>
|
|
</Dropdown>
|
|
{children}
|
|
<div className="flex flex-row justify-between mt-4">
|
|
<button
|
|
className={clsx(
|
|
"flex items-center justify-center px-4 py-2 text-white rounded-xl transition-colors duration-300",
|
|
`bg-ielts-${module}/70 border border-ielts-${module} hover:bg-ielts-${module} disabled:bg-ielts-${module}/30`,
|
|
"disabled:cursor-not-allowed disabled:text-gray-200"
|
|
)}
|
|
onClick={submitModule}
|
|
disabled={!canSubmit}
|
|
>
|
|
<FaFileUpload className="mr-2" size={18} />
|
|
Submit Module as Exam
|
|
</button>
|
|
<button
|
|
className={clsx(
|
|
"flex items-center justify-center px-4 py-2 text-white rounded-xl transition-colors duration-300",
|
|
`bg-ielts-${module}/70 border border-ielts-${module} hover:bg-ielts-${module} disabled:bg-ielts-${module}/30`,
|
|
"disabled:cursor-not-allowed disabled:text-gray-200"
|
|
)}
|
|
onClick={preview}
|
|
disabled={!canPreview}
|
|
>
|
|
<FaEye className="mr-2" size={18} />
|
|
Preview Module
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default SettingsEditor;
|