Exam generation rework, batch user tables, fastapi endpoint switch
This commit is contained in:
@@ -1,38 +0,0 @@
|
||||
import Button from "@/components/Low/Button";
|
||||
import { Module } from "@/interfaces";
|
||||
import { LevelPart, UserSolution } from "@/interfaces/exam";
|
||||
import clsx from "clsx";
|
||||
import { ReactNode } from "react";
|
||||
import { BsBook, BsClipboard, BsHeadphones, BsMegaphone, BsPen } from "react-icons/bs";
|
||||
|
||||
interface Props {
|
||||
partIndex: number;
|
||||
part: LevelPart // for now
|
||||
onNext: () => void;
|
||||
}
|
||||
|
||||
const PartDivider: React.FC<Props> = ({ partIndex, part, onNext }) => {
|
||||
|
||||
const moduleIcon: { [key in Module]: ReactNode } = {
|
||||
reading: <BsBook className="text-ielts-reading w-6 h-6" />,
|
||||
listening: <BsHeadphones className="text-ielts-listening w-6 h-6" />,
|
||||
writing: <BsPen className="text-ielts-writing w-6 h-6" />,
|
||||
speaking: <BsMegaphone className="text-ielts-speaking w-6 h-6" />,
|
||||
level: <BsClipboard className="text-white w-6 h-6" />,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={clsx("flex flex-col h-fit border bg-white rounded-3xl p-12 gap-8", part.intro ? "w-3/6" : "items-center my-auto")}>
|
||||
{/** only level for now */}
|
||||
<div className="flex flex-row gap-4 items-center"><div className="w-12 h-12 bg-ielts-level flex items-center justify-center rounded-lg">{moduleIcon["level"]}</div><p className="text-3xl">{part.intro ? `Part ${partIndex + 1}` : "Placement Test"}</p></div>
|
||||
{part.intro && part.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>)}
|
||||
<div className="flex items-center justify-center mt-4">
|
||||
<Button color="purple" onClick={() => onNext()} className="max-w-[200px] self-end w-full text-2xl">
|
||||
{partIndex === 0 ? `Start now`: `Start Part ${partIndex + 1}`}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PartDivider;
|
||||
@@ -147,9 +147,3 @@ function fisherYatesShuffle<T>(array: T[]): T[] {
|
||||
}
|
||||
return shuffled;
|
||||
}
|
||||
|
||||
const typeCheckWordsMC = (words: any[]): words is FillBlanksMCOption[] => {
|
||||
return Array.isArray(words) && words.every(
|
||||
word => word && typeof word === 'object' && 'id' in word && 'options' in word
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user