Merged in feature/ExamGenRework (pull request #118)

Exam Edit on ExamList

Approved-by: Tiago Ribeiro
This commit is contained in:
carlos.mesquita
2024-11-27 07:36:07 +00:00
committed by Tiago Ribeiro
13 changed files with 199 additions and 53 deletions

View File

@@ -22,18 +22,13 @@ const PromptEdit: React.FC<Props> = ({ value, onChange, wrapperCard = true }) =>
)); ));
}; };
const handleTextChange = (text: string) => {
const escapedText = text.replace(/\n/g, '\\n');
onChange(escapedText);
};
const promptEditTsx = ( const promptEditTsx = (
<div className="flex justify-between items-start gap-4"> <div className="flex justify-between items-start gap-4">
{editing ? ( {editing ? (
<AutoExpandingTextArea <AutoExpandingTextArea
className="flex-1 p-3 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:outline-none min-h-[100px]" className="flex-1 p-3 border rounded-lg focus:ring-2 focus:ring-blue-500 focus:outline-none min-h-[100px]"
value={value.replace(/\\n/g, '\n')} value={value}
onChange={handleTextChange} onChange={onChange}
onBlur={() => setEditing(false)} onBlur={() => setEditing(false)}
/> />
) : ( ) : (

View File

@@ -80,7 +80,6 @@ const SettingsEditor: React.FC<SettingsEditorProps> = ({
}); });
}, [updateLocalAndScheduleGlobal]); }, [updateLocalAndScheduleGlobal]);
return ( 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`}>
<div className={`w-full flex justify-center text-ielts-${module} font-bold text-xl`}>{sectionLabel} Settings</div> <div className={`w-full flex justify-center text-ielts-${module} font-bold text-xl`}>{sectionLabel} Settings</div>

View File

@@ -20,7 +20,7 @@ import { defaultSectionSettings } from "@/stores/examEditor/defaults";
const DIFFICULTIES: Difficulty[] = ["easy", "medium", "hard"]; const DIFFICULTIES: Difficulty[] = ["easy", "medium", "hard"];
const ExamEditor: React.FC = () => { const ExamEditor: React.FC<{ levelParts?: number }> = ({ levelParts = 0 }) => {
const { currentModule, dispatch } = useExamEditorStore(); const { currentModule, dispatch } = useExamEditorStore();
const { const {
sections, sections,
@@ -33,7 +33,23 @@ const ExamEditor: React.FC = () => {
importModule importModule
} = useExamEditorStore(state => state.modules[currentModule]); } = useExamEditorStore(state => state.modules[currentModule]);
const [numberOfLevelParts, setNumberOfLevelParts] = useState(1); const [numberOfLevelParts, setNumberOfLevelParts] = useState(levelParts !== undefined ? levelParts : 1);
useEffect(() => {
setNumberOfLevelParts(levelParts);
dispatch({
type: 'UPDATE_MODULE',
payload: {
updates: {
sectionLabels: Array.from({ length: levelParts }).map((_, i) => ({
id: i + 1,
label: `Part ${i + 1}`
}))
}
}
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [levelParts])
useEffect(() => { useEffect(() => {
const currentSections = sections; const currentSections = sections;
@@ -106,7 +122,7 @@ const ExamEditor: React.FC = () => {
return ( return (
<> <>
{showImport ? <ImportOrStartFromScratch module={currentModule} setNumberOfLevelParts={setNumberOfLevelParts}/> : ( {showImport ? <ImportOrStartFromScratch module={currentModule} setNumberOfLevelParts={setNumberOfLevelParts} /> : (
<> <>
<div className="flex gap-4 w-full items-center"> <div className="flex gap-4 w-full items-center">
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">

View File

@@ -1,11 +1,11 @@
import React, { useEffect, useRef, ChangeEvent } from 'react'; import React, { useRef, useEffect, ChangeEvent } from 'react';
interface Props { interface Props {
value: string; value: string;
onChange: (value: string) => void;
className?: string; className?: string;
placeholder?: string; placeholder?: string;
disabled?: boolean; disabled?: boolean;
onChange: (value: string) => void;
onBlur?: () => void; onBlur?: () => void;
} }
@@ -41,7 +41,7 @@ const AutoExpandingTextArea: React.FC<Props> = ({
return ( return (
<textarea <textarea
ref={textareaRef} ref={textareaRef}
value={value} value={value.replace(/\\n/g, '\n')}
onChange={handleChange} onChange={handleChange}
className={className} className={className}
placeholder={placeholder} placeholder={placeholder}

View File

@@ -24,6 +24,7 @@ import useExamTimer from "@/hooks/useExamTimer";
import ProgressButtons from "../components/ProgressButtons"; import ProgressButtons from "../components/ProgressButtons";
import useExamNavigation from "../Navigation/useExamNavigation"; import useExamNavigation from "../Navigation/useExamNavigation";
import { calculateExerciseIndex } from "../utils/calculateExerciseIndex"; import { calculateExerciseIndex } from "../utils/calculateExerciseIndex";
import { defaultExamUserSolutions } from "@/utils/exams";
const Level: React.FC<ExamProps<LevelExam>> = ({ exam, showSolutions = false, preview = false }) => { const Level: React.FC<ExamProps<LevelExam>> = ({ exam, showSolutions = false, preview = false }) => {
@@ -55,7 +56,6 @@ const Level: React.FC<ExamProps<LevelExam>> = ({ exam, showSolutions = false, pr
const { finalizeModule, timeIsUp } = flags; const { finalizeModule, timeIsUp } = flags;
const timer = useRef(exam.minTimer - timeSpentCurrentModule / 60); const timer = useRef(exam.minTimer - timeSpentCurrentModule / 60);
const [isFirstTimeRender, setIsFirstTimeRender] = useState(partIndex === 0 && exerciseIndex == 0 && !showSolutions);
// In case client want to switch back // In case client want to switch back
const textRenderDisabled = true; const textRenderDisabled = true;
@@ -92,12 +92,12 @@ const Level: React.FC<ExamProps<LevelExam>> = ({ exam, showSolutions = false, pr
const { const {
nextExercise, previousExercise, nextExercise, previousExercise,
showPartDivider, setShowPartDivider, showPartDivider, setShowPartDivider,
seenParts, setSeenParts, seenParts, setSeenParts, startNow
} = useExamNavigation( } = useExamNavigation(
{ {
exam, module: "level", showBlankModal: showQuestionsModal, exam, module: "level", showBlankModal: showQuestionsModal,
setShowBlankModal: setShowQuestionsModal, showSolutions, setShowBlankModal: setShowQuestionsModal, showSolutions,
preview, disableBetweenParts: true, modalBetweenParts: true ,modalKwargs preview, disableBetweenParts: true, modalBetweenParts: true, modalKwargs
} }
); );
@@ -106,6 +106,13 @@ const Level: React.FC<ExamProps<LevelExam>> = ({ exam, showSolutions = false, pr
setSolutionWasUpdated(true); setSolutionWasUpdated(true);
}, []); }, []);
useEffect(() => {
if (preview) {
setUserSolutions(defaultExamUserSolutions(exam));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const [contextWords, setContextWords] = useState<{ match: string, originalLine: string }[] | undefined>(undefined); const [contextWords, setContextWords] = useState<{ match: string, originalLine: string }[] | undefined>(undefined);
const [contextWordLines, setContextWordLines] = useState<number[] | undefined>(undefined); const [contextWordLines, setContextWordLines] = useState<number[] | undefined>(undefined);
const [totalLines, setTotalLines] = useState<number>(0); const [totalLines, setTotalLines] = useState<number>(0);
@@ -351,17 +358,17 @@ const Level: React.FC<ExamProps<LevelExam>> = ({ exam, showSolutions = false, pr
</Modal> </Modal>
<QuestionsModal isOpen={showQuestionsModal} {...questionModalKwargs} /> <QuestionsModal isOpen={showQuestionsModal} {...questionModalKwargs} />
{ {
!(partIndex === 0 && questionIndex === 0 && (showPartDivider || isFirstTimeRender)) && (!showPartDivider && !startNow) &&
<Timer minTimer={exam.minTimer} disableTimer={showSolutions || preview} standalone={true} /> <Timer minTimer={exam.minTimer} disableTimer={showSolutions || preview} standalone={true} />
} }
{(showPartDivider || isFirstTimeRender) ? {(showPartDivider || startNow) ?
<PartDivider <PartDivider
module="level" module="level"
sectionLabel="Part" sectionLabel="Part"
defaultTitle="Placement Test" defaultTitle="Placement Test"
section={exam.parts[partIndex]} section={exam.parts[partIndex]}
sectionIndex={partIndex} sectionIndex={partIndex}
onNext={() => { setShowPartDivider(false); setIsFirstTimeRender(false); setBgColor("bg-white"); setSeenParts(prev => new Set(prev).add(partIndex)); }} onNext={() => { setShowPartDivider(false); setBgColor("bg-white"); setSeenParts(prev => new Set(prev).add(partIndex)); }}
/> : ( /> : (
<> <>
{exam.parts.length > 1 && <SectionNavbar {exam.parts.length > 1 && <SectionNavbar

View File

@@ -90,6 +90,7 @@ const Listening: React.FC<ExamProps<ListeningExam>> = ({ exam, showSolutions = f
: (!startNow && !showPartDivider && !isBetweenParts && !showSolutions) && renderExercise(e, exam.id, registerSolution, preview))} : (!startNow && !showPartDivider && !isBetweenParts && !showSolutions) && renderExercise(e, exam.id, registerSolution, preview))}
</div> </div>
) )
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [partIndex, startNow, showPartDivider, isBetweenParts, showSolutions]); }, [partIndex, startNow, showPartDivider, isBetweenParts, showSolutions]);
@@ -128,6 +129,7 @@ const Listening: React.FC<ExamProps<ListeningExam>> = ({ exam, showSolutions = f
timesListened={timesListened} timesListened={timesListened}
setShowTextModal={setShowTextModal} setShowTextModal={setShowTextModal}
setTimesListened={setTimesListened} setTimesListened={setTimesListened}
// eslint-disable-next-line react-hooks/exhaustive-deps
/>, [partIndex, assignment, timesListened, setShowTextModal, setTimesListened]) />, [partIndex, assignment, timesListened, setShowTextModal, setTimesListened])
const memoizedInstructions = useMemo(()=> const memoizedInstructions = useMemo(()=>

View File

@@ -30,7 +30,22 @@ const PartDivider: React.FC<Props> = ({ sectionIndex, sectionLabel, section, mod
<div className={`w-12 h-12 bg-ielts-${module} flex items-center justify-center rounded-lg`}>{moduleIcon[module]}</div> <div className={`w-12 h-12 bg-ielts-${module} flex items-center justify-center rounded-lg`}>{moduleIcon[module]}</div>
<p className="text-3xl">{section.intro ? `${sectionLabel} ${sectionIndex + 1}` : defaultTitle}</p> <p className="text-3xl">{section.intro ? `${sectionLabel} ${sectionIndex + 1}` : defaultTitle}</p>
</div> </div>
{section.intro && section.intro.split('\\n\\n').map((x, index) => <p key={`line-${index}`} className="text-2xl text-clip" dangerouslySetInnerHTML={{ __html: x.replace('that is not correct', 'that is <span class="font-bold"><u>not correct</u></span>') }}></p>)} {section.intro && section.intro
.replace(/\\n/g, '\n')
.split('\n')
.map((x, index) => (
<p
key={`line-${index}`}
className="text-2xl text-clip"
dangerouslySetInnerHTML={{
__html: x.replace(
'that is not correct',
'that is <span class="font-bold"><u>not correct</u></span>'
)
}}
></p>
))
}
<div className="flex items-center justify-center mt-4"> <div className="flex items-center justify-center mt-4">
<button <button
onClick={() => onNext()} onClick={() => onNext()}

View File

@@ -17,7 +17,6 @@ type PartExam = {
const answeredEveryQuestionInPart = (exam: PartExam, partIndex: number, userSolutions: UserSolution[]) => { const answeredEveryQuestionInPart = (exam: PartExam, partIndex: number, userSolutions: UserSolution[]) => {
return exam.parts[partIndex].exercises.every((exercise) => { return exam.parts[partIndex].exercises.every((exercise) => {
const userSolution = userSolutions.find(x => x.exercise === exercise.id); const userSolution = userSolutions.find(x => x.exercise === exercise.id);
switch (exercise.type) { switch (exercise.type) {
case 'multipleChoice': case 'multipleChoice':
return userSolution?.solutions.length === exercise.questions!.length; return userSolution?.solutions.length === exercise.questions!.length;

View File

@@ -21,6 +21,9 @@ import { checkAccess } from "@/utils/permissions";
import useGroups from "@/hooks/useGroups"; import useGroups from "@/hooks/useGroups";
import Button from "@/components/Low/Button"; import Button from "@/components/Low/Button";
import { EntityWithRoles } from "@/interfaces/entity"; import { EntityWithRoles } from "@/interfaces/entity";
import { FiEdit, FiArrowRight } from 'react-icons/fi';
import { HiArrowRight } from "react-icons/hi";
import { BiEdit } from "react-icons/bi";
const searchFields = [["module"], ["id"], ["createdBy"]]; const searchFields = [["module"], ["id"], ["createdBy"]];
@@ -105,7 +108,7 @@ export default function ExamList({ user, entities }: { user: User; entities: Ent
return; return;
} }
dispatch({type: "INIT_EXAM", payload: {exams: [exam], modules: [module]}}) dispatch({ type: "INIT_EXAM", payload: { exams: [exam], modules: [module] } })
router.push("/exam"); router.push("/exam");
}; };
@@ -236,7 +239,7 @@ export default function ExamList({ user, entities }: { user: User; entities: Ent
{row.original.private ? <BsCircle /> : <BsBan />} {row.original.private ? <BsCircle /> : <BsBan />}
</button> </button>
{checkAccess(user, ["admin", "developer", "mastercorporate"]) && ( {checkAccess(user, ["admin", "developer", "mastercorporate"]) && (
<button data-tip="Edit owners" onClick={() => setSelectedExam(row.original)} className="cursor-pointer tooltip"> <button data-tip="Edit exam" onClick={() => setSelectedExam(row.original)} className="cursor-pointer tooltip">
<BsPencil /> <BsPencil />
</button> </button>
)} )}
@@ -265,12 +268,54 @@ export default function ExamList({ user, entities }: { user: User; entities: Ent
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
}); });
const handleExamEdit = () => {
router.push(`/generation?id=${selectedExam!.id}&module=${selectedExam!.module}`);
}
return ( return (
<div className="flex flex-col gap-4 w-full h-full"> <div className="flex flex-col gap-4 w-full h-full">
{renderSearch()} {renderSearch()}
<Modal isOpen={!!selectedExam} title={`Edit Exam Owners - ${selectedExam?.id}`} onClose={() => setSelectedExam(undefined)}> <Modal isOpen={!!selectedExam} onClose={() => setSelectedExam(undefined)} maxWidth="max-w-xl">
{!!selectedExam ? ( {!!selectedExam ? (
<ExamOwnerSelector options={filteredCorporates} exam={selectedExam} onSave={(owners) => updateExam(selectedExam, { owners })} /> <>
<div className="p-6">
<div className="mb-6">
<div className="flex items-center gap-2 mb-4">
<BiEdit className="w-5 h-5 text-gray-600" />
<span className="text-gray-600 font-medium">Ready to Edit</span>
</div>
<div className="bg-gray-50 rounded-lg p-4 mb-3">
<p className="font-medium mb-1">
Exam ID: {selectedExam.id}
</p>
</div>
<p className="text-gray-500 text-sm">
Click &apos;Next&apos; to proceed to the exam editor.
</p>
</div>
<div className="flex justify-between gap-4 mt-8">
<Button
color="purple"
variant="outline"
onClick={() => setSelectedExam(undefined)}
className="w-32"
>
Cancel
</Button>
<Button
color="purple"
onClick={handleExamEdit}
className="w-32 text-white flex items-center justify-center gap-2"
>
Proceed
</Button>
</div>
</div>
{/*<ExamOwnerSelector options={filteredCorporates} exam={selectedExam} onSave={(owners) => updateExam(selectedExam, { owners })} />*/}
</>
) : ( ) : (
<div /> <div />
)} )}

View File

@@ -345,7 +345,6 @@ export default function ExamPage({ page, user, destination = "/", hideSidebar =
setUserSolutions(userSolutions); setUserSolutions(userSolutions);
} }
setShuffles([]); setShuffles([]);
console.log(exam);
if (index === undefined) { if (index === undefined) {
setFlags({ reviewAll: true }); setFlags({ reviewAll: true });
setModuleIndex(0); setModuleIndex(0);

View File

@@ -21,7 +21,7 @@ import { requestUser } from "@/utils/api";
import { Module } from "@/interfaces"; import { Module } from "@/interfaces";
import { getExam, getExams } from "@/utils/exams.be"; import { getExam, getExams } from "@/utils/exams.be";
import { Exam, Exercise, InteractiveSpeakingExercise, ListeningPart, SpeakingExercise } from "@/interfaces/exam"; import { Exam, Exercise, InteractiveSpeakingExercise, ListeningPart, SpeakingExercise } from "@/interfaces/exam";
import { useEffect } from "react"; import { useEffect, useState } from "react";
import { getEntitiesWithRoles } from "@/utils/entities.be"; import { getEntitiesWithRoles } from "@/utils/entities.be";
import { isAdmin } from "@/utils/users"; import { isAdmin } from "@/utils/users";
import axios from "axios"; import axios from "axios";
@@ -47,26 +47,38 @@ export const getServerSideProps = withIronSessionSsr(async ({ req, res, query })
if (Object.keys(permissions).every(p => !permissions[p as Module])) return redirect("/") if (Object.keys(permissions).every(p => !permissions[p as Module])) return redirect("/")
const { id, module } = query as { id?: string, module?: Module } const { id, module: examModule } = query as { id?: string, module?: Module }
if (!id || !module) return { props: serialize({ user, permissions }) }; if (!id || !examModule) return { props: serialize({ user, permissions }) };
if (!permissions[module]) return redirect("/generation") //if (!permissions[module]) return redirect("/generation")
const exam = await getExam(module, id) const exam = await getExam(examModule, id)
if (!exam) return redirect("/generation") if (!exam) return redirect("/generation")
return { return {
props: serialize({ user, exam, permissions }), props: serialize({ id, user, exam, examModule, permissions }),
}; };
}, sessionOptions); }, sessionOptions);
export default function Generation({ user, exam, permissions }: { user: User; exam?: Exam, permissions: Permission }) { export default function Generation({ id, user, exam, examModule, permissions }: { id: string, user: User; exam?: Exam, examModule?: Module, permissions: Permission }) {
const { title, currentModule, modules, dispatch } = useExamEditorStore(); const { title, currentModule, modules, dispatch } = useExamEditorStore();
const [examLevelParts, setExamLevelParts] = useState<number | undefined>(undefined);
const updateRoot = (updates: Partial<ExamEditorStore>) => { const updateRoot = (updates: Partial<ExamEditorStore>) => {
dispatch({ type: 'UPDATE_ROOT', payload: { updates } }); dispatch({ type: 'UPDATE_ROOT', payload: { updates } });
}; };
useEffect(() => {
if (id && exam && examModule) {
if (examModule === "level" && exam.module === "level") {
setExamLevelParts(exam.parts.length);
}
updateRoot({currentModule: examModule})
dispatch({ type: "INIT_EXAM_EDIT", payload: { exam, id, examModule } })
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [id, exam, module])
useEffect(() => { useEffect(() => {
const fetchAvatars = async () => { const fetchAvatars = async () => {
const response = await axios.get("/api/exam/avatars"); const response = await axios.get("/api/exam/avatars");
@@ -118,15 +130,11 @@ export default function Generation({ user, exam, permissions }: { user: User; ex
}) })
} }
}); });
dispatch({type: 'FULL_RESET'}); dispatch({ type: 'FULL_RESET' });
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); }, []);
useEffect(() => {
if (exam) { }
}, [exam])
return ( return (
<> <>
<Head> <Head>
@@ -150,6 +158,7 @@ export default function Generation({ user, exam, permissions }: { user: User; ex
label="Title" label="Title"
onChange={(title) => updateRoot({ title })} onChange={(title) => updateRoot({ title })}
roundness="xl" roundness="xl"
value={title}
defaultValue={title} defaultValue={title}
required required
/> />
@@ -194,7 +203,7 @@ export default function Generation({ user, exam, permissions }: { user: User; ex
))} ))}
</RadioGroup> </RadioGroup>
</div> </div>
<ExamEditor /> <ExamEditor levelParts={examLevelParts} />
</Layout> </Layout>
)} )}
</> </>

View File

@@ -5,7 +5,7 @@ import { ExamPart, ModuleState, SectionState } from "./types"
import { levelPart, listeningSection, readingPart, speakingTask, writingTask } from "@/stores/examEditor/sections" import { levelPart, listeningSection, readingPart, speakingTask, writingTask } from "@/stores/examEditor/sections"
const defaultSettings = (module: Module) => { export const defaultSettings = (module: Module) => {
const baseSettings = { const baseSettings = {
category: '', category: '',
introOption: { label: 'None', value: 'None' }, introOption: { label: 'None', value: 'None' },
@@ -77,7 +77,7 @@ const defaultSettings = (module: Module) => {
} }
} }
const sectionLabels = (module: Module) => { export const sectionLabels = (module: Module, levelParts?: number) => {
switch (module) { switch (module) {
case 'reading': case 'reading':
return Array.from({ length: 3 }, (_, index) => ({ return Array.from({ length: 3 }, (_, index) => ({
@@ -131,7 +131,6 @@ const defaultSection = (module: Module, sectionId: number) => {
export const defaultSectionSettings = (module: Module, sectionId: number, part?: ExamPart) => { export const defaultSectionSettings = (module: Module, sectionId: number, part?: ExamPart) => {
return { return {
sectionId: sectionId, sectionId: sectionId,
sectionLabel: "",
settings: defaultSettings(module), settings: defaultSettings(module),
state: part !== undefined ? part : defaultSection(module, sectionId), state: part !== undefined ? part : defaultSection(module, sectionId),
generating: undefined, generating: undefined,

View File

@@ -1,7 +1,11 @@
import defaultModuleSettings from "../defaults"; import { Exam, ExerciseOnlyExam, PartExam } from "@/interfaces/exam";
import ExamEditorStore from "../types"; import defaultModuleSettings, { defaultSectionSettings, defaultSettings, sectionLabels } from "../defaults";
import ExamEditorStore, { SectionState } from "../types";
import { MODULE_ACTIONS, ModuleActions, moduleReducer } from "./moduleReducer"; import { MODULE_ACTIONS, ModuleActions, moduleReducer } from "./moduleReducer";
import { SECTION_ACTIONS, SectionActions, sectionReducer } from "./sectionReducer"; import { SECTION_ACTIONS, SectionActions, sectionReducer } from "./sectionReducer";
import { Module } from "@/interfaces";
import { updateExamWithUserSolutions } from "@/stores/exam/utils";
import { defaultExamUserSolutions } from "@/utils/exams";
type UpdateRoot = { type UpdateRoot = {
type: 'UPDATE_ROOT'; type: 'UPDATE_ROOT';
@@ -9,9 +13,9 @@ type UpdateRoot = {
updates: Partial<ExamEditorStore> updates: Partial<ExamEditorStore>
} }
}; };
type FullReset = { type: 'FULL_RESET' }; type RootActions = { type: 'FULL_RESET' } | { type: "INIT_EXAM_EDIT", payload: { exam: Exam; examModule: Module; id: string } };
export type Action = ModuleActions | SectionActions | UpdateRoot | FullReset; export type Action = ModuleActions | SectionActions | UpdateRoot | RootActions;
export const rootReducer = ( export const rootReducer = (
state: ExamEditorStore, state: ExamEditorStore,
@@ -63,6 +67,63 @@ export const rootReducer = (
listening: defaultModuleSettings("listening", 30), listening: defaultModuleSettings("listening", 30),
level: defaultModuleSettings("level", 60) level: defaultModuleSettings("level", 60)
}, },
};
case 'INIT_EXAM_EDIT': {
const { exam, id, examModule } = action.payload;
let typedExam;
let examState;
if (["reading", "listening", "level"].includes(examModule)) {
typedExam = updateExamWithUserSolutions(exam, defaultExamUserSolutions(exam)) as PartExam;
examState = typedExam.parts.map((part, index) => {
return {
...defaultSectionSettings(examModule, index + 1, part),
sectionId: index + 1,
settings: {
...defaultSettings(examModule),
category: part.category,
introOption: { label: 'Custom', value: 'Custom' },
currentIntro: part.intro
},
}
})
} else {
typedExam = updateExamWithUserSolutions(exam, defaultExamUserSolutions(exam)) as ExerciseOnlyExam;
examState = typedExam.exercises.map((exercise, index) => {
return {
...defaultSectionSettings(examModule, index + 1),
sectionId: index + 1,
settings: {
...defaultSettings(examModule),
category: exercise.category,
introOption: { label: 'Custom', value: 'Custom' },
currentIntro: exercise.intro
},
state: exercise,
}
})
}
return {
title: id,
modules: {
...state.modules,
[examModule]: {
...defaultModuleSettings(examModule, exam.minTimer),
examLabel: exam.label,
difficulty: exam.difficulty,
isPrivate: exam.private,
sections: examState,
importModule: false,
sectionLabels:
exam.module === "level" ?
sectionLabels(examModule, exam.parts.length) :
sectionLabels(examModule)
}
}
}
} }
default: default:
return {}; return {};