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

@@ -6,34 +6,33 @@ import { renderSolution } from "@/components/Solutions";
import { Module } from "@/interfaces";
import { Exercise, FillBlanksMCOption, LevelExam, MultipleChoiceExercise, UserSolution } from "@/interfaces/exam";
import useExamStore from "@/stores/examStore";
import { usePersistentExamStore } from "@/stores/examStore";
import { countExercises } from "@/utils/moduleUtils";
import clsx from "clsx";
import { use, useEffect, useMemo, useState } from "react";
import TextComponent from "./TextComponent";
import PartDivider from "./PartDivider";
import PartDivider from "../Navigation/SectionDivider";
import Timer from "@/components/Medium/Timer";
import shuffleExamExercise from "./Shuffle";
import { Tab } from "@headlessui/react";
import Modal from "@/components/Modal";
import { typeCheckWordsMC } from "@/utils/type.check";
import SectionNavbar from "../Navigation/SectionNavbar";
interface Props {
exam: LevelExam;
showSolutions?: boolean;
onFinish: (userSolutions: UserSolution[]) => void;
editing?: boolean;
preview?: boolean;
partDividers?: boolean;
}
const typeCheckWordsMC = (words: any[]): words is FillBlanksMCOption[] => {
return Array.isArray(words) && words.every(
word => word && typeof word === 'object' && 'id' in word && 'options' in word
);
}
export default function Level({ exam, showSolutions = false, onFinish, editing = false }: Props) {
export default function Level({ exam, showSolutions = false, onFinish, preview = false }: Props) {
const levelBgColor = "bg-ielts-level-light";
const examState = useExamStore((state) => state);
const persistentExamState = usePersistentExamStore((state) => state);
const {
userSolutions,
hasExamEnded,
@@ -50,7 +49,7 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
setQuestionIndex,
setShuffles,
setCurrentSolution
} = useExamStore((state) => state);
} = !preview ? examState : persistentExamState;
// In case client want to switch back
const textRenderDisabled = true;
@@ -74,7 +73,7 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
const [currentExercise, setCurrentExercise] = useState<Exercise | undefined>(undefined);
const [showPartDivider, setShowPartDivider] = useState<boolean>(typeof exam.parts[0].intro === "string" && !showSolutions);
const [startNow, setStartNow] = useState<boolean>(true && !showSolutions);
const [startNow, setStartNow] = useState<boolean>(!showSolutions);
useEffect(() => {
if (currentExercise === undefined && partIndex === 0 && exerciseIndex === 0) {
@@ -175,7 +174,7 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
return;
}
if (partIndex + 1 === exam.parts.length && exerciseIndex === exam.parts[partIndex].exercises.length - 1 && !continueAnyways) {
if (partIndex + 1 === exam.parts.length && exerciseIndex === exam.parts[partIndex].exercises.length - 1 && !continueAnyways && !showSolutions) {
modalKwargs();
setShowQuestionsModal(true);
}
@@ -414,9 +413,9 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
exam: exam,
partIndex: partIndex,
showSolutions: showSolutions,
"setExerciseIndex": setExerciseIndex,
"setPartIndex": setPartIndex,
"runOnClick": setQuestionIndex
setExerciseIndex: setExerciseIndex,
setPartIndex: setPartIndex,
runOnClick: setQuestionIndex
}
@@ -427,8 +426,8 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
{textRender && !textRenderDisabled ?
renderText() :
<>
{exam.parts[partIndex].context && renderText()}
{(showSolutions || editing) ?
{exam.parts[partIndex]?.context && renderText()}
{(showSolutions) ?
currentExercise && renderSolution(currentExercise, nextExercise, previousExercise) :
currentExercise && renderExercise(currentExercise, exam.id, next, previousExercise)
}
@@ -465,64 +464,55 @@ export default function Level({ exam, showSolutions = false, onFinish, editing =
!(partIndex === 0 && questionIndex === 0 && (showPartDivider || startNow)) &&
<Timer minTimer={exam.minTimer} disableTimer={showSolutions} standalone={true} />
}
{(showPartDivider || startNow) ? <PartDivider part={exam.parts[partIndex]} partIndex={partIndex} onNext={() => { setShowPartDivider(false); setStartNow(false); setBgColor("bg-white"); }} /> : (
<>
{exam.parts[0].intro && (
<div className="w-full">
<Tab.Group className="w-[90%]" selectedIndex={partIndex} onChange={setPartIndex}>
<Tab.List className="flex space-x-1 rounded-xl bg-ielts-level/20 p-1">
{exam.parts.map((_, index) =>
<Tab key={index} onClick={(e) => {
/*
// If client wants to revert uncomment and remove the added if statement
if (!seenParts.has(index)) {
e.preventDefault();
} else {
*/
setExerciseIndex(0);
setQuestionIndex(0);
if (!seenParts.has(index)) {
setShowPartDivider(true);
setBgColor(levelBgColor);
setSeenParts(prev => new Set(prev).add(index));
}
}}
className={({ selected }) =>
clsx(
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-ielts-level/80",
"ring-white ring-opacity-60 focus:outline-none",
"transition duration-300 ease-in-out hover:bg-white/70",
selected && "bg-white shadow",
// seenParts.includes(index) ? "hover:bg-white/70" : "cursor-not-allowed"
)
}
>{`Part ${index + 1}`}</Tab>
)
{(showPartDivider || startNow) ?
<PartDivider
module="level"
sectionLabel="Part"
defaultTitle="Placement Test"
section={exam.parts[partIndex]}
sectionIndex={partIndex}
onNext={() => { setShowPartDivider(false); setStartNow(false); setBgColor("bg-white"); }}
/> : (
<>
{exam.parts[0].intro && (
<SectionNavbar
module="level"
sections={exam.parts}
sectionLabel="Part"
sectionIndex={partIndex}
setSectionIndex={setPartIndex}
onClick={
(index: number) => {
setExerciseIndex(0);
setQuestionIndex(0);
if (!seenParts.has(index)) {
setShowPartDivider(true);
setBgColor(levelBgColor);
setSeenParts(prev => new Set(prev).add(index));
}
}
</Tab.List>
</Tab.Group>
} />
)}
<ModuleTitle
examLabel={exam.label}
partLabel={partLabel()}
minTimer={exam.minTimer}
exerciseIndex={calculateExerciseIndex()}
module="level"
totalExercises={countExercises(exam.parts.flatMap((x) => x.exercises))}
disableTimer={showSolutions}
showTimer={false}
{...mcNavKwargs}
/>
<div
className={clsx(
"mb-20 w-full",
!!exam.parts[partIndex].context && !textRender && "grid grid-cols-2 gap-4",
)}>
{memoizedRender}
</div>
)}
<ModuleTitle
examLabel={exam.label}
partLabel={partLabel()}
minTimer={exam.minTimer}
exerciseIndex={calculateExerciseIndex()}
module="level"
totalExercises={countExercises(exam.parts.flatMap((x) => x.exercises))}
disableTimer={showSolutions || editing}
showTimer={false}
{...mcNavKwargs}
/>
<div
className={clsx(
"mb-20 w-full",
!!exam.parts[partIndex].context && !textRender && "grid grid-cols-2 gap-4",
)}>
{memoizedRender}
</div>
</>
)}
</>
)}
</div>
</>
);