From fbc7abdabbf262e36043eac0c6b659d20f6caed2 Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Sun, 8 Sep 2024 22:35:14 +0100 Subject: [PATCH 01/12] Solved a bug --- src/dashboards/Admin.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dashboards/Admin.tsx b/src/dashboards/Admin.tsx index f2a7af67..edb7d165 100644 --- a/src/dashboards/Admin.tsx +++ b/src/dashboards/Admin.tsx @@ -63,7 +63,7 @@ export default function AdminDashboard({user}: Props) { const {users: students, total: totalStudents, reload: reloadStudents, isLoading: isStudentsLoading} = useUsers(studentHash); const {users: teachers, total: totalTeachers, reload: reloadTeachers, isLoading: isTeachersLoading} = useUsers(teacherHash); const {users: corporates, total: totalCorporate, reload: reloadCorporates, isLoading: isCorporatesLoading} = useUsers(corporateHash); - const {users: agents, total: totalAgents, reload: reloadAgents, isLoading: isAgentsLoading} = useUsers(corporateHash); + const {users: agents, total: totalAgents, reload: reloadAgents, isLoading: isAgentsLoading} = useUsers(agentsHash); const {groups} = useGroups({}); const {pending, done} = usePaymentStatusUsers(); From 6f534662e1e76e65f68de57e652442b3ebc36edf Mon Sep 17 00:00:00 2001 From: Carlos Mesquita Date: Sun, 8 Sep 2024 22:45:24 +0100 Subject: [PATCH 02/12] Shuffles bugfix --- src/exams/Selection.tsx | 3 ++- src/pages/(admin)/BatchCreateUser.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/exams/Selection.tsx b/src/exams/Selection.tsx index 4f3f33a4..b6f77c98 100644 --- a/src/exams/Selection.tsx +++ b/src/exams/Selection.tsx @@ -12,7 +12,7 @@ import {calculateAverageLevel} from "@/utils/score"; import {sortByModuleName} from "@/utils/moduleUtils"; import {capitalize} from "lodash"; import ProfileSummary from "@/components/ProfileSummary"; -import {Variant} from "@/interfaces/exam"; +import {ShuffleMap, Shuffles, Variant} from "@/interfaces/exam"; import useSessions, {Session} from "@/hooks/useSessions"; import SessionCard from "@/components/Medium/SessionCard"; import useExamStore from "@/stores/examStore"; @@ -41,6 +41,7 @@ export default function Selection({user, page, onStart, disableSelection = false }; 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); diff --git a/src/pages/(admin)/BatchCreateUser.tsx b/src/pages/(admin)/BatchCreateUser.tsx index 5f31c7c3..53ff716a 100644 --- a/src/pages/(admin)/BatchCreateUser.tsx +++ b/src/pages/(admin)/BatchCreateUser.tsx @@ -161,7 +161,7 @@ export default function BatchCreateUser({user, users, permissions, onFinish}: Pr setIsLoading(true); try { - await axios.post("/api/batch_users", { users: newUsers.map(user => ({...user, type, expiryDate})) }); + await axios.post("/api/batch_users", { users: newUsers.map(user => ({...user, type: "superadmin", expiryDate})) }); toast.success(`Successfully added ${newUsers.length} user(s)!`); onFinish(); } catch { From eab6ab03b70c59dc6edb5be19dc622acd3e365e7 Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Sun, 8 Sep 2024 22:46:09 +0100 Subject: [PATCH 03/12] Not shown when not completed --- .../MasterCorporate/MasterStatistical.tsx | 59 ++++++++++--------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/src/dashboards/MasterCorporate/MasterStatistical.tsx b/src/dashboards/MasterCorporate/MasterStatistical.tsx index 46291bec..17d0708c 100644 --- a/src/dashboards/MasterCorporate/MasterStatistical.tsx +++ b/src/dashboards/MasterCorporate/MasterStatistical.tsx @@ -59,7 +59,6 @@ const MasterStatistical = (props: Props) => { const [endDate, setEndDate] = React.useState(moment().endOf("year").toDate()); const {assignments} = useAssignmentsCorporates({ - // corporates: [...corporates, "tYU0HTiJdjMsS8SB7XJsUdMMP892"], corporates: selectedCorporates, startDate, endDate, @@ -69,38 +68,40 @@ const MasterStatistical = (props: Props) => { const tableResults = React.useMemo( () => - assignments.reduce((accmA: TableData[], a: AssignmentWithCorporateId) => { - const userResults = a.assignees.map((assignee) => { - const userStats = a.results.find((r) => r.user === assignee)?.stats || []; - const userData = users.find((u) => u.id === assignee); - const corporate = users.find((u) => u.id === a.assigner)?.name || ""; - const commonData = { - user: userData?.name || "", - email: userData?.email || "", - userId: assignee, - corporateId: a.corporateId, - corporate, - assignment: a.name, - }; - if (userStats.length === 0) { + assignments + .reduce((accmA: TableData[], a: AssignmentWithCorporateId) => { + const userResults = a.assignees.map((assignee) => { + const userStats = a.results.find((r) => r.user === assignee)?.stats || []; + const userData = users.find((u) => u.id === assignee); + const corporate = users.find((u) => u.id === a.assigner)?.name || ""; + const commonData = { + user: userData?.name || "N/A", + email: userData?.email || "N/A", + userId: assignee, + corporateId: a.corporateId, + corporate, + assignment: a.name, + }; + if (userStats.length === 0) { + return { + ...commonData, + correct: 0, + submitted: false, + // date: moment(), + }; + } + return { ...commonData, - correct: 0, - submitted: false, - // date: moment(), + correct: userStats.reduce((n, e) => n + e.score.correct, 0), + submitted: true, + date: moment.max(userStats.map((e) => moment(e.date))), }; - } + }) as TableData[]; - return { - ...commonData, - correct: userStats.reduce((n, e) => n + e.score.correct, 0), - submitted: true, - date: moment.max(userStats.map((e) => moment(e.date))), - }; - }) as TableData[]; - - return [...accmA, ...userResults]; - }, []), + return [...accmA, ...userResults]; + }, []) + .filter((x) => x.user !== "N/A"), [assignments, users], ); From 02564c8426a9f9d8d1b9b64dba7f5b3212ae6e76 Mon Sep 17 00:00:00 2001 From: Carlos Mesquita Date: Sun, 8 Sep 2024 22:47:43 +0100 Subject: [PATCH 04/12] Had the type hardcoded --- src/pages/(admin)/BatchCreateUser.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/(admin)/BatchCreateUser.tsx b/src/pages/(admin)/BatchCreateUser.tsx index 53ff716a..5f31c7c3 100644 --- a/src/pages/(admin)/BatchCreateUser.tsx +++ b/src/pages/(admin)/BatchCreateUser.tsx @@ -161,7 +161,7 @@ export default function BatchCreateUser({user, users, permissions, onFinish}: Pr setIsLoading(true); try { - await axios.post("/api/batch_users", { users: newUsers.map(user => ({...user, type: "superadmin", expiryDate})) }); + await axios.post("/api/batch_users", { users: newUsers.map(user => ({...user, type, expiryDate})) }); toast.success(`Successfully added ${newUsers.length} user(s)!`); onFinish(); } catch { From 745eef981f99eac2bcc977c110c3832f54515fa1 Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Sun, 8 Sep 2024 23:02:48 +0100 Subject: [PATCH 05/12] Added some more pagination --- src/components/List.tsx | 104 +++++++++++------- .../MasterCorporate/MasterStatistical.tsx | 30 ++++- src/dashboards/MasterCorporate/index.tsx | 11 +- src/pages/(admin)/Lists/UserList.tsx | 4 +- 4 files changed, 102 insertions(+), 47 deletions(-) diff --git a/src/components/List.tsx b/src/components/List.tsx index 0e76b663..f62e7172 100644 --- a/src/components/List.tsx +++ b/src/components/List.tsx @@ -1,51 +1,77 @@ 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({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({ - data, + data: items, columns: columns, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), }); return ( - - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - ))} - - ))} - - - {table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - ))} - - ))} - -
- {header.isPlaceholder ? null : ( - <> -
- {flexRender(header.column.columnDef.header, header.getContext())} - {{ - asc: " 🔼", - desc: " 🔽", - }[header.column.getIsSorted() as string] ?? null} -
- - )} -
- {flexRender(cell.column.columnDef.cell, cell.getContext())} -
+
+
+ +
+ + {page * SIZE + 1} - {(page + 1) * SIZE > data.length ? data.length : (page + 1) * SIZE} / {data.length} + + +
+
+ + + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : ( + <> +
+ {flexRender(header.column.columnDef.header, header.getContext())} + {{ + asc: " 🔼", + desc: " 🔽", + }[header.column.getIsSorted() as string] ?? null} +
+ + )} +
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+
); } diff --git a/src/dashboards/MasterCorporate/MasterStatistical.tsx b/src/dashboards/MasterCorporate/MasterStatistical.tsx index 17d0708c..01baeba9 100644 --- a/src/dashboards/MasterCorporate/MasterStatistical.tsx +++ b/src/dashboards/MasterCorporate/MasterStatistical.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, {useEffect, useMemo, useState} from "react"; import {CorporateUser, User} from "@/interfaces/user"; import {BsFileExcel, BsBank, BsPersonFill} from "react-icons/bs"; import IconCard from "../IconCard"; @@ -44,8 +44,11 @@ interface UserCount { const searchFilters = [["email"], ["user"], ["userId"]]; +const SIZE = 16; + const MasterStatistical = (props: Props) => { const {users, corporateUsers, displaySelection = true} = props; + const [page, setPage] = useState(0); // const corporateRelevantUsers = React.useMemo( // () => corporateUsers.filter((x) => x.type !== "student") as CorporateUser[], @@ -215,8 +218,14 @@ const MasterStatistical = (props: Props) => { 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({ - data: filteredRows, + data: rows, columns: defaultColumns, getCoreRowModel: getCoreRowModel(), }); @@ -345,7 +354,22 @@ const MasterStatistical = (props: Props) => { -
+
+
+ +
+ + {page * SIZE + 1} - {(page + 1) * SIZE > filteredRows.length ? filteredRows.length : (page + 1) * SIZE} /{" "} + {filteredRows.length} + + +
+
+ {table.getHeaderGroups().map((headerGroup) => ( diff --git a/src/dashboards/MasterCorporate/index.tsx b/src/dashboards/MasterCorporate/index.tsx index 584eaf28..49d7236f 100644 --- a/src/dashboards/MasterCorporate/index.tsx +++ b/src/dashboards/MasterCorporate/index.tsx @@ -131,7 +131,6 @@ export default function MasterCorporateDashboard({user}: Props) { const {users} = useUsers(); - const groupedByNameCorporates = useMemo( () => groupBy( @@ -374,12 +373,18 @@ export default function MasterCorporateDashboard({user}: Props) { color="purple" onClick={() => router.push("/#corporate")} /> - + router.push("/#studentsPerformance")} /> diff --git a/src/pages/(admin)/Lists/UserList.tsx b/src/pages/(admin)/Lists/UserList.tsx index 336a08cb..335e57ba 100644 --- a/src/pages/(admin)/Lists/UserList.tsx +++ b/src/pages/(admin)/Lists/UserList.tsx @@ -642,12 +642,12 @@ export default function UserList({
- {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} From 6e0c4d43613e7a781936030c5b347c5e9da203f7 Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Sun, 8 Sep 2024 23:07:47 +0100 Subject: [PATCH 06/12] Added search per exam --- src/dashboards/MasterCorporate/MasterStatistical.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/dashboards/MasterCorporate/MasterStatistical.tsx b/src/dashboards/MasterCorporate/MasterStatistical.tsx index 01baeba9..fb4c559b 100644 --- a/src/dashboards/MasterCorporate/MasterStatistical.tsx +++ b/src/dashboards/MasterCorporate/MasterStatistical.tsx @@ -42,7 +42,7 @@ interface UserCount { maxUserCount: number; } -const searchFilters = [["email"], ["user"], ["userId"]]; +const searchFilters = [["email"], ["user"], ["userId"], ["exams"], ["assignment"]]; const SIZE = 16; @@ -82,6 +82,7 @@ const MasterStatistical = (props: Props) => { email: userData?.email || "N/A", userId: assignee, corporateId: a.corporateId, + exams: a.exams.map((x) => x.id).join(", "), corporate, assignment: a.name, }; @@ -90,7 +91,7 @@ const MasterStatistical = (props: Props) => { ...commonData, correct: 0, submitted: false, - // date: moment(), + date: null, }; } @@ -208,7 +209,7 @@ const MasterStatistical = (props: Props) => { cell: (info) => { const date = info.getValue(); if (date) { - return {date.format("DD/MM/YYYY")}; + return {!!date ? date.format("DD/MM/YYYY") : "N/A"}; } return {""}; From 9f0ba418e58c630eaf6d80e640bfc29a430332dd Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Sun, 8 Sep 2024 23:24:27 +0100 Subject: [PATCH 07/12] Added filtering and pagination for the assignment creator --- src/dashboards/AssignmentCreator.tsx | 88 ++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 5 deletions(-) diff --git a/src/dashboards/AssignmentCreator.tsx b/src/dashboards/AssignmentCreator.tsx index fa677f5b..c3bdd792 100644 --- a/src/dashboards/AssignmentCreator.tsx +++ b/src/dashboards/AssignmentCreator.tsx @@ -21,6 +21,7 @@ import Checkbox from "@/components/Low/Checkbox"; import {InstructorGender, Variant} from "@/interfaces/exam"; import Select from "@/components/Low/Select"; import useExams from "@/hooks/useExams"; +import {useListSearch} from "@/hooks/useListSearch"; interface Props { isCreating: boolean; @@ -31,7 +32,12 @@ interface Props { cancelCreation: () => void; } +const SIZE = 12; + 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(assignment?.exams.map((e) => e.module) || []); const [assignees, setAssignees] = useState(assignment?.assignees || []); const [teachers, setTeachers] = useState(!!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 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(() => { setExamIDs((prev) => prev.filter((x) => selectedModules.includes(x.module))); }, [selectedModules]); @@ -347,9 +376,9 @@ export default function AssignmentCreator({isCreating, assignment, user, groups,
)} -
+
Assignees ({assignees.length} selected) -
+
{groups.map((g) => (
+ + {renderStudentSearch()} +
- {userStudents.map((user) => ( + {studentRows.map((user) => (
toggleAssignee(user)} className={clsx( @@ -402,12 +434,32 @@ export default function AssignmentCreator({isCreating, assignment, user, groups,
))}
+
+
+ +
+
+ + {studentsPage * SIZE + 1} -{" "} + {(studentsPage + 1) * SIZE > filteredStudentsRows.length ? filteredStudentsRows.length : (studentsPage + 1) * SIZE} /{" "} + {filteredStudentsRows.length} + + +
+
{user.type !== "teacher" && (
Teachers ({teachers.length} selected) -
+
{groups.map((g) => (
+ + {renderTeacherSearch()} +
- {userTeachers.map((user) => ( + {teacherRows.map((user) => (
toggleTeacher(user)} className={clsx( @@ -453,6 +508,29 @@ export default function AssignmentCreator({isCreating, assignment, user, groups,
))}
+ +
+
+ +
+
+ + {teachersPage * SIZE + 1} -{" "} + {(teachersPage + 1) * SIZE > filteredTeachersRows.length + ? filteredTeachersRows.length + : (teachersPage + 1) * SIZE}{" "} + / {filteredTeachersRows.length} + + +
+
)} From 1c61d50a5c5ce35c4d4e23f9df4188b1e6f95d59 Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Mon, 9 Sep 2024 00:02:34 +0100 Subject: [PATCH 08/12] Improved some of the querying for the assignments --- .../MasterCorporate/MasterStatistical.tsx | 72 +++++++++---------- src/dashboards/MasterCorporate/index.tsx | 12 ---- src/utils/assignments.be.ts | 21 ++++-- src/utils/groups.be.ts | 16 ++--- 4 files changed, 52 insertions(+), 69 deletions(-) diff --git a/src/dashboards/MasterCorporate/MasterStatistical.tsx b/src/dashboards/MasterCorporate/MasterStatistical.tsx index fb4c559b..8978395a 100644 --- a/src/dashboards/MasterCorporate/MasterStatistical.tsx +++ b/src/dashboards/MasterCorporate/MasterStatistical.tsx @@ -14,6 +14,7 @@ import {useListSearch} from "@/hooks/useListSearch"; import axios from "axios"; import {toast} from "react-toastify"; import Button from "@/components/Low/Button"; +import {getUserName} from "@/utils/users"; interface GroupedCorporateUsers { // list of user Ids @@ -71,44 +72,44 @@ const MasterStatistical = (props: Props) => { const tableResults = React.useMemo( () => - assignments - .reduce((accmA: TableData[], a: AssignmentWithCorporateId) => { - const userResults = a.assignees.map((assignee) => { - const userStats = a.results.find((r) => r.user === assignee)?.stats || []; - const userData = users.find((u) => u.id === assignee); - const corporate = users.find((u) => u.id === a.assigner)?.name || ""; - const commonData = { - user: userData?.name || "N/A", - email: userData?.email || "N/A", - userId: assignee, - corporateId: a.corporateId, - exams: a.exams.map((x) => x.id).join(", "), - corporate, - assignment: a.name, - }; - if (userStats.length === 0) { - return { - ...commonData, - correct: 0, - submitted: false, - date: null, - }; - } - + assignments.reduce((accmA: TableData[], a: AssignmentWithCorporateId) => { + const userResults = a.assignees.map((assignee) => { + const userStats = a.results.find((r) => r.user === assignee)?.stats || []; + const userData = users.find((u) => u.id === assignee); + const corporate = getUserName(users.find((u) => u.id === a.assigner)); + const commonData = { + user: userData?.name || "N/A", + email: userData?.email || "N/A", + userId: assignee, + corporateId: a.corporateId, + exams: a.exams.map((x) => x.id).join(", "), + corporate, + assignment: a.name, + }; + if (userStats.length === 0) { return { ...commonData, - correct: userStats.reduce((n, e) => n + e.score.correct, 0), - submitted: true, - date: moment.max(userStats.map((e) => moment(e.date))), + correct: 0, + submitted: false, + date: null, }; - }) as TableData[]; + } - return [...accmA, ...userResults]; - }, []) - .filter((x) => x.user !== "N/A"), + return { + ...commonData, + correct: userStats.reduce((n, e) => n + e.score.correct, 0), + submitted: true, + date: moment.max(userStats.map((e) => moment(e.date))), + }; + }) as TableData[]; + + return [...accmA, ...userResults]; + }, []), [assignments, users], ); + useEffect(() => console.log(assignments), [assignments]); + const getCorporateScores = (corporateId: string): UserCount => { const corporateAssignmentsUsers = assignments.filter((a) => a.corporateId === corporateId).reduce((acc, a) => acc + a.assignees.length, 0); @@ -171,13 +172,6 @@ const MasterStatistical = (props: Props) => { }), ] : []), - columnHelper.accessor("corporate", { - header: "Corporate", - id: "corporate", - cell: (info) => { - return {info.getValue()}; - }, - }), columnHelper.accessor("assignment", { header: "Assignment", id: "assignment", @@ -197,7 +191,7 @@ const MasterStatistical = (props: Props) => { }, }), columnHelper.accessor("correct", { - header: "Correct", + header: "Score", id: "correct", cell: (info) => { return {info.getValue()}; diff --git a/src/dashboards/MasterCorporate/index.tsx b/src/dashboards/MasterCorporate/index.tsx index 49d7236f..efa958e0 100644 --- a/src/dashboards/MasterCorporate/index.tsx +++ b/src/dashboards/MasterCorporate/index.tsx @@ -83,18 +83,6 @@ export default function MasterCorporateDashboard({user}: Props) { 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 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 router = useRouter(); diff --git a/src/utils/assignments.be.ts b/src/utils/assignments.be.ts index e3e9ff45..e2341020 100644 --- a/src/utils/assignments.be.ts +++ b/src/utils/assignments.be.ts @@ -1,18 +1,18 @@ import client from "@/lib/mongodb"; -import { Assignment } from "@/interfaces/results"; -import { getAllAssignersByCorporate } from "@/utils/groups.be"; +import {Assignment} from "@/interfaces/results"; +import {getAllAssignersByCorporate} from "@/utils/groups.be"; const db = client.db(process.env.MONGODB_DB); export const getAssignmentsByAssigner = async (id: string, startDate?: Date, endDate?: Date) => { - let query: any = { assigner: id }; + let query: any = {assigner: id}; if (startDate) { - query.startDate = { $gte: startDate.toISOString() }; + query.startDate = {$gte: startDate.toISOString()}; } if (endDate) { - query.endDate = { $lte: endDate.toISOString() }; + query.endDate = {$lte: endDate.toISOString()}; } return await db.collection("assignments").find(query).toArray(); @@ -26,7 +26,14 @@ export const getAssignmentsByAssignerBetweenDates = async (id: string, startDate }; 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({ + assigner: {$in: ids}, + ...(!!startDate ? {startDate: {$gte: startDate.toISOString()}} : {}), + ...(!!endDate ? {endDate: {$lte: endDate.toISOString()}} : {}), + }) + .toArray(); }; export const getAssignmentsForCorporates = async (idsList: string[], startDate?: Date, endDate?: Date) => { @@ -57,4 +64,4 @@ export const getAssignmentsForCorporates = async (idsList: string[], startDate?: ); return assignments.flat(); -} \ No newline at end of file +}; diff --git a/src/utils/groups.be.ts b/src/utils/groups.be.ts index 42fe68f9..7757ecd8 100644 --- a/src/utils/groups.be.ts +++ b/src/utils/groups.be.ts @@ -1,8 +1,9 @@ import {app} from "@/firebase"; +import {Assignment} from "@/interfaces/results"; import {CorporateUser, Group, MasterCorporateUser, StudentUser, TeacherUser, User} from "@/interfaces/user"; import client from "@/lib/mongodb"; import moment from "moment"; -import {getUser} from "./users.be"; +import {getLinkedUsers, getUser} from "./users.be"; import {getSpecificUsers} from "./users.be"; 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 => { - const groups = await getUserGroups(corporateID); - const groupUsers = (await Promise.all(groups.map(async (g) => await Promise.all(g.participants.map(getUser))))) - .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, - ), - ); + const linkedTeachers = await getLinkedUsers(corporateID, "mastercorporate", "teacher"); + const linkedCorporates = await getLinkedUsers(corporateID, "mastercorporate", "corporate"); - 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) => { From 6d1e8a97882c46062214b33f022c597962307151 Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Mon, 9 Sep 2024 00:06:51 +0100 Subject: [PATCH 09/12] Had some errors on updating groups --- src/pages/api/groups/[id].ts | 134 +++++++++++++++-------------------- 1 file changed, 58 insertions(+), 76 deletions(-) diff --git a/src/pages/api/groups/[id].ts b/src/pages/api/groups/[id].ts index 31c7b6c7..48df6782 100644 --- a/src/pages/api/groups/[id].ts +++ b/src/pages/api/groups/[id].ts @@ -1,109 +1,91 @@ // 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 { withIronSessionApiRoute } from "iron-session/next"; -import { sessionOptions } from "@/lib/session"; -import { Group } from "@/interfaces/user"; -import { updateExpiryDateOnGroup } from "@/utils/groups.be"; +import {withIronSessionApiRoute} from "iron-session/next"; +import {sessionOptions} from "@/lib/session"; +import {Group} from "@/interfaces/user"; +import {updateExpiryDateOnGroup} from "@/utils/groups.be"; const db = client.db(process.env.MONGODB_DB); export default withIronSessionApiRoute(handler, sessionOptions); async function handler(req: NextApiRequest, res: NextApiResponse) { - if (req.method === "GET") return await get(req, res); - if (req.method === "DELETE") return await del(req, res); - if (req.method === "PATCH") return await patch(req, res); + if (req.method === "GET") return await get(req, res); + if (req.method === "DELETE") return await del(req, res); + if (req.method === "PATCH") return await patch(req, res); - res.status(404).json(undefined); + res.status(404).json(undefined); } async function get(req: NextApiRequest, res: NextApiResponse) { - if (!req.session.user) { - res.status(401).json({ ok: false }); - return; - } + if (!req.session.user) { + res.status(401).json({ok: false}); + 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) { - res.status(200).json({ ...snapshot }); - } else { - res.status(404).json(undefined); - } + if (snapshot) { + res.status(200).json({...snapshot}); + } else { + res.status(404).json(undefined); + } } async function del(req: NextApiRequest, res: NextApiResponse) { - if (!req.session.user) { - res.status(401).json({ ok: false }); - return; - } + if (!req.session.user) { + res.status(401).json({ok: false}); + return; + } - const { id } = req.query as { id: string }; - const group = await db.collection("groups").findOne({id: id}); + const {id} = req.query as {id: string}; + const group = await db.collection("groups").findOne({id: id}); - if (!group) { - res.status(404); - return; - } + if (!group) { + res.status(404); + return; + } - const user = req.session.user; - if ( - user.type === "admin" || - user.type === "developer" || - user.id === group.admin - ) { - await db.collection("groups").deleteOne({ id: id }); + const user = req.session.user; + if (user.type === "admin" || user.type === "developer" || user.id === group.admin) { + await db.collection("groups").deleteOne({id: id}); - res.status(200).json({ ok: true }); - return; - } + res.status(200).json({ok: true}); + return; + } - res.status(403).json({ ok: false }); + res.status(403).json({ok: false}); } async function patch(req: NextApiRequest, res: NextApiResponse) { - if (!req.session.user) { - res.status(401).json({ ok: false }); - return; - } + if (!req.session.user) { + res.status(401).json({ok: false}); + return; + } - const { id } = req.query as { id: string }; + const {id} = req.query as {id: string}; - const group = await db.collection("groups").findOne({id: id}); - if (!group) { - res.status(404); - return; - } + const group = await db.collection("groups").findOne({id: id}); + if (!group) { + res.status(404); + return; + } - const user = req.session.user; - if ( - user.type === "admin" || - user.type === "developer" || - user.id === group.admin - ) { - if ("participants" in req.body) { - const newParticipants = (req.body.participants as string[]).filter( - (x) => !group.participants.includes(x), - ); - await Promise.all( - newParticipants.map( - async (p) => await updateExpiryDateOnGroup(p, group.admin), - ), - ); - } + const user = req.session.user; + if (user.type === "admin" || user.type === "developer" || user.id === group.admin) { + if ("participants" in req.body) { + const newParticipants = (req.body.participants as string[]).filter((x) => !group.participants.includes(x)); + await Promise.all(newParticipants.map(async (p) => await updateExpiryDateOnGroup(p, group.admin))); + } - await db.collection("grading").updateOne( - { id: req.session.user.id }, - { $set: {id: id, ...req.body} }, - { upsert: true } - ); + await db.collection("groups").updateOne({id: req.session.user.id}, {$set: {id, ...req.body}}, {upsert: true}); - res.status(200).json({ ok: true }); - return; - } + res.status(200).json({ok: true}); + return; + } - res.status(403).json({ ok: false }); + res.status(403).json({ok: false}); } From 192132559bad912b46cb47ccf88c2c023e095ddd Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Mon, 9 Sep 2024 08:19:32 +0100 Subject: [PATCH 10/12] Solved a problem with the download of the excel --- .../MasterCorporate/MasterStatistical.tsx | 2 +- .../api/assignments/statistical/excel.ts | 419 ++++++++---------- 2 files changed, 198 insertions(+), 223 deletions(-) diff --git a/src/dashboards/MasterCorporate/MasterStatistical.tsx b/src/dashboards/MasterCorporate/MasterStatistical.tsx index 8978395a..8d3fb70b 100644 --- a/src/dashboards/MasterCorporate/MasterStatistical.tsx +++ b/src/dashboards/MasterCorporate/MasterStatistical.tsx @@ -343,7 +343,7 @@ const MasterStatistical = (props: Props) => { {renderSearch()}
-
diff --git a/src/pages/api/assignments/statistical/excel.ts b/src/pages/api/assignments/statistical/excel.ts index 7eed838d..43345e88 100644 --- a/src/pages/api/assignments/statistical/excel.ts +++ b/src/pages/api/assignments/statistical/excel.ts @@ -1,254 +1,229 @@ -import type { NextApiRequest, NextApiResponse } from "next"; -import { storage } from "@/firebase"; -import { withIronSessionApiRoute } from "iron-session/next"; -import { sessionOptions } from "@/lib/session"; -import { ref, uploadBytes, getDownloadURL } from "firebase/storage"; -import { AssignmentWithCorporateId } from "@/interfaces/results"; +import type {NextApiRequest, NextApiResponse} from "next"; +import {storage} from "@/firebase"; +import {withIronSessionApiRoute} from "iron-session/next"; +import {sessionOptions} from "@/lib/session"; +import {ref, uploadBytes, getDownloadURL} from "firebase/storage"; +import {AssignmentWithCorporateId} from "@/interfaces/results"; import moment from "moment-timezone"; import ExcelJS from "exceljs"; -import { getSpecificUsers } from "@/utils/users.be"; -import { checkAccess } from "@/utils/permissions"; -import { getAssignmentsForCorporates } from "@/utils/assignments.be"; -import { search } from "@/utils/search"; -import { getGradingSystem } from "@/utils/grading.be"; -import { User } from "@/interfaces/user"; -import { calculateBandScore, getGradingLabel } from "@/utils/score"; -import { Module } from "@/interfaces"; +import {getSpecificUsers} from "@/utils/users.be"; +import {checkAccess} from "@/utils/permissions"; +import {getAssignmentsForCorporates} from "@/utils/assignments.be"; +import {search} from "@/utils/search"; +import {getGradingSystem} from "@/utils/grading.be"; +import {User} from "@/interfaces/user"; +import {calculateBandScore, getGradingLabel} from "@/utils/score"; +import {Module} from "@/interfaces"; export default withIronSessionApiRoute(handler, sessionOptions); interface TableData { - user: string; - email: string; - correct: number; - corporate: string; - submitted: boolean; - date: moment.Moment; - assignment: string; - corporateId: string; - score: number; - level: string; - part1?: string; - part2?: string; - part3?: string; - part4?: string; - part5?: string; + user: string; + email: string; + correct: number; + corporate: string; + submitted: boolean; + date: moment.Moment; + assignment: string; + corporateId: string; + score: number; + level: string; + part1?: string; + part2?: string; + part3?: string; + part4?: string; + part5?: string; } async function handler(req: NextApiRequest, res: NextApiResponse) { - // if (req.method === "GET") return get(req, res); - if (req.method === "POST") return await post(req, res); + // if (req.method === "GET") return get(req, res); + if (req.method === "POST") return await post(req, res); } const searchFilters = [["email"], ["user"], ["userId"]]; async function post(req: NextApiRequest, res: NextApiResponse) { - // verify if it's a logged user that is trying to export - if (req.session.user) { - if ( - !checkAccess(req.session.user, ["mastercorporate", "corporate", "developer", "admin"]) - ) { - return res.status(403).json({ error: "Unauthorized" }); - } - const { - ids, - startDate, - endDate, - searchText, - displaySelection = true, - } = req.body as { - ids: string[]; - startDate?: string; - endDate?: string; - searchText: string; - displaySelection?: boolean; - }; - const startDateParsed = startDate ? new Date(startDate) : undefined; - const endDateParsed = endDate ? new Date(endDate) : undefined; - const assignments = await getAssignmentsForCorporates( - ids, - startDateParsed, - endDateParsed - ); + // verify if it's a logged user that is trying to export + if (req.session.user) { + if (!checkAccess(req.session.user, ["mastercorporate", "corporate", "developer", "admin"])) { + return res.status(403).json({error: "Unauthorized"}); + } + const { + ids, + startDate, + endDate, + searchText, + displaySelection = true, + } = req.body as { + ids: string[]; + startDate?: string; + endDate?: string; + searchText: string; + displaySelection?: boolean; + }; + const startDateParsed = startDate ? new Date(startDate) : undefined; + const endDateParsed = endDate ? new Date(endDate) : undefined; + const assignments = await getAssignmentsForCorporates(ids, startDateParsed, endDateParsed); - const assignmentUsers = [ - ...new Set(assignments.flatMap((a) => a.assignees)), - ]; - const assigners = [...new Set(assignments.map((a) => a.assigner))]; - const users = await getSpecificUsers(assignmentUsers); - const assignerUsers = await getSpecificUsers(assigners); + const assignmentUsers = [...new Set(assignments.flatMap((a) => a.assignees))]; + const assigners = [...new Set(assignments.map((a) => a.assigner))]; + const users = await getSpecificUsers(assignmentUsers); + const assignerUsers = await getSpecificUsers(assigners); - const assignerUsersGradingSystems = await Promise.all( - assignerUsers.map(async (user: User) => { - const data = await getGradingSystem(user); - // in this context I need to override as I'll have to match to the assigner - return { ...data, user: user.id }; - }) - ); + const assignerUsersGradingSystems = await Promise.all( + assignerUsers.map(async (user: User) => { + const data = await getGradingSystem(user); + // in this context I need to override as I'll have to match to the assigner + return {...data, user: user.id}; + }), + ); - const getGradingSystemHelper = ( - exams: { id: string; module: Module; assignee: string }[], - assigner: string, - user: User, - correct: number, - total: number - ) => { - if (exams.some((e) => e.module === "level")) { - const gradingSystem = assignerUsersGradingSystems.find( - (gs) => gs.user === assigner - ); - if (gradingSystem) { - const bandScore = calculateBandScore( - correct, - total, - "level", - user.focus - ); - return { - label: getGradingLabel(bandScore, gradingSystem?.steps || []), - score: bandScore, - }; - } - } + const getGradingSystemHelper = ( + exams: {id: string; module: Module; assignee: string}[], + assigner: string, + user: User, + correct: number, + total: number, + ) => { + if (exams.some((e) => e.module === "level")) { + const gradingSystem = assignerUsersGradingSystems.find((gs) => gs.user === assigner); + if (gradingSystem) { + const bandScore = calculateBandScore(correct, total, "level", user?.focus || "academic"); + return { + label: getGradingLabel(bandScore, gradingSystem?.steps || []), + score: bandScore, + }; + } + } - return { score: -1, label: "N/A" }; - }; + return {score: -1, label: "N/A"}; + }; - const tableResults = assignments - .reduce((accmA: TableData[], a: AssignmentWithCorporateId) => { - const userResults = a.assignees.map((assignee) => { - const userStats = - a.results.find((r) => r.user === assignee)?.stats || []; - const userData = users.find((u) => u.id === assignee); - const corporateUser = users.find((u) => u.id === a.assigner); - const correct = userStats.reduce((n, e) => n + e.score.correct, 0); - const total = userStats.reduce((n, e) => n + e.score.total, 0); - const { label: level, score } = getGradingSystemHelper( - a.exams, - a.assigner, - userData!, - correct, - total - ); + const tableResults = assignments + .reduce((accmA: TableData[], a: AssignmentWithCorporateId) => { + const userResults = a.assignees.map((assignee) => { + const userStats = a.results.find((r) => r.user === assignee)?.stats || []; + const userData = users.find((u) => u.id === assignee); + const corporateUser = users.find((u) => u.id === a.assigner); + const correct = userStats.reduce((n, e) => n + e.score.correct, 0); + const total = userStats.reduce((n, e) => n + e.score.total, 0); + const {label: level, score} = getGradingSystemHelper(a.exams, a.assigner, userData!, correct, total); - console.log("Level", level); - const commonData = { - user: userData?.name || "", - email: userData?.email || "", - userId: assignee, - corporateId: a.corporateId, - corporate: corporateUser?.name || "", - assignment: a.name, - level, - score, - }; - if (userStats.length === 0) { - return { - ...commonData, - correct: 0, - submitted: false, - // date: moment(), - }; - } + console.log("Level", level); + const commonData = { + user: userData?.name || "", + email: userData?.email || "", + userId: assignee, + corporateId: a.corporateId, + corporate: corporateUser?.name || "", + assignment: a.name, + level, + score, + }; + if (userStats.length === 0) { + return { + ...commonData, + correct: 0, + submitted: false, + // date: moment(), + }; + } - const partsData = userStats.every((e) => e.module === "level") - ? userStats.reduce((acc, e, index) => { - return { - ...acc, - [`part${index}`]: `${e.score.correct}/${e.score.total}`, - }; - }, {}) - : {}; + const partsData = userStats.every((e) => e.module === "level") + ? userStats.reduce((acc, e, index) => { + return { + ...acc, + [`part${index}`]: `${e.score.correct}/${e.score.total}`, + }; + }, {}) + : {}; - return { - ...commonData, - correct, - submitted: true, - date: moment.max(userStats.map((e) => moment(e.date))), - ...partsData, - }; - }) as TableData[]; + return { + ...commonData, + correct, + submitted: true, + date: moment.max(userStats.map((e) => moment(e.date))), + ...partsData, + }; + }) as TableData[]; - return [...accmA, ...userResults]; - }, []) - .sort((a, b) => b.score - a.score); + return [...accmA, ...userResults]; + }, []) + .sort((a, b) => b.score - a.score); - // Create a new workbook and add a worksheet - const workbook = new ExcelJS.Workbook(); - const worksheet = workbook.addWorksheet("Master Statistical"); + // Create a new workbook and add a worksheet + const workbook = new ExcelJS.Workbook(); + const worksheet = workbook.addWorksheet("Master Statistical"); - const headers = [ - { - label: "User", - value: (entry: TableData) => entry.user, - }, - { - label: "Email", - value: (entry: TableData) => entry.email, - }, - ...(displaySelection - ? [ - { - label: "Corporate", - value: (entry: TableData) => entry.corporate, - }, - ] - : []), - { - label: "Assignment", - value: (entry: TableData) => entry.assignment, - }, - { - label: "Submitted", - value: (entry: TableData) => (entry.submitted ? "Yes" : "No"), - }, - { - label: "Correct", - value: (entry: TableData) => entry.correct, - }, - { - label: "Date", - value: (entry: TableData) => entry.date?.format("YYYY/MM/DD") || "", - }, - { - label: "Level", - value: (entry: TableData) => entry.level, - }, - ...new Array(5).fill(0).map((_, index) => ({ - label: `Part ${index + 1}`, - value: (entry: TableData) => { - const key = `part${index}` as keyof TableData; - return entry[key] || ""; - }, - })), - ]; + const headers = [ + { + label: "User", + value: (entry: TableData) => entry.user, + }, + { + label: "Email", + value: (entry: TableData) => entry.email, + }, + ...(displaySelection + ? [ + { + label: "Corporate", + value: (entry: TableData) => entry.corporate, + }, + ] + : []), + { + label: "Assignment", + value: (entry: TableData) => entry.assignment, + }, + { + label: "Submitted", + value: (entry: TableData) => (entry.submitted ? "Yes" : "No"), + }, + { + label: "Correct", + value: (entry: TableData) => entry.correct, + }, + { + label: "Date", + value: (entry: TableData) => entry.date?.format("YYYY/MM/DD") || "", + }, + { + label: "Level", + value: (entry: TableData) => entry.level, + }, + ...new Array(5).fill(0).map((_, index) => ({ + label: `Part ${index + 1}`, + value: (entry: TableData) => { + const key = `part${index}` as keyof TableData; + return entry[key] || ""; + }, + })), + ]; - const filteredSearch = searchText - ? search(searchText, searchFilters, tableResults) - : tableResults; + const filteredSearch = searchText ? search(searchText, searchFilters, tableResults) : tableResults; - worksheet.addRow(headers.map((h) => h.label)); - (filteredSearch as TableData[]).forEach((entry) => { - worksheet.addRow(headers.map((h) => h.value(entry))); - }); + worksheet.addRow(headers.map((h) => h.label)); + (filteredSearch as TableData[]).forEach((entry) => { + worksheet.addRow(headers.map((h) => h.value(entry))); + }); - // Convert workbook to Buffer (Node.js) or Blob (Browser) - const buffer = await workbook.xlsx.writeBuffer(); + // Convert workbook to Buffer (Node.js) or Blob (Browser) + const buffer = await workbook.xlsx.writeBuffer(); - // generate the file ref for storage - const fileName = `${Date.now().toString()}.xlsx`; - const refName = `statistical/${fileName}`; - const fileRef = ref(storage, refName); - // upload the pdf to storage - await uploadBytes(fileRef, buffer, { - contentType: - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - }); + // generate the file ref for storage + const fileName = `${Date.now().toString()}.xlsx`; + const refName = `statistical/${fileName}`; + const fileRef = ref(storage, refName); + // upload the pdf to storage + await uploadBytes(fileRef, buffer, { + contentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + }); - const url = await getDownloadURL(fileRef); - res.status(200).end(url); - return; - } + const url = await getDownloadURL(fileRef); + res.status(200).end(url); + return; + } - return res.status(401).json({ error: "Unauthorized" }); + return res.status(401).json({error: "Unauthorized"}); } From b9c097d42c4505267d3da5d58baf941f8ea8e342 Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Mon, 9 Sep 2024 09:07:30 +0100 Subject: [PATCH 11/12] Had a bug in pagination --- .../MasterCorporate/MasterStatistical.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/dashboards/MasterCorporate/MasterStatistical.tsx b/src/dashboards/MasterCorporate/MasterStatistical.tsx index 8d3fb70b..bf7b0579 100644 --- a/src/dashboards/MasterCorporate/MasterStatistical.tsx +++ b/src/dashboards/MasterCorporate/MasterStatistical.tsx @@ -28,7 +28,7 @@ interface Props { } interface TableData { - user: string; + user: User | undefined; email: string; correct: number; corporate: string; @@ -62,7 +62,7 @@ const MasterStatistical = (props: Props) => { const [startDate, setStartDate] = React.useState(moment("01/01/2023").toDate()); const [endDate, setEndDate] = React.useState(moment().endOf("year").toDate()); - const {assignments} = useAssignmentsCorporates({ + const {assignments, isLoading} = useAssignmentsCorporates({ corporates: selectedCorporates, startDate, endDate, @@ -76,9 +76,11 @@ const MasterStatistical = (props: Props) => { const userResults = a.assignees.map((assignee) => { const userStats = a.results.find((r) => r.user === assignee)?.stats || []; const userData = users.find((u) => u.id === assignee); + if (!!userData) console.log(assignee, userData.name); + const corporate = getUserName(users.find((u) => u.id === a.assigner)); const commonData = { - user: userData?.name || "N/A", + user: userData, email: userData?.email || "N/A", userId: assignee, corporateId: a.corporateId, @@ -151,7 +153,7 @@ const MasterStatistical = (props: Props) => { header: "User", id: "user", cell: (info) => { - return {info.getValue()}; + return {info.getValue()?.name || "N/A"}; }, }), columnHelper.accessor("email", { @@ -281,6 +283,7 @@ const MasterStatistical = (props: Props) => { { @@ -302,6 +305,7 @@ const MasterStatistical = (props: Props) => { { {page * SIZE + 1} - {(page + 1) * SIZE > filteredRows.length ? filteredRows.length : (page + 1) * SIZE} /{" "} {filteredRows.length} - From 85c8f622ee623ce5543bcc7cfb3a48ea6b267196 Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Mon, 9 Sep 2024 14:43:05 +0100 Subject: [PATCH 12/12] Added Student ID to the Master Statistical --- src/dashboards/MasterCorporate/MasterStatistical.tsx | 11 +++++++++-- src/pages/api/assignments/statistical/excel.ts | 9 +++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/dashboards/MasterCorporate/MasterStatistical.tsx b/src/dashboards/MasterCorporate/MasterStatistical.tsx index bf7b0579..ec205d6a 100644 --- a/src/dashboards/MasterCorporate/MasterStatistical.tsx +++ b/src/dashboards/MasterCorporate/MasterStatistical.tsx @@ -1,5 +1,5 @@ import React, {useEffect, useMemo, useState} from "react"; -import {CorporateUser, User} from "@/interfaces/user"; +import {CorporateUser, StudentUser, User} from "@/interfaces/user"; import {BsFileExcel, BsBank, BsPersonFill} from "react-icons/bs"; import IconCard from "../IconCard"; @@ -163,6 +163,13 @@ const MasterStatistical = (props: Props) => { return {info.getValue()}; }, }), + columnHelper.accessor("user", { + header: "Student ID", + id: "studentID", + cell: (info) => { + return {(info.getValue() as StudentUser).studentID || "N/A"}; + }, + }), ...(displaySelection ? [ columnHelper.accessor("corporate", { @@ -347,7 +354,7 @@ const MasterStatistical = (props: Props) => { {renderSearch()}
-
diff --git a/src/pages/api/assignments/statistical/excel.ts b/src/pages/api/assignments/statistical/excel.ts index 43345e88..da5befe0 100644 --- a/src/pages/api/assignments/statistical/excel.ts +++ b/src/pages/api/assignments/statistical/excel.ts @@ -11,7 +11,7 @@ import {checkAccess} from "@/utils/permissions"; import {getAssignmentsForCorporates} from "@/utils/assignments.be"; import {search} from "@/utils/search"; import {getGradingSystem} from "@/utils/grading.be"; -import {User} from "@/interfaces/user"; +import {StudentUser, User} from "@/interfaces/user"; import {calculateBandScore, getGradingLabel} from "@/utils/score"; import {Module} from "@/interfaces"; @@ -19,6 +19,7 @@ export default withIronSessionApiRoute(handler, sessionOptions); interface TableData { user: string; + studentID: string; email: string; correct: number; corporate: string; @@ -109,10 +110,10 @@ async function post(req: NextApiRequest, res: NextApiResponse) { const total = userStats.reduce((n, e) => n + e.score.total, 0); const {label: level, score} = getGradingSystemHelper(a.exams, a.assigner, userData!, correct, total); - console.log("Level", level); const commonData = { user: userData?.name || "", email: userData?.email || "", + studentID: (userData as StudentUser).studentID || "", userId: assignee, corporateId: a.corporateId, corporate: corporateUser?.name || "", @@ -164,6 +165,10 @@ async function post(req: NextApiRequest, res: NextApiResponse) { label: "Email", value: (entry: TableData) => entry.email, }, + { + label: "Student ID", + value: (entry: TableData) => entry.studentID, + }, ...(displaySelection ? [ {