From 49022394b02dd8d78cbc5ae6e4bba5b4578a9aa6 Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Sat, 7 Sep 2024 17:53:16 +0100 Subject: [PATCH] Did the same treatment to Corporate and Teacher dashboards --- src/dashboards/Corporate.tsx | 510 ------------------ .../Corporate/StudentPerformanceList.tsx | 154 ++++++ .../Corporate/StudentPerformancePage.tsx | 49 ++ src/dashboards/Corporate/index.tsx | 399 ++++++++++++++ src/dashboards/MasterCorporate/index.tsx | 1 - src/dashboards/Teacher.tsx | 258 +++++---- src/dashboards/views/AssignmentsPage.tsx | 388 ++++++------- 7 files changed, 896 insertions(+), 863 deletions(-) delete mode 100644 src/dashboards/Corporate.tsx create mode 100644 src/dashboards/Corporate/StudentPerformanceList.tsx create mode 100644 src/dashboards/Corporate/StudentPerformancePage.tsx create mode 100644 src/dashboards/Corporate/index.tsx diff --git a/src/dashboards/Corporate.tsx b/src/dashboards/Corporate.tsx deleted file mode 100644 index 54091dd8..00000000 --- a/src/dashboards/Corporate.tsx +++ /dev/null @@ -1,510 +0,0 @@ -/* eslint-disable @next/next/no-img-element */ -import Modal from "@/components/Modal"; -import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser"; -import useUsers, { userHashStudent, userHashTeacher, userHashCorporate} from "@/hooks/useUsers"; -import {CorporateUser, Group, MasterCorporateUser, Stat, User } from "@/interfaces/user"; -import UserList from "@/pages/(admin)/Lists/UserList"; -import {dateSorter} from "@/utils"; -import moment from "moment"; -import {useEffect, useMemo, useState} from "react"; -import { - BsArrowLeft, - BsClipboard2Data, - BsClipboard2DataFill, - BsClock, - BsGlobeCentralSouthAsia, - BsPaperclip, - BsPerson, - BsPersonAdd, - BsPersonFill, - BsPersonFillGear, - BsPersonGear, - BsPencilSquare, - BsPersonBadge, - BsPersonCheck, - BsPeople, - BsArrowRepeat, - BsPlus, - BsEnvelopePaper, -} from "react-icons/bs"; -import UserCard from "@/components/UserCard"; -import useGroups from "@/hooks/useGroups"; -import {averageLevelCalculator, calculateAverageLevel, calculateBandScore} from "@/utils/score"; -import {MODULE_ARRAY} from "@/utils/moduleUtils"; -import {Module} from "@/interfaces"; -import {groupByExam} from "@/utils/stats"; -import IconCard from "./IconCard"; -import GroupList from "@/pages/(admin)/Lists/GroupList"; -import useFilterStore from "@/stores/listFilterStore"; -import {useRouter} from "next/router"; -import useCodes from "@/hooks/useCodes"; -import {getUserCorporate} from "@/utils/groups"; -import useAssignments from "@/hooks/useAssignments"; -import {Assignment} from "@/interfaces/results"; -import AssignmentView from "./AssignmentView"; -import AssignmentCreator from "./AssignmentCreator"; -import clsx from "clsx"; -import AssignmentCard from "./AssignmentCard"; -import {createColumnHelper} from "@tanstack/react-table"; -import Checkbox from "@/components/Low/Checkbox"; -import List from "@/components/List"; -import {getUserCompanyName} from "@/resources/user"; -import {futureAssignmentFilter, pastAssignmentFilter, archivedAssignmentFilter, activeAssignmentFilter} from "@/utils/assignments"; -import useUserBalance from "@/hooks/useUserBalance"; -import AssignmentsPage from "./views/AssignmentsPage"; - -interface Props { - user: CorporateUser; - linkedCorporate?: CorporateUser | MasterCorporateUser; -} - -type StudentPerformanceItem = User & {corporateName: string; group: string}; -const StudentPerformanceList = ({items, stats, users}: {items: StudentPerformanceItem[]; stats: Stat[]; users: User[]}) => { - 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("demographicInformation.passport_id", { - header: "ID", - cell: (info) => info.getValue() || "N/A", - }), - columnHelper.accessor("group", { - header: "Group", - cell: (info) => info.getValue(), - }), - columnHelper.accessor("corporateName", { - header: "Corporate", - 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( - users, - 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( - users, - stats.filter((x) => x.user === b.id), - ) - - averageLevelCalculator( - users, - stats.filter((x) => x.user === a.id), - ), - )} - columns={columns} - /> -
- ); -}; - - -export default function CorporateDashboard({user, linkedCorporate}: Props) { - const [selectedUser, setSelectedUser] = useState(); - const [showModal, setShowModal] = useState(false); - - const {data: stats} = useFilterRecordsByUser(); - const {groups} = useGroups({admin: user.id}); - const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({corporate: user.id}); - const {balance} = useUserBalance(); - - const {users: students, reload: reloadStudents, isLoading: isStudentsLoading} = useUsers(userHashStudent); - const {users: teachers, reload: reloadTeachers, isLoading: isTeachersLoading} = useUsers(userHashTeacher); - - const appendUserFilters = useFilterStore((state) => state.appendUserFilter); - const router = useRouter(); - - const assignmentsGroups = useMemo(() => groups.filter((x) => x.admin === user.id || x.participants.includes(user.id)), [groups, user.id]); - - const assignmentsUsers = useMemo( - () => - [...teachers, ...students].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, teachers, students, selectedUser], - ); - - useEffect(() => { - setShowModal(!!selectedUser && router.asPath === "/#"); - }, [selectedUser, router.asPath]); - - const getStatsByStudent = (user: User) => stats.filter((s) => s.user === user.id); - - const UserDisplay = (displayUser: User) => ( -
setSelectedUser(displayUser)} - className="flex w-full p-4 gap-4 items-center hover:bg-mti-purple-ultralight cursor-pointer transition ease-in-out duration-300"> - {displayUser.name} -
- {displayUser.name} - {displayUser.email} -
-
- ); - - const GroupsList = () => { - const filter = (x: Group) => x.admin === user.id || x.participants.includes(user.id); - - return ( - <> -
-
router.push("/")} - className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300"> - - Back -
-

Groups ({groups.filter(filter).length})

-
- - - - ); - }; - - const StudentPerformancePage = () => { - const performanceStudents = students.map((u) => ({ - ...u, - group: groups.find((x) => x.participants.includes(u.id))?.name || "N/A", - corporateName: getUserCompanyName(user, [], groups), - })); - - return ( - <> -
-
router.push("/")} - className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300"> - - Back -
-
- Reload - -
-
- - - ); - }; - - const averageLevelCalculator = (studentStats: Stat[]) => { - const formattedStats = studentStats - .map((s) => ({ - focus: students.find((u) => u.id === s.user)?.focus, - score: s.score, - module: s.module, - })) - .filter((f) => !!f.focus); - const bandScores = formattedStats.map((s) => ({ - module: s.module, - level: calculateBandScore(s.score.correct, s.score.total, s.module, s.focus!), - })); - - const levels: {[key in Module]: number} = { - reading: 0, - listening: 0, - writing: 0, - speaking: 0, - level: 0, - }; - bandScores.forEach((b) => (levels[b.module] += b.level)); - - return calculateAverageLevel(levels); - }; - - const DefaultDashboard = () => ( - <> - {!!linkedCorporate && ( -
- Linked to: {linkedCorporate?.corporateInformation?.companyInformation.name || linkedCorporate.name} -
- )} -
- router.push("/#students")} - isLoading={isStudentsLoading} - Icon={BsPersonFill} - label="Students" - value={students.length} - color="purple" - /> - router.push("/#teachers")} - isLoading={isTeachersLoading} - Icon={BsPencilSquare} - label="Teachers" - value={teachers.length} - color="purple" - /> - groups.flatMap((g) => g.participants).includes(s.user)).length} - color="purple" - /> - groups.flatMap((g) => g.participants).includes(s.user))).toFixed(1)} - color="purple" - /> - router.push("/#groups")} Icon={BsPeople} label="Groups" value={groups.length} color="purple" /> - - - router.push("/#studentsPerformance")} - /> - -
- -
-
- Latest students -
- {students - .sort((a, b) => dateSorter(a, b, "desc", "registrationDate")) - .map((x) => ( - - ))} -
-
-
- Latest teachers -
- {teachers - .sort((a, b) => dateSorter(a, b, "desc", "registrationDate")) - .map((x) => ( - - ))} -
-
-
- Highest level students -
- {students - .sort((a, b) => calculateAverageLevel(b.levels) - calculateAverageLevel(a.levels)) - .map((x) => ( - - ))} -
-
-
- Highest exam count students -
- {students - .sort( - (a, b) => - Object.keys(groupByExam(getStatsByStudent(b))).length - Object.keys(groupByExam(getStatsByStudent(a))).length, - ) - .map((x) => ( - - ))} -
-
-
- - ); - - return ( - <> - setSelectedUser(undefined)}> - <> - {selectedUser && ( -
- { - setSelectedUser(undefined); - if (shouldReload && selectedUser!.type === "student") reloadStudents(); - if (shouldReload && selectedUser!.type === "teacher") reloadTeachers(); - }} - onViewStudents={ - selectedUser.type === "corporate" || selectedUser.type === "teacher" - ? () => { - appendUserFilters({ - id: "view-students", - filter: (x: User) => x.type === "student", - }); - appendUserFilters({ - id: "belongs-to-admin", - filter: (x: User) => - groups - .filter((g) => g.admin === selectedUser.id || g.participants.includes(selectedUser.id)) - .flatMap((g) => g.participants) - .includes(x.id), - }); - - router.push("/list/users"); - } - : undefined - } - onViewTeachers={ - selectedUser.type === "corporate" || selectedUser.type === "student" - ? () => { - appendUserFilters({ - id: "view-teachers", - filter: (x: User) => x.type === "teacher", - }); - appendUserFilters({ - id: "belongs-to-admin", - filter: (x: User) => - groups - .filter((g) => g.admin === selectedUser.id || g.participants.includes(selectedUser.id)) - .flatMap((g) => g.participants) - .includes(x.id), - }); - - router.push("/list/users"); - } - : undefined - } - user={selectedUser} - /> -
- )} - -
- {router.asPath === "/#students" && ( - ( -
-
router.push("/")} - className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300"> - - Back -
-

Students ({total})

-
- )} - /> - )} - {router.asPath === "/#teachers" && ( - ( -
-
router.push("/")} - className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300"> - - Back -
-

Teachers ({total})

-
- )} - /> - )} - {router.asPath === "/#groups" && } - {router.asPath === "/#assignments" && ( - router.push("/")} - /> - )} - {router.asPath === "/#studentsPerformance" && } - {router.asPath === "/" && } - - ); -} diff --git a/src/dashboards/Corporate/StudentPerformanceList.tsx b/src/dashboards/Corporate/StudentPerformanceList.tsx new file mode 100644 index 00000000..ca0e920a --- /dev/null +++ b/src/dashboards/Corporate/StudentPerformanceList.tsx @@ -0,0 +1,154 @@ +/* eslint-disable @next/next/no-img-element */ +import Modal from "@/components/Modal"; +import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser"; +import useUsers, {userHashStudent, userHashTeacher, userHashCorporate} from "@/hooks/useUsers"; +import {CorporateUser, Group, MasterCorporateUser, Stat, User} from "@/interfaces/user"; +import UserList from "@/pages/(admin)/Lists/UserList"; +import {dateSorter} from "@/utils"; +import moment from "moment"; +import {useEffect, useMemo, useState} from "react"; +import { + BsArrowLeft, + BsClipboard2Data, + BsClipboard2DataFill, + BsClock, + BsGlobeCentralSouthAsia, + BsPaperclip, + BsPerson, + BsPersonAdd, + BsPersonFill, + BsPersonFillGear, + BsPersonGear, + BsPencilSquare, + BsPersonBadge, + BsPersonCheck, + BsPeople, + BsArrowRepeat, + BsPlus, + BsEnvelopePaper, +} from "react-icons/bs"; +import UserCard from "@/components/UserCard"; +import useGroups from "@/hooks/useGroups"; +import {averageLevelCalculator, calculateAverageLevel, calculateBandScore} from "@/utils/score"; +import {MODULE_ARRAY} from "@/utils/moduleUtils"; +import {Module} from "@/interfaces"; +import {groupByExam} from "@/utils/stats"; +import IconCard from "../IconCard"; +import GroupList from "@/pages/(admin)/Lists/GroupList"; +import useFilterStore from "@/stores/listFilterStore"; +import {useRouter} from "next/router"; +import useCodes from "@/hooks/useCodes"; +import {getUserCorporate} from "@/utils/groups"; +import useAssignments from "@/hooks/useAssignments"; +import {Assignment} from "@/interfaces/results"; +import AssignmentView from "../AssignmentView"; +import AssignmentCreator from "../AssignmentCreator"; +import clsx from "clsx"; +import AssignmentCard from "../AssignmentCard"; +import {createColumnHelper} from "@tanstack/react-table"; +import Checkbox from "@/components/Low/Checkbox"; +import List from "@/components/List"; +import {getUserCompanyName} from "@/resources/user"; +import {futureAssignmentFilter, pastAssignmentFilter, archivedAssignmentFilter, activeAssignmentFilter} from "@/utils/assignments"; +import useUserBalance from "@/hooks/useUserBalance"; +import AssignmentsPage from "../views/AssignmentsPage"; + +type StudentPerformanceItem = User & {corporateName: string; group: string}; +const StudentPerformanceList = ({items, stats, users}: {items: StudentPerformanceItem[]; stats: Stat[]; users: User[]}) => { + 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("demographicInformation.passport_id", { + header: "ID", + cell: (info) => info.getValue() || "N/A", + }), + columnHelper.accessor("group", { + header: "Group", + cell: (info) => info.getValue(), + }), + columnHelper.accessor("corporateName", { + header: "Corporate", + 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( + users, + 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( + users, + stats.filter((x) => x.user === b.id), + ) - + averageLevelCalculator( + users, + stats.filter((x) => x.user === a.id), + ), + )} + columns={columns} + /> +
+ ); +}; + +export default StudentPerformanceList; diff --git a/src/dashboards/Corporate/StudentPerformancePage.tsx b/src/dashboards/Corporate/StudentPerformancePage.tsx new file mode 100644 index 00000000..21092ac2 --- /dev/null +++ b/src/dashboards/Corporate/StudentPerformancePage.tsx @@ -0,0 +1,49 @@ +import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser"; +import useGroups from "@/hooks/useGroups"; +import useUsers, {userHashStudent} from "@/hooks/useUsers"; +import {Stat, User} from "@/interfaces/user"; +import {getUserCompanyName} from "@/resources/user"; +import clsx from "clsx"; +import {useRouter} from "next/router"; +import {BsArrowLeft, BsArrowRepeat} from "react-icons/bs"; +import StudentPerformanceList from "./StudentPerformanceList"; + +interface Props { + user: User; +} + +const StudentPerformancePage = ({user}: Props) => { + const {groups} = useGroups({admin: user.id}); + const {users: students, reload: reloadStudents, isLoading: isStudentsLoading} = useUsers(userHashStudent); + 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", + corporateName: getUserCompanyName(user, [], groups), + })); + + return ( + <> +
+
router.push("/")} + className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300"> + + Back +
+
+ Reload + +
+
+ + + ); +}; + +export default StudentPerformancePage; diff --git a/src/dashboards/Corporate/index.tsx b/src/dashboards/Corporate/index.tsx new file mode 100644 index 00000000..60197e58 --- /dev/null +++ b/src/dashboards/Corporate/index.tsx @@ -0,0 +1,399 @@ +/* eslint-disable @next/next/no-img-element */ +import Modal from "@/components/Modal"; +import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser"; +import useUsers, {userHashStudent, userHashTeacher, userHashCorporate} from "@/hooks/useUsers"; +import {CorporateUser, Group, MasterCorporateUser, Stat, User} from "@/interfaces/user"; +import UserList from "@/pages/(admin)/Lists/UserList"; +import {dateSorter} from "@/utils"; +import moment from "moment"; +import {useEffect, useMemo, useState} from "react"; +import { + BsArrowLeft, + BsClipboard2Data, + BsClipboard2DataFill, + BsClock, + BsGlobeCentralSouthAsia, + BsPaperclip, + BsPerson, + BsPersonAdd, + BsPersonFill, + BsPersonFillGear, + BsPersonGear, + BsPencilSquare, + BsPersonBadge, + BsPersonCheck, + BsPeople, + BsArrowRepeat, + BsPlus, + BsEnvelopePaper, +} from "react-icons/bs"; +import UserCard from "@/components/UserCard"; +import useGroups from "@/hooks/useGroups"; +import {averageLevelCalculator, calculateAverageLevel, calculateBandScore} from "@/utils/score"; +import {MODULE_ARRAY} from "@/utils/moduleUtils"; +import {Module} from "@/interfaces"; +import {groupByExam} from "@/utils/stats"; +import IconCard from "../IconCard"; +import GroupList from "@/pages/(admin)/Lists/GroupList"; +import useFilterStore from "@/stores/listFilterStore"; +import {useRouter} from "next/router"; +import useCodes from "@/hooks/useCodes"; +import {getUserCorporate} from "@/utils/groups"; +import useAssignments from "@/hooks/useAssignments"; +import {Assignment} from "@/interfaces/results"; +import AssignmentView from "../AssignmentView"; +import AssignmentCreator from "../AssignmentCreator"; +import clsx from "clsx"; +import AssignmentCard from "../AssignmentCard"; +import {createColumnHelper} from "@tanstack/react-table"; +import Checkbox from "@/components/Low/Checkbox"; +import List from "@/components/List"; +import {getUserCompanyName} from "@/resources/user"; +import {futureAssignmentFilter, pastAssignmentFilter, archivedAssignmentFilter, activeAssignmentFilter} from "@/utils/assignments"; +import useUserBalance from "@/hooks/useUserBalance"; +import AssignmentsPage from "../views/AssignmentsPage"; +import StudentPerformancePage from "./StudentPerformancePage"; + +interface Props { + user: CorporateUser; + linkedCorporate?: CorporateUser | MasterCorporateUser; +} + +const studentHash = { + type: "student", + orderBy: "registrationDate", + size: 25, +}; + +const teacherHash = { + type: "teacher", + orderBy: "registrationDate", + size: 25, +}; + +export default function CorporateDashboard({user, linkedCorporate}: Props) { + const [selectedUser, setSelectedUser] = useState(); + const [showModal, setShowModal] = useState(false); + + const {data: stats} = useFilterRecordsByUser(); + const {groups} = useGroups({admin: user.id}); + const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({corporate: user.id}); + const {balance} = useUserBalance(); + + const {users: students, total: totalStudents, reload: reloadStudents, isLoading: isStudentsLoading} = useUsers(studentHash); + const {users: teachers, total: totalTeachers, reload: reloadTeachers, isLoading: isTeachersLoading} = useUsers(teacherHash); + + const appendUserFilters = useFilterStore((state) => state.appendUserFilter); + const router = useRouter(); + + const assignmentsGroups = useMemo(() => groups.filter((x) => x.admin === user.id || x.participants.includes(user.id)), [groups, user.id]); + + const assignmentsUsers = useMemo( + () => + [...teachers, ...students].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, teachers, students, selectedUser], + ); + + useEffect(() => { + setShowModal(!!selectedUser && router.asPath === "/#"); + }, [selectedUser, router.asPath]); + + const getStatsByStudent = (user: User) => stats.filter((s) => s.user === user.id); + + const UserDisplay = (displayUser: User) => ( +
setSelectedUser(displayUser)} + className="flex w-full p-4 gap-4 items-center hover:bg-mti-purple-ultralight cursor-pointer transition ease-in-out duration-300"> + {displayUser.name} +
+ {displayUser.name} + {displayUser.email} +
+
+ ); + + const GroupsList = () => { + const filter = (x: Group) => x.admin === user.id || x.participants.includes(user.id); + + return ( + <> +
+
router.push("/")} + className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300"> + + Back +
+

Groups ({groups.filter(filter).length})

+
+ + + + ); + }; + + const averageLevelCalculator = (studentStats: Stat[]) => { + const formattedStats = studentStats + .map((s) => ({ + focus: students.find((u) => u.id === s.user)?.focus, + score: s.score, + module: s.module, + })) + .filter((f) => !!f.focus); + const bandScores = formattedStats.map((s) => ({ + module: s.module, + level: calculateBandScore(s.score.correct, s.score.total, s.module, s.focus!), + })); + + const levels: {[key in Module]: number} = { + reading: 0, + listening: 0, + writing: 0, + speaking: 0, + level: 0, + }; + bandScores.forEach((b) => (levels[b.module] += b.level)); + + return calculateAverageLevel(levels); + }; + + if (router.asPath === "/#students") + return ( + ( +
+
router.push("/")} + className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300"> + + Back +
+

Students ({total})

+
+ )} + /> + ); + + if (router.asPath === "/#teachers") + return ( + ( +
+
router.push("/")} + className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300"> + + Back +
+

Teachers ({total})

+
+ )} + /> + ); + + if (router.asPath === "/#groups") return ; + if (router.asPath === "/#studentsPerformance") return ; + + if (router.asPath === "/#assignments") + return ( + router.push("/")} + /> + ); + + return ( + <> + setSelectedUser(undefined)}> + <> + {selectedUser && ( +
+ { + setSelectedUser(undefined); + if (shouldReload && selectedUser!.type === "student") reloadStudents(); + if (shouldReload && selectedUser!.type === "teacher") reloadTeachers(); + }} + onViewStudents={ + selectedUser.type === "corporate" || selectedUser.type === "teacher" + ? () => { + appendUserFilters({ + id: "view-students", + filter: (x: User) => x.type === "student", + }); + appendUserFilters({ + id: "belongs-to-admin", + filter: (x: User) => + groups + .filter((g) => g.admin === selectedUser.id || g.participants.includes(selectedUser.id)) + .flatMap((g) => g.participants) + .includes(x.id), + }); + + router.push("/list/users"); + } + : undefined + } + onViewTeachers={ + selectedUser.type === "corporate" || selectedUser.type === "student" + ? () => { + appendUserFilters({ + id: "view-teachers", + filter: (x: User) => x.type === "teacher", + }); + appendUserFilters({ + id: "belongs-to-admin", + filter: (x: User) => + groups + .filter((g) => g.admin === selectedUser.id || g.participants.includes(selectedUser.id)) + .flatMap((g) => g.participants) + .includes(x.id), + }); + + router.push("/list/users"); + } + : undefined + } + user={selectedUser} + /> +
+ )} + +
+ + <> + {!!linkedCorporate && ( +
+ Linked to: {linkedCorporate?.corporateInformation?.companyInformation.name || linkedCorporate.name} +
+ )} +
+ router.push("/#students")} + isLoading={isStudentsLoading} + Icon={BsPersonFill} + label="Students" + value={totalStudents} + color="purple" + /> + router.push("/#teachers")} + isLoading={isTeachersLoading} + Icon={BsPencilSquare} + label="Teachers" + value={totalTeachers} + color="purple" + /> + groups.flatMap((g) => g.participants).includes(s.user)).length} + color="purple" + /> + groups.flatMap((g) => g.participants).includes(s.user))).toFixed(1)} + color="purple" + /> + router.push("/#groups")} Icon={BsPeople} label="Groups" value={groups.length} color="purple" /> + + + router.push("/#studentsPerformance")} + /> + +
+ +
+
+ Latest students +
+ {students + .sort((a, b) => dateSorter(a, b, "desc", "registrationDate")) + .map((x) => ( + + ))} +
+
+
+ Latest teachers +
+ {teachers + .sort((a, b) => dateSorter(a, b, "desc", "registrationDate")) + .map((x) => ( + + ))} +
+
+
+ Highest level students +
+ {students + .sort((a, b) => calculateAverageLevel(b.levels) - calculateAverageLevel(a.levels)) + .map((x) => ( + + ))} +
+
+
+ Highest exam count students +
+ {students + .sort( + (a, b) => + Object.keys(groupByExam(getStatsByStudent(b))).length - Object.keys(groupByExam(getStatsByStudent(a))).length, + ) + .map((x) => ( + + ))} +
+
+
+ + + ); +} diff --git a/src/dashboards/MasterCorporate/index.tsx b/src/dashboards/MasterCorporate/index.tsx index 2ca8b282..41951c21 100644 --- a/src/dashboards/MasterCorporate/index.tsx +++ b/src/dashboards/MasterCorporate/index.tsx @@ -177,7 +177,6 @@ export default function MasterCorporateDashboard({user}: Props) { corporateAssignments={corporateAssignments} groups={assignmentsGroups} user={user} - users={assignmentsUsers} reloadAssignments={reloadAssignments} isLoading={isAssignmentsLoading} onBack={() => router.push("/")} diff --git a/src/dashboards/Teacher.tsx b/src/dashboards/Teacher.tsx index ca914368..18927ee4 100644 --- a/src/dashboards/Teacher.tsx +++ b/src/dashboards/Teacher.tsx @@ -1,7 +1,7 @@ /* eslint-disable @next/next/no-img-element */ import Modal from "@/components/Modal"; import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser"; -import useUsers, { userHashStudent, userHashTeacher, userHashCorporate } from "@/hooks/useUsers"; +import useUsers, {userHashStudent, userHashTeacher, userHashCorporate} from "@/hooks/useUsers"; import {CorporateUser, Group, MasterCorporateUser, Stat, User} from "@/interfaces/user"; import UserList from "@/pages/(admin)/Lists/UserList"; import {dateSorter} from "@/utils"; @@ -58,6 +58,12 @@ interface Props { linkedCorporate?: CorporateUser | MasterCorporateUser; } +const studentHash = { + type: "student", + orderBy: "registrationDate", + size: 25, +}; + export default function TeacherDashboard({user, linkedCorporate}: Props) { const [selectedUser, setSelectedUser] = useState(); const [showModal, setShowModal] = useState(false); @@ -67,26 +73,13 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) { const {permissions} = usePermissions(user.id); const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({assigner: user.id}); - const {users: students, reload: reloadStudents, isLoading: isStudentsLoading} = useUsers(userHashStudent); + const {users: students, total: totalStudents, reload: reloadStudents, isLoading: isStudentsLoading} = useUsers(studentHash); const appendUserFilters = useFilterStore((state) => state.appendUserFilter); const router = useRouter(); const assignmentsGroups = useMemo(() => groups.filter((x) => x.admin === user.id || x.participants.includes(user.id)), [groups, user.id]); - const assignmentsUsers = useMemo( - () => - students.filter((x) => - !!selectedUser - ? groups - .filter((g) => g.admin === selectedUser.id) - .flatMap((g) => g.participants) - .includes(x.id) - : groups.flatMap((g) => g.participants).includes(x.id), - ), - [groups, students, selectedUser], - ); - useEffect(() => { setShowModal(!!selectedUser && router.asPath === "/#"); }, [selectedUser, router.asPath]); @@ -150,96 +143,36 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) { return calculateAverageLevel(levels); }; - const DefaultDashboard = () => ( - <> - {linkedCorporate && ( -
- Linked to: {linkedCorporate?.corporateInformation?.companyInformation.name || linkedCorporate.name} -
- )} -
- router.push("/#students")} - isLoading={isStudentsLoading} - Icon={BsPersonFill} - label="Students" - value={students.length} - color="purple" - /> - groups.flatMap((g) => g.participants).includes(s.user)).length} - color="purple" - /> - groups.flatMap((g) => g.participants).includes(s.user))).toFixed(1)} - color="purple" - /> - {checkAccess(user, ["teacher", "developer"], permissions, "viewGroup") && ( - x.admin === user.id).length} - color="purple" - onClick={() => router.push("/#groups")} - /> + if (router.asPath === "/#students") + return ( + ( +
+
router.push("/")} + className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300"> + + Back +
+

Students ({total})

+
)} -
router.push("/#assignments")} - className="bg-white rounded-xl shadow p-4 flex flex-col gap-4 items-center w-96 h-52 justify-center cursor-pointer hover:shadow-xl transition ease-in-out duration-300"> - - - Assignments - {assignments.filter((a) => !a.archived).length} - -
-
- -
-
- Latest students -
- {students - .sort((a, b) => dateSorter(a, b, "desc", "registrationDate")) - .map((x) => ( - - ))} -
-
-
- Highest level students -
- {students - .sort((a, b) => calculateAverageLevel(b.levels) - calculateAverageLevel(a.levels)) - .map((x) => ( - - ))} -
-
-
- Highest exam count students -
- {students - .sort( - (a, b) => - Object.keys(groupByExam(getStatsByStudent(b))).length - Object.keys(groupByExam(getStatsByStudent(a))).length, - ) - .map((x) => ( - - ))} -
-
-
- - ); + /> + ); + if (router.asPath === "/#assignments") + return ( + router.push("/")} + /> + ); + if (router.asPath === "/#groups") return ; return ( <> @@ -299,36 +232,95 @@ export default function TeacherDashboard({user, linkedCorporate}: Props) { )} - {router.asPath === "/#students" && ( - ( -
-
router.push("/")} - className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300"> - - Back -
-

Students ({total})

-
+ + <> + {linkedCorporate && ( +
+ Linked to: {linkedCorporate?.corporateInformation?.companyInformation.name || linkedCorporate.name} +
+ )} +
+ router.push("/#students")} + isLoading={isStudentsLoading} + Icon={BsPersonFill} + label="Students" + value={totalStudents} + color="purple" + /> + groups.flatMap((g) => g.participants).includes(s.user)).length} + color="purple" + /> + groups.flatMap((g) => g.participants).includes(s.user))).toFixed(1)} + color="purple" + /> + {checkAccess(user, ["teacher", "developer"], permissions, "viewGroup") && ( + x.admin === user.id).length} + color="purple" + onClick={() => router.push("/#groups")} + /> )} - /> - )} - {router.asPath === "/#groups" && } - {router.asPath === "/#assignments" && ( - router.push("/")} - /> - )} - {router.asPath === "/" && } +
router.push("/#assignments")} + className="bg-white rounded-xl shadow p-4 flex flex-col gap-4 items-center w-96 h-52 justify-center cursor-pointer hover:shadow-xl transition ease-in-out duration-300"> + + + Assignments + {assignments.filter((a) => !a.archived).length} + +
+
+ +
+
+ Latest students +
+ {students + .sort((a, b) => dateSorter(a, b, "desc", "registrationDate")) + .map((x) => ( + + ))} +
+
+
+ Highest level students +
+ {students + .sort((a, b) => calculateAverageLevel(b.levels) - calculateAverageLevel(a.levels)) + .map((x) => ( + + ))} +
+
+
+ Highest exam count students +
+ {students + .sort( + (a, b) => + Object.keys(groupByExam(getStatsByStudent(b))).length - Object.keys(groupByExam(getStatsByStudent(a))).length, + ) + .map((x) => ( + + ))} +
+
+
+ ); } diff --git a/src/dashboards/views/AssignmentsPage.tsx b/src/dashboards/views/AssignmentsPage.tsx index 48690114..07513fb2 100644 --- a/src/dashboards/views/AssignmentsPage.tsx +++ b/src/dashboards/views/AssignmentsPage.tsx @@ -1,233 +1,183 @@ -import { Assignment } from "@/interfaces/results"; -import { CorporateUser, Group, User } from "@/interfaces/user"; -import { getUserCompanyName } from "@/resources/user"; +import useUsers from "@/hooks/useUsers"; +import {Assignment} from "@/interfaces/results"; +import {CorporateUser, Group, User} from "@/interfaces/user"; +import {getUserCompanyName} from "@/resources/user"; import { - activeAssignmentFilter, - archivedAssignmentFilter, - futureAssignmentFilter, - pastAssignmentFilter, - startHasExpiredAssignmentFilter, + activeAssignmentFilter, + archivedAssignmentFilter, + futureAssignmentFilter, + pastAssignmentFilter, + startHasExpiredAssignmentFilter, } from "@/utils/assignments"; import clsx from "clsx"; -import { groupBy } from "lodash"; -import { useState } from "react"; -import { BsArrowLeft, BsArrowRepeat, BsPlus } from "react-icons/bs"; +import {groupBy} from "lodash"; +import {useState} from "react"; +import {BsArrowLeft, BsArrowRepeat, BsPlus} from "react-icons/bs"; import AssignmentCard from "../AssignmentCard"; import AssignmentCreator from "../AssignmentCreator"; import AssignmentView from "../AssignmentView"; interface Props { - assignments: Assignment[]; - corporateAssignments?: ({ corporate?: CorporateUser } & Assignment)[]; - groups: Group[]; - users: User[]; - isLoading: boolean; - user: User; - onBack: () => void; - reloadAssignments: () => void; + assignments: Assignment[]; + corporateAssignments?: ({corporate?: CorporateUser} & Assignment)[]; + groups: Group[]; + isLoading: boolean; + user: User; + onBack: () => void; + reloadAssignments: () => void; } -export default function AssignmentsPage({ - assignments, - corporateAssignments, - user, - groups, - users, - isLoading, - onBack, - reloadAssignments, -}: Props) { - const [selectedAssignment, setSelectedAssignment] = useState(); - const [isCreatingAssignment, setIsCreatingAssignment] = useState(false); +export default function AssignmentsPage({assignments, corporateAssignments, user, groups, isLoading, onBack, reloadAssignments}: Props) { + const [selectedAssignment, setSelectedAssignment] = useState(); + const [isCreatingAssignment, setIsCreatingAssignment] = useState(false); - const displayAssignmentView = !!selectedAssignment && !isCreatingAssignment; + const {users} = useUsers(); - const assignmentsPastExpiredStart = assignments.filter(startHasExpiredAssignmentFilter); + const displayAssignmentView = !!selectedAssignment && !isCreatingAssignment; - return ( - <> - {displayAssignmentView && ( - { - setSelectedAssignment(undefined); - setIsCreatingAssignment(false); - reloadAssignments(); - }} - assignment={selectedAssignment} - /> - )} - {/** I'll be using this is creating assingment as a workaround for a key to trigger a new rendering */} - {isCreatingAssignment && ( - { - setIsCreatingAssignment(false); - setSelectedAssignment(undefined); - reloadAssignments(); - }} - /> - )} -
-
- - Back -
-
- Reload - -
-
-
- Active Assignments Status -
- - Total:{" "} - {assignments - .filter(activeAssignmentFilter) - .reduce((acc, curr) => acc + curr.results.length, 0)} - / - {assignments - .filter(activeAssignmentFilter) - .reduce((acc, curr) => curr.exams.length + acc, 0)} - - {Object.keys( - groupBy(corporateAssignments, (x) => x.corporate?.id) - ).map((x) => ( -
- - {getUserCompanyName( - users.find((u) => u.id === x)!, - users, - groups - )} - :{" "} - - - {groupBy(corporateAssignments, (x) => x.corporate?.id)[ - x - ].reduce((acc, curr) => curr.results.length + acc, 0)} - / - {groupBy(corporateAssignments, (x) => x.corporate?.id)[ - x - ].reduce((acc, curr) => curr.exams.length + acc, 0)} - -
- ))} -
-
-
-

- Active Assignments ( - {assignments.filter(activeAssignmentFilter).length}) -

-
- {assignments.filter(activeAssignmentFilter).map((a) => ( - setSelectedAssignment(a)} - key={a.id} - /> - ))} -
-
-
-

