Exam generation rework, batch user tables, fastapi endpoint switch

This commit is contained in:
Carlos-Mesquita
2024-11-04 23:29:14 +00:00
parent a2bc997e8f
commit 15c9c4d4bd
148 changed files with 11348 additions and 3901 deletions

View File

@@ -0,0 +1,189 @@
import useSectionEdit from "@/components/ExamEditor/Hooks/useSectionEdit";
import { Card, CardContent } from "@/components/ui/card";
import { WriteBlanksExercise, ReadingPart } from "@/interfaces/exam";
import useExamEditorStore from "@/stores/examEditor";
import { useState, useReducer, useEffect } from "react";
import { toast } from "react-toastify";
import BlanksEditor from "..";
import { AlertItem } from "../../Shared/Alert";
import setEditingAlert from "../../Shared/setEditingAlert";
import { blanksReducer } from "../FillBlanksReducer";
import { validateWriteBlanks } from "./validation";
import AlternativeSolutions from "./AlternativeSolutions";
import clsx from "clsx";
const WriteBlanksFill: React.FC<{ exercise: WriteBlanksExercise; sectionId: number }> = ({ exercise, sectionId }) => {
const { currentModule, dispatch } = useExamEditorStore();
const { state } = useExamEditorStore(
(state) => state.modules[currentModule].sections.find((section) => section.sectionId === sectionId)!
);
const section = state as ReadingPart;
const [alerts, setAlerts] = useState<AlertItem[]>([]);
const [local, setLocal] = useState(exercise);
const [selectedBlankId, setSelectedBlankId] = useState<string | null>(null);
const [editing, setEditing] = useState(false);
const [blanksState, blanksDispatcher] = useReducer(blanksReducer, {
text: exercise.text || "",
blanks: [],
selectedBlankId: null,
draggedItemId: null,
textMode: false,
setEditing,
});
const { handleSave, handleDiscard, modeHandle } = useSectionEdit({
sectionId,
editing,
setEditing,
onSave: () => {
if (!validateWriteBlanks(local.solutions, local.maxWords, setAlerts)) {
toast.error("Please fix the errors before saving!");
return;
}
setEditing(false);
setAlerts([]);
const updatedExercise = {
...local,
text: blanksState.text,
};
const newState = { ...section };
newState.exercises = newState.exercises.map((ex) =>
ex.id === exercise.id ? updatedExercise : ex
);
dispatch({ type: 'UPDATE_SECTION_STATE', payload: { sectionId, update: newState } });
},
onDiscard: () => {
setSelectedBlankId(null);
setLocal(exercise);
blanksDispatcher({ type: "RESET", payload: { text: exercise.text } });
},
onMode: () => {
const newSection = {
...section,
exercises: section.exercises.filter((ex) => ex.id !== local.id)
};
dispatch({ type: "UPDATE_SECTION_STATE", payload: { sectionId, update: newSection } });
}
});
useEffect(() => {
if (!editing) {
setLocal(exercise);
}
}, [exercise, editing]);
const handleAddSolution = (blankId: string) => {
if (!editing) setEditing(true);
setLocal(prev => ({
...prev,
solutions: prev.solutions.map(s =>
s.id === blankId
? { ...s, solution: [...s.solution, ""] }
: s
)
}));
};
const handleRemoveSolution = (blankId: string, index: number) => {
if (!editing) setEditing(true);
const solutions = local.solutions.find(s => s.id === blankId);
if (solutions && solutions.solution.length <= 1) {
toast.error("Each blank must have at least one solution!");
return;
}
setLocal(prev => ({
...prev,
solutions: prev.solutions.map(s =>
s.id === blankId
? { ...s, solution: s.solution.filter((_, i) => i !== index) }
: s
)
}));
};
const handleEditSolution = (blankId: string, index: number, value: string) => {
if (!editing) setEditing(true);
setLocal(prev => ({
...prev,
solutions: prev.solutions.map(s =>
s.id === blankId
? {
...s,
solution: s.solution.map((sol, i) => i === index ? value : sol)
}
: s
)
}));
};
useEffect(() => {
validateWriteBlanks(local.solutions, local.maxWords, setAlerts);
}, [local.solutions, local.maxWords]);
useEffect(() => {
setEditingAlert(editing, setAlerts);
}, [editing]);
return (
<div className="space-y-4">
<BlanksEditor
title="Write Blanks: Fill"
alerts={alerts}
editing={editing}
state={blanksState}
blanksDispatcher={blanksDispatcher}
description={local.prompt}
initialText={local.text}
module={currentModule}
showBlankBank={true}
onBlankSelect={(blankId) => setSelectedBlankId(blankId?.toString() || null)}
onSave={handleSave}
onDiscard={handleDiscard}
onDelete={modeHandle}
setEditing={setEditing}
>
{!blanksState.textMode && (
<Card>
<CardContent className="p-4">
<div className="flex items-center justify-between mb-4">
<span className="text-lg font-semibold">
{selectedBlankId
? `Solutions for Blank ${selectedBlankId}`
: "Click a blank to edit its solutions"}
</span>
{selectedBlankId && (
<span className="text-sm text-gray-500">
Max words per solution: {local.maxWords}
</span>
)}
</div>
<div className="grid grid-cols-1 gap-4">
{selectedBlankId && (
<AlternativeSolutions
solutions={local.solutions.find(s => s.id === selectedBlankId)?.solution || []}
onAdd={() => handleAddSolution(selectedBlankId)}
onRemove={(index: number) => handleRemoveSolution(selectedBlankId, index)}
onEdit={(index: number, value: string) => handleEditSolution(selectedBlankId, index, value)}
/>
)}
</div>
</CardContent>
</Card>
)}
</BlanksEditor>
</div>
);
};
export default WriteBlanksFill;