Merged main into feature/training-content

This commit is contained in:
carlos.mesquita
2024-09-09 00:40:36 +00:00
8 changed files with 297 additions and 198 deletions

View File

@@ -1,14 +1,37 @@
import {Column, flexRender, getCoreRowModel, getSortedRowModel, useReactTable} from "@tanstack/react-table"; import {Column, flexRender, getCoreRowModel, getSortedRowModel, useReactTable} from "@tanstack/react-table";
import {useMemo, useState} from "react";
import Button from "./Low/Button";
const SIZE = 25;
export default function List<T>({data, columns}: {data: T[]; columns: any[]}) { export default function List<T>({data, columns}: {data: T[]; columns: any[]}) {
const [page, setPage] = useState(0);
const items = useMemo(() => data.slice(page * SIZE, (page + 1) * SIZE > data.length ? data.length : (page + 1) * SIZE), [data, page]);
const table = useReactTable({ const table = useReactTable({
data, data: items,
columns: columns, columns: columns,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getSortedRowModel: getSortedRowModel(), getSortedRowModel: getSortedRowModel(),
}); });
return ( return (
<div className="w-full h-full flex flex-col gap-2">
<div className="w-full flex gap-2 justify-between">
<Button className="w-full max-w-[200px]" disabled={page === 0} onClick={() => setPage((prev) => prev - 1)}>
Previous Page
</Button>
<div className="flex items-center gap-4 w-fit">
<span className="opacity-80">
{page * SIZE + 1} - {(page + 1) * SIZE > data.length ? data.length : (page + 1) * SIZE} / {data.length}
</span>
<Button className="w-[200px]" disabled={(page + 1) * SIZE >= data.length} onClick={() => setPage((prev) => prev + 1)}>
Next Page
</Button>
</div>
</div>
<table className="rounded-xl bg-mti-purple-ultralight/40 w-full"> <table className="rounded-xl bg-mti-purple-ultralight/40 w-full">
<thead> <thead>
{table.getHeaderGroups().map((headerGroup) => ( {table.getHeaderGroups().map((headerGroup) => (
@@ -19,7 +42,9 @@ export default function List<T>({data, columns}: {data: T[]; columns: any[]}) {
<> <>
<div <div
{...{ {...{
className: header.column.getCanSort() ? "cursor-pointer select-none py-4 text-left first:pl-4" : "", className: header.column.getCanSort()
? "cursor-pointer select-none py-4 text-left first:pl-4"
: "",
onClick: header.column.getToggleSortingHandler(), onClick: header.column.getToggleSortingHandler(),
}}> }}>
{flexRender(header.column.columnDef.header, header.getContext())} {flexRender(header.column.columnDef.header, header.getContext())}
@@ -47,5 +72,6 @@ export default function List<T>({data, columns}: {data: T[]; columns: any[]}) {
))} ))}
</tbody> </tbody>
</table> </table>
</div>
); );
} }

View File

@@ -21,6 +21,7 @@ import Checkbox from "@/components/Low/Checkbox";
import {InstructorGender, Variant} from "@/interfaces/exam"; import {InstructorGender, Variant} from "@/interfaces/exam";
import Select from "@/components/Low/Select"; import Select from "@/components/Low/Select";
import useExams from "@/hooks/useExams"; import useExams from "@/hooks/useExams";
import {useListSearch} from "@/hooks/useListSearch";
interface Props { interface Props {
isCreating: boolean; isCreating: boolean;
@@ -31,7 +32,12 @@ interface Props {
cancelCreation: () => void; cancelCreation: () => void;
} }
const SIZE = 12;
export default function AssignmentCreator({isCreating, assignment, user, groups, users, cancelCreation}: Props) { export default function AssignmentCreator({isCreating, assignment, user, groups, users, cancelCreation}: Props) {
const [studentsPage, setStudentsPage] = useState(0);
const [teachersPage, setTeachersPage] = useState(0);
const [selectedModules, setSelectedModules] = useState<Module[]>(assignment?.exams.map((e) => e.module) || []); const [selectedModules, setSelectedModules] = useState<Module[]>(assignment?.exams.map((e) => e.module) || []);
const [assignees, setAssignees] = useState<string[]>(assignment?.assignees || []); const [assignees, setAssignees] = useState<string[]>(assignment?.assignees || []);
const [teachers, setTeachers] = useState<string[]>(!!assignment ? assignment.teachers || [] : [...(user.type === "teacher" ? [user.id] : [])]); const [teachers, setTeachers] = useState<string[]>(!!assignment ? assignment.teachers || [] : [...(user.type === "teacher" ? [user.id] : [])]);
@@ -69,6 +75,29 @@ export default function AssignmentCreator({isCreating, assignment, user, groups,
const userStudents = useMemo(() => users.filter((x) => x.type === "student"), [users]); const userStudents = useMemo(() => users.filter((x) => x.type === "student"), [users]);
const userTeachers = useMemo(() => users.filter((x) => x.type === "teacher"), [users]); const userTeachers = useMemo(() => users.filter((x) => x.type === "teacher"), [users]);
const {rows: filteredStudentsRows, renderSearch: renderStudentSearch, text: studentText} = useListSearch([["name"], ["email"]], userStudents);
const {rows: filteredTeachersRows, renderSearch: renderTeacherSearch, text: teacherText} = useListSearch([["name"], ["email"]], userTeachers);
useEffect(() => setStudentsPage(0), [studentText]);
const studentRows = useMemo(
() =>
filteredStudentsRows.slice(
studentsPage * SIZE,
(studentsPage + 1) * SIZE > filteredStudentsRows.length ? filteredStudentsRows.length : (studentsPage + 1) * SIZE,
),
[filteredStudentsRows, studentsPage],
);
useEffect(() => setTeachersPage(0), [teacherText]);
const teacherRows = useMemo(
() =>
filteredTeachersRows.slice(
teachersPage * SIZE,
(teachersPage + 1) * SIZE > filteredTeachersRows.length ? filteredTeachersRows.length : (teachersPage + 1) * SIZE,
),
[filteredTeachersRows, teachersPage],
);
useEffect(() => { useEffect(() => {
setExamIDs((prev) => prev.filter((x) => selectedModules.includes(x.module))); setExamIDs((prev) => prev.filter((x) => selectedModules.includes(x.module)));
}, [selectedModules]); }, [selectedModules]);
@@ -347,9 +376,9 @@ export default function AssignmentCreator({isCreating, assignment, user, groups,
</div> </div>
)} )}
<section className="w-full flex flex-col gap-3"> <section className="w-full flex flex-col gap-4">
<span className="font-semibold">Assignees ({assignees.length} selected)</span> <span className="font-semibold">Assignees ({assignees.length} selected)</span>
<div className="flex gap-4 overflow-x-scroll scrollbar-hide"> <div className="grid grid-cols-5 gap-4">
{groups.map((g) => ( {groups.map((g) => (
<button <button
key={g.id} key={g.id}
@@ -371,8 +400,11 @@ export default function AssignmentCreator({isCreating, assignment, user, groups,
</button> </button>
))} ))}
</div> </div>
{renderStudentSearch()}
<div className="flex flex-wrap -md:justify-center gap-4"> <div className="flex flex-wrap -md:justify-center gap-4">
{userStudents.map((user) => ( {studentRows.map((user) => (
<div <div
onClick={() => toggleAssignee(user)} onClick={() => toggleAssignee(user)}
className={clsx( className={clsx(
@@ -402,12 +434,32 @@ export default function AssignmentCreator({isCreating, assignment, user, groups,
</div> </div>
))} ))}
</div> </div>
<div className="w-full flex gap-2 justify-between items-center">
<div className="flex items-center gap-4 w-fit">
<Button className="w-[200px] h-fit" disabled={studentsPage === 0} onClick={() => setStudentsPage((prev) => prev - 1)}>
Previous Page
</Button>
</div>
<div className="flex items-center gap-4 w-fit">
<span className="opacity-80">
{studentsPage * SIZE + 1} -{" "}
{(studentsPage + 1) * SIZE > filteredStudentsRows.length ? filteredStudentsRows.length : (studentsPage + 1) * SIZE} /{" "}
{filteredStudentsRows.length}
</span>
<Button
className="w-[200px]"
disabled={(studentsPage + 1) * SIZE >= filteredStudentsRows.length}
onClick={() => setStudentsPage((prev) => prev + 1)}>
Next Page
</Button>
</div>
</div>
</section> </section>
{user.type !== "teacher" && ( {user.type !== "teacher" && (
<section className="w-full flex flex-col gap-3"> <section className="w-full flex flex-col gap-3">
<span className="font-semibold">Teachers ({teachers.length} selected)</span> <span className="font-semibold">Teachers ({teachers.length} selected)</span>
<div className="flex gap-4 overflow-x-scroll scrollbar-hide"> <div className="grid grid-cols-5 gap-4">
{groups.map((g) => ( {groups.map((g) => (
<button <button
key={g.id} key={g.id}
@@ -429,8 +481,11 @@ export default function AssignmentCreator({isCreating, assignment, user, groups,
</button> </button>
))} ))}
</div> </div>
{renderTeacherSearch()}
<div className="flex flex-wrap -md:justify-center gap-4"> <div className="flex flex-wrap -md:justify-center gap-4">
{userTeachers.map((user) => ( {teacherRows.map((user) => (
<div <div
onClick={() => toggleTeacher(user)} onClick={() => toggleTeacher(user)}
className={clsx( className={clsx(
@@ -453,6 +508,29 @@ export default function AssignmentCreator({isCreating, assignment, user, groups,
</div> </div>
))} ))}
</div> </div>
<div className="w-full flex gap-2 justify-between items-center">
<div className="flex items-center gap-4 w-fit">
<Button className="w-[200px] h-fit" disabled={teachersPage === 0} onClick={() => setTeachersPage((prev) => prev - 1)}>
Previous Page
</Button>
</div>
<div className="flex items-center gap-4 w-fit">
<span className="opacity-80">
{teachersPage * SIZE + 1} -{" "}
{(teachersPage + 1) * SIZE > filteredTeachersRows.length
? filteredTeachersRows.length
: (teachersPage + 1) * SIZE}{" "}
/ {filteredTeachersRows.length}
</span>
<Button
className="w-[200px]"
disabled={(teachersPage + 1) * SIZE >= filteredTeachersRows.length}
onClick={() => setTeachersPage((prev) => prev + 1)}>
Next Page
</Button>
</div>
</div>
</section> </section>
)} )}

View File

@@ -1,4 +1,4 @@
import React from "react"; import React, {useEffect, useMemo, useState} from "react";
import {CorporateUser, User} from "@/interfaces/user"; import {CorporateUser, User} from "@/interfaces/user";
import {BsFileExcel, BsBank, BsPersonFill} from "react-icons/bs"; import {BsFileExcel, BsBank, BsPersonFill} from "react-icons/bs";
import IconCard from "../IconCard"; import IconCard from "../IconCard";
@@ -14,6 +14,7 @@ import {useListSearch} from "@/hooks/useListSearch";
import axios from "axios"; import axios from "axios";
import {toast} from "react-toastify"; import {toast} from "react-toastify";
import Button from "@/components/Low/Button"; import Button from "@/components/Low/Button";
import {getUserName} from "@/utils/users";
interface GroupedCorporateUsers { interface GroupedCorporateUsers {
// list of user Ids // list of user Ids
@@ -42,10 +43,13 @@ interface UserCount {
maxUserCount: number; maxUserCount: number;
} }
const searchFilters = [["email"], ["user"], ["userId"]]; const searchFilters = [["email"], ["user"], ["userId"], ["exams"], ["assignment"]];
const SIZE = 16;
const MasterStatistical = (props: Props) => { const MasterStatistical = (props: Props) => {
const {users, corporateUsers, displaySelection = true} = props; const {users, corporateUsers, displaySelection = true} = props;
const [page, setPage] = useState(0);
// const corporateRelevantUsers = React.useMemo( // const corporateRelevantUsers = React.useMemo(
// () => corporateUsers.filter((x) => x.type !== "student") as CorporateUser[], // () => corporateUsers.filter((x) => x.type !== "student") as CorporateUser[],
@@ -68,17 +72,17 @@ const MasterStatistical = (props: Props) => {
const tableResults = React.useMemo( const tableResults = React.useMemo(
() => () =>
assignments assignments.reduce((accmA: TableData[], a: AssignmentWithCorporateId) => {
.reduce((accmA: TableData[], a: AssignmentWithCorporateId) => {
const userResults = a.assignees.map((assignee) => { const userResults = a.assignees.map((assignee) => {
const userStats = a.results.find((r) => r.user === assignee)?.stats || []; const userStats = a.results.find((r) => r.user === assignee)?.stats || [];
const userData = users.find((u) => u.id === assignee); const userData = users.find((u) => u.id === assignee);
const corporate = users.find((u) => u.id === a.assigner)?.name || ""; const corporate = getUserName(users.find((u) => u.id === a.assigner));
const commonData = { const commonData = {
user: userData?.name || "N/A", user: userData?.name || "N/A",
email: userData?.email || "N/A", email: userData?.email || "N/A",
userId: assignee, userId: assignee,
corporateId: a.corporateId, corporateId: a.corporateId,
exams: a.exams.map((x) => x.id).join(", "),
corporate, corporate,
assignment: a.name, assignment: a.name,
}; };
@@ -87,7 +91,7 @@ const MasterStatistical = (props: Props) => {
...commonData, ...commonData,
correct: 0, correct: 0,
submitted: false, submitted: false,
// date: moment(), date: null,
}; };
} }
@@ -100,11 +104,12 @@ const MasterStatistical = (props: Props) => {
}) as TableData[]; }) as TableData[];
return [...accmA, ...userResults]; return [...accmA, ...userResults];
}, []) }, []),
.filter((x) => x.user !== "N/A"),
[assignments, users], [assignments, users],
); );
useEffect(() => console.log(assignments), [assignments]);
const getCorporateScores = (corporateId: string): UserCount => { const getCorporateScores = (corporateId: string): UserCount => {
const corporateAssignmentsUsers = assignments.filter((a) => a.corporateId === corporateId).reduce((acc, a) => acc + a.assignees.length, 0); const corporateAssignmentsUsers = assignments.filter((a) => a.corporateId === corporateId).reduce((acc, a) => acc + a.assignees.length, 0);
@@ -167,13 +172,6 @@ const MasterStatistical = (props: Props) => {
}), }),
] ]
: []), : []),
columnHelper.accessor("corporate", {
header: "Corporate",
id: "corporate",
cell: (info) => {
return <span>{info.getValue()}</span>;
},
}),
columnHelper.accessor("assignment", { columnHelper.accessor("assignment", {
header: "Assignment", header: "Assignment",
id: "assignment", id: "assignment",
@@ -193,7 +191,7 @@ const MasterStatistical = (props: Props) => {
}, },
}), }),
columnHelper.accessor("correct", { columnHelper.accessor("correct", {
header: "Correct", header: "Score",
id: "correct", id: "correct",
cell: (info) => { cell: (info) => {
return <span>{info.getValue()}</span>; return <span>{info.getValue()}</span>;
@@ -205,7 +203,7 @@ const MasterStatistical = (props: Props) => {
cell: (info) => { cell: (info) => {
const date = info.getValue(); const date = info.getValue();
if (date) { if (date) {
return <span>{date.format("DD/MM/YYYY")}</span>; return <span>{!!date ? date.format("DD/MM/YYYY") : "N/A"}</span>;
} }
return <span>{""}</span>; return <span>{""}</span>;
@@ -215,8 +213,14 @@ const MasterStatistical = (props: Props) => {
const {rows: filteredRows, renderSearch, text: searchText} = useListSearch(searchFilters, tableResults); const {rows: filteredRows, renderSearch, text: searchText} = useListSearch(searchFilters, tableResults);
useEffect(() => setPage(0), [searchText]);
const rows = useMemo(
() => filteredRows.slice(page * SIZE, (page + 1) * SIZE > filteredRows.length ? filteredRows.length : (page + 1) * SIZE),
[filteredRows, page],
);
const table = useReactTable({ const table = useReactTable({
data: filteredRows, data: rows,
columns: defaultColumns, columns: defaultColumns,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
}); });
@@ -345,7 +349,22 @@ const MasterStatistical = (props: Props) => {
</div> </div>
</div> </div>
<div> <div className="w-full h-full flex flex-col gap-4">
<div className="w-full flex gap-2 justify-between">
<Button className="w-full max-w-[200px]" disabled={page === 0} onClick={() => setPage((prev) => prev - 1)}>
Previous Page
</Button>
<div className="flex items-center gap-4 w-fit">
<span className="opacity-80">
{page * SIZE + 1} - {(page + 1) * SIZE > filteredRows.length ? filteredRows.length : (page + 1) * SIZE} /{" "}
{filteredRows.length}
</span>
<Button className="w-[200px]" disabled={(page + 1) * SIZE >= rows.length} onClick={() => setPage((prev) => prev + 1)}>
Next Page
</Button>
</div>
</div>
<table className="rounded-xl h-full bg-mti-purple-ultralight/40 w-full"> <table className="rounded-xl h-full bg-mti-purple-ultralight/40 w-full">
<thead> <thead>
{table.getHeaderGroups().map((headerGroup) => ( {table.getHeaderGroups().map((headerGroup) => (

View File

@@ -83,18 +83,6 @@ export default function MasterCorporateDashboard({user}: Props) {
const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({corporate: user.id}); const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({corporate: user.id});
const assignmentsGroups = useMemo(() => groups.filter((x) => x.admin === user.id || x.participants.includes(user.id)), [groups, user.id]); const assignmentsGroups = useMemo(() => groups.filter((x) => x.admin === user.id || x.participants.includes(user.id)), [groups, user.id]);
const assignmentsUsers = useMemo(
() =>
[...students, ...teachers].filter((x) =>
!!selectedUser
? groups
.filter((g) => g.admin === selectedUser.id)
.flatMap((g) => g.participants)
.includes(x.id) || false
: groups.flatMap((g) => g.participants).includes(x.id),
),
[groups, selectedUser, teachers, students],
);
const appendUserFilters = useFilterStore((state) => state.appendUserFilter); const appendUserFilters = useFilterStore((state) => state.appendUserFilter);
const router = useRouter(); const router = useRouter();
@@ -131,7 +119,6 @@ export default function MasterCorporateDashboard({user}: Props) {
const {users} = useUsers(); const {users} = useUsers();
const groupedByNameCorporates = useMemo( const groupedByNameCorporates = useMemo(
() => () =>
groupBy( groupBy(
@@ -374,12 +361,18 @@ export default function MasterCorporateDashboard({user}: Props) {
color="purple" color="purple"
onClick={() => router.push("/#corporate")} onClick={() => router.push("/#corporate")}
/> />
<IconCard Icon={BsBank} label="Corporate" value={groupedByNameCorporatesKeys.length} isLoading={isCorporatesLoading} color="purple" /> <IconCard
Icon={BsBank}
label="Corporate"
value={groupedByNameCorporatesKeys.length}
isLoading={isCorporatesLoading}
color="purple"
/>
<IconCard <IconCard
Icon={BsPersonFillGear} Icon={BsPersonFillGear}
isLoading={isStudentsLoading} isLoading={isStudentsLoading}
label="Student Performance" label="Student Performance"
value={students.length} value={totalStudents}
color="purple" color="purple"
onClick={() => router.push("/#studentsPerformance")} onClick={() => router.push("/#studentsPerformance")}
/> />

View File

@@ -632,12 +632,12 @@ export default function UserList({
</Button> </Button>
<div className="flex items-center gap-4 w-fit"> <div className="flex items-center gap-4 w-fit">
<span className="opacity-80"> <span className="opacity-80">
{page * 16 + 1} - {(page + 1) * 16 > total ? total : (page + 1) * 16} / {total} {page * userHash.size + 1} - {(page + 1) * userHash.size > total ? total : (page + 1) * userHash.size} / {total}
</span> </span>
<Button <Button
isLoading={isLoading} isLoading={isLoading}
className="w-[200px]" className="w-[200px]"
disabled={page * 16 >= total} disabled={(page + 1) * userHash.size >= total}
onClick={() => setPage((prev) => prev + 1)}> onClick={() => setPage((prev) => prev + 1)}>
Next Page Next Page
</Button> </Button>

View File

@@ -1,10 +1,10 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from "next"; import type {NextApiRequest, NextApiResponse} from "next";
import client from "@/lib/mongodb"; import client from "@/lib/mongodb";
import { withIronSessionApiRoute } from "iron-session/next"; import {withIronSessionApiRoute} from "iron-session/next";
import { sessionOptions } from "@/lib/session"; import {sessionOptions} from "@/lib/session";
import { Group } from "@/interfaces/user"; import {Group} from "@/interfaces/user";
import { updateExpiryDateOnGroup } from "@/utils/groups.be"; import {updateExpiryDateOnGroup} from "@/utils/groups.be";
const db = client.db(process.env.MONGODB_DB); const db = client.db(process.env.MONGODB_DB);
@@ -20,16 +20,16 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
async function get(req: NextApiRequest, res: NextApiResponse) { async function get(req: NextApiRequest, res: NextApiResponse) {
if (!req.session.user) { if (!req.session.user) {
res.status(401).json({ ok: false }); res.status(401).json({ok: false});
return; return;
} }
const { id } = req.query as { id: string }; const {id} = req.query as {id: string};
const snapshot = await db.collection("groups").findOne({ id: id}); const snapshot = await db.collection("groups").findOne({id: id});
if (snapshot) { if (snapshot) {
res.status(200).json({ ...snapshot }); res.status(200).json({...snapshot});
} else { } else {
res.status(404).json(undefined); res.status(404).json(undefined);
} }
@@ -37,11 +37,11 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
async function del(req: NextApiRequest, res: NextApiResponse) { async function del(req: NextApiRequest, res: NextApiResponse) {
if (!req.session.user) { if (!req.session.user) {
res.status(401).json({ ok: false }); res.status(401).json({ok: false});
return; return;
} }
const { id } = req.query as { id: string }; const {id} = req.query as {id: string};
const group = await db.collection("groups").findOne<Group>({id: id}); const group = await db.collection("groups").findOne<Group>({id: id});
if (!group) { if (!group) {
@@ -50,27 +50,23 @@ async function del(req: NextApiRequest, res: NextApiResponse) {
} }
const user = req.session.user; const user = req.session.user;
if ( if (user.type === "admin" || user.type === "developer" || user.id === group.admin) {
user.type === "admin" || await db.collection("groups").deleteOne({id: id});
user.type === "developer" ||
user.id === group.admin
) {
await db.collection("groups").deleteOne({ id: id });
res.status(200).json({ ok: true }); res.status(200).json({ok: true});
return; return;
} }
res.status(403).json({ ok: false }); res.status(403).json({ok: false});
} }
async function patch(req: NextApiRequest, res: NextApiResponse) { async function patch(req: NextApiRequest, res: NextApiResponse) {
if (!req.session.user) { if (!req.session.user) {
res.status(401).json({ ok: false }); res.status(401).json({ok: false});
return; return;
} }
const { id } = req.query as { id: string }; const {id} = req.query as {id: string};
const group = await db.collection("groups").findOne<Group>({id: id}); const group = await db.collection("groups").findOne<Group>({id: id});
if (!group) { if (!group) {
@@ -79,31 +75,17 @@ async function patch(req: NextApiRequest, res: NextApiResponse) {
} }
const user = req.session.user; const user = req.session.user;
if ( if (user.type === "admin" || user.type === "developer" || user.id === group.admin) {
user.type === "admin" ||
user.type === "developer" ||
user.id === group.admin
) {
if ("participants" in req.body) { if ("participants" in req.body) {
const newParticipants = (req.body.participants as string[]).filter( const newParticipants = (req.body.participants as string[]).filter((x) => !group.participants.includes(x));
(x) => !group.participants.includes(x), await Promise.all(newParticipants.map(async (p) => await updateExpiryDateOnGroup(p, group.admin)));
);
await Promise.all(
newParticipants.map(
async (p) => await updateExpiryDateOnGroup(p, group.admin),
),
);
} }
await db.collection("grading").updateOne( await db.collection("groups").updateOne({id: req.session.user.id}, {$set: {id, ...req.body}}, {upsert: true});
{ id: req.session.user.id },
{ $set: {id: id, ...req.body} },
{ upsert: true }
);
res.status(200).json({ ok: true }); res.status(200).json({ok: true});
return; return;
} }
res.status(403).json({ ok: false }); res.status(403).json({ok: false});
} }

View File

@@ -1,18 +1,18 @@
import client from "@/lib/mongodb"; import client from "@/lib/mongodb";
import { Assignment } from "@/interfaces/results"; import {Assignment} from "@/interfaces/results";
import { getAllAssignersByCorporate } from "@/utils/groups.be"; import {getAllAssignersByCorporate} from "@/utils/groups.be";
const db = client.db(process.env.MONGODB_DB); const db = client.db(process.env.MONGODB_DB);
export const getAssignmentsByAssigner = async (id: string, startDate?: Date, endDate?: Date) => { export const getAssignmentsByAssigner = async (id: string, startDate?: Date, endDate?: Date) => {
let query: any = { assigner: id }; let query: any = {assigner: id};
if (startDate) { if (startDate) {
query.startDate = { $gte: startDate.toISOString() }; query.startDate = {$gte: startDate.toISOString()};
} }
if (endDate) { if (endDate) {
query.endDate = { $lte: endDate.toISOString() }; query.endDate = {$lte: endDate.toISOString()};
} }
return await db.collection("assignments").find<Assignment>(query).toArray(); return await db.collection("assignments").find<Assignment>(query).toArray();
@@ -26,7 +26,14 @@ export const getAssignmentsByAssignerBetweenDates = async (id: string, startDate
}; };
export const getAssignmentsByAssigners = async (ids: string[], startDate?: Date, endDate?: Date) => { export const getAssignmentsByAssigners = async (ids: string[], startDate?: Date, endDate?: Date) => {
return (await Promise.all(ids.map((id) => getAssignmentsByAssigner(id, startDate, endDate)))).flat(); return await db
.collection("assignments")
.find<Assignment>({
assigner: {$in: ids},
...(!!startDate ? {startDate: {$gte: startDate.toISOString()}} : {}),
...(!!endDate ? {endDate: {$lte: endDate.toISOString()}} : {}),
})
.toArray();
}; };
export const getAssignmentsForCorporates = async (idsList: string[], startDate?: Date, endDate?: Date) => { export const getAssignmentsForCorporates = async (idsList: string[], startDate?: Date, endDate?: Date) => {
@@ -57,4 +64,4 @@ export const getAssignmentsForCorporates = async (idsList: string[], startDate?:
); );
return assignments.flat(); return assignments.flat();
} };

View File

@@ -1,8 +1,9 @@
import {app} from "@/firebase"; import {app} from "@/firebase";
import {Assignment} from "@/interfaces/results";
import {CorporateUser, Group, MasterCorporateUser, StudentUser, TeacherUser, User} from "@/interfaces/user"; import {CorporateUser, Group, MasterCorporateUser, StudentUser, TeacherUser, User} from "@/interfaces/user";
import client from "@/lib/mongodb"; import client from "@/lib/mongodb";
import moment from "moment"; import moment from "moment";
import {getUser} from "./users.be"; import {getLinkedUsers, getUser} from "./users.be";
import {getSpecificUsers} from "./users.be"; import {getSpecificUsers} from "./users.be";
const db = client.db(process.env.MONGODB_DB); const db = client.db(process.env.MONGODB_DB);
@@ -71,17 +72,10 @@ export const getUsersGroups = async (ids: string[]) => {
}; };
export const getAllAssignersByCorporate = async (corporateID: string): Promise<string[]> => { export const getAllAssignersByCorporate = async (corporateID: string): Promise<string[]> => {
const groups = await getUserGroups(corporateID); const linkedTeachers = await getLinkedUsers(corporateID, "mastercorporate", "teacher");
const groupUsers = (await Promise.all(groups.map(async (g) => await Promise.all(g.participants.map(getUser))))) const linkedCorporates = await getLinkedUsers(corporateID, "mastercorporate", "corporate");
.flat()
.filter((x) => !!x) as User[];
const teacherPromises = await Promise.all(
groupUsers.map(async (u) =>
u.type === "teacher" ? u.id : u.type === "corporate" ? [...(await getAllAssignersByCorporate(u.id)), u.id] : undefined,
),
);
return teacherPromises.filter((x) => !!x).flat() as string[]; return [...linkedTeachers.users.map((x) => x.id), ...linkedCorporates.users.map((x) => x.id)];
}; };
export const getGroupsForUser = async (admin?: string, participant?: string) => { export const getGroupsForUser = async (admin?: string, participant?: string) => {