diff --git a/src/dashboards/Admin.tsx b/src/dashboards/Admin.tsx index edb7d165..656a4377 100644 --- a/src/dashboards/Admin.tsx +++ b/src/dashboards/Admin.tsx @@ -581,7 +581,7 @@ export default function AdminDashboard({user}: Props) { .includes(x.id), }); - router.push("/list/users"); + router.push("/users"); } : undefined } @@ -601,7 +601,7 @@ export default function AdminDashboard({user}: Props) { .includes(x.id), }); - router.push("/list/users"); + router.push("/users"); } : undefined } @@ -621,7 +621,7 @@ export default function AdminDashboard({user}: Props) { .includes(x.id), }); - router.push("/list/users"); + router.push("/users"); } : undefined } diff --git a/src/dashboards/Corporate/index.tsx b/src/dashboards/Corporate/index.tsx index 453a9005..b21aaf28 100644 --- a/src/dashboards/Corporate/index.tsx +++ b/src/dashboards/Corporate/index.tsx @@ -217,7 +217,7 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) { .includes(x.id), }); - router.push("/list/users"); + router.push("/users"); } : undefined } @@ -237,7 +237,7 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) { .includes(x.id), }); - router.push("/list/users"); + router.push("/users"); } : undefined } diff --git a/src/dashboards/MasterCorporate/index.tsx b/src/dashboards/MasterCorporate/index.tsx index efa958e0..72d34c25 100644 --- a/src/dashboards/MasterCorporate/index.tsx +++ b/src/dashboards/MasterCorporate/index.tsx @@ -276,7 +276,7 @@ export default function MasterCorporateDashboard({user}: Props) { .includes(x.id), }); - router.push("/list/users"); + router.push("/users"); } : undefined } @@ -296,7 +296,7 @@ export default function MasterCorporateDashboard({user}: Props) { .includes(x.id), }); - router.push("/list/users"); + router.push("/users"); } : undefined } diff --git a/src/dashboards/Teacher.tsx b/src/dashboards/Teacher.tsx index 18927ee4..b10c379c 100644 --- a/src/dashboards/Teacher.tsx +++ b/src/dashboards/Teacher.tsx @@ -202,7 +202,7 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) { .includes(x.id), }); - router.push("/list/users"); + router.push("/users"); } : undefined } @@ -222,7 +222,7 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) { .includes(x.id), }); - router.push("/list/users"); + router.push("/users"); } : undefined } diff --git a/src/pages/(admin)/Lists/StudentPerformanceList.tsx b/src/pages/(admin)/Lists/StudentPerformanceList.tsx new file mode 100644 index 00000000..f202c088 --- /dev/null +++ b/src/pages/(admin)/Lists/StudentPerformanceList.tsx @@ -0,0 +1,111 @@ +/* eslint-disable @next/next/no-img-element */ +import {Stat, StudentUser, User} from "@/interfaces/user"; +import {useState} from "react"; +import {averageLevelCalculator} from "@/utils/score"; +import {groupByExam} from "@/utils/stats"; +import {createColumnHelper} from "@tanstack/react-table"; +import Checkbox from "@/components/Low/Checkbox"; +import List from "@/components/List"; +import Table from "@/components/High/Table"; + +type StudentPerformanceItem = StudentUser & {entitiesLabel: string; group: string}; + +const StudentPerformanceList = ({items = [], stats}: {items: StudentPerformanceItem[]; stats: Stat[]}) => { + const [isShowingAmount, setIsShowingAmount] = useState(false); + + const columnHelper = createColumnHelper(); + + const columns = [ + columnHelper.accessor("name", { + header: "Student Name", + cell: (info) => info.getValue(), + }), + columnHelper.accessor("email", { + header: "E-mail", + cell: (info) => info.getValue(), + }), + columnHelper.accessor("studentID", { + header: "ID", + cell: (info) => info.getValue() || "N/A", + }), + columnHelper.accessor("group", { + header: "Group", + cell: (info) => info.getValue(), + }), + columnHelper.accessor("entitiesLabel", { + header: "Entities", + cell: (info) => info.getValue() || "N/A", + }), + columnHelper.accessor("levels.reading", { + header: "Reading", + cell: (info) => + !isShowingAmount + ? info.getValue() || 0 + : `${Object.keys(groupByExam(stats.filter((x) => x.module === "reading" && x.user === info.row.original.id))).length} exams`, + }), + columnHelper.accessor("levels.listening", { + header: "Listening", + cell: (info) => + !isShowingAmount + ? info.getValue() || 0 + : `${Object.keys(groupByExam(stats.filter((x) => x.module === "listening" && x.user === info.row.original.id))).length} exams`, + }), + columnHelper.accessor("levels.writing", { + header: "Writing", + cell: (info) => + !isShowingAmount + ? info.getValue() || 0 + : `${Object.keys(groupByExam(stats.filter((x) => x.module === "writing" && x.user === info.row.original.id))).length} exams`, + }), + columnHelper.accessor("levels.speaking", { + header: "Speaking", + cell: (info) => + !isShowingAmount + ? info.getValue() || 0 + : `${Object.keys(groupByExam(stats.filter((x) => x.module === "speaking" && x.user === info.row.original.id))).length} exams`, + }), + columnHelper.accessor("levels.level", { + header: "Level", + cell: (info) => + !isShowingAmount + ? info.getValue() || 0 + : `${Object.keys(groupByExam(stats.filter((x) => x.module === "level" && x.user === info.row.original.id))).length} exams`, + }), + columnHelper.accessor("levels", { + id: "overall_level", + header: "Overall", + cell: (info) => + !isShowingAmount + ? averageLevelCalculator( + items, + stats.filter((x) => x.user === info.row.original.id), + ).toFixed(1) + : `${Object.keys(groupByExam(stats.filter((x) => x.user === info.row.original.id))).length} exams`, + }), + ]; + + return ( +
+ + Show Utilization + + + data={items.sort( + (a, b) => + averageLevelCalculator( + items, + stats.filter((x) => x.user === b.id), + ) - + averageLevelCalculator( + items, + stats.filter((x) => x.user === a.id), + ), + )} + columns={columns} + searchFields={[["name"], ["email"], ["studentID"], ["entitiesLabel"], ["group"]]} + /> +
+ ); +}; + +export default StudentPerformanceList; diff --git a/src/pages/(admin)/Lists/UserList.tsx b/src/pages/(admin)/Lists/UserList.tsx index 93224b0b..e9092fb6 100644 --- a/src/pages/(admin)/Lists/UserList.tsx +++ b/src/pages/(admin)/Lists/UserList.tsx @@ -329,7 +329,7 @@ export default function UserList({ filter: belongsToAdminFilter, }); - router.push("/list/users"); + router.push("/users"); } : undefined } @@ -345,7 +345,7 @@ export default function UserList({ filter: belongsToAdminFilter, }); - router.push("/list/users"); + router.push("/users"); } : undefined } @@ -361,7 +361,7 @@ export default function UserList({ filter: belongsToAdminFilter }); - router.push("/list/users"); + router.push("/users"); } : undefined } diff --git a/src/pages/dashboard/admin.tsx b/src/pages/dashboard/admin.tsx index 52cc3d02..a330adcd 100644 --- a/src/pages/dashboard/admin.tsx +++ b/src/pages/dashboard/admin.tsx @@ -123,14 +123,14 @@ export default function Dashboard({ user, users, entities, assignments, stats, g
router.push("/list/users?type=student")} + onClick={() => router.push("/users?type=student")} Icon={BsPersonFill} label="Students" value={students.length} color="purple" /> router.push("/list/users?type=teacher")} + onClick={() => router.push("/users?type=teacher")} Icon={BsPencilSquare} label="Teachers" value={teachers.length} @@ -138,14 +138,14 @@ export default function Dashboard({ user, users, entities, assignments, stats, g /> router.push("/list/users?type=corporate")} + onClick={() => router.push("/users?type=corporate")} label="Corporates" value={corporates.length} color="purple" /> router.push("/list/users?type=mastercorporate")} + onClick={() => router.push("/users?type=mastercorporate")} label="Master Corporates" value={masterCorporates.length} color="purple" @@ -154,7 +154,12 @@ export default function Dashboard({ user, users, entities, assignments, stats, g - + router.push("/users/performance")} + label="Student Performance" + value={students.length} + color="purple" + /> router.push("/assignments")} diff --git a/src/pages/dashboard/corporate.tsx b/src/pages/dashboard/corporate.tsx index 1c7576d6..fe87ba44 100644 --- a/src/pages/dashboard/corporate.tsx +++ b/src/pages/dashboard/corporate.tsx @@ -137,14 +137,14 @@ export default function Dashboard({ user, users, entities, assignments, stats, g )}
router.push("/list/users?type=student")} + onClick={() => router.push("/users?type=student")} Icon={BsPersonFill} label="Students" value={students.length} color="purple" /> router.push("/list/users?type=teacher")} + onClick={() => router.push("/users?type=teacher")} Icon={BsPencilSquare} label="Teachers" value={teachers.length} @@ -160,7 +160,12 @@ export default function Dashboard({ user, users, entities, assignments, stats, g - + router.push("/users/performance")} + label="Student Performance" + value={students.length} + color="purple" + />
router.push("/list/users?type=student")} + onClick={() => router.push("/users?type=student")} Icon={BsPersonFill} label="Students" value={students.length} color="purple" /> router.push("/list/users?type=teacher")} + onClick={() => router.push("/users?type=teacher")} Icon={BsPencilSquare} label="Teachers" value={teachers.length} @@ -138,14 +138,14 @@ export default function Dashboard({ user, users, entities, assignments, stats, g /> router.push("/list/users?type=corporate")} + onClick={() => router.push("/users?type=corporate")} label="Corporates" value={corporates.length} color="purple" /> router.push("/list/users?type=mastercorporate")} + onClick={() => router.push("/users?type=mastercorporate")} label="Master Corporates" value={masterCorporates.length} color="purple" @@ -160,7 +160,12 @@ export default function Dashboard({ user, users, entities, assignments, stats, g - + router.push("/users/performance")} + label="Student Performance" + value={students.length} + color="purple" + /> router.push("/assignments")} diff --git a/src/pages/dashboard/mastercorporate.tsx b/src/pages/dashboard/mastercorporate.tsx index 01e58e2d..392bd0a1 100644 --- a/src/pages/dashboard/mastercorporate.tsx +++ b/src/pages/dashboard/mastercorporate.tsx @@ -134,26 +134,31 @@ export default function Dashboard({ user, users, entities, assignments, stats, g
router.push("/list/users?type=student")} + onClick={() => router.push("/users?type=student")} Icon={BsPersonFill} label="Students" value={students.length} color="purple" /> router.push("/list/users?type=teacher")} + onClick={() => router.push("/users?type=teacher")} Icon={BsPencilSquare} label="Teachers" value={teachers.length} color="purple" /> router.push("/list/users?type=corporate")} Icon={BsBank} label="Corporate Accounts" value={corporates.length} color="purple" /> + onClick={() => router.push("/users?type=corporate")} Icon={BsBank} label="Corporate Accounts" value={corporates.length} color="purple" /> router.push("/classrooms")} Icon={BsPeople} label="Classrooms" value={groups.length} color="purple" /> - + router.push("/users/performance")} + label="Student Performance" + value={students.length} + color="purple" + /> router.push("/assignments")} diff --git a/src/pages/list/users.tsx b/src/pages/users/index.tsx similarity index 99% rename from src/pages/list/users.tsx rename to src/pages/users/index.tsx index 06c6fdcd..a70d9f6c 100644 --- a/src/pages/list/users.tsx +++ b/src/pages/users/index.tsx @@ -55,7 +55,6 @@ export default function UsersListPage({ user, type }: Props) { - {user && ( - )} ); } diff --git a/src/pages/users/performance.tsx b/src/pages/users/performance.tsx new file mode 100644 index 00000000..6d369bc9 --- /dev/null +++ b/src/pages/users/performance.tsx @@ -0,0 +1,96 @@ +import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser"; +import useGroups from "@/hooks/useGroups"; +import useUsers, {userHashStudent} from "@/hooks/useUsers"; +import {Group, Stat, StudentUser, User} from "@/interfaces/user"; +import {getUserCompanyName} from "@/resources/user"; +import clsx from "clsx"; +import {useRouter} from "next/router"; +import {BsArrowLeft, BsArrowRepeat, BsChevronLeft} from "react-icons/bs"; +import { mapBy, serialize } from "@/utils"; +import {withIronSessionSsr} from "iron-session/next"; +import { getEntitiesUsers, getUsers } from "@/utils/users.be"; +import { sessionOptions } from "@/lib/session"; +import { checkAccess } from "@/utils/permissions"; +import { getEntities } from "@/utils/entities.be"; +import { Entity } from "@/interfaces/entity"; +import { getParticipantGroups, getParticipantsGroups } from "@/utils/groups.be"; +import StudentPerformanceList from "../(admin)/Lists/StudentPerformanceList"; +import Head from "next/head"; +import { ToastContainer } from "react-toastify"; +import Layout from "@/components/High/Layout"; + +export const getServerSideProps = withIronSessionSsr(async ({req, res, query}) => { + const user = req.session.user as User; + + if (!user) { + return { + redirect: { + destination: "/login", + permanent: false, + }, + }; + } + + const entityIDs = mapBy(user.entities, 'id') + + const entities = await getEntities(checkAccess(user, ["admin", 'developer']) ? undefined : entityIDs) + const students = await (checkAccess(user, ["admin", 'developer']) + ? getUsers({type: 'student'}) + : getEntitiesUsers(entityIDs, {type: 'student'}) + ) + const groups = await getParticipantsGroups(mapBy(students, 'id')) + + return { + props: serialize({user, students, entities, groups}), + }; +}, sessionOptions); + +interface Props { + user: User; + students: StudentUser[] + entities: Entity[] + groups: Group[] +} + +const StudentPerformance = ({user, students, entities, groups}: Props) => { + const {data: stats} = useFilterRecordsByUser(); + + const router = useRouter(); + + const performanceStudents = students.map((u) => ({ + ...u, + group: groups.find((x) => x.participants.includes(u.id))?.name || "N/A", + entitiesLabel: mapBy(u.entities, 'id').map((id) => entities.find((e) => e.id === id)?.label).filter((e) => !!e).join(', '), + })); + + return ( + <> + + EnCoach + + + + + + + +
+ +

Student Performance ({ students.length })

+
+ +
+ + ); +}; + +export default StudentPerformance; diff --git a/src/utils/groups.be.ts b/src/utils/groups.be.ts index 1981f519..d5018ef1 100644 --- a/src/utils/groups.be.ts +++ b/src/utils/groups.be.ts @@ -85,6 +85,10 @@ export const getParticipantGroups = async (id: string) => { return await db.collection("groups").find({ participants: id }).toArray(); }; +export const getParticipantsGroups = async (ids: string[]) => { + return await db.collection("groups").find({ participants: { $in: ids } }).toArray(); +}; + export const getUserGroups = async (id: string): Promise => { return await db.collection("groups").find({ admin: id }).toArray(); };