Merged develop into feature/ExamGenRework
This commit is contained in:
99
src/components/High/AssignmentCard.tsx
Normal file
99
src/components/High/AssignmentCard.tsx
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import { Session } from "@/hooks/useSessions";
|
||||||
|
import { Assignment } from "@/interfaces/results";
|
||||||
|
import { User } from "@/interfaces/user";
|
||||||
|
import { sortByModuleName } from "@/utils/moduleUtils";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import moment from "moment";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
import Button from "../Low/Button";
|
||||||
|
import ModuleBadge from "../ModuleBadge";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
assignment: Assignment
|
||||||
|
user: User
|
||||||
|
session?: Session
|
||||||
|
startAssignment: (assignment: Assignment) => void
|
||||||
|
resumeAssignment: (session: Session) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function AssignmentCard({ user, assignment, session, startAssignment, resumeAssignment }: Props) {
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"border-mti-gray-anti-flash flex min-w-[350px] flex-col gap-6 rounded-xl border p-4",
|
||||||
|
assignment.results.map((r) => r.user).includes(user.id) && "border-mti-green-light",
|
||||||
|
)}
|
||||||
|
key={assignment.id}>
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<h3 className="text-mti-black/90 text-xl font-semibold">{assignment.name}</h3>
|
||||||
|
<span className="flex justify-between gap-1 text-lg">
|
||||||
|
<span>{moment(assignment.startDate).format("DD/MM/YY, HH:mm")}</span>
|
||||||
|
<span>-</span>
|
||||||
|
<span>{moment(assignment.endDate).format("DD/MM/YY, HH:mm")}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex w-full items-center justify-between">
|
||||||
|
<div className="-md:mt-2 grid w-fit min-w-[140px] grid-cols-2 grid-rows-2 place-items-center justify-between gap-4">
|
||||||
|
{assignment.exams
|
||||||
|
.filter((e) => e.assignee === user.id)
|
||||||
|
.map((e) => e.module)
|
||||||
|
.sort(sortByModuleName)
|
||||||
|
.map((module) => (
|
||||||
|
<ModuleBadge className="scale-110 w-full" key={module} module={module} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
{!assignment.results.map((r) => r.user).includes(user.id) && (
|
||||||
|
<>
|
||||||
|
<div
|
||||||
|
className="tooltip flex h-full w-full items-center justify-end pl-8 md:hidden"
|
||||||
|
data-tip="Your screen size is too small to perform an assignment">
|
||||||
|
<Button className="h-full w-full !rounded-xl" variant="outline">
|
||||||
|
Start
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{!session && (
|
||||||
|
<div
|
||||||
|
data-tip="You have already started this assignment!"
|
||||||
|
className={clsx(
|
||||||
|
"-md:hidden h-full w-full max-w-[50%] cursor-pointer",
|
||||||
|
!!session && "tooltip",
|
||||||
|
)}>
|
||||||
|
<Button
|
||||||
|
className={clsx("w-full h-full !rounded-xl")}
|
||||||
|
onClick={() => startAssignment(assignment)}
|
||||||
|
variant="outline">
|
||||||
|
Start
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!!session && (
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"-md:hidden h-full w-full max-w-[50%] cursor-pointer"
|
||||||
|
)}>
|
||||||
|
<Button
|
||||||
|
className={clsx("w-full h-full !rounded-xl")}
|
||||||
|
onClick={() => resumeAssignment(session)}
|
||||||
|
color="green"
|
||||||
|
variant="outline">
|
||||||
|
Resume
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{assignment.results.map((r) => r.user).includes(user.id) && (
|
||||||
|
<Button
|
||||||
|
onClick={() => router.push("/record")}
|
||||||
|
color="green"
|
||||||
|
className="-md:hidden h-full w-full max-w-[50%] !rounded-xl"
|
||||||
|
variant="outline">
|
||||||
|
Submitted
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -13,10 +13,12 @@ export default function SessionCard({
|
|||||||
session,
|
session,
|
||||||
reload,
|
reload,
|
||||||
loadSession,
|
loadSession,
|
||||||
|
disableDelete = false
|
||||||
}: {
|
}: {
|
||||||
session: Session;
|
session: Session;
|
||||||
reload: () => void;
|
reload: () => void;
|
||||||
loadSession: (session: Session) => Promise<void>;
|
loadSession: (session: Session) => Promise<void>;
|
||||||
|
disableDelete?: boolean
|
||||||
}) {
|
}) {
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
@@ -95,7 +97,7 @@ export default function SessionCard({
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={deleteSession}
|
onClick={deleteSession}
|
||||||
disabled={isLoading}
|
disabled={isLoading || disableDelete}
|
||||||
className="bg-mti-red-ultralight w-full hover:bg-mti-red-light rounded-lg p-2 px-4 transition duration-300 ease-in-out hover:text-white disabled:cursor-not-allowed">
|
className="bg-mti-red-ultralight w-full hover:bg-mti-red-light rounded-lg p-2 px-4 transition duration-300 ease-in-out hover:text-white disabled:cursor-not-allowed">
|
||||||
{!isLoading && "Delete"}
|
{!isLoading && "Delete"}
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
|
|||||||
@@ -189,10 +189,7 @@ const StatsGridItem: React.FC<StatsGridItemProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const shouldRenderPDFIcon = () => {
|
const shouldRenderPDFIcon = () => {
|
||||||
if (assignment) {
|
if (assignment) return assignment.released;
|
||||||
return assignment.released;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,11 @@ interface Props {
|
|||||||
label: string;
|
label: string;
|
||||||
tooltip?: string;
|
tooltip?: string;
|
||||||
}[];
|
}[];
|
||||||
|
removeLevel?: boolean
|
||||||
children?: ReactElement;
|
children?: ReactElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ProfileSummary({user, items}: Props) {
|
export default function ProfileSummary({user, items, removeLevel = false}: Props) {
|
||||||
return (
|
return (
|
||||||
<section className="w-full flex -md:flex-col gap-4 md:gap-8">
|
<section className="w-full flex -md:flex-col gap-4 md:gap-8">
|
||||||
<img
|
<img
|
||||||
@@ -30,21 +31,29 @@ export default function ProfileSummary({user, items}: Props) {
|
|||||||
<div className="flex -md:flex-col justify-between w-full gap-8">
|
<div className="flex -md:flex-col justify-between w-full gap-8">
|
||||||
<div className="flex flex-col gap-2 py-2">
|
<div className="flex flex-col gap-2 py-2">
|
||||||
<h1 className="font-bold text-2xl md:text-4xl">{user.name}</h1>
|
<h1 className="font-bold text-2xl md:text-4xl">{user.name}</h1>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<h6 className="font-normal text-base text-mti-gray-taupe">{user.email}</h6>
|
||||||
|
<span> - </span>
|
||||||
<h6 className="font-normal text-base text-mti-gray-taupe">{USER_TYPE_LABELS[user.type]}</h6>
|
<h6 className="font-normal text-base text-mti-gray-taupe">{USER_TYPE_LABELS[user.type]}</h6>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{!removeLevel && (
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
label={`Level ${calculateAverageLevel(user.levels).toFixed(1)}`}
|
label={`Level ${calculateAverageLevel(user.levels).toFixed(1)}`}
|
||||||
percentage={100}
|
percentage={100}
|
||||||
color="purple"
|
color="purple"
|
||||||
className="max-w-xs w-32 md:self-end h-10 -md:hidden"
|
className="max-w-xs w-32 md:self-end h-10 -md:hidden"
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{!removeLevel && (
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
label=""
|
label=""
|
||||||
percentage={Math.round((calculateAverageLevel(user.levels) * 100) / calculateAverageLevel(user.desiredLevels))}
|
percentage={Math.round((calculateAverageLevel(user.levels) * 100) / calculateAverageLevel(user.desiredLevels))}
|
||||||
color="red"
|
color="red"
|
||||||
className="w-full h-3 drop-shadow-lg -md:hidden"
|
className="w-full h-3 drop-shadow-lg -md:hidden"
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex justify-between w-full mt-8 -md:hidden">
|
<div className="flex justify-between w-full mt-8 -md:hidden">
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
|
|||||||
188
src/pages/official-exam.tsx
Normal file
188
src/pages/official-exam.tsx
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
/* 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 ProgressBar from "@/components/Low/ProgressBar";
|
||||||
|
import Separator from "@/components/Low/Separator";
|
||||||
|
import InviteWithUserCard from "@/components/Medium/InviteWithUserCard";
|
||||||
|
import SessionCard from "@/components/Medium/SessionCard";
|
||||||
|
import ModuleBadge from "@/components/ModuleBadge";
|
||||||
|
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/examStore";
|
||||||
|
import {findBy, mapBy, redirect, serialize} from "@/utils";
|
||||||
|
import { requestUser } from "@/utils/api";
|
||||||
|
import {activeAssignmentFilter} from "@/utils/assignments";
|
||||||
|
import {getAssignmentsByAssignee} from "@/utils/assignments.be";
|
||||||
|
import {getEntitiesWithRoles, getEntityWithRoles} from "@/utils/entities.be";
|
||||||
|
import {getExamsByIds} from "@/utils/exams.be";
|
||||||
|
import {getGradingSystemByEntity} from "@/utils/grading.be";
|
||||||
|
import {convertInvitersToEntity, getInvitesByInvitee} from "@/utils/invites.be";
|
||||||
|
import {countExamModules, countFullExams, MODULE_ARRAY, sortByModule, sortByModuleName} from "@/utils/moduleUtils";
|
||||||
|
import {checkAccess} from "@/utils/permissions";
|
||||||
|
import {getGradingLabel} from "@/utils/score";
|
||||||
|
import {getSessionsByUser} from "@/utils/sessions.be";
|
||||||
|
import {averageScore} from "@/utils/stats";
|
||||||
|
import {getStatsByUser} from "@/utils/stats.be";
|
||||||
|
import axios from "axios";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import {withIronSessionSsr} from "iron-session/next";
|
||||||
|
import {capitalize, uniqBy} from "lodash";
|
||||||
|
import moment from "moment";
|
||||||
|
import Head from "next/head";
|
||||||
|
import {useRouter} from "next/router";
|
||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import {BsArrowRepeat, BsBook, BsClipboard, BsFileEarmarkText, BsHeadphones, BsMegaphone, BsPen, BsPencil, BsStar} 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) =>
|
||||||
|
a.exams.filter((e) => e.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);
|
||||||
|
|
||||||
|
export default function OfficialExam({user, entities, assignments, sessions, exams}: Props) {
|
||||||
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const state = useExamStore((state) => state);
|
||||||
|
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)) {
|
||||||
|
state.setUserSolutions([]);
|
||||||
|
state.setShowSolutions(false);
|
||||||
|
state.setExams(assignmentExams.sort(sortByModule));
|
||||||
|
state.setSelectedModules(mapBy(assignmentExams.sort(sortByModule), 'module'));
|
||||||
|
state.setAssignment(assignment);
|
||||||
|
|
||||||
|
router.push("/exam");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadSession = async (session: 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.push("/exam");
|
||||||
|
};
|
||||||
|
|
||||||
|
const logout = async () => {
|
||||||
|
axios.post("/api/logout").finally(() => {
|
||||||
|
setTimeout(() => router.reload(), 500);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const studentAssignments = useMemo(() => assignments.filter(activeAssignmentFilter), [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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,17 +3,15 @@ import Head from "next/head";
|
|||||||
import {withIronSessionSsr} from "iron-session/next";
|
import {withIronSessionSsr} from "iron-session/next";
|
||||||
import {sessionOptions} from "@/lib/session";
|
import {sessionOptions} from "@/lib/session";
|
||||||
import {Stat, User} from "@/interfaces/user";
|
import {Stat, User} from "@/interfaces/user";
|
||||||
import {useEffect, useMemo, useRef, useState} from "react";
|
import {useEffect, useMemo, useState} from "react";
|
||||||
import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser";
|
import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser";
|
||||||
import {groupByDate} from "@/utils/stats";
|
import {groupByDate} from "@/utils/stats";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import useUsers from "@/hooks/useUsers";
|
|
||||||
import useExamStore from "@/stores/examStore";
|
import useExamStore from "@/stores/examStore";
|
||||||
import {ToastContainer} from "react-toastify";
|
import {ToastContainer} from "react-toastify";
|
||||||
import Layout from "@/components/High/Layout";
|
import Layout from "@/components/High/Layout";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
import {shouldRedirectHome} from "@/utils/navigation.disabled";
|
import {shouldRedirectHome} from "@/utils/navigation.disabled";
|
||||||
import useAssignments from "@/hooks/useAssignments";
|
|
||||||
import {uuidv4} from "@firebase/util";
|
import {uuidv4} from "@firebase/util";
|
||||||
import {usePDFDownload} from "@/hooks/usePDFDownload";
|
import {usePDFDownload} from "@/hooks/usePDFDownload";
|
||||||
import useRecordStore from "@/stores/recordStore";
|
import useRecordStore from "@/stores/recordStore";
|
||||||
@@ -23,7 +21,7 @@ import {useRouter} from "next/router";
|
|||||||
import useTrainingContentStore from "@/stores/trainingContentStore";
|
import useTrainingContentStore from "@/stores/trainingContentStore";
|
||||||
import {Assignment} from "@/interfaces/results";
|
import {Assignment} from "@/interfaces/results";
|
||||||
import {getEntitiesUsers, getUsers} from "@/utils/users.be";
|
import {getEntitiesUsers, getUsers} from "@/utils/users.be";
|
||||||
import {getAssignments, getAssignmentsByAssigner, getEntitiesAssignments} from "@/utils/assignments.be";
|
import {getAssignments, getEntitiesAssignments} from "@/utils/assignments.be";
|
||||||
import useGradingSystem from "@/hooks/useGrading";
|
import useGradingSystem from "@/hooks/useGrading";
|
||||||
import { mapBy, redirect, serialize } from "@/utils";
|
import { mapBy, redirect, serialize } from "@/utils";
|
||||||
import { getEntitiesWithRoles } from "@/utils/entities.be";
|
import { getEntitiesWithRoles } from "@/utils/entities.be";
|
||||||
@@ -32,7 +30,6 @@ import { getGroups, getGroupsByEntities } from "@/utils/groups.be";
|
|||||||
import { getGradingSystemByEntity } from "@/utils/grading.be";
|
import { getGradingSystemByEntity } from "@/utils/grading.be";
|
||||||
import { Grading } from "@/interfaces";
|
import { Grading } from "@/interfaces";
|
||||||
import { EntityWithRoles } from "@/interfaces/entity";
|
import { EntityWithRoles } from "@/interfaces/entity";
|
||||||
import { useListSearch } from "@/hooks/useListSearch";
|
|
||||||
import CardList from "@/components/High/CardList";
|
import CardList from "@/components/High/CardList";
|
||||||
import { requestUser } from "@/utils/api";
|
import { requestUser } from "@/utils/api";
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import client from "@/lib/mongodb";
|
|||||||
|
|
||||||
const db = client.db(process.env.MONGODB_DB);
|
const db = client.db(process.env.MONGODB_DB);
|
||||||
|
|
||||||
export const getSessionsByUser = async (id: string, limit?: number) =>
|
export const getSessionsByUser = async (id: string, limit = 0, filter = {}) =>
|
||||||
await db
|
await db
|
||||||
.collection("sessions")
|
.collection("sessions")
|
||||||
.find<Session>({user: id})
|
.find<Session>({user: id, ...filter})
|
||||||
.limit(limit || 0)
|
.limit(limit || 0)
|
||||||
.toArray();
|
.toArray();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user