176 lines
6.2 KiB
TypeScript
176 lines
6.2 KiB
TypeScript
/* eslint-disable @next/next/no-img-element */
|
|
import AssignmentCard from "@/components/High/AssignmentCard";
|
|
import Layout from "@/components/High/Layout";
|
|
import Button from "@/components/Low/Button";
|
|
import Separator from "@/components/Low/Separator";
|
|
import ProfileSummary from "@/components/ProfileSummary";
|
|
import { Session } from "@/hooks/useSessions";
|
|
import { Grading } from "@/interfaces";
|
|
import { EntityWithRoles } from "@/interfaces/entity";
|
|
import { Exam } from "@/interfaces/exam";
|
|
import { InviteWithEntity } from "@/interfaces/invite";
|
|
import { Assignment } from "@/interfaces/results";
|
|
import { Stat, User } from "@/interfaces/user";
|
|
import { sessionOptions } from "@/lib/session";
|
|
import useExamStore from "@/stores/exam";
|
|
import { filterBy, findBy, mapBy, redirect, serialize } from "@/utils";
|
|
import { requestUser } from "@/utils/api";
|
|
import { activeAssignmentFilter, futureAssignmentFilter } from "@/utils/assignments";
|
|
import { getAssignmentsByAssignee } from "@/utils/assignments.be";
|
|
import { getEntitiesWithRoles } from "@/utils/entities.be";
|
|
import { getExamsByIds } from "@/utils/exams.be";
|
|
import { sortByModule } from "@/utils/moduleUtils";
|
|
import { checkAccess } from "@/utils/permissions";
|
|
import { getSessionsByUser } from "@/utils/sessions.be";
|
|
import axios from "axios";
|
|
import clsx from "clsx";
|
|
import { withIronSessionSsr } from "iron-session/next";
|
|
import { uniqBy } from "lodash";
|
|
import moment from "moment";
|
|
import Head from "next/head";
|
|
import { useRouter } from "next/router";
|
|
import { useMemo, useState } from "react";
|
|
import { BsArrowRepeat } from "react-icons/bs";
|
|
import { ToastContainer } from "react-toastify";
|
|
|
|
interface Props {
|
|
user: User;
|
|
entities: EntityWithRoles[];
|
|
assignments: Assignment[];
|
|
stats: Stat[];
|
|
exams: Exam[];
|
|
sessions: Session[];
|
|
invites: InviteWithEntity[];
|
|
grading: Grading;
|
|
}
|
|
|
|
export const getServerSideProps = withIronSessionSsr(async ({ req, res }) => {
|
|
const user = await requestUser(req, res)
|
|
const destination = Buffer.from(req.url || "/").toString("base64")
|
|
if (!user) return redirect(`/login?destination=${destination}`)
|
|
|
|
if (!checkAccess(user, ["admin", "developer", "student"]))
|
|
return redirect("/")
|
|
|
|
const entityIDS = mapBy(user.entities, "id") || [];
|
|
|
|
const entities = await getEntitiesWithRoles(entityIDS);
|
|
const assignments = await getAssignmentsByAssignee(user.id, { archived: { $ne: true } });
|
|
const sessions = await getSessionsByUser(user.id, 0, { "assignment.id": { $in: mapBy(assignments, 'id') } });
|
|
|
|
const examIDs = uniqBy(
|
|
assignments.flatMap((a) =>
|
|
filterBy(a.exams, 'assignee', user.id).map((e) => ({ module: e.module, id: e.id, key: `${e.module}_${e.id}` })),
|
|
),
|
|
"key",
|
|
);
|
|
const exams = await getExamsByIds(examIDs);
|
|
|
|
return { props: serialize({ user, entities, assignments, exams, sessions }) };
|
|
}, sessionOptions);
|
|
|
|
const destination = Buffer.from("/official-exam").toString("base64")
|
|
|
|
export default function OfficialExam({ user, entities, assignments, sessions, exams }: Props) {
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
|
|
const router = useRouter();
|
|
|
|
const dispatch = useExamStore((state) => state.dispatch);
|
|
|
|
const reload = () => {
|
|
setIsLoading(true)
|
|
router.replace(router.asPath)
|
|
setTimeout(() => setIsLoading(false), 500)
|
|
}
|
|
|
|
const startAssignment = (assignment: Assignment) => {
|
|
const assignmentExams = exams.filter(e => {
|
|
const exam = findBy(assignment.exams, 'id', e.id)
|
|
return !!exam && exam.module === e.module
|
|
})
|
|
|
|
if (assignmentExams.every((x) => !!x)) {
|
|
dispatch({
|
|
type: "INIT_EXAM", payload: {
|
|
exams: assignmentExams.sort(sortByModule),
|
|
modules: mapBy(assignmentExams.sort(sortByModule), 'module'),
|
|
assignment
|
|
}
|
|
})
|
|
router.push(`/exam?assignment=${assignment.id}&destination=${destination}`);
|
|
}
|
|
};
|
|
|
|
const loadSession = async (session: Session) => {
|
|
dispatch({type: "SET_SESSION", payload: {session}});
|
|
router.push(`/exam?assignment=${session.assignment?.id}&destination=${destination}`);
|
|
};
|
|
|
|
const logout = async () => {
|
|
axios.post("/api/logout").finally(() => {
|
|
setTimeout(() => router.reload(), 500);
|
|
});
|
|
};
|
|
|
|
const studentAssignments = useMemo(() => [
|
|
...assignments.filter(activeAssignmentFilter), ...assignments.filter(futureAssignmentFilter)],
|
|
[assignments]
|
|
);
|
|
|
|
const assignmentSessions = useMemo(() => sessions.filter(s => mapBy(studentAssignments, 'id').includes(s.assignment?.id || "")), [sessions, studentAssignments])
|
|
|
|
return (
|
|
<>
|
|
<Head>
|
|
<title>EnCoach</title>
|
|
<meta
|
|
name="description"
|
|
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
|
|
/>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<link rel="icon" href="/favicon.ico" />
|
|
</Head>
|
|
<ToastContainer />
|
|
<Layout user={user} hideSidebar>
|
|
{entities.length > 0 && (
|
|
<div className="absolute right-4 top-4 rounded-lg bg-neutral-200 px-2 py-1">
|
|
<b>{mapBy(entities, "label")?.join(", ")}</b>
|
|
</div>
|
|
)}
|
|
|
|
<ProfileSummary user={user} items={[]} removeLevel />
|
|
|
|
<Separator />
|
|
|
|
{/* Assignments */}
|
|
<section className="flex flex-col gap-1 md:gap-3">
|
|
<div
|
|
onClick={reload}
|
|
className="text-mti-purple-light hover:text-mti-purple-dark flex cursor-pointer items-center gap-2 transition duration-300 ease-in-out">
|
|
<span className="text-mti-black text-lg font-bold">Assignments</span>
|
|
<BsArrowRepeat className={clsx("text-xl", isLoading && "animate-spin")} />
|
|
</div>
|
|
<span className="text-mti-gray-taupe scrollbar-hide flex gap-8 overflow-x-scroll">
|
|
{studentAssignments.length === 0 && "Assignments will appear here. It seems that for now there are no assignments for you."}
|
|
{studentAssignments
|
|
.sort((a, b) => moment(a.startDate).diff(b.startDate))
|
|
.map((a) =>
|
|
<AssignmentCard
|
|
key={a.id}
|
|
assignment={a}
|
|
user={user}
|
|
session={assignmentSessions.find(s => s.assignment?.id === a.id)}
|
|
startAssignment={startAssignment}
|
|
resumeAssignment={loadSession}
|
|
/>
|
|
)}
|
|
</span>
|
|
</section>
|
|
|
|
<Button onClick={logout} variant="outline" color="red" className="max-w-[200px] w-full absolute bottom-8 left-8">Sign out</Button>
|
|
</Layout>
|
|
</>
|
|
);
|
|
}
|