- Planned Assignments ( - {assignments.filter(futureAssignmentFilter).length}) -

-
-
setIsCreatingAssignment(true)} - className="w-[250px] h-[200px] flex flex-col gap-2 items-center justify-center bg-white hover:bg-mti-purple-ultralight text-mti-purple-light hover:text-mti-purple-dark border border-mti-gray-platinum hover:drop-shadow p-4 cursor-pointer rounded-xl transition ease-in-out duration-300" - > - - New Assignment -
- {assignments.filter(futureAssignmentFilter).map((a) => ( - { - setSelectedAssignment(a); - setIsCreatingAssignment(true); - }} - key={a.id} - /> - ))} -
-
-
-

- Past Assignments ({assignments.filter(pastAssignmentFilter).length}) -

-
- {assignments.filter(pastAssignmentFilter).map((a) => ( - setSelectedAssignment(a)} - key={a.id} - allowDownload - reload={reloadAssignments} - allowArchive - allowExcelDownload - /> - ))} -
-
-
-

- Assignments start expired ({assignmentsPastExpiredStart.length}) -

-
- {assignments.filter(startHasExpiredAssignmentFilter).map((a) => ( - setSelectedAssignment(a)} - key={a.id} - allowDownload - reload={reloadAssignments} - allowArchive - allowExcelDownload - /> - ))} -
-
-
-

