Finalized the Level Generation
This commit is contained in:
@@ -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;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user