Files
encoach_frontend/src/components/ExamEditor/SettingsEditor/index.tsx

198 lines
8.5 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: (requiresApproval: boolean) => 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 -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
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-col gap-3 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(true)}
disabled={!canSubmit}
>
<FaFileUpload className="mr-2" size={18} />
Submit module as exam for approval
</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={() => {
if (!confirm(`Are you sure you want to skip the approval process for this exam?`)) return;
submitModule(false);
}}
disabled={!canSubmit}
>
<FaFileUpload className="mr-2" size={18} />
Submit module as exam and skip approval process
</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;