Finalized the Level Generation

This commit is contained in:
Tiago Ribeiro
2024-07-26 14:09:08 +01:00
parent 6a803fe137
commit 8f6639b7fc
4 changed files with 318 additions and 434 deletions

View File

@@ -18,8 +18,6 @@ function Question({
const renderPrompt = (prompt: string) => { const renderPrompt = (prompt: string) => {
return reactStringReplace(prompt, /((<u>)\w+(<\/u>))/g, (match) => { return reactStringReplace(prompt, /((<u>)\w+(<\/u>))/g, (match) => {
const word = match.replaceAll("<u>", "").replaceAll("</u>", ""); const word = match.replaceAll("<u>", "").replaceAll("</u>", "");
console.log(word);
return word.length > 0 ? <u>{word}</u> : null; return word.length > 0 ? <u>{word}</u> : null;
}); });
}; };

View File

@@ -1,382 +1,229 @@
import clsx from "clsx"; import clsx from "clsx";
import { IconType } from "react-icons"; import {IconType} from "react-icons";
import { MdSpaceDashboard } from "react-icons/md"; import {MdSpaceDashboard} from "react-icons/md";
import { import {
BsFileEarmarkText, BsFileEarmarkText,
BsClockHistory, BsClockHistory,
BsPencil, BsPencil,
BsGraphUp, BsGraphUp,
BsChevronBarRight, BsChevronBarRight,
BsChevronBarLeft, BsChevronBarLeft,
BsShieldFill, BsShieldFill,
BsCloudFill, BsCloudFill,
BsCurrencyDollar, BsCurrencyDollar,
BsClipboardData, BsClipboardData,
BsFileLock, BsFileLock,
} from "react-icons/bs"; } from "react-icons/bs";
import { RiLogoutBoxFill } from "react-icons/ri"; import {RiLogoutBoxFill} from "react-icons/ri";
import { SlPencil } from "react-icons/sl"; import {SlPencil} from "react-icons/sl";
import { FaAward } from "react-icons/fa"; import {FaAward} from "react-icons/fa";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import {useRouter} from "next/router";
import axios from "axios"; import axios from "axios";
import FocusLayer from "@/components/FocusLayer"; import FocusLayer from "@/components/FocusLayer";
import { preventNavigation } from "@/utils/navigation.disabled"; import {preventNavigation} from "@/utils/navigation.disabled";
import { useEffect, useState } from "react"; import {useEffect, useState} from "react";
import usePreferencesStore from "@/stores/preferencesStore"; import usePreferencesStore from "@/stores/preferencesStore";
import { User } from "@/interfaces/user"; import {User} from "@/interfaces/user";
import useTicketsListener from "@/hooks/useTicketsListener"; import useTicketsListener from "@/hooks/useTicketsListener";
import { checkAccess, getTypesOfUser } from "@/utils/permissions"; import {checkAccess, getTypesOfUser} from "@/utils/permissions";
interface Props { interface Props {
path: string; path: string;
navDisabled?: boolean; navDisabled?: boolean;
focusMode?: boolean; focusMode?: boolean;
onFocusLayerMouseEnter?: () => void; onFocusLayerMouseEnter?: () => void;
className?: string; className?: string;
user: User; user: User;
} }
interface NavProps { interface NavProps {
Icon: IconType; Icon: IconType;
label: string; label: string;
path: string; path: string;
keyPath: string; keyPath: string;
disabled?: boolean; disabled?: boolean;
isMinimized?: boolean; isMinimized?: boolean;
badge?: number; badge?: number;
} }
const Nav = ({ const Nav = ({Icon, label, path, keyPath, disabled = false, isMinimized = false, badge}: NavProps) => {
Icon, return (
label, <Link
path, href={!disabled ? keyPath : ""}
keyPath, className={clsx(
disabled = false, "flex items-center gap-4 rounded-full p-4 text-gray-500 hover:text-white",
isMinimized = false, "transition-all duration-300 ease-in-out relative",
badge, disabled ? "hover:bg-mti-gray-dim cursor-not-allowed" : "hover:bg-mti-purple-light cursor-pointer",
}: NavProps) => { path === keyPath && "bg-mti-purple-light text-white",
return ( isMinimized ? "w-fit" : "w-full min-w-[200px] px-8 2xl:min-w-[220px]",
<Link )}>
href={!disabled ? keyPath : ""} <Icon size={24} />
className={clsx( {!isMinimized && <span className="text-lg font-semibold">{label}</span>}
"flex items-center gap-4 rounded-full p-4 text-gray-500 hover:text-white", {!!badge && badge > 0 && (
"transition-all duration-300 ease-in-out relative", <div
disabled className={clsx(
? "hover:bg-mti-gray-dim cursor-not-allowed" "bg-mti-purple-light h-5 w-5 text-xs rounded-full flex items-center justify-center text-white",
: "hover:bg-mti-purple-light cursor-pointer", "transition ease-in-out duration-300",
path === keyPath && "bg-mti-purple-light text-white", isMinimized && "absolute right-0 top-0",
isMinimized ? "w-fit" : "w-full min-w-[200px] px-8 2xl:min-w-[220px]" )}>
)} {badge}
> </div>
<Icon size={24} /> )}
{!isMinimized && <span className="text-lg font-semibold">{label}</span>} </Link>
{!!badge && badge > 0 && ( );
<div
className={clsx(
"bg-mti-purple-light h-5 w-5 text-xs rounded-full flex items-center justify-center text-white",
"transition ease-in-out duration-300",
isMinimized && "absolute right-0 top-0"
)}
>
{badge}
</div>
)}
</Link>
);
}; };
export default function Sidebar({ export default function Sidebar({path, navDisabled = false, focusMode = false, user, onFocusLayerMouseEnter, className}: Props) {
path, const router = useRouter();
navDisabled = false,
focusMode = false,
user,
onFocusLayerMouseEnter,
className,
}: Props) {
const router = useRouter();
const [isMinimized, toggleMinimize] = usePreferencesStore((state) => [ const [isMinimized, toggleMinimize] = usePreferencesStore((state) => [state.isSidebarMinimized, state.toggleSidebarMinimized]);
state.isSidebarMinimized,
state.toggleSidebarMinimized,
]);
const { totalAssignedTickets } = useTicketsListener(user.id); const {totalAssignedTickets} = useTicketsListener(user.id);
const logout = async () => { const logout = async () => {
axios.post("/api/logout").finally(() => { axios.post("/api/logout").finally(() => {
setTimeout(() => router.reload(), 500); setTimeout(() => router.reload(), 500);
}); });
}; };
const disableNavigation = preventNavigation(navDisabled, focusMode); const disableNavigation = preventNavigation(navDisabled, focusMode);
return ( return (
<section <section
className={clsx( className={clsx(
"relative flex h-full flex-col justify-between bg-transparent px-4 py-4 pb-8", "relative flex h-full flex-col justify-between bg-transparent px-4 py-4 pb-8",
isMinimized ? "w-fit" : "-xl:w-fit w-1/6", isMinimized ? "w-fit" : "-xl:w-fit w-1/6",
className className,
)} )}>
> <div className="-xl:hidden flex-col gap-3 xl:flex">
<div className="-xl:hidden flex-col gap-3 xl:flex"> <Nav disabled={disableNavigation} Icon={MdSpaceDashboard} label="Dashboard" path={path} keyPath="/" isMinimized={isMinimized} />
<Nav {checkAccess(user, ["student", "teacher", "developer"], "viewExams") && (
disabled={disableNavigation} <Nav disabled={disableNavigation} Icon={BsFileEarmarkText} label="Exams" path={path} keyPath="/exam" isMinimized={isMinimized} />
Icon={MdSpaceDashboard} )}
label="Dashboard" {checkAccess(user, ["student", "teacher", "developer"], "viewExercises") && (
path={path} <Nav disabled={disableNavigation} Icon={BsPencil} label="Exercises" path={path} keyPath="/exercises" isMinimized={isMinimized} />
keyPath="/" )}
isMinimized={isMinimized} {checkAccess(user, getTypesOfUser(["agent"]), "viewStats") && (
/> <Nav disabled={disableNavigation} Icon={BsGraphUp} label="Stats" path={path} keyPath="/stats" isMinimized={isMinimized} />
{checkAccess( )}
user, {checkAccess(user, getTypesOfUser(["agent"]), "viewRecords") && (
["student", "teacher", "developer"], <Nav disabled={disableNavigation} Icon={BsClockHistory} label="Record" path={path} keyPath="/record" isMinimized={isMinimized} />
"viewExams" )}
) && ( {checkAccess(user, ["admin", "developer", "agent", "corporate", "mastercorporate"], "viewPaymentRecords") && (
<Nav <Nav
disabled={disableNavigation} disabled={disableNavigation}
Icon={BsFileEarmarkText} Icon={BsCurrencyDollar}
label="Exams" label="Payment Record"
path={path} path={path}
keyPath="/exam" keyPath="/payment-record"
isMinimized={isMinimized} isMinimized={isMinimized}
/> />
)} )}
{checkAccess( {checkAccess(user, ["admin", "developer", "corporate", "teacher", "mastercorporate"]) && (
user, <Nav
["student", "teacher", "developer"], disabled={disableNavigation}
"viewExercises" Icon={BsShieldFill}
) && ( label="Settings"
<Nav path={path}
disabled={disableNavigation} keyPath="/settings"
Icon={BsPencil} isMinimized={isMinimized}
label="Exercises" />
path={path} )}
keyPath="/exercises" {checkAccess(user, ["admin", "developer", "agent"], "viewTickets") && (
isMinimized={isMinimized} <Nav
/> disabled={disableNavigation}
)} Icon={BsClipboardData}
{checkAccess(user, getTypesOfUser(["agent"]), "viewStats") && ( label="Tickets"
<Nav path={path}
disabled={disableNavigation} keyPath="/tickets"
Icon={BsGraphUp} isMinimized={isMinimized}
label="Stats" badge={totalAssignedTickets}
path={path} />
keyPath="/stats" )}
isMinimized={isMinimized} {checkAccess(user, ["developer", "admin"]) && (
/> <>
)} <Nav
{checkAccess(user, getTypesOfUser(["agent"]), "viewRecords") && ( disabled={disableNavigation}
<Nav Icon={BsCloudFill}
disabled={disableNavigation} label="Generation"
Icon={BsClockHistory} path={path}
label="Record" keyPath="/generation"
path={path} isMinimized={isMinimized}
keyPath="/record" />
isMinimized={isMinimized} <Nav
/> disabled={disableNavigation}
)} Icon={BsFileLock}
{checkAccess( label="Permissions"
user, path={path}
["admin", "developer", "agent", "corporate", "mastercorporate"], keyPath="/permissions"
"viewPaymentRecords" isMinimized={isMinimized}
) && ( />
<Nav </>
disabled={disableNavigation} )}
Icon={BsCurrencyDollar} </div>
label="Payment Record" <div className="-xl:flex flex-col gap-3 xl:hidden">
path={path} <Nav disabled={disableNavigation} Icon={MdSpaceDashboard} label="Dashboard" path={path} keyPath="/" isMinimized={true} />
keyPath="/payment-record" <Nav disabled={disableNavigation} Icon={BsFileEarmarkText} label="Exams" path={path} keyPath="/exam" isMinimized={true} />
isMinimized={isMinimized} <Nav disabled={disableNavigation} Icon={BsPencil} label="Exercises" path={path} keyPath="/exercises" isMinimized={true} />
/> {checkAccess(user, getTypesOfUser(["agent"]), "viewStats") && (
)} <Nav disabled={disableNavigation} Icon={BsGraphUp} label="Stats" path={path} keyPath="/stats" isMinimized={true} />
{checkAccess(user, [ )}
"admin", {checkAccess(user, getTypesOfUser(["agent"]), "viewRecords") && (
"developer", <Nav disabled={disableNavigation} Icon={BsClockHistory} label="Record" path={path} keyPath="/record" isMinimized={true} />
"corporate", )}
"teacher", {checkAccess(user, getTypesOfUser(["student"])) && (
"mastercorporate", <Nav disabled={disableNavigation} Icon={BsShieldFill} label="Settings" path={path} keyPath="/settings" isMinimized={true} />
]) && ( )}
<Nav {checkAccess(user, getTypesOfUser(["student"])) && (
disabled={disableNavigation} <Nav disabled={disableNavigation} Icon={BsShieldFill} label="Permissions" path={path} keyPath="/permissions" isMinimized={true} />
Icon={BsShieldFill} )}
label="Settings" {checkAccess(user, ["developer"]) && (
path={path} <>
keyPath="/settings" <Nav
isMinimized={isMinimized} disabled={disableNavigation}
/> Icon={BsCloudFill}
)} label="Generation"
{checkAccess(user, [ path={path}
"admin", keyPath="/generation"
"developer", isMinimized={true}
"corporate", />
"teacher", <Nav
"mastercorporate", disabled={disableNavigation}
]) && ( Icon={BsFileLock}
<Nav label="Permissions"
disabled={disableNavigation} path={path}
Icon={BsShieldFill} keyPath="/permissions"
label="Permissions" isMinimized={true}
path={path} />
keyPath="/permissions" </>
isMinimized={isMinimized} )}
/> </div>
)}
{checkAccess(user, ["admin", "developer", "agent"], "viewTickets") && (
<Nav
disabled={disableNavigation}
Icon={BsClipboardData}
label="Tickets"
path={path}
keyPath="/tickets"
isMinimized={isMinimized}
badge={totalAssignedTickets}
/>
)}
{checkAccess(user, ["developer"]) && (
<>
<Nav
disabled={disableNavigation}
Icon={BsCloudFill}
label="Generation"
path={path}
keyPath="/generation"
isMinimized={isMinimized}
/>
<Nav
disabled={disableNavigation}
Icon={BsFileLock}
label="Permissions"
path={path}
keyPath="/permissions"
isMinimized={isMinimized}
/>
</>
)}
</div>
<div className="-xl:flex flex-col gap-3 xl:hidden">
<Nav
disabled={disableNavigation}
Icon={MdSpaceDashboard}
label="Dashboard"
path={path}
keyPath="/"
isMinimized={true}
/>
<Nav
disabled={disableNavigation}
Icon={BsFileEarmarkText}
label="Exams"
path={path}
keyPath="/exam"
isMinimized={true}
/>
<Nav
disabled={disableNavigation}
Icon={BsPencil}
label="Exercises"
path={path}
keyPath="/exercises"
isMinimized={true}
/>
{checkAccess(user, getTypesOfUser(["agent"]), "viewStats") && (
<Nav
disabled={disableNavigation}
Icon={BsGraphUp}
label="Stats"
path={path}
keyPath="/stats"
isMinimized={true}
/>
)}
{checkAccess(user, getTypesOfUser(["agent"]), "viewRecords") && (
<Nav
disabled={disableNavigation}
Icon={BsClockHistory}
label="Record"
path={path}
keyPath="/record"
isMinimized={true}
/>
)}
{checkAccess(user, getTypesOfUser(["student"])) && (
<Nav
disabled={disableNavigation}
Icon={BsShieldFill}
label="Settings"
path={path}
keyPath="/settings"
isMinimized={true}
/>
)}
{checkAccess(user, getTypesOfUser(["student"])) && (
<Nav
disabled={disableNavigation}
Icon={BsShieldFill}
label="Permissions"
path={path}
keyPath="/permissions"
isMinimized={true}
/>
)}
{checkAccess(user, ["developer"]) && (
<>
<Nav
disabled={disableNavigation}
Icon={BsCloudFill}
label="Generation"
path={path}
keyPath="/generation"
isMinimized={true}
/>
<Nav
disabled={disableNavigation}
Icon={BsFileLock}
label="Permissions"
path={path}
keyPath="/permissions"
isMinimized={true}
/>
</>
)}
</div>
<div className="fixed bottom-12 flex flex-col gap-0"> <div className="fixed bottom-12 flex flex-col gap-0">
<div <div
role="button" role="button"
tabIndex={1} tabIndex={1}
onClick={toggleMinimize} onClick={toggleMinimize}
className={clsx( className={clsx(
"hover:text-mti-rose -xl:hidden flex cursor-pointer items-center gap-4 rounded-full p-4 text-black transition duration-300 ease-in-out", "hover:text-mti-rose -xl:hidden flex cursor-pointer items-center gap-4 rounded-full p-4 text-black transition duration-300 ease-in-out",
isMinimized ? "w-fit" : "w-full min-w-[250px] px-8" isMinimized ? "w-fit" : "w-full min-w-[250px] px-8",
)} )}>
> {isMinimized ? <BsChevronBarRight size={24} /> : <BsChevronBarLeft size={24} />}
{isMinimized ? ( {!isMinimized && <span className="text-lg font-medium">Minimize</span>}
<BsChevronBarRight size={24} /> </div>
) : ( <div
<BsChevronBarLeft size={24} /> role="button"
)} tabIndex={1}
{!isMinimized && ( onClick={focusMode ? () => {} : logout}
<span className="text-lg font-medium">Minimize</span> className={clsx(
)} "hover:text-mti-rose flex cursor-pointer items-center gap-4 rounded-full p-4 text-black transition duration-300 ease-in-out",
</div> isMinimized ? "w-fit" : "w-full min-w-[250px] px-8",
<div )}>
role="button" <RiLogoutBoxFill size={24} />
tabIndex={1} {!isMinimized && <span className="-xl:hidden text-lg font-medium">Log Out</span>}
onClick={focusMode ? () => {} : logout} </div>
className={clsx( </div>
"hover:text-mti-rose flex cursor-pointer items-center gap-4 rounded-full p-4 text-black transition duration-300 ease-in-out", {focusMode && <FocusLayer onFocusLayerMouseEnter={onFocusLayerMouseEnter} />}
isMinimized ? "w-fit" : "w-full min-w-[250px] px-8" </section>
)} );
>
<RiLogoutBoxFill size={24} />
{!isMinimized && (
<span className="-xl:hidden text-lg font-medium">Log Out</span>
)}
</div>
</div>
{focusMode && (
<FocusLayer onFocusLayerMouseEnter={onFocusLayerMouseEnter} />
)}
</section>
);
} }

View File

@@ -18,8 +18,6 @@ function Question({
const renderPrompt = (prompt: string) => { const renderPrompt = (prompt: string) => {
return reactStringReplace(prompt, /((<u>)\w+(<\/u>))/g, (match) => { return reactStringReplace(prompt, /((<u>)\w+(<\/u>))/g, (match) => {
const word = match.replaceAll("<u>", "").replaceAll("</u>", ""); const word = match.replaceAll("<u>", "").replaceAll("</u>", "");
console.log(word);
return word.length > 0 ? <u>{word}</u> : null; return word.length > 0 ? <u>{word}</u> : null;
}); });
}; };

View File

@@ -1,3 +1,5 @@
import FillBlanksEdit from "@/components/Generation/fill.blanks.edit";
import WriteBlankEdits from "@/components/Generation/write.blanks.edit";
import Input from "@/components/Low/Input"; import Input from "@/components/Low/Input";
import Select from "@/components/Low/Select"; import Select from "@/components/Low/Select";
import { import {
@@ -8,6 +10,7 @@ import {
LevelPart, LevelPart,
FillBlanksExercise, FillBlanksExercise,
WriteBlanksExercise, WriteBlanksExercise,
Exercise,
} from "@/interfaces/exam"; } from "@/interfaces/exam";
import useExamStore from "@/stores/examStore"; import useExamStore from "@/stores/examStore";
import {getExamById} from "@/utils/exams"; import {getExamById} from "@/utils/exams";
@@ -42,8 +45,6 @@ const QuestionDisplay = ({question, onUpdate}: {question: MultipleChoiceQuestion
const renderPrompt = (prompt: string) => { const renderPrompt = (prompt: string) => {
return reactStringReplace(prompt, /((<u>)\w+(<\/u>))/g, (match) => { return reactStringReplace(prompt, /((<u>)\w+(<\/u>))/g, (match) => {
const word = match.replaceAll("<u>", "").replaceAll("</u>", ""); const word = match.replaceAll("<u>", "").replaceAll("</u>", "");
console.log(word);
return word.length > 0 ? <u>{word}</u> : null; return word.length > 0 ? <u>{word}</u> : null;
}); });
}; };
@@ -118,10 +119,67 @@ const TaskTab = ({section, setSection}: {section: LevelSection; setSection: (sec
questions: (x as MultipleChoiceExercise).questions.map((q) => (q.id === question.id ? question : q)), questions: (x as MultipleChoiceExercise).questions.map((q) => (q.id === question.id ? question : q)),
})), })),
}; };
console.log(updatedExam);
setSection(updatedExam as any); setSection(updatedExam as any);
}; };
const renderExercise = (exercise: Exercise) => {
if (exercise.type === "multipleChoice")
return (
<div key={exercise.id} className="w-full h-full flex flex-col gap-2">
<div className="flex gap-2">
<span className="text-xl font-semibold">Multiple Choice</span>
<span className="rounded-xl bg-white border border-ielts-level p-1 px-4 w-fit">{exercise.questions.length} questions</span>
</div>
<span>{exercise.prompt}</span>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{exercise.questions.map((question) => (
<QuestionDisplay question={question} onUpdate={onUpdate} key={question.id} />
))}
</div>
</div>
);
if (exercise.type === "fillBlanks")
return (
<div key={exercise.id} className="w-full h-full flex flex-col gap-2">
<div className="flex gap-2">
<span className="text-xl font-semibold">Fill Blanks</span>
</div>
<span>{exercise.prompt}</span>
<FillBlanksEdit
exercise={exercise}
key={exercise.id}
updateExercise={(data: any) =>
setSection({
...section,
part: {...section.part!, exercises: section.part!.exercises.map((x) => (x.id === exercise.id ? {...x, ...data} : x))},
})
}
/>
</div>
);
if (exercise.type === "writeBlanks")
return (
<div key={exercise.id} className="w-full h-full flex flex-col gap-2">
<div className="flex gap-2">
<span className="text-xl font-semibold">Write Blanks</span>
</div>
<span>{exercise.prompt}</span>
<WriteBlankEdits
exercise={exercise}
key={exercise.id}
updateExercise={(data: any) =>
setSection({
...section,
part: {...section.part!, exercises: section.part!.exercises.map((x) => (x.id === exercise.id ? {...x, ...data} : x))},
})
}
/>
</div>
);
};
return ( return (
<Tab.Panel className="w-full bg-ielts-level/20 min-h-[600px] h-full rounded-xl p-6 flex flex-col gap-4"> <Tab.Panel className="w-full bg-ielts-level/20 min-h-[600px] h-full rounded-xl p-6 flex flex-col gap-4">
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
@@ -159,28 +217,8 @@ const TaskTab = ({section, setSection}: {section: LevelSection; setSection: (sec
)} )}
{section?.part && ( {section?.part && (
<div className="flex flex-col gap-2 w-full overflow-y-scroll scrollbar-hide h-full"> <div className="flex flex-col gap-2 w-full overflow-y-scroll scrollbar-hide h-full">
{section.part.exercises {section.part.context && <div>{section.part.context}</div>}
.filter((x) => x.type === "multipleChoice") {section.part.exercises.map(renderExercise)}
.map((ex) => {
const exercise = ex as MultipleChoiceExercise;
return (
<div key={ex.id} className="w-full h-full flex flex-col gap-2">
<div className="flex gap-2">
<span className="text-xl font-semibold">Multiple Choice</span>
<span className="rounded-xl bg-white border border-ielts-level p-1 px-4 w-fit">
{exercise.questions.length} questions
</span>
</div>
<span>{exercise.prompt}</span>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{exercise.questions.map((question) => (
<QuestionDisplay question={question} onUpdate={onUpdate} key={question.id} />
))}
</div>
</div>
);
})}
</div> </div>
)} )}
</Tab.Panel> </Tab.Panel>
@@ -237,6 +275,8 @@ const LevelGeneration = () => {
} }
}); });
let newParts = [...parts];
axios axios
.post<{exercises: {[key: string]: any}}>("/api/exam/level/generate/level", {nr_exercises: numberOfParts, ...body}) .post<{exercises: {[key: string]: any}}>("/api/exam/level/generate/level", {nr_exercises: numberOfParts, ...body})
.then((result) => { .then((result) => {
@@ -270,22 +310,20 @@ const LevelGeneration = () => {
userSolutions: [], userSolutions: [],
}; };
setParts((prev) => const item = {
prev.map((p, i) =>
i === index
? {
...p,
part: {
exercises: [exercise],
},
}
: p,
),
);
return {
exercises: [exercise], exercises: [exercise],
}; };
newParts = newParts.map((p, i) =>
i === index
? {
...p,
part: item,
}
: p,
);
return item;
} }
if (part.type === "blank_space_text") { if (part.type === "blank_space_text") {
@@ -300,22 +338,20 @@ const LevelGeneration = () => {
userSolutions: [], userSolutions: [],
}; };
setParts((prev) => const item = {
prev.map((p, i) =>
i === index
? {
...p,
part: {
exercises: [exercise],
},
}
: p,
),
);
return {
exercises: [exercise], exercises: [exercise],
}; };
newParts = newParts.map((p, i) =>
i === index
? {
...p,
part: item,
}
: p,
);
return item;
} }
const mcExercise: MultipleChoiceExercise = { const mcExercise: MultipleChoiceExercise = {
@@ -339,29 +375,26 @@ const LevelGeneration = () => {
userSolutions: [], userSolutions: [],
}; };
setParts((prev) => const item = {
prev.map((p, i) =>
i === index
? {
...p,
part: {
context: currentExercise.text.content,
exercises: [mcExercise, wbExercise],
},
}
: p,
),
);
return {
context: currentExercise.text.content, context: currentExercise.text.content,
exercises: [mcExercise, wbExercise], exercises: [mcExercise, wbExercise],
}; };
newParts = newParts.map((p, i) =>
i === index
? {
...p,
part: item,
}
: p,
);
return item;
}) })
.filter((x) => !!x) as LevelPart[], .filter((x) => !!x) as LevelPart[],
}; };
console.log(exam); setParts(newParts);
setGeneratedExam(exam); setGeneratedExam(exam);
}) })
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
@@ -375,8 +408,13 @@ const LevelGeneration = () => {
setIsLoading(true); setIsLoading(true);
const exam = {
...generatedExam,
parts: generatedExam.parts.map((p, i) => ({...p, exercises: parts[i].part!.exercises})),
};
axios axios
.post(`/api/exam/level`, generatedExam) .post(`/api/exam/level`, exam)
.then((result) => { .then((result) => {
playSound("sent"); playSound("sent");
console.log(`Generated Exam ID: ${result.data.id}`); console.log(`Generated Exam ID: ${result.data.id}`);
@@ -437,7 +475,10 @@ const LevelGeneration = () => {
<TaskTab <TaskTab
key={index} key={index}
section={parts[index]} section={parts[index]}
setSection={(part) => setParts((prev) => prev.map((x, i) => (i === index ? part : x)))} setSection={(part) => {
console.log(part);
setParts((prev) => prev.map((x, i) => (i === index ? part : x)));
}}
/> />
))} ))}
</Tab.Panels> </Tab.Panels>