diff --git a/src/components/ExamEditor/Exercises/Blanks/FillBlanksReducer.tsx b/src/components/ExamEditor/Exercises/Blanks/BlanksReducer.tsx similarity index 100% rename from src/components/ExamEditor/Exercises/Blanks/FillBlanksReducer.tsx rename to src/components/ExamEditor/Exercises/Blanks/BlanksReducer.tsx diff --git a/src/components/ExamEditor/Exercises/Blanks/Letters/index.tsx b/src/components/ExamEditor/Exercises/Blanks/Letters/index.tsx index 2086380b..4d11f4b1 100644 --- a/src/components/ExamEditor/Exercises/Blanks/Letters/index.tsx +++ b/src/components/ExamEditor/Exercises/Blanks/Letters/index.tsx @@ -6,7 +6,7 @@ import { MdEdit, MdEditOff } from "react-icons/md"; import FillBlanksWord from "./FillBlanksWord"; import { FaPlus } from "react-icons/fa"; import useExamEditorStore from "@/stores/examEditor"; -import { blanksReducer, BlankState, getTextSegments } from "../FillBlanksReducer"; +import { blanksReducer, BlankState, getTextSegments } from "../BlanksReducer"; import useSectionEdit from "@/components/ExamEditor/Hooks/useSectionEdit"; import { AlertItem } from "../../Shared/Alert"; import validateBlanks from "../validateBlanks"; @@ -75,7 +75,6 @@ const FillBlanksLetters: React.FC<{ exercise: FillBlanksExercise; sectionId: num ); dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState } }); - dispatch({ type: "REORDER_EXERCISES" }); }, onDiscard: () => { setSelectedBlankId(null); @@ -103,7 +102,6 @@ const FillBlanksLetters: React.FC<{ exercise: FillBlanksExercise; sectionId: num exercises: section.exercises.filter((ex) => ex.id !== local.id) }; dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: newSection } }); - dispatch({ type: "REORDER_EXERCISES" }); } }); @@ -211,11 +209,30 @@ const FillBlanksLetters: React.FC<{ exercise: FillBlanksExercise; sectionId: num }); }; + const handleBlankRemove = (blankId: number) => { + if (!editing) setEditing(true); + + const newAnswers = new Map(answers); + newAnswers.delete(blankId.toString()); + setAnswers(newAnswers); + + setLocal(prev => ({ + ...prev, + solutions: Array.from(newAnswers.entries()).map(([id, solution]) => ({ + id, + solution + })) + })); + blanksDispatcher({ type: "REMOVE_BLANK", payload: blankId }); + }; + useEffect(() => { validateBlanks(blanksState.blanks, answers, alerts, setAlerts); // eslint-disable-next-line react-hooks/exhaustive-deps }, [answers, blanksState.blanks, blanksState.textMode]) + + useEffect(()=> { setEditingAlert(editing, setAlerts); }, [editing]) @@ -232,6 +249,7 @@ const FillBlanksLetters: React.FC<{ exercise: FillBlanksExercise; sectionId: num module={currentModule} showBlankBank={true} onBlankSelect={(blankId) => setSelectedBlankId(blankId?.toString() || null)} + onBlankRemove={handleBlankRemove} onSave={handleSave} onDiscard={handleDiscard} onDelete={modeHandle} diff --git a/src/components/ExamEditor/Exercises/Blanks/MultipleChoice/index.tsx b/src/components/ExamEditor/Exercises/Blanks/MultipleChoice/index.tsx index f0908fea..aa78c1e6 100644 --- a/src/components/ExamEditor/Exercises/Blanks/MultipleChoice/index.tsx +++ b/src/components/ExamEditor/Exercises/Blanks/MultipleChoice/index.tsx @@ -3,7 +3,7 @@ import { useEffect, useReducer, useState } from "react"; import BlanksEditor from ".."; import { Card, CardContent } from "@/components/ui/card"; import useExamEditorStore from "@/stores/examEditor"; -import { blanksReducer, BlankState, getTextSegments } from "../FillBlanksReducer"; +import { blanksReducer, BlankState, getTextSegments } from "../BlanksReducer"; import useSectionEdit from "@/components/ExamEditor/Hooks/useSectionEdit"; import { AlertItem } from "../../Shared/Alert"; import validateBlanks from "../validateBlanks"; @@ -73,7 +73,6 @@ const FillBlanksMC: React.FC<{ exercise: FillBlanksExercise; sectionId: number } ); dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState } }); - dispatch({ type: "REORDER_EXERCISES" }); }, onDiscard: () => { setSelectedBlankId(null); @@ -99,7 +98,6 @@ const FillBlanksMC: React.FC<{ exercise: FillBlanksExercise; sectionId: number } exercises: section.exercises.filter((ex) => ex.id !== local.id) }; dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: newSection } }); - dispatch({ type: "REORDER_EXERCISES" }); } }); @@ -191,6 +189,7 @@ const FillBlanksMC: React.FC<{ exercise: FillBlanksExercise; sectionId: number } useEffect(() => { validateBlanks(blanksState.blanks, answers, alerts, setAlerts); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [answers, blanksState.blanks, blanksState.textMode]); useEffect(() => { @@ -216,8 +215,28 @@ const FillBlanksMC: React.FC<{ exercise: FillBlanksExercise; sectionId: number } solution ]) )); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + const handleBlankRemove = (blankId: number) => { + if (!editing) setEditing(true); + + const newAnswers = new Map(answers); + newAnswers.delete(blankId.toString()); + setAnswers(newAnswers); + + setLocal(prev => ({ + ...prev, + words: (prev.words as FillBlanksMCOption[]).filter(w => w.id !== blankId.toString()), + solutions: Array.from(newAnswers.entries()).map(([id, solution]) => ({ + id, + solution + })) + })); + + blanksDispatcher({ type: "REMOVE_BLANK", payload: blankId }); + }; + return (
{!blanksState.textMode && selectedBlankId && ( diff --git a/src/components/ExamEditor/Exercises/Blanks/WriteBlankFill/index.tsx b/src/components/ExamEditor/Exercises/Blanks/WriteBlankFill/index.tsx index 58507d72..99cae042 100644 --- a/src/components/ExamEditor/Exercises/Blanks/WriteBlankFill/index.tsx +++ b/src/components/ExamEditor/Exercises/Blanks/WriteBlankFill/index.tsx @@ -7,7 +7,7 @@ import { toast } from "react-toastify"; import BlanksEditor from ".."; import { AlertItem } from "../../Shared/Alert"; import setEditingAlert from "../../Shared/setEditingAlert"; -import { blanksReducer } from "../FillBlanksReducer"; +import { blanksReducer } from "../BlanksReducer"; import { validateWriteBlanks } from "./validation"; import AlternativeSolutions from "./AlternativeSolutions"; import clsx from "clsx"; @@ -126,6 +126,16 @@ const WriteBlanksFill: React.FC<{ exercise: WriteBlanksExercise; sectionId: numb })); }; + const handleBlankRemove = (blankId: number) => { + if (!editing) setEditing(true); + setLocal(prev => ({ + ...prev, + solutions: prev.solutions.filter(s => s.id !== blankId.toString()) + })); + + blanksDispatcher({ type: "REMOVE_BLANK", payload: blankId }); + }; + useEffect(() => { validateWriteBlanks(local.solutions, local.maxWords, setAlerts); }, [local.solutions, local.maxWords]); @@ -147,6 +157,7 @@ const WriteBlanksFill: React.FC<{ exercise: WriteBlanksExercise; sectionId: numb module={currentModule} showBlankBank={true} onBlankSelect={(blankId) => setSelectedBlankId(blankId?.toString() || null)} + onBlankRemove={handleBlankRemove} onSave={handleSave} onDiscard={handleDiscard} onDelete={modeHandle} diff --git a/src/components/ExamEditor/Exercises/Blanks/WriteBlankFill/validation.ts b/src/components/ExamEditor/Exercises/Blanks/WriteBlankFill/validation.ts index b23f0b4c..538581c5 100644 --- a/src/components/ExamEditor/Exercises/Blanks/WriteBlankFill/validation.ts +++ b/src/components/ExamEditor/Exercises/Blanks/WriteBlankFill/validation.ts @@ -1,5 +1,5 @@ import { AlertItem } from "../../Shared/Alert"; -import { BlankState } from "../FillBlanksReducer"; +import { BlankState } from "../BlanksReducer"; export const validateWriteBlanks = ( diff --git a/src/components/ExamEditor/Exercises/Blanks/index.tsx b/src/components/ExamEditor/Exercises/Blanks/index.tsx index 553180ac..a93a3b9e 100644 --- a/src/components/ExamEditor/Exercises/Blanks/index.tsx +++ b/src/components/ExamEditor/Exercises/Blanks/index.tsx @@ -18,7 +18,7 @@ import Alert, { AlertItem } from "../Shared/Alert"; import clsx from "clsx"; import { Card, CardContent } from "@/components/ui/card"; import { Blank, DropZone } from "./DragNDrop"; -import { getTextSegments, BlankState, BlanksState, BlanksAction } from "./FillBlanksReducer"; +import { getTextSegments, BlankState, BlanksState, BlanksAction } from "./BlanksReducer"; interface Props { @@ -33,6 +33,7 @@ interface Props { setEditing: React.Dispatch>; blanksDispatcher: React.Dispatch onBlankSelect?: (blankId: number | null) => void; + onBlankRemove: (blankId: number) => void; onSave: () => void; onDiscard: () => void; onDelete: () => void; @@ -51,6 +52,7 @@ const BlanksEditor: React.FC = ({ alerts, blanksDispatcher, onBlankSelect, + onBlankRemove, onSave, onDiscard, onDelete, @@ -99,9 +101,24 @@ const BlanksEditor: React.FC = ({ const handleTextChange = useCallback( (newText: string) => { const processedText = newText.replace(/\[(\d+)\]/g, "{{$1}}"); + + const existingBlankIds = getTextSegments(state.text) + .filter(token => token.type === 'blank') + .map(token => token.id); + + const newBlankIds = getTextSegments(processedText) + .filter(token => token.type === 'blank') + .map(token => token.id); + + const removedBlankIds = existingBlankIds.filter(id => !newBlankIds.includes(id)); + + removedBlankIds.forEach(id => { + onBlankRemove(id); + }); + blanksDispatcher({ type: "SET_TEXT", payload: processedText }); }, - [blanksDispatcher] + [blanksDispatcher, state.text, onBlankRemove] ); useEffect(() => { @@ -116,8 +133,9 @@ const BlanksEditor: React.FC = ({ }; const handleBlankRemove = useCallback((blankId: number) => { + onBlankRemove(blankId); blanksDispatcher({ type: "REMOVE_BLANK", payload: blankId }); - }, [blanksDispatcher]); + }, [blanksDispatcher, onBlankRemove]); const sensors = useSensors( useSensor(PointerSensor, { diff --git a/src/components/ExamEditor/Exercises/Blanks/validateBlanks.ts b/src/components/ExamEditor/Exercises/Blanks/validateBlanks.ts index 9c0ff825..fce25610 100644 --- a/src/components/ExamEditor/Exercises/Blanks/validateBlanks.ts +++ b/src/components/ExamEditor/Exercises/Blanks/validateBlanks.ts @@ -1,5 +1,5 @@ import { AlertItem } from "../Shared/Alert"; -import { BlankState } from "./FillBlanksReducer"; +import { BlankState } from "./BlanksReducer"; const validateBlanks = ( diff --git a/src/components/ExamEditor/Exercises/MatchSentences/index.tsx b/src/components/ExamEditor/Exercises/MatchSentences/index.tsx index a81aa89b..eb724fe0 100644 --- a/src/components/ExamEditor/Exercises/MatchSentences/index.tsx +++ b/src/components/ExamEditor/Exercises/MatchSentences/index.tsx @@ -48,7 +48,6 @@ const MatchSentences: React.FC<{ exercise: MatchSentencesExercise, sectionId: nu const newState = { ...section }; newState.exercises = newState.exercises.map((ex) => ex.id === exercise.id ? local : ex); dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState } }); - dispatch({ type: "REORDER_EXERCISES" }); }, onDiscard: () => { setLocal(exercise); @@ -61,7 +60,6 @@ const MatchSentences: React.FC<{ exercise: MatchSentencesExercise, sectionId: nu exercises: section.exercises.filter((ex) => ex.id !== local.id) }; dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: newSection } }); - dispatch({ type: "REORDER_EXERCISES" }); } }); @@ -206,16 +204,16 @@ const MatchSentences: React.FC<{ exercise: MatchSentencesExercise, sectionId: nu ))} - - + {(section.text.content.split("\n\n").length - 1) === local.sentences.length && ( + + )}
- { setLocal(exercise); @@ -98,7 +97,6 @@ const UnderlineMultipleChoice: React.FC<{exercise: MultipleChoiceExercise, secti exercises: section.exercises.filter((ex) => ex.id !== local.id) }; dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: newSection } }); - dispatch({ type: "REORDER_EXERCISES" }); }, }); diff --git a/src/components/ExamEditor/Exercises/MultipleChoice/Vanilla/index.tsx b/src/components/ExamEditor/Exercises/MultipleChoice/Vanilla/index.tsx index b3686cfa..76e1c396 100644 --- a/src/components/ExamEditor/Exercises/MultipleChoice/Vanilla/index.tsx +++ b/src/components/ExamEditor/Exercises/MultipleChoice/Vanilla/index.tsx @@ -165,7 +165,6 @@ const MultipleChoice: React.FC = ({ exercise, sectionId, op exercises: section.exercises.map((ex) => ex.id === local.id ? local : ex) }; dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: newSection } }); - dispatch({ type: "REORDER_EXERCISES" }); }, onDiscard: () => { setLocal(exercise); @@ -176,7 +175,6 @@ const MultipleChoice: React.FC = ({ exercise, sectionId, op exercises: section.exercises.filter((ex) => ex.id !== local.id) }; dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: newSection } }); - dispatch({ type: "REORDER_EXERCISES" }); }, }); diff --git a/src/components/ExamEditor/Exercises/TrueFalse/index.tsx b/src/components/ExamEditor/Exercises/TrueFalse/index.tsx index d548a4ad..33cdcffe 100644 --- a/src/components/ExamEditor/Exercises/TrueFalse/index.tsx +++ b/src/components/ExamEditor/Exercises/TrueFalse/index.tsx @@ -97,7 +97,6 @@ const TrueFalse: React.FC<{ exercise: TrueFalseExercise, sectionId: number }> = exercises: section.exercises.map((ex) => ex.id === local.id ? local : ex) }; dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: newSection } }); - dispatch({ type: "REORDER_EXERCISES" }) }, onDiscard: () => { setLocal(exercise); @@ -108,7 +107,6 @@ const TrueFalse: React.FC<{ exercise: TrueFalseExercise, sectionId: number }> = exercises: section.exercises.filter((ex) => ex.id !== local.id) }; dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: newSection } }); - dispatch({ type: "REORDER_EXERCISES" }) }, }); diff --git a/src/components/ExamEditor/Exercises/WriteBlanks/index.tsx b/src/components/ExamEditor/Exercises/WriteBlanks/index.tsx index 705bd519..f54ce4cf 100644 --- a/src/components/ExamEditor/Exercises/WriteBlanks/index.tsx +++ b/src/components/ExamEditor/Exercises/WriteBlanks/index.tsx @@ -198,6 +198,7 @@ const WriteBlanks: React.FC<{ sectionId: number; exercise: WriteBlanksExercise } const handleDragEnd = (event: DragEndEvent) => { setEditing(true); + console.log("ASOJNFOAI+SHJOIPFAS"); setLocal(handleWriteBlanksReorder(event, local)); } diff --git a/src/components/ExamEditor/Exercises/Writing/index.tsx b/src/components/ExamEditor/Exercises/Writing/index.tsx index 717c92e6..54a9f681 100644 --- a/src/components/ExamEditor/Exercises/Writing/index.tsx +++ b/src/components/ExamEditor/Exercises/Writing/index.tsx @@ -17,7 +17,7 @@ interface Props { const Writing: React.FC = ({ sectionId }) => { const { currentModule, dispatch } = useExamEditorStore(); - const {edit } = useExamEditorStore((store) => store.modules[currentModule]); + const { edit } = useExamEditorStore((store) => store.modules[currentModule]); const { generating, genResult, state } = useExamEditorStore( (state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)! ); @@ -62,8 +62,9 @@ const Writing: React.FC = ({ sectionId }) => { if (genResult !== undefined && generating === "context") { setEditing(true); setPrompt(genResult[0].prompt); - dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, module: currentModule, field: "genResult", value: undefined }}) + dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, module: currentModule, field: "genResult", value: undefined } }) } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [genResult, dispatch, sectionId, setEditing, currentModule]); useEffect(() => { diff --git a/src/components/ExamEditor/SectionRenderer/SectionContext/reading.tsx b/src/components/ExamEditor/SectionRenderer/SectionContext/reading.tsx index b0efe76c..5c5276ac 100644 --- a/src/components/ExamEditor/SectionRenderer/SectionContext/reading.tsx +++ b/src/components/ExamEditor/SectionRenderer/SectionContext/reading.tsx @@ -23,7 +23,7 @@ const ReadingContext: React.FC<{sectionId: number;}> = ({sectionId}) => { sectionId, mode: "edit", onSave: () => { - const newState = {...state} as ReadingPart; + let newState = {...state} as ReadingPart; newState.text.title = title; newState.text.content = content; dispatch({type: 'UPDATE_SECTION_STATE', payload: {sectionId, update: newState}}) @@ -45,6 +45,7 @@ const ReadingContext: React.FC<{sectionId: number;}> = ({sectionId}) => { setContent(genResult[0].text) dispatch({type: "UPDATE_SECTION_SINGLE_FIELD", payload: {sectionId, module: currentModule, field: "genResult", value: undefined}}) } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [genResult, dispatch, sectionId, setEditing, currentModule]); diff --git a/src/components/ExamEditor/SectionRenderer/SectionExercises/index.tsx b/src/components/ExamEditor/SectionRenderer/SectionExercises/index.tsx index 49f8ad21..d635b04d 100644 --- a/src/components/ExamEditor/SectionRenderer/SectionExercises/index.tsx +++ b/src/components/ExamEditor/SectionRenderer/SectionExercises/index.tsx @@ -40,17 +40,16 @@ const SectionExercises: React.FC<{ sectionId: number; }> = ({ sectionId }) => { useEffect(() => { if (genResult !== undefined && generating === "exercises") { const newExercises = genResult[0].exercises; - const newState = state as ExamPart; - newState.exercises = [...newState.exercises, ...newExercises] dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: { - exercises: newExercises + exercises: [...(state as ExamPart).exercises, ...newExercises] } } }) dispatch({ type: "UPDATE_SECTION_SINGLE_FIELD", payload: { sectionId, module: currentModule, field: "genResult", value: undefined } }) } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [genResult, dispatch, sectionId, currentModule]); const currentSection = sections.find((s) => s.sectionId === sectionId)!; diff --git a/src/components/ExamEditor/SectionRenderer/SectionExercises/reading.tsx b/src/components/ExamEditor/SectionRenderer/SectionExercises/reading.tsx index d1cb7810..3264fb85 100644 --- a/src/components/ExamEditor/SectionRenderer/SectionExercises/reading.tsx +++ b/src/components/ExamEditor/SectionRenderer/SectionExercises/reading.tsx @@ -23,7 +23,7 @@ const getExerciseItems = (exercises: ReadingExercise[], sectionId: number): Exer sectionId, label: ( "{previewLabel(exercise.prompt)}..." @@ -41,7 +41,7 @@ const getExerciseItems = (exercises: ReadingExercise[], sectionId: number): Exer sectionId, label: ( "{previewLabel(exercise.prompt)}..." @@ -59,7 +59,7 @@ const getExerciseItems = (exercises: ReadingExercise[], sectionId: number): Exer sectionId, label: ( "{previewLabel(exercise.prompt)}..." @@ -77,7 +77,7 @@ const getExerciseItems = (exercises: ReadingExercise[], sectionId: number): Exer sectionId, label: ( "{previewLabel(exercise.prompt)}..." diff --git a/src/components/ExamEditor/SettingsEditor/index.tsx b/src/components/ExamEditor/SettingsEditor/index.tsx index 420426db..c27b48bb 100644 --- a/src/components/ExamEditor/SettingsEditor/index.tsx +++ b/src/components/ExamEditor/SettingsEditor/index.tsx @@ -1,5 +1,5 @@ import React, { ReactNode, useCallback, useEffect, useMemo, useState, useRef } from "react"; -import { FaEye } from "react-icons/fa"; +import { FaEye, FaFileUpload } from "react-icons/fa"; import clsx from "clsx"; import Select from "@/components/Low/Select"; import Input from "@/components/Low/Input"; @@ -18,6 +18,8 @@ interface SettingsEditorProps { introPresets: Option[]; children?: ReactNode; canPreview: boolean; + canSubmit: boolean; + submitModule: () => void; preview: () => void; } @@ -28,7 +30,9 @@ const SettingsEditor: React.FC = ({ introPresets, children, preview, + submitModule, canPreview, + canSubmit }) => { const examLabel = useExamEditorStore((state) => state.modules[module].examLabel) || ''; const { localSettings, updateLocalAndScheduleGlobal } = useSettingsState( @@ -76,6 +80,10 @@ const SettingsEditor: React.FC = ({ }); }, [updateLocalAndScheduleGlobal]); + const submitExam = () => { + + } + return (
{sectionLabel} Settings
@@ -118,7 +126,19 @@ const SettingsEditor: React.FC = ({
{children} -
+
+ + {exerciseIndex > -1 && + partIndex > -1 && + exerciseIndex < exam.parts[partIndex].exercises.length && + showSolutions && + renderSolution(exam.parts[partIndex].exercises[exerciseIndex], nextExercise, previousExercise)} +
+ {exerciseIndex > -1 && partIndex > -1 && exerciseIndex < exam.parts[partIndex].exercises.length && ( + + )} +
+ {exerciseIndex === -1 && partIndex > 0 && ( +
+ + + +
+ )} + {exerciseIndex === -1 && partIndex === 0 && ( + + )} + )} - - {exerciseIndex === -1 && partIndex > 0 && ( -
- - - -
- )} - {exerciseIndex === -1 && partIndex === 0 && ( - - )} ); } diff --git a/src/stores/examEditor/reducers/index.ts b/src/stores/examEditor/reducers/index.ts index dd2f0221..9a84aef3 100644 --- a/src/stores/examEditor/reducers/index.ts +++ b/src/stores/examEditor/reducers/index.ts @@ -9,8 +9,10 @@ export const rootReducer = ( state: ExamEditorStore, action: Action ): Partial => { + console.log(action.type); + if (MODULE_ACTIONS.includes(action.type as any)) { - if (action.type === "REORDER_EXERCISES" && "payload" in action && "event" in action.payload) { + if (action.type === "REORDER_EXERCISES") { const updatedState = sectionReducer(state, action as SectionActions); if (!updatedState.modules) return state; @@ -26,7 +28,7 @@ export const rootReducer = ( if (SECTION_ACTIONS.includes(action.type as any)) { if (action.type === "UPDATE_SECTION_STATE") { const updatedState = sectionReducer(state, action as SectionActions); - if (!updatedState.modules) return state; + if (!updatedState.modules) return state; return moduleReducer({ ...state, diff --git a/src/stores/examEditor/reducers/moduleReducer.ts b/src/stores/examEditor/reducers/moduleReducer.ts index 39536f79..af81fb65 100644 --- a/src/stores/examEditor/reducers/moduleReducer.ts +++ b/src/stores/examEditor/reducers/moduleReducer.ts @@ -1,6 +1,6 @@ import { Module } from "@/interfaces"; import { defaultSectionSettings } from "../defaults"; -import { reorderExercises } from "../reorder/global"; +import { reorderModule } from "../reorder/global"; import ExamEditorStore, { ModuleState } from "../types"; export type ModuleActions = @@ -97,7 +97,7 @@ export const moduleReducer = ( ...state, modules: { ...state.modules, - [currentModule]: reorderExercises(currentModuleState) + [currentModule]: reorderModule(currentModuleState) } }; } diff --git a/src/stores/examEditor/reducers/sectionReducer.ts b/src/stores/examEditor/reducers/sectionReducer.ts index 13be294f..37a09a03 100644 --- a/src/stores/examEditor/reducers/sectionReducer.ts +++ b/src/stores/examEditor/reducers/sectionReducer.ts @@ -2,6 +2,7 @@ import { Module } from "@/interfaces"; import ExamEditorStore, { Generating, ReadingSectionSettings, Section, SectionSettings, SectionState } from "../types"; import { DragEndEvent } from "@dnd-kit/core"; import { LevelPart, ListeningPart, ReadingPart } from "@/interfaces/exam"; +import { reorderSection } from "../reorder/global"; export type SectionActions = | { type: 'UPDATE_SECTION_SINGLE_FIELD'; payload: { module: Module; sectionId: number; field: string; value: any } } @@ -80,7 +81,7 @@ export const sectionReducer = ( ...modules[currentModule], sections: sections.map(section => section.sectionId === sectionId - ? { ...section, state: {...section.state, ...updatedState} } + ? { ...section, state: { ...section.state, ...updatedState } } : section ) } @@ -93,10 +94,18 @@ export const sectionReducer = ( const oldIndex = active.id as number; const newIndex = over.id as number; - const currentSectionState = sections.find((s) => s.sectionId = sectionId)!.state as ReadingPart | ListeningPart | LevelPart; + const currentSectionState = sections.find((s) => s.sectionId === sectionId)!.state as ReadingPart | ListeningPart | LevelPart; + const exercises = [...currentSectionState.exercises]; + const [removed] = exercises.splice(oldIndex, 1); + exercises.splice(newIndex, 0, removed); + + const { exercises: reorderedExercises } = reorderSection(exercises, 1); + + const newSectionState = { + ...currentSectionState, + exercises: reorderedExercises + }; - const [removed] = currentSectionState.exercises.splice(oldIndex, 1); - currentSectionState.exercises.splice(newIndex, 0, removed); return { ...state, modules: { @@ -105,7 +114,7 @@ export const sectionReducer = ( ...modules[currentModule], sections: sections.map(section => section.sectionId === sectionId - ? { ...section, state: currentSectionState } + ? { ...section, state: newSectionState } : section ) } diff --git a/src/stores/examEditor/reorder/global.ts b/src/stores/examEditor/reorder/global.ts index 1725dfee..21efeded 100644 --- a/src/stores/examEditor/reorder/global.ts +++ b/src/stores/examEditor/reorder/global.ts @@ -1,59 +1,9 @@ -import { FillBlanksExercise, LevelPart, ListeningPart, MatchSentencesExercise, MultipleChoiceExercise, ReadingPart, TrueFalseExercise, WriteBlanksExercise } from "@/interfaces/exam"; +import { Exercise, FillBlanksExercise, LevelPart, ListeningPart, MatchSentencesExercise, MultipleChoiceExercise, ReadingPart, TrueFalseExercise, WriteBlanksExercise } from "@/interfaces/exam"; import { ModuleState } from "../types"; import ReorderResult from "./types"; const reorderFillBlanks = (exercise: FillBlanksExercise, startId: number): ReorderResult => { - const newSolutions = exercise.solutions - .sort((a, b) => parseInt(a.id) - parseInt(b.id)) - .map((solution, index) => ({ - ...solution, - id: (startId + index).toString() - })); - - const idMapping = exercise.solutions - .sort((a, b) => parseInt(a.id) - parseInt(b.id)) - .reduce((acc, solution, index) => { - acc[solution.id] = (startId + index).toString(); - return acc; - }, {} as Record); - - let newText = exercise.text; - Object.entries(idMapping).forEach(([oldId, newId]) => { - const regex = new RegExp(`\\{\\{${oldId}\\}\\}`, 'g'); - newText = newText.replace(regex, `{{${newId}}}`); - }); - - - const newWords = exercise.words.map(word => { - if (typeof word === 'string') { - return word; - } else if ('letter' in word && 'word' in word) { - return word; - } else if ('options' in word) { - return word; - } - return word; - }); - - const newUserSolutions = exercise.userSolutions?.map(solution => ({ - ...solution, - id: idMapping[solution.id] || solution.id - })); - - return { - exercise: { - ...exercise, - solutions: newSolutions, - text: newText, - words: newWords, - userSolutions: newUserSolutions - }, - lastId: startId + newSolutions.length - 1 - }; - -}; - -const reorderWriteBlanks = (exercise: WriteBlanksExercise, startId: number): ReorderResult => { + console.log(); const newSolutions = exercise.solutions .sort((a, b) => parseInt(a.id) - parseInt(b.id)) .map((solution, index) => ({ @@ -61,21 +11,78 @@ const reorderWriteBlanks = (exercise: WriteBlanksExercise, startId: number): Reo id: (startId + index).toString() })); + const idMapping = exercise.solutions + .sort((a, b) => parseInt(a.id) - parseInt(b.id)) + .reduce((acc, solution, index) => { + acc[solution.id] = (startId + index).toString(); + return acc; + }, {} as Record); + + let newText = exercise.text; + Object.entries(idMapping).forEach(([oldId, newId]) => { + const regex = new RegExp(`\\{\\{${oldId}\\}\\}`, 'g'); + newText = newText.replace(regex, `{{${newId}}}`); + }); + + + const newWords = exercise.words.map(word => { + if (typeof word === 'string') { + return word; + } else if ('letter' in word && 'word' in word) { + return word; + } else if ('options' in word) { + return word; + } + return word; + }); + + const newUserSolutions = exercise.userSolutions?.map(solution => ({ + ...solution, + id: idMapping[solution.id] || solution.id + })); + return { exercise: { ...exercise, solutions: newSolutions, - text: newSolutions.reduce((text, solution, index) => { - return text.replace( - new RegExp(`\\{\\{${solution.id}\\}\\}`), - `{{${startId + index}}}` - ); - }, exercise.text) + text: newText, + words: newWords, + userSolutions: newUserSolutions }, lastId: startId + newSolutions.length }; + }; +const reorderWriteBlanks = (exercise: WriteBlanksExercise, startId: number): ReorderResult => { + const oldIds = exercise.solutions.map(s => s.id); + const newIds = oldIds.map((_, index) => (startId + index).toString()); + + const newSolutions = exercise.solutions.map((solution, index) => ({ + id: newIds[index], + solution: [...solution.solution] + })); + + let newText = exercise.text; + oldIds.forEach((oldId, index) => { + newText = newText.replace( + `{{${oldId}}}`, + `{{${newIds[index]}}}` + ); + }); + + const result = { + exercise: { + ...exercise, + solutions: newSolutions, + text: newText + }, + lastId: startId + newSolutions.length + }; + + return result; + }; + const reorderTrueFalse = (exercise: TrueFalseExercise, startId: number): ReorderResult => { const newQuestions = exercise.questions .sort((a, b) => parseInt(a.id) - parseInt(b.id)) @@ -129,60 +136,79 @@ const reorderMultipleChoice = (exercise: MultipleChoiceExercise, startId: number }; +const reorderSection = (exercises: Exercise[], startId: number): { exercises: Exercise[], lastId: number } => { + let currentId = startId; + const reorderedExercises = exercises.map(exercise => { + let result; + switch (exercise.type) { + case 'fillBlanks': + console.log("Reordering FillBlanks"); + result = reorderFillBlanks(exercise, currentId); + currentId = result.lastId; + return result.exercise; -const reorderExercises = (moduleState: ModuleState) => { - let currentId = 1; + case 'writeBlanks': + result = reorderWriteBlanks(exercise, currentId); + currentId = result.lastId; + return result.exercise; - const reorderedSections = moduleState.sections.map(section => { - const currentSection = section.state as ReadingPart | ListeningPart |LevelPart; - const reorderedExercises = currentSection.exercises.map(exercise => { - let result; - switch (exercise.type) { - case 'fillBlanks': - result = reorderFillBlanks(exercise, currentId); - currentId = result.lastId; - return result.exercise; - case 'writeBlanks': - result = reorderWriteBlanks(exercise, currentId); - currentId = result.lastId; - return result.exercise; - case 'trueFalse': - result = reorderTrueFalse(exercise, currentId); - currentId = result.lastId; - return result.exercise; - case 'matchSentences': - result = reorderMatchSentences(exercise, currentId); - currentId = result.lastId; - return result.exercise; - case 'multipleChoice': - result = reorderMultipleChoice(exercise, currentId); - currentId = result.lastId - return result.exercise; - default: - return exercise; - } - }); - return { - ...section, - state: { - ...currentSection, - exercises: reorderedExercises - } - }; + case 'trueFalse': + result = reorderTrueFalse(exercise, currentId); + currentId = result.lastId; + return result.exercise; + + case 'matchSentences': + result = reorderMatchSentences(exercise, currentId); + currentId = result.lastId; + return result.exercise; + + case 'multipleChoice': + result = reorderMultipleChoice(exercise, currentId); + currentId = result.lastId; + return result.exercise; + + default: + return exercise; + } }); return { - ...moduleState, - sections: reorderedSections + exercises: reorderedExercises, + lastId: currentId }; }; + +const reorderModule = (moduleState: ModuleState) => { + let currentId = 1; + const reorderedSections = moduleState.sections.map(section => { + const currentSection = section.state as ReadingPart | ListeningPart | LevelPart; + console.log(currentSection.exercises); + const result = reorderSection(currentSection.exercises, currentId); + currentId = result.lastId; + console.log(result); + return { + ...section, + state: { + ...currentSection, + exercises: result.exercises + } + }; + }); + + return { + ...moduleState, + sections: reorderedSections + }; + }; + export { reorderFillBlanks, reorderWriteBlanks, reorderTrueFalse, reorderMatchSentences, - reorderExercises, + reorderSection, + reorderModule, reorderMultipleChoice, }; \ No newline at end of file