- Archived Assignments ( - {assignments.filter(archivedAssignmentFilter).length}) -

-
- {assignments.filter(archivedAssignmentFilter).map((a) => ( - setSelectedAssignment(a)} - key={a.id} - allowDownload - reload={reloadAssignments} - allowUnarchive - allowExcelDownload - /> - ))} -
-
- - ); + const assignmentsPastExpiredStart = assignments.filter(startHasExpiredAssignmentFilter); + + return ( + <> + {displayAssignmentView && ( + { + setSelectedAssignment(undefined); + setIsCreatingAssignment(false); + reloadAssignments(); + }} + assignment={selectedAssignment} + /> + )} + {/** I'll be using this is creating assingment as a workaround for a key to trigger a new rendering */} + {isCreatingAssignment && ( + { + setIsCreatingAssignment(false); + setSelectedAssignment(undefined); + reloadAssignments(); + }} + /> + )} +
+
+ + Back +
+
+ Reload + +
+
+
+ Active Assignments Status +
+ + Total: {assignments.filter(activeAssignmentFilter).reduce((acc, curr) => acc + curr.results.length, 0)}/ + {assignments.filter(activeAssignmentFilter).reduce((acc, curr) => curr.exams.length + acc, 0)} + + {Object.keys(groupBy(corporateAssignments, (x) => x.corporate?.id)).map((x) => ( +
+ {getUserCompanyName(users.find((u) => u.id === x)!, users, groups)}: + + {groupBy(corporateAssignments, (x) => x.corporate?.id)[x].reduce((acc, curr) => curr.results.length + acc, 0)}/ + {groupBy(corporateAssignments, (x) => x.corporate?.id)[x].reduce((acc, curr) => curr.exams.length + acc, 0)} + +
+ ))} +
+
+
+

