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}
-
+
+