Merged develop into approval-workflows
This commit is contained in:
@@ -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 flex-wrap gap-2 items-center px-2 pb-4">
|
||||
<div className="flex flex-row flex-wrap gap-2 items-center justify-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
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
import Dropdown from "../Shared/SettingsDropdown";
|
||||
import ExercisePicker from "../../ExercisePicker";
|
||||
import SettingsEditor from "..";
|
||||
import GenerateBtn from "../Shared/GenerateBtn";
|
||||
import { useCallback, useState } from "react";
|
||||
import { generate } from "../Shared/Generate";
|
||||
import { Generating, LevelSectionSettings, ListeningSectionSettings } from "@/stores/examEditor/types";
|
||||
import { ListeningSectionSettings } from "@/stores/examEditor/types";
|
||||
import Option from "@/interfaces/option";
|
||||
import useExamEditorStore from "@/stores/examEditor";
|
||||
import useSettingsState from "../../Hooks/useSettingsState";
|
||||
import { ListeningExam, ListeningPart } from "@/interfaces/exam";
|
||||
import Input from "@/components/Low/Input";
|
||||
import openDetachedTab from "@/utils/popout";
|
||||
import { useRouter } from "next/router";
|
||||
import axios from "axios";
|
||||
@@ -17,7 +11,6 @@ import { usePersistentExamStore } from "@/stores/exam";
|
||||
import { playSound } from "@/utils/sound";
|
||||
import { toast } from "react-toastify";
|
||||
import ListeningComponents from "./components";
|
||||
import { getExamById } from "@/utils/exams";
|
||||
|
||||
const ListeningSettings: React.FC = () => {
|
||||
const router = useRouter();
|
||||
|
||||
@@ -82,7 +82,7 @@ const ReadingComponents: React.FC<Props> = ({
|
||||
disabled={generatePassageDisabled}
|
||||
>
|
||||
<div
|
||||
className="flex flex-row flex-wrap gap-2 items-center px-2 pb-4 "
|
||||
className="flex flex-row flex-wrap gap-2 items-center justify-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">
|
||||
|
||||
@@ -12,7 +12,6 @@ import axios from "axios";
|
||||
import { playSound } from "@/utils/sound";
|
||||
import { toast } from "react-toastify";
|
||||
import ReadingComponents from "./components";
|
||||
import { getExamById } from "@/utils/exams";
|
||||
|
||||
const ReadingSettings: React.FC = () => {
|
||||
const router = useRouter();
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import clsx from "clsx";
|
||||
import SectionRenderer from "./SectionRenderer";
|
||||
import Checkbox from "../Low/Checkbox";
|
||||
import Input from "../Low/Input";
|
||||
import Select from "../Low/Select";
|
||||
import { capitalize } from "lodash";
|
||||
import { AccessType, ACCESSTYPE, Difficulty } from "@/interfaces/exam";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { toast } from "react-toastify";
|
||||
import { ModuleState, SectionState } from "@/stores/examEditor/types";
|
||||
import { Module } from "@/interfaces";
|
||||
@@ -21,13 +20,36 @@ 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: Difficulty[] = ["A1", "A2", "B1", "B2", "C1", "C2"];
|
||||
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<Module, React.ComponentType> = {
|
||||
reading: ReadingSettings,
|
||||
writing: WritingSettings,
|
||||
speaking: SpeakingSettings,
|
||||
listening: ListeningSettings,
|
||||
level: LevelSettings,
|
||||
};
|
||||
|
||||
const ExamEditor: React.FC<{
|
||||
levelParts?: number;
|
||||
entitiesAllowEditPrivacy: EntityWithRoles[];
|
||||
}> = ({ levelParts = 0, entitiesAllowEditPrivacy = [] }) => {
|
||||
entitiesAllowConfExams: EntityWithRoles[];
|
||||
entitiesAllowPublicExams: EntityWithRoles[];
|
||||
}> = ({
|
||||
levelParts = 0,
|
||||
entitiesAllowEditPrivacy = [],
|
||||
entitiesAllowConfExams = [],
|
||||
entitiesAllowPublicExams = [],
|
||||
}) => {
|
||||
const { currentModule, dispatch } = useExamEditorStore();
|
||||
const {
|
||||
sections,
|
||||
@@ -111,7 +133,10 @@ const ExamEditor: React.FC<{
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [numberOfLevelParts]);
|
||||
|
||||
const sectionIds = sections.map((section) => section.sectionId);
|
||||
const sectionIds = useMemo(
|
||||
() => sections.map((section) => section.sectionId),
|
||||
[sections]
|
||||
);
|
||||
|
||||
const updateModule = useCallback(
|
||||
(updates: Partial<ModuleState>) => {
|
||||
@@ -120,29 +145,42 @@ const ExamEditor: React.FC<{
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const toggleSection = (sectionId: number) => {
|
||||
if (expandedSections.length === 1 && sectionIds.includes(sectionId)) {
|
||||
toast.error("Include at least one section!");
|
||||
return;
|
||||
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" });
|
||||
}
|
||||
dispatch({ type: "TOGGLE_SECTION", payload: { sectionId } });
|
||||
};
|
||||
if (entitiesAllowPublicExams.length > 0) {
|
||||
options.push({ value: "public", label: "Public" });
|
||||
}
|
||||
return options;
|
||||
}, [entitiesAllowConfExams.length, entitiesAllowPublicExams.length]);
|
||||
|
||||
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) => {
|
||||
const updateLevelParts = useCallback((parts: number) => {
|
||||
setNumberOfLevelParts(parts);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -161,9 +199,14 @@ const ExamEditor: React.FC<{
|
||||
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">
|
||||
<div
|
||||
className={clsx(
|
||||
"flex gap-4 w-full",
|
||||
sectionLabels.length > 3 ? "-2xl:flex-col" : "-xl:flex-col"
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-row gap-3">
|
||||
<div className="flex flex-col gap-3 ">
|
||||
<label className="font-normal text-base text-mti-gray-dim">
|
||||
Timer
|
||||
</label>
|
||||
@@ -176,19 +219,16 @@ const ExamEditor: React.FC<{
|
||||
})
|
||||
}
|
||||
value={minTimer}
|
||||
className="max-w-[300px]"
|
||||
className="max-w-[125px] min-w-[100px] w-min"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col gap-3 flex-grow">
|
||||
<div className="flex flex-col gap-3 ">
|
||||
<label className="font-normal text-base text-mti-gray-dim">
|
||||
Difficulty
|
||||
</label>
|
||||
<Select
|
||||
isMulti={true}
|
||||
options={DIFFICULTIES.map((x) => ({
|
||||
value: x,
|
||||
label: capitalize(x),
|
||||
}))}
|
||||
options={DIFFICULTIES}
|
||||
onChange={(values) => {
|
||||
const selectedDifficulties = values
|
||||
? values.map((v) => v.value as Difficulty)
|
||||
@@ -214,12 +254,12 @@ const ExamEditor: React.FC<{
|
||||
<label className="font-normal text-base text-mti-gray-dim">
|
||||
{sectionLabels[0].label.split(" ")[0]}
|
||||
</label>
|
||||
<div className="flex flex-row gap-8">
|
||||
<div className="flex flex-row gap-3">
|
||||
{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",
|
||||
"px-6 py-4 w-40 2xl: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`
|
||||
@@ -246,22 +286,24 @@ const ExamEditor: React.FC<{
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-row gap-3 w-64">
|
||||
<Select
|
||||
label="Access Type"
|
||||
options={ACCESSTYPE.map((item) => ({
|
||||
value: item,
|
||||
label: capitalize(item),
|
||||
}))}
|
||||
onChange={(value) => {
|
||||
if (value?.value) {
|
||||
updateModule({ access: value.value! as AccessType });
|
||||
<div className="max-w-[200px] w-full">
|
||||
<Select
|
||||
label="Access Type"
|
||||
disabled={
|
||||
accessTypeOptions.length === 0 ||
|
||||
entitiesAllowEditPrivacy.length === 0
|
||||
}
|
||||
}}
|
||||
value={{ value: access, label: capitalize(access) }}
|
||||
/>
|
||||
options={accessTypeOptions}
|
||||
onChange={(value) => {
|
||||
if (value?.value) {
|
||||
updateModule({ access: value.value! as AccessType });
|
||||
}
|
||||
}}
|
||||
value={{ value: access, label: capitalize(access) }}
|
||||
/>
|
||||
</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">
|
||||
@@ -286,7 +328,7 @@ const ExamEditor: React.FC<{
|
||||
Reset Module
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex flex-row gap-8 -2xl:flex-col">
|
||||
<div className="flex flex-row gap-8 -xl:flex-col">
|
||||
<Settings />
|
||||
<div className="flex-grow max-w-[66%] -2xl:max-w-full">
|
||||
<SectionRenderer />
|
||||
|
||||
Reference in New Issue
Block a user