Created a system to go directly to an assignment from a URL
This commit is contained in:
@@ -11,16 +11,18 @@ interface Props {
|
||||
className?: string;
|
||||
navDisabled?: boolean;
|
||||
focusMode?: boolean;
|
||||
hideSidebar?: boolean
|
||||
bgColor?: string;
|
||||
onFocusLayerMouseEnter?: () => void;
|
||||
}
|
||||
|
||||
export default function Layout({user, children, className, bgColor="bg-white", navDisabled = false, focusMode = false, onFocusLayerMouseEnter}: Props) {
|
||||
export default function Layout({user, children, className, bgColor="bg-white", hideSidebar, navDisabled = false, focusMode = false, onFocusLayerMouseEnter}: Props) {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<main className={clsx("w-full min-h-full h-screen flex flex-col bg-mti-gray-smoke relative")}>
|
||||
<ToastContainer />
|
||||
{!hideSidebar && (
|
||||
<Navbar
|
||||
path={router.pathname}
|
||||
user={user}
|
||||
@@ -28,7 +30,9 @@ export default function Layout({user, children, className, bgColor="bg-white", n
|
||||
focusMode={focusMode}
|
||||
onFocusLayerMouseEnter={onFocusLayerMouseEnter}
|
||||
/>
|
||||
<div className="h-full w-full flex gap-2">
|
||||
)}
|
||||
<div className={clsx("h-full w-full flex gap-2")}>
|
||||
{!hideSidebar && (
|
||||
<Sidebar
|
||||
path={router.pathname}
|
||||
navDisabled={navDisabled}
|
||||
@@ -37,10 +41,12 @@ export default function Layout({user, children, className, bgColor="bg-white", n
|
||||
className="-md:hidden"
|
||||
user={user}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={clsx(
|
||||
`w-full min-h-full md:mr-8 ${bgColor} shadow-md rounded-2xl p-4 xl:p-10 pb-8 flex flex-col gap-8 relative overflow-hidden mt-2`,
|
||||
`w-full min-h-full ${bgColor} shadow-md rounded-2xl p-4 xl:p-10 pb-8 flex flex-col gap-8 relative overflow-hidden mt-2`,
|
||||
bgColor !== "bg-white" ? "justify-center" : "h-fit",
|
||||
hideSidebar ? "md:mx-8" : "md:mr-8",
|
||||
className,
|
||||
)}>
|
||||
{children}
|
||||
|
||||
@@ -25,13 +25,16 @@ import useSessions from "@/hooks/useSessions";
|
||||
import ShortUniqueId from "short-unique-id";
|
||||
import clsx from "clsx";
|
||||
import useGradingSystem from "@/hooks/useGrading";
|
||||
import { Assignment } from "@/interfaces/results";
|
||||
import { mapBy } from "@/utils";
|
||||
|
||||
interface Props {
|
||||
page: "exams" | "exercises";
|
||||
user: User;
|
||||
hideSidebar?: boolean
|
||||
}
|
||||
|
||||
export default function ExamPage({page, user}: Props) {
|
||||
export default function ExamPage({page, user, hideSidebar = false}: Props) {
|
||||
const [variant, setVariant] = useState<Variant>("full");
|
||||
const [avoidRepeated, setAvoidRepeated] = useState(false);
|
||||
const [hasBeenUploaded, setHasBeenUploaded] = useState(false);
|
||||
@@ -210,7 +213,7 @@ export default function ExamPage({page, user}: Props) {
|
||||
}, [setModuleIndex, showSolutions]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
console.log(selectedModules)
|
||||
if (selectedModules.length > 0 && exams.length > 0 && moduleIndex < selectedModules.length) {
|
||||
const nextExam = exams[moduleIndex];
|
||||
|
||||
@@ -218,7 +221,6 @@ export default function ExamPage({page, user}: Props) {
|
||||
if (exerciseIndex === -1 && !["reading", "listening"].includes(nextExam?.module)) setExerciseIndex(0);
|
||||
setExam(nextExam ? updateExamWithUserSolutions(nextExam) : undefined);
|
||||
}
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedModules, moduleIndex, exams]);
|
||||
|
||||
@@ -520,6 +522,7 @@ export default function ExamPage({page, user}: Props) {
|
||||
<Layout
|
||||
user={user}
|
||||
bgColor={bgColor}
|
||||
hideSidebar={hideSidebar}
|
||||
className="justify-between"
|
||||
focusMode={selectedModules.length !== 0 && !showSolutions && moduleIndex < selectedModules.length}
|
||||
onFocusLayerMouseEnter={() => setShowAbandonPopup(true)}>
|
||||
|
||||
@@ -52,7 +52,8 @@ export const getServerSideProps = withIronSessionSsr(async ({req, res, params})
|
||||
const entity = await getEntityWithRoles(assignment.entity || "")
|
||||
if (!entity) return redirect("/assignments")
|
||||
|
||||
if (!doesEntityAllow(user, entity, 'view_assignments')) return redirect("/assignments")
|
||||
if (!doesEntityAllow(user, entity, 'view_assignments') && !["admin", "developer"].includes(user.type))
|
||||
return redirect("/assignments")
|
||||
|
||||
const users = await (checkAccess(user, ["developer", "admin"]) ? getUsers() : getEntityUsers(entity.id));
|
||||
|
||||
|
||||
@@ -119,15 +119,6 @@ export default function AssignmentsPage({assignments, corporateAssignments, enti
|
||||
<b>Total:</b> {activeAssignments.reduce((acc, curr) => acc + curr.results.length, 0)}/
|
||||
{activeAssignments.reduce((acc, curr) => curr.exams.length + acc, 0)}
|
||||
</span>
|
||||
{Object.keys(groupBy(corporateAssignments, (x) => x.corporate?.id)).map((x) => (
|
||||
<div key={x}>
|
||||
<span className="font-semibold">{getUserCompanyName(users.find((u) => u.id === x)!, users, groups)}: </span>
|
||||
<span>
|
||||
{groupBy(corporateAssignments, (x) => x.corporate?.id)[x].reduce((acc, curr) => curr.results.length + acc, 0)}/
|
||||
{groupBy(corporateAssignments, (x) => x.corporate?.id)[x].reduce((acc, curr) => curr.exams.length + acc, 0)}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -9,11 +9,11 @@ import {Entity, EntityWithRoles} from "@/interfaces/entity";
|
||||
import {User} from "@/interfaces/user";
|
||||
import {sessionOptions} from "@/lib/session";
|
||||
import {USER_TYPE_LABELS} from "@/resources/user";
|
||||
import {mapBy, redirect, serialize} from "@/utils";
|
||||
import {filterBy, mapBy, redirect, serialize} from "@/utils";
|
||||
import {getEntities, getEntitiesWithRoles} from "@/utils/entities.be";
|
||||
import {shouldRedirectHome} from "@/utils/navigation.disabled";
|
||||
import {getUserName} from "@/utils/users";
|
||||
import {getLinkedUsers} from "@/utils/users.be";
|
||||
import {getEntitiesUsers, getLinkedUsers} from "@/utils/users.be";
|
||||
import axios from "axios";
|
||||
import clsx from "clsx";
|
||||
import {withIronSessionSsr} from "iron-session/next";
|
||||
@@ -22,7 +22,7 @@ import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import {useRouter} from "next/router";
|
||||
import {Divider} from "primereact/divider";
|
||||
import {useState} from "react";
|
||||
import {useMemo, useState} from "react";
|
||||
import {BsCheck, BsChevronLeft, BsClockFill, BsEnvelopeFill, BsStopwatchFill} from "react-icons/bs";
|
||||
import {toast, ToastContainer} from "react-toastify";
|
||||
import { requestUser } from "@/utils/api";
|
||||
@@ -34,12 +34,12 @@ export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
|
||||
|
||||
if (shouldRedirectHome(user)) return redirect("/")
|
||||
|
||||
const linkedUsers = await getLinkedUsers(user.id, user.type);
|
||||
const users = await getEntitiesUsers(mapBy(user.entities, 'id'))
|
||||
const entities = await getEntitiesWithRoles(mapBy(user.entities, "id"));
|
||||
const allowedEntities = findAllowedEntities(user, entities, "create_classroom")
|
||||
|
||||
return {
|
||||
props: serialize({user, entities: allowedEntities, users: linkedUsers.users.filter((x) => x.id !== user.id)}),
|
||||
props: serialize({user, entities: allowedEntities, users: users.filter((x) => x.id !== user.id)}),
|
||||
};
|
||||
}, sessionOptions);
|
||||
|
||||
@@ -55,7 +55,9 @@ export default function Home({user, users, entities}: Props) {
|
||||
const [name, setName] = useState("");
|
||||
const [entity, setEntity] = useState<string | undefined>(entities[0]?.id);
|
||||
|
||||
const {rows, renderSearch} = useListSearch<User>([["name"], ["corporateInformation", "companyInformation", "name"]], users);
|
||||
const entityUsers = useMemo(() => !entity ? users : users.filter(u => mapBy(u.entities, 'id').includes(entity)), [entity, users])
|
||||
|
||||
const {rows, renderSearch} = useListSearch<User>([["name"], ["corporateInformation", "companyInformation", "name"]], entityUsers);
|
||||
const {items, renderMinimal} = usePagination<User>(rows, 16);
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
@@ -6,15 +6,52 @@ import {shouldRedirectHome} from "@/utils/navigation.disabled";
|
||||
import ExamPage from "./(exam)/ExamPage";
|
||||
import Head from "next/head";
|
||||
import {User} from "@/interfaces/user";
|
||||
import { redirect, serialize } from "@/utils";
|
||||
import { filterBy, findBy, redirect, serialize } from "@/utils";
|
||||
import { requestUser } from "@/utils/api";
|
||||
import { getAssignment, getAssignments, getAssignmentsByAssignee } from "@/utils/assignments.be";
|
||||
import { Assignment } from "@/interfaces/results";
|
||||
import useExamStore from "@/stores/examStore";
|
||||
import { useEffect } from "react";
|
||||
import { Exam } from "@/interfaces/exam";
|
||||
import { getExamsByIds } from "@/utils/exams.be";
|
||||
import { sortByModule } from "@/utils/moduleUtils";
|
||||
import { uniqBy } from "lodash";
|
||||
import { useRouter } from "next/router";
|
||||
import { getSessionByAssignment, getSessionsByUser } from "@/utils/sessions.be";
|
||||
import { Session } from "@/hooks/useSessions";
|
||||
import moment from "moment";
|
||||
|
||||
export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
|
||||
export const getServerSideProps = withIronSessionSsr(async ({req, res, query}) => {
|
||||
const user = await requestUser(req, res)
|
||||
if (!user) return redirect("/login")
|
||||
const destination = Buffer.from(req.url || "/").toString("base64")
|
||||
if (!user) return redirect(`/login?destination=${destination}`)
|
||||
|
||||
if (shouldRedirectHome(user)) return redirect("/")
|
||||
|
||||
const {assignment: assignmentID} = query as {assignment?: string}
|
||||
|
||||
if (assignmentID) {
|
||||
const assignment = await getAssignment(assignmentID)
|
||||
|
||||
if (!assignment) return redirect("/exam")
|
||||
if (!assignment.assignees.includes(user.id) && !["admin", "developer"].includes(user.type))
|
||||
return redirect("/exam")
|
||||
|
||||
const exams = await getExamsByIds(uniqBy(assignment.exams, "id"))
|
||||
const session = await getSessionByAssignment(assignmentID)
|
||||
|
||||
if (
|
||||
filterBy(assignment.results, 'user', user.id).length > 0 ||
|
||||
moment(assignment.startDate).isAfter(moment()) ||
|
||||
moment(assignment.endDate).isBefore(moment())
|
||||
)
|
||||
return redirect("/exam")
|
||||
|
||||
return {
|
||||
props: serialize({user, assignment, exams, session})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
props: serialize({user}),
|
||||
};
|
||||
@@ -22,9 +59,55 @@ export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
|
||||
|
||||
interface Props {
|
||||
user: User;
|
||||
assignment?: Assignment
|
||||
exams?: Exam[]
|
||||
session?: Session
|
||||
}
|
||||
|
||||
export default function Page({user}: Props) {
|
||||
export default function Page({user, assignment, exams = [], session}: Props) {
|
||||
const router = useRouter()
|
||||
|
||||
const state = useExamStore((state) => state)
|
||||
|
||||
useEffect(() => {
|
||||
if (assignment && exams.length > 0 && !state.assignment && !session) {
|
||||
state.setUserSolutions([]);
|
||||
state.setShowSolutions(false);
|
||||
state.setAssignment(assignment);
|
||||
state.setExams(exams.sort(sortByModule));
|
||||
state.setSelectedModules(
|
||||
exams
|
||||
.map((x) => x!)
|
||||
.sort(sortByModule)
|
||||
.map((x) => x!.module),
|
||||
);
|
||||
|
||||
router.replace(router.asPath)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [assignment, exams, session])
|
||||
|
||||
useEffect(() => {
|
||||
if (assignment && exams.length > 0 && !state.assignment && !!session) {
|
||||
state.setShuffles(session.userSolutions.map((x) => ({exerciseID: x.exercise, shuffles: x.shuffleMaps ? x.shuffleMaps : []})));
|
||||
state.setSelectedModules(session.selectedModules);
|
||||
state.setExam(session.exam);
|
||||
state.setExams(session.exams);
|
||||
state.setSessionId(session.sessionId);
|
||||
state.setAssignment(session.assignment);
|
||||
state.setExerciseIndex(session.exerciseIndex);
|
||||
state.setPartIndex(session.partIndex);
|
||||
state.setModuleIndex(session.moduleIndex);
|
||||
state.setTimeSpent(session.timeSpent);
|
||||
state.setUserSolutions(session.userSolutions);
|
||||
state.setShowSolutions(false);
|
||||
state.setQuestionIndex(session.questionIndex);
|
||||
|
||||
router.replace(router.asPath)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [assignment, exams, session])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
@@ -36,7 +119,7 @@ export default function Page({user}: Props) {
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<ExamPage page="exams" user={user} />
|
||||
<ExamPage page="exams" user={user} hideSidebar={!!assignment} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,15 +6,51 @@ import {shouldRedirectHome} from "@/utils/navigation.disabled";
|
||||
import ExamPage from "./(exam)/ExamPage";
|
||||
import Head from "next/head";
|
||||
import {User} from "@/interfaces/user";
|
||||
import { redirect, serialize } from "@/utils";
|
||||
import { filterBy, findBy, redirect, serialize } from "@/utils";
|
||||
import { requestUser } from "@/utils/api";
|
||||
import { getAssignment, getAssignments, getAssignmentsByAssignee } from "@/utils/assignments.be";
|
||||
import { Assignment } from "@/interfaces/results";
|
||||
import useExamStore from "@/stores/examStore";
|
||||
import { useEffect } from "react";
|
||||
import { Exam } from "@/interfaces/exam";
|
||||
import { getExamsByIds } from "@/utils/exams.be";
|
||||
import { sortByModule } from "@/utils/moduleUtils";
|
||||
import { uniqBy } from "lodash";
|
||||
import { useRouter } from "next/router";
|
||||
import { getSessionByAssignment, getSessionsByUser } from "@/utils/sessions.be";
|
||||
import { Session } from "@/hooks/useSessions";
|
||||
import moment from "moment";
|
||||
|
||||
export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
|
||||
export const getServerSideProps = withIronSessionSsr(async ({req, res, query}) => {
|
||||
const user = await requestUser(req, res)
|
||||
if (!user) return redirect("/login")
|
||||
const destination = Buffer.from(req.url || "/").toString("base64")
|
||||
if (!user) return redirect(`/login?destination=${destination}`)
|
||||
|
||||
if (shouldRedirectHome(user)) return redirect("/")
|
||||
|
||||
const {assignment: assignmentID} = query as {assignment?: string}
|
||||
|
||||
if (assignmentID) {
|
||||
const assignment = await getAssignment(assignmentID)
|
||||
|
||||
if (!assignment) return redirect("/exercises")
|
||||
if (!["admin", "developer"].includes(user.type) && !assignment.assignees.includes(user.id)) return redirect("/exercises")
|
||||
|
||||
const exams = await getExamsByIds(uniqBy(assignment.exams, "id"))
|
||||
const session = await getSessionByAssignment(assignmentID)
|
||||
|
||||
if (
|
||||
filterBy(assignment.results, 'user', user.id) ||
|
||||
moment(assignment.startDate).isBefore(moment()) ||
|
||||
moment(assignment.endDate).isAfter(moment())
|
||||
)
|
||||
return redirect("/exercises")
|
||||
|
||||
return {
|
||||
props: serialize({user, assignment, exams, session})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
props: serialize({user}),
|
||||
};
|
||||
@@ -22,13 +58,59 @@ export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
|
||||
|
||||
interface Props {
|
||||
user: User;
|
||||
assignment?: Assignment
|
||||
exams?: Exam[]
|
||||
session?: Session
|
||||
}
|
||||
|
||||
export default function Page({user}: Props) {
|
||||
export default function Page({user, assignment, exams = [], session}: Props) {
|
||||
const router = useRouter()
|
||||
|
||||
const state = useExamStore((state) => state)
|
||||
|
||||
useEffect(() => {
|
||||
if (assignment && exams.length > 0 && !state.assignment && !session) {
|
||||
state.setUserSolutions([]);
|
||||
state.setShowSolutions(false);
|
||||
state.setAssignment(assignment);
|
||||
state.setExams(exams.sort(sortByModule));
|
||||
state.setSelectedModules(
|
||||
exams
|
||||
.map((x) => x!)
|
||||
.sort(sortByModule)
|
||||
.map((x) => x!.module),
|
||||
);
|
||||
|
||||
router.replace(router.asPath)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [assignment, exams, session])
|
||||
|
||||
useEffect(() => {
|
||||
if (assignment && exams.length > 0 && !state.assignment && !!session) {
|
||||
state.setShuffles(session.userSolutions.map((x) => ({exerciseID: x.exercise, shuffles: x.shuffleMaps ? x.shuffleMaps : []})));
|
||||
state.setSelectedModules(session.selectedModules);
|
||||
state.setExam(session.exam);
|
||||
state.setExams(session.exams);
|
||||
state.setSessionId(session.sessionId);
|
||||
state.setAssignment(session.assignment);
|
||||
state.setExerciseIndex(session.exerciseIndex);
|
||||
state.setPartIndex(session.partIndex);
|
||||
state.setModuleIndex(session.moduleIndex);
|
||||
state.setTimeSpent(session.timeSpent);
|
||||
state.setUserSolutions(session.userSolutions);
|
||||
state.setShowSolutions(false);
|
||||
state.setQuestionIndex(session.questionIndex);
|
||||
|
||||
router.replace(router.asPath)
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [assignment, exams, session])
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Exercises | EnCoach</title>
|
||||
<title>Exams | EnCoach</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="A training platform for the IELTS exam provided by the Muscat Training Institute and developed by eCrop."
|
||||
@@ -36,7 +118,7 @@ export default function Page({user}: Props) {
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<ExamPage page="exercises" user={user} />
|
||||
<ExamPage page="exams" user={user} hideSidebar={!!assignment} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,16 +20,17 @@ import { redirect } from "@/utils";
|
||||
|
||||
const EMAIL_REGEX = new RegExp(/^[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*@[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)*$/g);
|
||||
|
||||
export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
|
||||
export const getServerSideProps = withIronSessionSsr(async ({req, res, query}) => {
|
||||
const destination = !query.destination ? "/" : Buffer.from(query.destination as string, 'base64').toString()
|
||||
const user = await requestUser(req, res)
|
||||
if (user) return redirect("/")
|
||||
if (user) return redirect(destination)
|
||||
|
||||
return {
|
||||
props: {user: null},
|
||||
props: {user: null, destination},
|
||||
};
|
||||
}, sessionOptions);
|
||||
|
||||
export default function Login() {
|
||||
export default function Login({ destination }: { destination: string }) {
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [rememberPassword, setRememberPassword] = useState(false);
|
||||
@@ -38,13 +39,13 @@ export default function Login() {
|
||||
const router = useRouter();
|
||||
|
||||
const {user, mutateUser} = useUser({
|
||||
redirectTo: "/",
|
||||
redirectTo: destination,
|
||||
redirectIfFound: true,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (user) router.push("/");
|
||||
}, [router, user]);
|
||||
if (user) router.push(destination);
|
||||
}, [router, user, destination]);
|
||||
|
||||
const forgotPassword = () => {
|
||||
if (!email || email.length < 0 || !EMAIL_REGEX.test(email)) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import {getUserCorporate} from "./groups.be";
|
||||
import {Db, ObjectId} from "mongodb";
|
||||
import client from "@/lib/mongodb";
|
||||
import {MODULE_ARRAY} from "./moduleUtils";
|
||||
import { mapBy } from ".";
|
||||
|
||||
const db = client.db(process.env.MONGODB_DB);
|
||||
|
||||
@@ -37,7 +38,7 @@ export const getExamsByIds = async (ids: {module: Module; id: string}[]) => {
|
||||
async (m) =>
|
||||
await db
|
||||
.collection(m)
|
||||
.find<Exam>({id: {$in: groupedByModule[m]}})
|
||||
.find<Exam>({id: {$in: mapBy(groupedByModule[m], 'id')}})
|
||||
.toArray(),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -9,3 +9,8 @@ export const getSessionsByUser = async (id: string, limit?: number) =>
|
||||
.find<Session>({user: id})
|
||||
.limit(limit || 0)
|
||||
.toArray();
|
||||
|
||||
export const getSessionByAssignment = async (assignmentID: string) =>
|
||||
await db
|
||||
.collection("sessions")
|
||||
.findOne<Session>({"assignment.id": assignmentID})
|
||||
|
||||
Reference in New Issue
Block a user