Active Assignments ({assignments.filter(activeAssignmentFilter).length})

+
+ {assignments.filter(activeAssignmentFilter).map((a) => ( + setSelectedAssignment(a)} key={a.id} /> + ))} +
+
+
+

Planned Assignments ({assignments.filter(futureAssignmentFilter).length})

+
+
setIsCreatingAssignment(true)} + className="w-[250px] h-[200px] flex flex-col gap-2 items-center justify-center bg-white hover:bg-mti-purple-ultralight text-mti-purple-light hover:text-mti-purple-dark border border-mti-gray-platinum hover:drop-shadow p-4 cursor-pointer rounded-xl transition ease-in-out duration-300"> + + New Assignment +
+ {assignments.filter(futureAssignmentFilter).map((a) => ( + { + setSelectedAssignment(a); + setIsCreatingAssignment(true); + }} + key={a.id} + /> + ))} +
+
+
+

Past Assignments ({assignments.filter(pastAssignmentFilter).length})

+
+ {assignments.filter(pastAssignmentFilter).map((a) => ( + setSelectedAssignment(a)} + key={a.id} + allowDownload + reload={reloadAssignments} + allowArchive + allowExcelDownload + /> + ))} +
+
+
+

Assignments start expired ({assignmentsPastExpiredStart.length})

+
+ {assignments.filter(startHasExpiredAssignmentFilter).map((a) => ( + setSelectedAssignment(a)} + key={a.id} + allowDownload + reload={reloadAssignments} + allowArchive + allowExcelDownload + /> + ))} +
+
+
+

Archived Assignments ({assignments.filter(archivedAssignmentFilter).length})

+
+ {assignments.filter(archivedAssignmentFilter).map((a) => ( + setSelectedAssignment(a)} + key={a.id} + allowDownload + reload={reloadAssignments} + allowUnarchive + allowExcelDownload + /> + ))} +
+
+ + ); }