diff --git a/package.json b/package.json index 526ca5a6..c351d119 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "react-diff-viewer": "^3.1.1", "react-dom": "18.2.0", "react-firebase-hooks": "^5.1.1", - "react-icons": "^4.8.0", + "react-icons": "^5.3.0", "react-lineto": "^3.3.0", "react-media-recorder": "1.6.5", "react-phone-number-input": "^3.3.6", diff --git a/src/components/DemographicInformationInput.tsx b/src/components/DemographicInformationInput.tsx index 0728ca4e..2505b919 100644 --- a/src/components/DemographicInformationInput.tsx +++ b/src/components/DemographicInformationInput.tsx @@ -106,7 +106,7 @@ export default function DemographicInformationInput({user, mutateUser}: Props) { {user.type === "corporate" && ( - + )} {user.type !== "corporate" && } diff --git a/src/components/Medium/Timer.tsx b/src/components/Medium/Timer.tsx index 58ce4751..b75b6c64 100644 --- a/src/components/Medium/Timer.tsx +++ b/src/components/Medium/Timer.tsx @@ -1,80 +1,80 @@ import useExamStore from "@/stores/examStore"; -import { useEffect, useState } from "react"; -import { motion } from "framer-motion"; +import {useEffect, useState} from "react"; +import {motion} from "framer-motion"; import TimerEndedModal from "../TimerEndedModal"; import clsx from "clsx"; -import { BsStopwatch } from "react-icons/bs"; +import {BsStopwatch} from "react-icons/bs"; interface Props { - minTimer: number; - disableTimer?: boolean; - standalone?: boolean; + minTimer: number; + disableTimer?: boolean; + standalone?: boolean; } const Timer: React.FC = ({minTimer, disableTimer, standalone = false}) => { - const [timer, setTimer] = useState(minTimer * 60); - const [showModal, setShowModal] = useState(false); - const [warningMode, setWarningMode] = useState(false); + const [timer, setTimer] = useState(minTimer * 60); + const [showModal, setShowModal] = useState(false); + const [warningMode, setWarningMode] = useState(false); - const setHasExamEnded = useExamStore((state) => state.setHasExamEnded); - const { timeSpent } = useExamStore((state) => state); + const setHasExamEnded = useExamStore((state) => state.setHasExamEnded); + const {timeSpent} = useExamStore((state) => state); - useEffect(() => setTimer((prev) => prev - timeSpent), [timeSpent]); + useEffect(() => setTimer((prev) => prev - timeSpent), [timeSpent]); - useEffect(() => { - if (!disableTimer) { - const timerInterval = setInterval(() => setTimer((prev) => prev - 1), 1000); + useEffect(() => { + if (!disableTimer) { + const timerInterval = setInterval(() => setTimer((prev) => prev - 1), 1000); - return () => { - clearInterval(timerInterval); - }; - } - }, [disableTimer, minTimer]); + return () => { + clearInterval(timerInterval); + }; + } + }, [disableTimer, minTimer]); - useEffect(() => { - if (timer <= 0) setShowModal(true); - }, [timer]); + useEffect(() => { + if (timer <= 0) setShowModal(true); + }, [timer]); - useEffect(() => { - if (timer < 300 && !warningMode) setWarningMode(true); - }, [timer, warningMode]); + useEffect(() => { + if (timer < 300 && !warningMode) setWarningMode(true); + }, [timer, warningMode]); - return ( - <> - { - setHasExamEnded(true); - setShowModal(false); - }} - /> - - - - {timer > 0 && ( - <> - {Math.floor(timer / 60) - .toString(10) - .padStart(2, "0")} - : - {Math.floor(timer % 60) - .toString(10) - .padStart(2, "0")} - - )} - {timer <= 0 && <>00:00} - - - - ); -} + return ( + <> + { + setHasExamEnded(true); + setShowModal(false); + }} + /> + + + + {timer > 0 && ( + <> + {Math.floor(timer / 60) + .toString(10) + .padStart(2, "0")} + : + {Math.floor(timer % 60) + .toString(10) + .padStart(2, "0")} + + )} + {timer <= 0 && <>00:00} + + + + ); +}; export default Timer; diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx index 6c1320e5..67fe943d 100644 --- a/src/components/Navbar.tsx +++ b/src/components/Navbar.tsx @@ -1,219 +1,163 @@ -import { User } from "@/interfaces/user"; +import {User} from "@/interfaces/user"; import Link from "next/link"; import FocusLayer from "@/components/FocusLayer"; -import { preventNavigation } from "@/utils/navigation.disabled"; -import { useRouter } from "next/router"; -import { BsList, BsQuestionCircle, BsQuestionCircleFill } from "react-icons/bs"; +import {preventNavigation} from "@/utils/navigation.disabled"; +import {useRouter} from "next/router"; +import {BsList, BsQuestionCircle, BsQuestionCircleFill} from "react-icons/bs"; import clsx from "clsx"; import moment from "moment"; import MobileMenu from "./MobileMenu"; -import { useEffect, useState } from "react"; -import { Type } from "@/interfaces/user"; -import { USER_TYPE_LABELS } from "@/resources/user"; +import {useEffect, useState} from "react"; +import {Type} from "@/interfaces/user"; +import {USER_TYPE_LABELS} from "@/resources/user"; import useGroups from "@/hooks/useGroups"; -import { isUserFromCorporate } from "@/utils/groups"; +import {isUserFromCorporate} from "@/utils/groups"; import Button from "./Low/Button"; import Modal from "./Modal"; import Input from "./Low/Input"; import TicketSubmission from "./High/TicketSubmission"; -import { Module } from "@/interfaces"; +import {Module} from "@/interfaces"; import Badge from "./Low/Badge"; -import { - BsArrowRepeat, - BsBook, - BsCheck, - BsCheckCircle, - BsClipboard, - BsHeadphones, - BsMegaphone, - BsPen, - BsXCircle, -} from "react-icons/bs"; +import {BsArrowRepeat, BsBook, BsCheck, BsCheckCircle, BsClipboard, BsHeadphones, BsMegaphone, BsPen, BsXCircle} from "react-icons/bs"; interface Props { - user: User; - navDisabled?: boolean; - focusMode?: boolean; - onFocusLayerMouseEnter?: () => void; - path: string; + user: User; + navDisabled?: boolean; + focusMode?: boolean; + onFocusLayerMouseEnter?: () => void; + path: string; } /* eslint-disable @next/next/no-img-element */ -export default function Navbar({ - user, - path, - navDisabled = false, - focusMode = false, - onFocusLayerMouseEnter, -}: Props) { - const [isMenuOpen, setIsMenuOpen] = useState(false); - const [disablePaymentPage, setDisablePaymentPage] = useState(true); - const [isTicketOpen, setIsTicketOpen] = useState(false); +export default function Navbar({user, path, navDisabled = false, focusMode = false, onFocusLayerMouseEnter}: Props) { + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [disablePaymentPage, setDisablePaymentPage] = useState(true); + const [isTicketOpen, setIsTicketOpen] = useState(false); - const router = useRouter(); + const router = useRouter(); - const disableNavigation = preventNavigation(navDisabled, focusMode); + const disableNavigation = preventNavigation(navDisabled, focusMode); - const expirationDateColor = (date: Date) => { - const momentDate = moment(date); - const today = moment(new Date()); + const expirationDateColor = (date: Date) => { + const momentDate = moment(date); + const today = moment(new Date()); - if (today.add(1, "days").isAfter(momentDate)) - return "!bg-mti-red-ultralight border-mti-red-light"; - if (today.add(3, "days").isAfter(momentDate)) - return "!bg-mti-rose-ultralight border-mti-rose-light"; - if (today.add(7, "days").isAfter(momentDate)) - return "!bg-mti-orange-ultralight border-mti-orange-light"; - }; + if (today.add(1, "days").isAfter(momentDate)) return "!bg-mti-red-ultralight border-mti-red-light"; + if (today.add(3, "days").isAfter(momentDate)) return "!bg-mti-rose-ultralight border-mti-rose-light"; + if (today.add(7, "days").isAfter(momentDate)) return "!bg-mti-orange-ultralight border-mti-orange-light"; + }; - const showExpirationDate = () => { - if (!user.subscriptionExpirationDate) return false; + const showExpirationDate = () => { + if (!user.subscriptionExpirationDate) return false; - const momentDate = moment(user.subscriptionExpirationDate); - const today = moment(new Date()); + const momentDate = moment(user.subscriptionExpirationDate); + const today = moment(new Date()); - return today.add(7, "days").isAfter(momentDate); - }; + return today.add(7, "days").isAfter(momentDate); + }; - useEffect(() => { - if (user.type !== "student" && user.type !== "teacher") - return setDisablePaymentPage(false); - isUserFromCorporate(user.id).then((result) => - setDisablePaymentPage(result) - ); - }, [user]); + useEffect(() => { + if (user.type !== "student" && user.type !== "teacher") return setDisablePaymentPage(false); + isUserFromCorporate(user.id).then((result) => setDisablePaymentPage(result)); + }, [user]); - const badges = [ - { - module: "reading", - icon: () => , - achieved: user.levels.reading >= user.desiredLevels.reading, - }, + const badges = [ + { + module: "reading", + icon: () => , + achieved: user.levels.reading >= user.desiredLevels.reading, + }, - { - module: "listening", - icon: () => , - achieved: user.levels.listening >= user.desiredLevels.listening, - }, - { - module: "writing", - icon: () => , - achieved: user.levels.writing >= user.desiredLevels.writing, - }, - { - module: "speaking", - icon: () => , - achieved: user.levels.speaking >= user.desiredLevels.speaking, - }, - { - module: "level", - icon: () => , - achieved: user.levels.level >= user.desiredLevels.level, - }, - ]; + { + module: "listening", + icon: () => , + achieved: user.levels.listening >= user.desiredLevels.listening, + }, + { + module: "writing", + icon: () => , + achieved: user.levels.writing >= user.desiredLevels.writing, + }, + { + module: "speaking", + icon: () => , + achieved: user.levels.speaking >= user.desiredLevels.speaking, + }, + { + module: "level", + icon: () => , + achieved: user.levels.level >= user.desiredLevels.level, + }, + ]; - return ( - <> - setIsTicketOpen(false)} - title="Submit a ticket" - > - setIsTicketOpen(false)} - /> - + return ( + <> + setIsTicketOpen(false)} title="Submit a ticket"> + setIsTicketOpen(false)} /> + - {user && ( - setIsMenuOpen(false)} - user={user} - /> - )} -
- - EnCoach's Logo -

EnCoach

- -
- {user.type === "student" && - badges.map((badge) => ( -
- {badge.icon()} -
- ))} - {/* OPEN TICKET SYSTEM */} - + {user && ( + setIsMenuOpen(false)} user={user} /> + )} +
+ + EnCoach's Logo +

EnCoach

+ +
+ {user.type === "student" && + badges.map((badge) => ( +
+ {badge.icon()} +
+ ))} + {/* OPEN TICKET SYSTEM */} + - {showExpirationDate() && ( - - {!user.subscriptionExpirationDate && "Unlimited"} - {user.subscriptionExpirationDate && - moment(user.subscriptionExpirationDate).format("DD/MM/YYYY")} - - )} - - {user.name} - - {user.type === "corporate" - ? `${user.corporateInformation?.companyInformation.name} |` - : ""}{" "} - {user.name} | {USER_TYPE_LABELS[user.type]} - - -
setIsMenuOpen(true)} - > - -
-
- {focusMode && ( - - )} -
- - ); + {showExpirationDate() && ( + + {!user.subscriptionExpirationDate && "Unlimited"} + {user.subscriptionExpirationDate && moment(user.subscriptionExpirationDate).format("DD/MM/YYYY")} + + )} + + {user.name} + + {user.type === "corporate" ? `${user.corporateInformation?.companyInformation.name} |` : ""} {user.name} |{" "} + {USER_TYPE_LABELS[user.type]} + {user.type === "corporate" && + !!user.demographicInformation?.position && + ` | ${user.demographicInformation?.position || "N/A"}`} + + +
setIsMenuOpen(true)}> + +
+
+ {focusMode && } +
+ + ); } diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index cf1bdeee..19d5d40f 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -212,7 +212,7 @@ export default function Sidebar({path, navDisabled = false, focusMode = false, u )} -
+
{ - if (user.type === "corporate" || (user.type === "mastercorporate" && (!paymentValue || paymentValue < 0))) + if ((user.type === "corporate" || user.type === "mastercorporate") && (!paymentValue || paymentValue < 0)) return toast.error("Please set a price for the user's package before updating!"); + if (!confirm(`Are you sure you want to update ${user.name}'s account?`)) return; axios @@ -213,7 +214,7 @@ const UserCard = ({ }, { icon: , - value: user.corporateInformation.companyInformation.userAmount, + value: user.corporateInformation?.companyInformation?.userAmount, label: "Number of Users", }, ] @@ -277,7 +278,15 @@ const UserCard = ({ onChange={setCompanyName} placeholder="Enter corporate name" defaultValue={companyName} - disabled={disabled || checkAccess(loggedInUser, getTypesOfUser(["developer", "admin"]))} + disabled={ + disabled || + checkAccess( + loggedInUser, + getTypesOfUser( + user.type === "mastercorporate" ? ["developer", "admin"] : ["developer", "admin", "mastercorporate"], + ), + ) + } /> void; } -export default function AssignmentCreator({isCreating, assignment, assigner, groups, users, cancelCreation}: Props) { +export default function AssignmentCreator({isCreating, assignment, groups, users, cancelCreation}: Props) { const [selectedModules, setSelectedModules] = useState(assignment?.exams.map((e) => e.module) || []); const [assignees, setAssignees] = useState(assignment?.assignees || []); const [name, setName] = useState( diff --git a/src/dashboards/Corporate.tsx b/src/dashboards/Corporate.tsx index 2f0c0f5e..fa961a46 100644 --- a/src/dashboards/Corporate.tsx +++ b/src/dashboards/Corporate.tsx @@ -2,805 +2,636 @@ import Modal from "@/components/Modal"; import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser"; import useUsers from "@/hooks/useUsers"; -import { CorporateUser, Group, Stat, User } from "@/interfaces/user"; +import {CorporateUser, Group, MasterCorporateUser, Stat, User} from "@/interfaces/user"; import UserList from "@/pages/(admin)/Lists/UserList"; -import { dateSorter } from "@/utils"; +import {dateSorter} from "@/utils"; import moment from "moment"; -import { useEffect, useState } from "react"; +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, + 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 {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 {useRouter} from "next/router"; import useCodes from "@/hooks/useCodes"; -import { getUserCorporate } from "@/utils/groups"; +import {getUserCorporate} from "@/utils/groups"; import useAssignments from "@/hooks/useAssignments"; -import { Assignment } from "@/interfaces/results"; +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 {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 {getUserCompanyName} from "@/resources/user"; +import {futureAssignmentFilter, pastAssignmentFilter, archivedAssignmentFilter, activeAssignmentFilter} from "@/utils/assignments"; import useUserBalance from "@/hooks/useUserBalance"; interface Props { - user: CorporateUser; + 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); +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 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`, - }), - ]; + 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} - /> -
- ); + 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 }: Props) { - const [page, setPage] = useState(""); - const [selectedUser, setSelectedUser] = useState(); - const [showModal, setShowModal] = useState(false); - const [corporateUserToShow, setCorporateUserToShow] = - useState(); - const [selectedAssignment, setSelectedAssignment] = useState(); - const [isCreatingAssignment, setIsCreatingAssignment] = useState(false); +export default function CorporateDashboard({user, linkedCorporate}: Props) { + const [page, setPage] = useState(""); + const [selectedUser, setSelectedUser] = useState(); + const [showModal, setShowModal] = useState(false); + const [selectedAssignment, setSelectedAssignment] = useState(); + const [isCreatingAssignment, setIsCreatingAssignment] = useState(false); - const { data: stats } = useFilterRecordsByUser(); - const { users, reload, isLoading } = useUsers(); - const { codes } = useCodes(user.id); - const { groups } = useGroups({ admin: user.id }); - const { - assignments, - isLoading: isAssignmentsLoading, - reload: reloadAssignments, - } = useAssignments({ corporate: user.id }); - const { balance } = useUserBalance(); + const {data: stats} = useFilterRecordsByUser(); + const {users, reload, isLoading} = useUsers(); + const {codes} = useCodes(user.id); + const {groups} = useGroups({admin: user.id}); + const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({corporate: user.id}); + const {balance} = useUserBalance(); - const appendUserFilters = useFilterStore((state) => state.appendUserFilter); - const router = useRouter(); + const appendUserFilters = useFilterStore((state) => state.appendUserFilter); + const router = useRouter(); - useEffect(() => { - setShowModal(!!selectedUser && page === ""); - }, [selectedUser, page]); + const assignmentsGroups = useMemo(() => groups.filter((x) => x.admin === user.id || x.participants.includes(user.id)), [groups, user.id]); - useEffect(() => { - // in this case it fetches the master corporate account - getUserCorporate(user.id).then(setCorporateUserToShow); - }, [user]); + const assignmentsUsers = useMemo( + () => + users.filter( + (x) => + x.type === "student" && + (!!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, users, selectedUser], + ); - const studentFilter = (user: User) => - user.type === "student" && - groups.flatMap((g) => g.participants).includes(user.id); - const teacherFilter = (user: User) => - user.type === "teacher" && - groups.flatMap((g) => g.participants).includes(user.id); + useEffect(() => { + setShowModal(!!selectedUser && page === ""); + }, [selectedUser, page]); - const getStatsByStudent = (user: User) => - stats.filter((s) => s.user === user.id); + const studentFilter = (user: User) => user.type === "student" && groups.flatMap((g) => g.participants).includes(user.id); + const teacherFilter = (user: User) => user.type === "teacher" && groups.flatMap((g) => g.participants).includes(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 getStatsByStudent = (user: User) => stats.filter((s) => s.user === user.id); - const StudentsList = () => { - const filter = (x: User) => - x.type === "student" && - (!!selectedUser - ? groups - .filter((g) => g.admin === selectedUser.id) - .flatMap((g) => g.participants) - .includes(x.id) || false - : groups.flatMap((g) => g.participants).includes(x.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} +
+
+ ); - return ( - ( -
-
setPage("")} - 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})

-
- )} - /> - ); - }; + const StudentsList = () => { + const filter = (x: User) => + x.type === "student" && + (!!selectedUser + ? groups + .filter((g) => g.admin === selectedUser.id) + .flatMap((g) => g.participants) + .includes(x.id) || false + : groups.flatMap((g) => g.participants).includes(x.id)); - const TeachersList = () => { - const filter = (x: User) => - x.type === "teacher" && - (!!selectedUser - ? groups - .filter((g) => g.admin === selectedUser.id) - .flatMap((g) => g.participants) - .includes(x.id) || false - : groups.flatMap((g) => g.participants).includes(x.id)); + return ( + ( +
+
setPage("")} + 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})

+
+ )} + /> + ); + }; - return ( - ( -
-
setPage("")} - 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})

-
- )} - /> - ); - }; + const TeachersList = () => { + const filter = (x: User) => + x.type === "teacher" && + (!!selectedUser + ? groups + .filter((g) => g.admin === selectedUser.id) + .flatMap((g) => g.participants) + .includes(x.id) || false + : groups.flatMap((g) => g.participants).includes(x.id)); - const GroupsList = () => { - const filter = (x: Group) => - x.admin === user.id || x.participants.includes(user.id); + return ( + ( +
+
setPage("")} + 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})

+
+ )} + /> + ); + }; - return ( - <> -
-
setPage("")} - 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 GroupsList = () => { + const filter = (x: Group) => x.admin === user.id || x.participants.includes(user.id); - - - ); - }; + return ( + <> +
+
setPage("")} + 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 AssignmentsPage = () => { - return ( - <> - { - setSelectedAssignment(undefined); - setIsCreatingAssignment(false); - reloadAssignments(); - }} - assignment={selectedAssignment} - /> - x.admin === user.id || x.participants.includes(user.id) - )} - users={users.filter( - (x) => - x.type === "student" && - (!!selectedUser - ? groups - .filter((g) => g.admin === selectedUser.id) - .flatMap((g) => g.participants) - .includes(x.id) || false - : groups.flatMap((g) => g.participants).includes(x.id)) - )} - assigner={user.id} - isCreating={isCreatingAssignment} - cancelCreation={() => { - setIsCreatingAssignment(false); - setSelectedAssignment(undefined); - reloadAssignments(); - }} - /> -
-
setPage("")} - 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 - -
-
-
-

- 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 - /> - ))} -
-
-
-

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

-
- {assignments.filter(archivedAssignmentFilter).map((a) => ( - setSelectedAssignment(a)} - key={a.id} - allowDownload - reload={reloadAssignments} - allowUnarchive - allowExcelDownload - /> - ))} -
-
- - ); - }; + + + ); + }; - const StudentPerformancePage = () => { - const students = users - .filter( - (x) => - x.type === "student" && - groups.flatMap((g) => g.participants).includes(x.id) - ) - .map((u) => ({ - ...u, - group: groups.find((x) => x.participants.includes(u.id))?.name || "N/A", - corporateName: getUserCompanyName(u, users, groups), - })); + const AssignmentsPage = () => { + return ( + <> + { + setSelectedAssignment(undefined); + setIsCreatingAssignment(false); + reloadAssignments(); + }} + assignment={selectedAssignment} + /> + { + setIsCreatingAssignment(false); + setSelectedAssignment(undefined); + reloadAssignments(); + }} + /> +
+
setPage("")} + 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 + +
+
+
+

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 + /> + ))} +
+
+
+

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

+
+ {assignments.filter(archivedAssignmentFilter).map((a) => ( + setSelectedAssignment(a)} + key={a.id} + allowDownload + reload={reloadAssignments} + allowUnarchive + allowExcelDownload + /> + ))} +
+
+ + ); + }; - return ( - <> -
-
setPage("")} - 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 StudentPerformancePage = () => { + const students = users + .filter((x) => x.type === "student" && groups.flatMap((g) => g.participants).includes(x.id)) + .map((u) => ({ + ...u, + group: groups.find((x) => x.participants.includes(u.id))?.name || "N/A", + corporateName: getUserCompanyName(u, users, groups), + })); - const averageLevelCalculator = (studentStats: Stat[]) => { - const formattedStats = studentStats - .map((s) => ({ - focus: users.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! - ), - })); + return ( + <> +
+
setPage("")} + 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 levels: { [key in Module]: number } = { - reading: 0, - listening: 0, - writing: 0, - speaking: 0, - level: 0, - }; - bandScores.forEach((b) => (levels[b.module] += b.level)); + const averageLevelCalculator = (studentStats: Stat[]) => { + const formattedStats = studentStats + .map((s) => ({ + focus: users.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!), + })); - return calculateAverageLevel(levels); - }; + const levels: {[key in Module]: number} = { + reading: 0, + listening: 0, + writing: 0, + speaking: 0, + level: 0, + }; + bandScores.forEach((b) => (levels[b.module] += b.level)); - const DefaultDashboard = () => ( - <> - {corporateUserToShow && ( -
- Linked to:{" "} - - {corporateUserToShow?.corporateInformation?.companyInformation - .name || corporateUserToShow.name} - -
- )} -
- setPage("students")} - Icon={BsPersonFill} - label="Students" - value={users.filter(studentFilter).length} - color="purple" - /> - setPage("teachers")} - Icon={BsPencilSquare} - label="Teachers" - value={users.filter(teacherFilter).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" - /> - setPage("groups")} - Icon={BsPeople} - label="Groups" - value={groups.length} - color="purple" - /> - - - setPage("studentsPerformance")} - /> - -
+ return calculateAverageLevel(levels); + }; -
-
- Latest students -
- {users - .filter(studentFilter) - .sort((a, b) => dateSorter(a, b, "desc", "registrationDate")) - .map((x) => ( - - ))} -
-
-
- Latest teachers -
- {users - .filter(teacherFilter) - .sort((a, b) => dateSorter(a, b, "desc", "registrationDate")) - .map((x) => ( - - ))} -
-
-
- Highest level students -
- {users - .filter(studentFilter) - .sort( - (a, b) => - calculateAverageLevel(b.levels) - - calculateAverageLevel(a.levels) - ) - .map((x) => ( - - ))} -
-
-
- Highest exam count students -
- {users - .filter(studentFilter) - .sort( - (a, b) => - Object.keys(groupByExam(getStatsByStudent(b))).length - - Object.keys(groupByExam(getStatsByStudent(a))).length - ) - .map((x) => ( - - ))} -
-
-
- - ); + const DefaultDashboard = () => ( + <> + {!!linkedCorporate && ( +
+ Linked to: {linkedCorporate?.corporateInformation?.companyInformation.name || linkedCorporate.name} +
+ )} +
+ setPage("students")} + Icon={BsPersonFill} + label="Students" + value={users.filter(studentFilter).length} + color="purple" + /> + setPage("teachers")} + Icon={BsPencilSquare} + label="Teachers" + value={users.filter(teacherFilter).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" + /> + setPage("groups")} Icon={BsPeople} label="Groups" value={groups.length} color="purple" /> + + + setPage("studentsPerformance")} + /> + +
- return ( - <> - setSelectedUser(undefined)}> - <> - {selectedUser && ( -
- { - setSelectedUser(undefined); - if (shouldReload) reload(); - }} - 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), - }); +
+
+ Latest students +
+ {users + .filter(studentFilter) + .sort((a, b) => dateSorter(a, b, "desc", "registrationDate")) + .map((x) => ( + + ))} +
+
+
+ Latest teachers +
+ {users + .filter(teacherFilter) + .sort((a, b) => dateSorter(a, b, "desc", "registrationDate")) + .map((x) => ( + + ))} +
+
+
+ Highest level students +
+ {users + .filter(studentFilter) + .sort((a, b) => calculateAverageLevel(b.levels) - calculateAverageLevel(a.levels)) + .map((x) => ( + + ))} +
+
+
+ Highest exam count students +
+ {users + .filter(studentFilter) + .sort( + (a, b) => + Object.keys(groupByExam(getStatsByStudent(b))).length - Object.keys(groupByExam(getStatsByStudent(a))).length, + ) + .map((x) => ( + + ))} +
+
+
+ + ); - 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), - }); + return ( + <> + setSelectedUser(undefined)}> + <> + {selectedUser && ( +
+ { + setSelectedUser(undefined); + if (shouldReload) reload(); + }} + 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 - } - user={selectedUser} - /> -
- )} - -
- {page === "students" && } - {page === "teachers" && } - {page === "groups" && } - {page === "assignments" && } - {page === "studentsPerformance" && } - {page === "" && } - - ); + 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} + /> +
+ )} + +
+ {page === "students" && } + {page === "teachers" && } + {page === "groups" && } + {page === "assignments" && } + {page === "studentsPerformance" && } + {page === "" && } + + ); } diff --git a/src/dashboards/MasterCorporate.tsx b/src/dashboards/MasterCorporate.tsx index 9c2a0554..f3dd3534 100644 --- a/src/dashboards/MasterCorporate.tsx +++ b/src/dashboards/MasterCorporate.tsx @@ -2,1103 +2,841 @@ import Modal from "@/components/Modal"; import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser"; import useUsers from "@/hooks/useUsers"; -import { - CorporateUser, - Group, - MasterCorporateUser, - Stat, - User, -} from "@/interfaces/user"; +import {CorporateUser, Group, MasterCorporateUser, Stat, User} from "@/interfaces/user"; import UserList from "@/pages/(admin)/Lists/UserList"; -import { dateSorter } from "@/utils"; +import {dateSorter} from "@/utils"; import moment from "moment"; -import { useEffect, useState, useMemo } from "react"; +import {useEffect, useState, useMemo} from "react"; import { - BsArrowLeft, - BsClipboard2Data, - BsClock, - BsPaperclip, - BsPersonFill, - BsPencilSquare, - BsPersonCheck, - BsPeople, - BsBank, - BsEnvelopePaper, - BsArrowRepeat, - BsPlus, - BsPersonFillGear, - BsFilter, - BsDatabase, + BsArrowLeft, + BsClipboard2Data, + BsClock, + BsPaperclip, + BsPersonFill, + BsPencilSquare, + BsPersonCheck, + BsPeople, + BsBank, + BsEnvelopePaper, + BsArrowRepeat, + BsPlus, + BsPersonFillGear, + BsFilter, + BsDatabase, } 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 {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 {useRouter} from "next/router"; import useCodes from "@/hooks/useCodes"; import useAssignments from "@/hooks/useAssignments"; -import { Assignment } from "@/interfaces/results"; +import {Assignment} from "@/interfaces/results"; import AssignmentView from "./AssignmentView"; import AssignmentCreator from "./AssignmentCreator"; import clsx from "clsx"; import AssignmentCard from "./AssignmentCard"; -import { createColumn, createColumnHelper } from "@tanstack/react-table"; +import {createColumn, createColumnHelper} from "@tanstack/react-table"; import List from "@/components/List"; -import { getUserCorporate } from "@/utils/groups"; -import { getCorporateUser, getUserCompanyName } from "@/resources/user"; +import {getUserCorporate} from "@/utils/groups"; +import {getCorporateUser, getUserCompanyName} from "@/resources/user"; import Checkbox from "@/components/Low/Checkbox"; -import { groupBy, uniq, uniqBy } from "lodash"; +import {groupBy, uniq, uniqBy} from "lodash"; import Select from "@/components/Low/Select"; -import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/react"; -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover"; +import {Menu, MenuButton, MenuItem, MenuItems} from "@headlessui/react"; +import {Popover, PopoverContent, PopoverTrigger} from "@/components/ui/popover"; import MasterStatistical from "./MasterStatistical"; -import { - futureAssignmentFilter, - pastAssignmentFilter, - archivedAssignmentFilter, - activeAssignmentFilter, -} from "@/utils/assignments"; +import {futureAssignmentFilter, pastAssignmentFilter, archivedAssignmentFilter, activeAssignmentFilter} from "@/utils/assignments"; import useUserBalance from "@/hooks/useUserBalance"; interface Props { - user: MasterCorporateUser; + user: MasterCorporateUser; } type StudentPerformanceItem = User & { - corporate?: CorporateUser; - group?: Group; + corporate?: CorporateUser; + group?: Group; }; -const StudentPerformanceList = ({ - items, - stats, - users, - groups, -}: { - items: StudentPerformanceItem[]; - stats: Stat[]; - users: User[]; - groups: Group[]; -}) => { - const [isShowingAmount, setIsShowingAmount] = useState(false); - const [availableCorporates] = useState( - uniqBy( - items.map((x) => x.corporate), - "id" - ) - ); - const [availableGroups] = useState( - uniqBy( - items.map((x) => x.group), - "id" - ) - ); +const StudentPerformanceList = ({items, stats, users, groups}: {items: StudentPerformanceItem[]; stats: Stat[]; users: User[]; groups: Group[]}) => { + const [isShowingAmount, setIsShowingAmount] = useState(false); + const [availableCorporates] = useState( + uniqBy( + items.map((x) => x.corporate), + "id", + ), + ); + const [availableGroups] = useState( + uniqBy( + items.map((x) => x.group), + "id", + ), + ); - const [selectedCorporate, setSelectedCorporate] = useState< - CorporateUser | null | undefined - >(null); - const [selectedGroup, setSelectedGroup] = useState( - null - ); + const [selectedCorporate, setSelectedCorporate] = useState(null); + const [selectedGroup, setSelectedGroup] = useState(null); - const columnHelper = createColumnHelper(); + 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()?.name || "N/A", - }), - columnHelper.accessor("corporate", { - header: "Corporate", - cell: (info) => - !!info.getValue() - ? getUserCompanyName(info.getValue() as User, users, groups) - : "N/A", - }), - columnHelper.accessor("levels.reading", { - header: "Reading", - cell: (info) => - !isShowingAmount - ? calculateBandScore( - stats - .filter( - (x) => - x.module === "reading" && x.user === info.row.original.id - ) - .reduce((acc, curr) => acc + curr.score.correct, 0), - stats - .filter( - (x) => - x.module === "reading" && x.user === info.row.original.id - ) - .reduce((acc, curr) => acc + curr.score.total, 0), - "level", - info.row.original.focus || "academic" - ) || 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 - ? calculateBandScore( - stats - .filter( - (x) => - x.module === "listening" && x.user === info.row.original.id - ) - .reduce((acc, curr) => acc + curr.score.correct, 0), - stats - .filter( - (x) => - x.module === "listening" && x.user === info.row.original.id - ) - .reduce((acc, curr) => acc + curr.score.total, 0), - "level", - info.row.original.focus || "academic" - ) || 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 - ? calculateBandScore( - stats - .filter( - (x) => - x.module === "writing" && x.user === info.row.original.id - ) - .reduce((acc, curr) => acc + curr.score.correct, 0), - stats - .filter( - (x) => - x.module === "writing" && x.user === info.row.original.id - ) - .reduce((acc, curr) => acc + curr.score.total, 0), - "level", - info.row.original.focus || "academic" - ) || 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 - ? calculateBandScore( - stats - .filter( - (x) => - x.module === "speaking" && x.user === info.row.original.id - ) - .reduce((acc, curr) => acc + curr.score.correct, 0), - stats - .filter( - (x) => - x.module === "speaking" && x.user === info.row.original.id - ) - .reduce((acc, curr) => acc + curr.score.total, 0), - "level", - info.row.original.focus || "academic" - ) || 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 - ? calculateBandScore( - stats - .filter( - (x) => x.module === "level" && x.user === info.row.original.id - ) - .reduce((acc, curr) => acc + curr.score.correct, 0), - stats - .filter( - (x) => x.module === "level" && x.user === info.row.original.id - ) - .reduce((acc, curr) => acc + curr.score.total, 0), - "level", - info.row.original.focus || "academic" - ) || 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`, - }), - ]; + 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()?.name || "N/A", + }), + columnHelper.accessor("corporate", { + header: "Corporate", + cell: (info) => (!!info.getValue() ? getUserCompanyName(info.getValue() as User, users, groups) : "N/A"), + }), + columnHelper.accessor("levels.reading", { + header: "Reading", + cell: (info) => + !isShowingAmount + ? calculateBandScore( + stats + .filter((x) => x.module === "reading" && x.user === info.row.original.id) + .reduce((acc, curr) => acc + curr.score.correct, 0), + stats + .filter((x) => x.module === "reading" && x.user === info.row.original.id) + .reduce((acc, curr) => acc + curr.score.total, 0), + "level", + info.row.original.focus || "academic", + ) || 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 + ? calculateBandScore( + stats + .filter((x) => x.module === "listening" && x.user === info.row.original.id) + .reduce((acc, curr) => acc + curr.score.correct, 0), + stats + .filter((x) => x.module === "listening" && x.user === info.row.original.id) + .reduce((acc, curr) => acc + curr.score.total, 0), + "level", + info.row.original.focus || "academic", + ) || 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 + ? calculateBandScore( + stats + .filter((x) => x.module === "writing" && x.user === info.row.original.id) + .reduce((acc, curr) => acc + curr.score.correct, 0), + stats + .filter((x) => x.module === "writing" && x.user === info.row.original.id) + .reduce((acc, curr) => acc + curr.score.total, 0), + "level", + info.row.original.focus || "academic", + ) || 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 + ? calculateBandScore( + stats + .filter((x) => x.module === "speaking" && x.user === info.row.original.id) + .reduce((acc, curr) => acc + curr.score.correct, 0), + stats + .filter((x) => x.module === "speaking" && x.user === info.row.original.id) + .reduce((acc, curr) => acc + curr.score.total, 0), + "level", + info.row.original.focus || "academic", + ) || 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 + ? calculateBandScore( + stats + .filter((x) => x.module === "level" && x.user === info.row.original.id) + .reduce((acc, curr) => acc + curr.score.correct, 0), + stats + .filter((x) => x.module === "level" && x.user === info.row.original.id) + .reduce((acc, curr) => acc + curr.score.total, 0), + "level", + info.row.original.focus || "academic", + ) || 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`, + }), + ]; - const filterUsers = (data: StudentPerformanceItem[]) => { - console.log(data, selectedCorporate); - const filterByCorporate = (item: StudentPerformanceItem) => - item.corporate?.id === selectedCorporate?.id; - const filterByGroup = (item: StudentPerformanceItem) => - item.group?.id === selectedGroup?.id; + const filterUsers = (data: StudentPerformanceItem[]) => { + console.log(data, selectedCorporate); + const filterByCorporate = (item: StudentPerformanceItem) => item.corporate?.id === selectedCorporate?.id; + const filterByGroup = (item: StudentPerformanceItem) => item.group?.id === selectedGroup?.id; - const filters: ((item: StudentPerformanceItem) => boolean)[] = []; - if (selectedCorporate !== null) filters.push(filterByCorporate); - if (selectedGroup !== null) filters.push(filterByGroup); + const filters: ((item: StudentPerformanceItem) => boolean)[] = []; + if (selectedCorporate !== null) filters.push(filterByCorporate); + if (selectedGroup !== null) filters.push(filterByGroup); - return filters.reduce((d, f) => d.filter(f), data); - }; + return filters.reduce((d, f) => d.filter(f), data); + }; - return ( -
-
- - Show Utilization - - - -
- -
-
- -
- Filters - ({ - value: x?.id || "N/A", - label: x?.name || "N/A", - }))} - isClearable - value={ - selectedGroup === null - ? null - : { - value: selectedGroup?.id || "N/A", - label: selectedGroup?.name || "N/A", - } - } - placeholder="Select a Group..." - onChange={(value) => - !value - ? setSelectedGroup(null) - : setSelectedGroup( - value.value === "N/A" - ? undefined - : availableGroups.find((x) => x?.id === value.value) - ) - } - /> -
-
-
-
- - data={filterUsers( - items.sort( - (a, b) => - averageLevelCalculator( - users, - stats.filter((x) => x.user === b.id) - ) - - averageLevelCalculator( - users, - stats.filter((x) => x.user === a.id) - ) - ) - )} - columns={columns} - /> -
- ); + return ( +
+
+ + Show Utilization + + + +
+ +
+
+ +
+ Filters + ({ + value: x?.id || "N/A", + label: x?.name || "N/A", + }))} + isClearable + value={ + selectedGroup === null + ? null + : { + value: selectedGroup?.id || "N/A", + label: selectedGroup?.name || "N/A", + } + } + placeholder="Select a Group..." + onChange={(value) => + !value + ? setSelectedGroup(null) + : setSelectedGroup(value.value === "N/A" ? undefined : availableGroups.find((x) => x?.id === value.value)) + } + /> +
+
+
+
+ + data={filterUsers( + 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 MasterCorporateDashboard({ user }: Props) { - const [page, setPage] = useState(""); - const [selectedUser, setSelectedUser] = useState(); - const [showModal, setShowModal] = useState(false); - const [selectedAssignment, setSelectedAssignment] = useState(); - const [isCreatingAssignment, setIsCreatingAssignment] = useState(false); - const [corporateAssignments, setCorporateAssignments] = useState< - (Assignment & { corporate?: CorporateUser })[] - >([]); +export default function MasterCorporateDashboard({user}: Props) { + const [page, setPage] = useState(""); + const [selectedUser, setSelectedUser] = useState(); + const [showModal, setShowModal] = useState(false); + const [selectedAssignment, setSelectedAssignment] = useState(); + const [isCreatingAssignment, setIsCreatingAssignment] = useState(false); + const [corporateAssignments, setCorporateAssignments] = useState<(Assignment & {corporate?: CorporateUser})[]>([]); - const { data: stats } = useFilterRecordsByUser(); - const { users, reload } = useUsers(); - const { codes } = useCodes(user.id); - const { groups } = useGroups({ admin: user.id, userType: user.type }); - const { balance } = useUserBalance(); + const {data: stats} = useFilterRecordsByUser(); + const {users, reload} = useUsers(); + const {groups} = useGroups({admin: user.id, userType: user.type}); + const {balance} = useUserBalance(); - const masterCorporateUserGroups = useMemo(() => [ - ...new Set( - groups.filter((u) => u.admin === user.id).flatMap((g) => g.participants) - ), - ], [groups, user.id]); + const masterCorporateUserGroups = useMemo( + () => [...new Set(groups.filter((u) => u.admin === user.id).flatMap((g) => g.participants))], + [groups, user.id], + ); - const corporateUserGroups = [ - ...new Set(groups.flatMap((g) => g.participants)), - ]; + const corporateUserGroups = useMemo(() => [...new Set(groups.flatMap((g) => g.participants))], [groups]); - const { - assignments, - isLoading: isAssignmentsLoading, - reload: reloadAssignments, - } = useAssignments({ corporate: user.id }); + const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({corporate: user.id}); - 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( + () => + users.filter( + (x) => + x.type === "student" && + (!!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, users, selectedUser], + ); - useEffect(() => { - setShowModal(!!selectedUser && page === ""); - }, [selectedUser, page]); + const appendUserFilters = useFilterStore((state) => state.appendUserFilter); + const router = useRouter(); - useEffect(() => { - setCorporateAssignments( - assignments.filter(activeAssignmentFilter).map((a) => ({ - ...a, - corporate: !!users.find((x) => x.id === a.assigner) - ? getCorporateUser( - users.find((x) => x.id === a.assigner)!, - users, - groups - ) - : undefined, - })) - ); - }, [assignments, groups, users]); + useEffect(() => { + setShowModal(!!selectedUser && page === ""); + }, [selectedUser, page]); - const studentFilter = (user: User) => - user.type === "student" && corporateUserGroups.includes(user.id); - const teacherFilter = (user: User) => - user.type === "teacher" && corporateUserGroups.includes(user.id); - const getStatsByStudent = (user: User) => - stats.filter((s) => s.user === user.id); + useEffect(() => { + setCorporateAssignments( + assignments.filter(activeAssignmentFilter).map((a) => ({ + ...a, + corporate: !!users.find((x) => x.id === a.assigner) + ? getCorporateUser(users.find((x) => x.id === a.assigner)!, users, groups) + : undefined, + })), + ); + }, [assignments, groups, users]); - 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 studentFilter = (user: User) => user.type === "student" && corporateUserGroups.includes(user.id); + const teacherFilter = (user: User) => user.type === "teacher" && corporateUserGroups.includes(user.id); + const getStatsByStudent = (user: User) => stats.filter((s) => s.user === user.id); - const StudentsList = () => { - const filter = (x: User) => - x.type === "student" && - (!!selectedUser - ? corporateUserGroups.includes(x.id) || false - : corporateUserGroups.includes(x.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} +
+
+ ); - return ( - ( -
-
setPage("")} - 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})

-
- )} - /> - ); - }; + const StudentsList = () => { + const filter = (x: User) => + x.type === "student" && (!!selectedUser ? corporateUserGroups.includes(x.id) || false : corporateUserGroups.includes(x.id)); - const TeachersList = () => { - const filter = (x: User) => - x.type === "teacher" && - (!!selectedUser - ? corporateUserGroups.includes(x.id) || false - : corporateUserGroups.includes(x.id)); + return ( + ( +
+
setPage("")} + 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})

+
+ )} + /> + ); + }; - return ( - ( -
-
setPage("")} - 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})

-
- )} - /> - ); - }; + const TeachersList = () => { + const filter = (x: User) => + x.type === "teacher" && (!!selectedUser ? corporateUserGroups.includes(x.id) || false : corporateUserGroups.includes(x.id)); - const corporateUserFilter = (x: User) => - x.type === "corporate" && - (!!selectedUser - ? masterCorporateUserGroups.includes(x.id) || false - : masterCorporateUserGroups.includes(x.id)); + return ( + ( +
+
setPage("")} + 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})

+
+ )} + /> + ); + }; - const CorporateList = () => { - return ( - ( -
-
setPage("")} - className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300" - > - - Back -
-

Corporates ({total})

-
- )} - /> - ); - }; + const corporateUserFilter = (x: User) => + x.type === "corporate" && (!!selectedUser ? masterCorporateUserGroups.includes(x.id) || false : masterCorporateUserGroups.includes(x.id)); - const GroupsList = () => { - return ( - <> -
-
setPage("")} - 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.length})

-
+ const CorporateList = () => { + return ( + ( +
+
setPage("")} + className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300"> + + Back +
+

Corporates ({total})

+
+ )} + /> + ); + }; - - - ); - }; + const GroupsList = () => { + return ( + <> +
+
setPage("")} + 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.length})

+
- const StudentPerformancePage = () => { - const students = users - .filter( - (x) => - x.type === "student" && - groups.flatMap((g) => g.participants).includes(x.id) - ) - .map((u) => ({ - ...u, - group: groups.find((x) => x.participants.includes(u.id)), - corporate: getCorporateUser(u, users, groups), - })); + + + ); + }; - return ( - <> -
-
setPage("")} - 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 StudentPerformancePage = () => { + const students = users + .filter((x) => x.type === "student" && groups.flatMap((g) => g.participants).includes(x.id)) + .map((u) => ({ + ...u, + group: groups.find((x) => x.participants.includes(u.id)), + corporate: getCorporateUser(u, users, groups), + })); - const AssignmentsPage = () => { - return ( - <> - { - setSelectedAssignment(undefined); - setIsCreatingAssignment(false); - reloadAssignments(); - }} - assignment={selectedAssignment} - /> - x.admin === user.id || x.participants.includes(user.id) - )} - users={users.filter( - (x) => - x.type === "student" && - (!!selectedUser - ? groups - .filter((g) => g.admin === selectedUser.id) - .flatMap((g) => g.participants) - .includes(x.id) || false - : groups.flatMap((g) => g.participants).includes(x.id)) - )} - assigner={user.id} - isCreating={isCreatingAssignment} - cancelCreation={() => { - setIsCreatingAssignment(false); - setSelectedAssignment(undefined); - reloadAssignments(); - }} - /> -
-
setPage("")} - 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 - -
-
-
- 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 - /> - ))} -
-
-
-

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

-
- {assignments.filter(archivedAssignmentFilter).map((a) => ( - setSelectedAssignment(a)} - key={a.id} - allowDownload - reload={reloadAssignments} - allowUnarchive - allowExcelDownload - /> - ))} -
-
- - ); - }; + return ( + <> +
+
setPage("")} + 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 masterCorporateUsers = useMemo( - () => - masterCorporateUserGroups.reduce((accm: CorporateUser[], id) => { - const user = users.find((u) => u.id === id) as CorporateUser; - if (user) return [...accm, user]; - return accm; - }, []), - [masterCorporateUserGroups, users] - ); + const AssignmentsPage = () => { + return ( + <> + { + setSelectedAssignment(undefined); + setIsCreatingAssignment(false); + reloadAssignments(); + }} + assignment={selectedAssignment} + /> + { + setIsCreatingAssignment(false); + setSelectedAssignment(undefined); + reloadAssignments(); + }} + /> +
+
setPage("")} + 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 + +
+
+
+ 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 + /> + ))} +
+
+
+

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

+
+ {assignments.filter(archivedAssignmentFilter).map((a) => ( + setSelectedAssignment(a)} + key={a.id} + allowDownload + reload={reloadAssignments} + allowUnarchive + allowExcelDownload + /> + ))} +
+
+ + ); + }; - const MasterStatisticalPage = () => { - return ( - <> -
-
setPage("")} - className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300" - > - - Back -
-

Master Statistical

-
- - - ); - }; + const masterCorporateUsers = useMemo( + () => + masterCorporateUserGroups.reduce((accm: CorporateUser[], id) => { + const user = users.find((u) => u.id === id) as CorporateUser; + if (user) return [...accm, user]; + return accm; + }, []), + [masterCorporateUserGroups, users], + ); - const DefaultDashboard = () => ( - <> -
- setPage("students")} - Icon={BsPersonFill} - label="Students" - value={users.filter(studentFilter).length} - color="purple" - /> - setPage("teachers")} - Icon={BsPencilSquare} - label="Teachers" - value={users.filter(teacherFilter).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" - /> - setPage("groups")} - Icon={BsPeople} - label="Groups" - value={groups.length} - color="purple" - /> - - - setPage("corporate")} - /> - setPage("studentsPerformance")} - /> - setPage("statistical")} - /> - -
+ const MasterStatisticalPage = () => { + return ( + <> +
+
setPage("")} + className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300"> + + Back +
+

Master Statistical

+
+ + + ); + }; -
-
- Latest students -
- {users - .filter(studentFilter) - .sort((a, b) => dateSorter(a, b, "desc", "registrationDate")) - .map((x) => ( - - ))} -
-
-
- Latest teachers -
- {users - .filter(teacherFilter) - .sort((a, b) => dateSorter(a, b, "desc", "registrationDate")) - .map((x) => ( - - ))} -
-
-
- Highest level students -
- {users - .filter(studentFilter) - .sort( - (a, b) => - calculateAverageLevel(b.levels) - - calculateAverageLevel(a.levels) - ) - .map((x) => ( - - ))} -
-
-
- Highest exam count students -
- {users - .filter(studentFilter) - .sort( - (a, b) => - Object.keys(groupByExam(getStatsByStudent(b))).length - - Object.keys(groupByExam(getStatsByStudent(a))).length - ) - .map((x) => ( - - ))} -
-
-
- - ); + const DefaultDashboard = () => ( + <> +
+ setPage("students")} + Icon={BsPersonFill} + label="Students" + value={users.filter(studentFilter).length} + color="purple" + /> + setPage("teachers")} + Icon={BsPencilSquare} + label="Teachers" + value={users.filter(teacherFilter).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" + /> + setPage("groups")} Icon={BsPeople} label="Groups" value={groups.length} color="purple" /> + + + setPage("corporate")} + /> + setPage("studentsPerformance")} + /> + setPage("statistical")} + /> + +
- return ( - <> - setSelectedUser(undefined)}> - <> - {selectedUser && ( -
- { - setSelectedUser(undefined); - if (shouldReload) reload(); - }} - 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), - }); +
+
+ Latest students +
+ {users + .filter(studentFilter) + .sort((a, b) => dateSorter(a, b, "desc", "registrationDate")) + .map((x) => ( + + ))} +
+
+
+ Latest teachers +
+ {users + .filter(teacherFilter) + .sort((a, b) => dateSorter(a, b, "desc", "registrationDate")) + .map((x) => ( + + ))} +
+
+
+ Highest level students +
+ {users + .filter(studentFilter) + .sort((a, b) => calculateAverageLevel(b.levels) - calculateAverageLevel(a.levels)) + .map((x) => ( + + ))} +
+
+
+ Highest exam count students +
+ {users + .filter(studentFilter) + .sort( + (a, b) => + Object.keys(groupByExam(getStatsByStudent(b))).length - Object.keys(groupByExam(getStatsByStudent(a))).length, + ) + .map((x) => ( + + ))} +
+
+
+ + ); - 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), - }); + return ( + <> + setSelectedUser(undefined)}> + <> + {selectedUser && ( +
+ { + setSelectedUser(undefined); + if (shouldReload) reload(); + }} + 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 - } - user={selectedUser} - /> -
- )} - -
- {page === "students" && } - {page === "teachers" && } - {page === "groups" && } - {page === "corporate" && } - {page === "assignments" && } - {page === "studentsPerformance" && } - {page === "statistical" && } - {page === "" && } - - ); + 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} + /> +
+ )} + +
+ {page === "students" && } + {page === "teachers" && } + {page === "groups" && } + {page === "corporate" && } + {page === "assignments" && } + {page === "studentsPerformance" && } + {page === "statistical" && } + {page === "" && } + + ); } diff --git a/src/dashboards/Student.tsx b/src/dashboards/Student.tsx index 5fd2a862..805a2d3b 100644 --- a/src/dashboards/Student.tsx +++ b/src/dashboards/Student.tsx @@ -9,7 +9,7 @@ import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser"; import useUsers from "@/hooks/useUsers"; import {Invite} from "@/interfaces/invite"; import {Assignment} from "@/interfaces/results"; -import {CorporateUser, Stat, User} from "@/interfaces/user"; +import {CorporateUser, MasterCorporateUser, Stat, User} from "@/interfaces/user"; import useExamStore from "@/stores/examStore"; import {getExamById} from "@/utils/exams"; import {getUserCorporate} from "@/utils/groups"; @@ -30,11 +30,10 @@ import {toast} from "react-toastify"; interface Props { user: User; + linkedCorporate?: CorporateUser | MasterCorporateUser; } -export default function StudentDashboard({user}: Props) { - const [corporateUserToShow, setCorporateUserToShow] = useState(); - +export default function StudentDashboard({user, linkedCorporate}: Props) { const {users} = useUsers(); const {gradingSystem} = useGradingSystem(); const {data: stats} = useFilterRecordsByUser(user.id, !user?.id); @@ -49,10 +48,6 @@ export default function StudentDashboard({user}: Props) { const setSelectedModules = useExamStore((state) => state.setSelectedModules); const setAssignment = useExamStore((state) => state.setAssignment); - useEffect(() => { - getUserCorporate(user.id).then(setCorporateUserToShow); - }, [user]); - const startAssignment = (assignment: Assignment) => { const examPromises = assignment.exams.filter((e) => e.assignee === user.id).map((e) => getExamById(e.module, e.id)); @@ -76,9 +71,9 @@ export default function StudentDashboard({user}: Props) { return ( <> - {corporateUserToShow && ( + {linkedCorporate && (
- Linked to: {corporateUserToShow?.corporateInformation?.companyInformation.name || corporateUserToShow.name} + Linked to: {linkedCorporate?.corporateInformation?.companyInformation.name || linkedCorporate.name}
)} (); - const [showModal, setShowModal] = useState(false); - const [selectedAssignment, setSelectedAssignment] = useState(); - const [isCreatingAssignment, setIsCreatingAssignment] = useState(false); - const [corporateUserToShow, setCorporateUserToShow] = - useState(); +export default function TeacherDashboard({user, linkedCorporate}: Props) { + const [page, setPage] = useState(""); + const [selectedUser, setSelectedUser] = useState(); + const [showModal, setShowModal] = useState(false); + const [selectedAssignment, setSelectedAssignment] = useState(); + const [isCreatingAssignment, setIsCreatingAssignment] = useState(false); - const { data: stats } = useFilterRecordsByUser(); - const { users, reload } = useUsers(); - const { groups } = useGroups({ adminAdmins: user.id }); - const { permissions } = usePermissions(user.id); - const { - assignments, - isLoading: isAssignmentsLoading, - reload: reloadAssignments, - } = useAssignments({ assigner: user.id }); + const {data: stats} = useFilterRecordsByUser(); + const {users, reload} = useUsers(); + const {groups} = useGroups({adminAdmins: user.id}); + const {permissions} = usePermissions(user.id); + const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({assigner: user.id}); - useEffect(() => { - setShowModal(!!selectedUser && page === ""); - }, [selectedUser, page]); + const assignmentsGroups = useMemo(() => groups.filter((x) => x.admin === user.id || x.participants.includes(user.id)), [groups, user.id]); - useEffect(() => { - getUserCorporate(user.id).then(setCorporateUserToShow); - }, [user]); + const assignmentsUsers = useMemo( + () => + users.filter( + (x) => + x.type === "student" && + (!!selectedUser + ? groups + .filter((g) => g.admin === selectedUser.id) + .flatMap((g) => g.participants) + .includes(x.id) + : groups.flatMap((g) => g.participants).includes(x.id)), + ), + [groups, users, selectedUser], + ); - const studentFilter = (user: User) => - user.type === "student" && - groups.flatMap((g) => g.participants).includes(user.id); + useEffect(() => { + setShowModal(!!selectedUser && page === ""); + }, [selectedUser, page]); - const getStatsByStudent = (user: User) => - stats.filter((s) => s.user === user.id); + const studentFilter = (user: User) => user.type === "student" && groups.flatMap((g) => g.participants).includes(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 getStatsByStudent = (user: User) => stats.filter((s) => s.user === user.id); - const StudentsList = () => { - const filter = (x: User) => - x.type === "student" && - (!!selectedUser - ? groups - .filter((g) => g.admin === selectedUser.id) - .flatMap((g) => g.participants) - .includes(x.id) || false - : groups.flatMap((g) => g.participants).includes(x.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} +
+
+ ); - return ( - ( -
-
setPage("")} - 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})

-
- )} - /> - ); - }; + const StudentsList = () => { + const filter = (x: User) => + x.type === "student" && + (!!selectedUser + ? groups + .filter((g) => g.admin === selectedUser.id) + .flatMap((g) => g.participants) + .includes(x.id) || false + : groups.flatMap((g) => g.participants).includes(x.id)); - const GroupsList = () => { - const filter = (x: Group) => x.admin === user.id; + return ( + ( +
+
setPage("")} + 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})

+
+ )} + /> + ); + }; - return ( - <> -
-
setPage("")} - 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 GroupsList = () => { + const filter = (x: Group) => x.admin === user.id; - - - ); - }; + return ( + <> +
+
setPage("")} + 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: users.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)); + const averageLevelCalculator = (studentStats: Stat[]) => { + const formattedStats = studentStats + .map((s) => ({ + focus: users.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!), + })); - return calculateAverageLevel(levels); - }; + const levels: {[key in Module]: number} = { + reading: 0, + listening: 0, + writing: 0, + speaking: 0, + level: 0, + }; + bandScores.forEach((b) => (levels[b.module] += b.level)); - const AssignmentsPage = () => { - return ( - <> - { - setSelectedAssignment(undefined); - setIsCreatingAssignment(false); - reloadAssignments(); - }} - assignment={selectedAssignment} - /> - x.admin === user.id || x.participants.includes(user.id) - )} - users={users.filter( - (x) => - x.type === "student" && - (!!selectedUser - ? groups - .filter((g) => g.admin === selectedUser.id) - .flatMap((g) => g.participants) - .includes(x.id) - : groups.flatMap((g) => g.participants).includes(x.id)) - )} - assigner={user.id} - isCreating={isCreatingAssignment} - cancelCreation={() => { - setIsCreatingAssignment(false); - setSelectedAssignment(undefined); - reloadAssignments(); - }} - /> -
-
setPage("")} - 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 - -
-
-
-

- 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 - /> - ))} -
-
-
-

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

-
- {assignments.filter(archivedAssignmentFilter).map((a) => ( - setSelectedAssignment(a)} - key={a.id} - allowDownload - reload={reloadAssignments} - allowUnarchive - allowExcelDownload - /> - ))} -
-
- - ); - }; + return calculateAverageLevel(levels); + }; - const DefaultDashboard = () => ( - <> - {corporateUserToShow && ( -
- Linked to:{" "} - - {corporateUserToShow?.corporateInformation?.companyInformation - .name || corporateUserToShow.name} - -
- )} -
- setPage("students")} - Icon={BsPersonFill} - label="Students" - value={users.filter(studentFilter).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={() => setPage("groups")} - /> - )} -
setPage("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} - - -
-
+ const AssignmentsPage = () => { + return ( + <> + { + setSelectedAssignment(undefined); + setIsCreatingAssignment(false); + reloadAssignments(); + }} + assignment={selectedAssignment} + /> + { + setIsCreatingAssignment(false); + setSelectedAssignment(undefined); + reloadAssignments(); + }} + /> +
+
setPage("")} + 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 + +
+
+
+

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 + /> + ))} +
+
+
+

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

+
+ {assignments.filter(archivedAssignmentFilter).map((a) => ( + setSelectedAssignment(a)} + key={a.id} + allowDownload + reload={reloadAssignments} + allowUnarchive + allowExcelDownload + /> + ))} +
+
+ + ); + }; -
-
- Latest students -
- {users - .filter(studentFilter) - .sort((a, b) => dateSorter(a, b, "desc", "registrationDate")) - .map((x) => ( - - ))} -
-
-
- Highest level students -
- {users - .filter(studentFilter) - .sort( - (a, b) => - calculateAverageLevel(b.levels) - - calculateAverageLevel(a.levels) - ) - .map((x) => ( - - ))} -
-
-
- Highest exam count students -
- {users - .filter(studentFilter) - .sort( - (a, b) => - Object.keys(groupByExam(getStatsByStudent(b))).length - - Object.keys(groupByExam(getStatsByStudent(a))).length - ) - .map((x) => ( - - ))} -
-
-
- - ); + const DefaultDashboard = () => ( + <> + {linkedCorporate && ( +
+ Linked to: {linkedCorporate?.corporateInformation?.companyInformation.name || linkedCorporate.name} +
+ )} +
+ setPage("students")} + Icon={BsPersonFill} + label="Students" + value={users.filter(studentFilter).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={() => setPage("groups")} + /> + )} +
setPage("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} + +
+
- return ( - <> - setSelectedUser(undefined)}> - <> - {selectedUser && ( -
- { - setSelectedUser(undefined); - if (shouldReload) reload(); - }} - onViewStudents={ - selectedUser.type === "corporate" || - selectedUser.type === "teacher" - ? () => setPage("students") - : undefined - } - onViewTeachers={ - selectedUser.type === "corporate" - ? () => setPage("teachers") - : undefined - } - user={selectedUser} - /> -
- )} - -
- {page === "students" && } - {page === "groups" && } - {page === "assignments" && } - {page === "" && } - - ); +
+
+ Latest students +
+ {users + .filter(studentFilter) + .sort((a, b) => dateSorter(a, b, "desc", "registrationDate")) + .map((x) => ( + + ))} +
+
+
+ Highest level students +
+ {users + .filter(studentFilter) + .sort((a, b) => calculateAverageLevel(b.levels) - calculateAverageLevel(a.levels)) + .map((x) => ( + + ))} +
+
+
+ Highest exam count students +
+ {users + .filter(studentFilter) + .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) reload(); + }} + onViewStudents={ + selectedUser.type === "corporate" || selectedUser.type === "teacher" ? () => setPage("students") : undefined + } + onViewTeachers={selectedUser.type === "corporate" ? () => setPage("teachers") : undefined} + user={selectedUser} + /> +
+ )} + +
+ {page === "students" && } + {page === "groups" && } + {page === "assignments" && } + {page === "" && } + + ); } diff --git a/src/pages/(admin)/BatchCodeGenerator.tsx b/src/pages/(admin)/BatchCodeGenerator.tsx index ac6764b4..f10dc2c1 100644 --- a/src/pages/(admin)/BatchCodeGenerator.tsx +++ b/src/pages/(admin)/BatchCodeGenerator.tsx @@ -55,7 +55,14 @@ const USER_TYPE_PERMISSIONS: { }, }; -export default function BatchCodeGenerator({user, onFinish}: {user: User; onFinish: () => void}) { +interface Props { + user: User; + users: User[]; + permissions: PermissionType[]; + onFinish: () => void; +} + +export default function BatchCodeGenerator({user, users, permissions, onFinish}: Props) { const [infos, setInfos] = useState<{email: string; name: string; passport_id: string}[]>([]); const [isLoading, setIsLoading] = useState(false); const [expiryDate, setExpiryDate] = useState( @@ -65,9 +72,6 @@ export default function BatchCodeGenerator({user, onFinish}: {user: User; onFini const [type, setType] = useState("student"); const [showHelp, setShowHelp] = useState(false); - const {users} = useUsers(); - const {permissions} = usePermissions(user?.id || ""); - const {openFilePicker, filesContent, clear} = useFilePicker({ accept: ".xlsx", multiple: false, diff --git a/src/pages/(admin)/BatchCreateUser.tsx b/src/pages/(admin)/BatchCreateUser.tsx index bf08962a..03417eba 100644 --- a/src/pages/(admin)/BatchCreateUser.tsx +++ b/src/pages/(admin)/BatchCreateUser.tsx @@ -61,7 +61,14 @@ const USER_TYPE_PERMISSIONS: { }, }; -export default function BatchCreateUser({user, onFinish}: {user: User; onFinish: () => void}) { +interface Props { + user: User; + users: User[]; + permissions: PermissionType[]; + onFinish: () => void; +} + +export default function BatchCreateUser({user, users, permissions, onFinish}: Props) { const [infos, setInfos] = useState< { email: string; @@ -83,9 +90,6 @@ export default function BatchCreateUser({user, onFinish}: {user: User; onFinish: const [type, setType] = useState("student"); const [showHelp, setShowHelp] = useState(false); - const {users} = useUsers(); - const {permissions} = usePermissions(user?.id || ""); - const {openFilePicker, filesContent, clear} = useFilePicker({ accept: ".xlsx", multiple: false, diff --git a/src/pages/(admin)/CodeGenerator.tsx b/src/pages/(admin)/CodeGenerator.tsx index cbd1b5fb..c1799783 100644 --- a/src/pages/(admin)/CodeGenerator.tsx +++ b/src/pages/(admin)/CodeGenerator.tsx @@ -48,14 +48,19 @@ const USER_TYPE_PERMISSIONS: { }, }; -export default function CodeGenerator({user, onFinish}: {user: User; onFinish: () => void}) { +interface Props { + user: User; + permissions: PermissionType[]; + onFinish: () => void; +} + +export default function CodeGenerator({user, permissions, onFinish}: Props) { const [generatedCode, setGeneratedCode] = useState(); const [expiryDate, setExpiryDate] = useState( user?.subscriptionExpirationDate ? moment(user.subscriptionExpirationDate).toDate() : null, ); const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true); const [type, setType] = useState("student"); - const {permissions} = usePermissions(user?.id || ""); useEffect(() => { if (!isExpiryDateEnabled) setExpiryDate(null); diff --git a/src/pages/(admin)/Lists/GroupList.tsx b/src/pages/(admin)/Lists/GroupList.tsx index 54948f7a..3f7c1a6a 100644 --- a/src/pages/(admin)/Lists/GroupList.tsx +++ b/src/pages/(admin)/Lists/GroupList.tsx @@ -7,7 +7,7 @@ import {CorporateUser, Group, User} from "@/interfaces/user"; import {createColumnHelper, flexRender, getCoreRowModel, useReactTable} from "@tanstack/react-table"; import axios from "axios"; import {capitalize, uniq} from "lodash"; -import {useEffect, useState} from "react"; +import {useEffect, useMemo, useState} from "react"; import {BsPencil, BsQuestionCircleFill, BsTrash} from "react-icons/bs"; import Select from "react-select"; import {toast} from "react-toastify"; @@ -65,6 +65,14 @@ const CreatePanel = ({user, users, group, onClose}: CreateDialogProps) => { readAs: "ArrayBuffer", }); + const availableUsers = useMemo(() => { + if (user.type === "teacher") return users.filter((x) => ["student"].includes(x.type)); + if (user.type === "corporate") return users.filter((x) => ["teacher", "student"].includes(x.type)); + if (user.type === "mastercorporate") return users.filter((x) => ["corporate", "teacher", "student"].includes(x.type)); + + return users; + }, [user, users]); + useEffect(() => { if (filesContent.length > 0) { setIsLoading(true); @@ -155,15 +163,7 @@ const CreatePanel = ({user, users, group, onClose}: CreateDialogProps) => { value: x, label: `${users.find((y) => y.id === x)?.email} - ${users.find((y) => y.id === x)?.name}`, }))} - options={users - .filter((x) => - user.type === "teacher" - ? x.type === "student" - : user.type === "corporate" - ? x.type === "student" || x.type === "teacher" - : x.type === "student" || x.type === "teacher" || x.type === "corporate", - ) - .map((x) => ({value: x.id, label: `${x.email} - ${x.name}`}))} + options={availableUsers.map((x) => ({value: x.id, label: `${x.email} - ${x.name}`}))} onChange={(value) => setParticipants(value.map((x) => x.value))} isMulti isSearchable diff --git a/src/pages/(admin)/Lists/index.tsx b/src/pages/(admin)/Lists/index.tsx index e9626763..477e40e0 100644 --- a/src/pages/(admin)/Lists/index.tsx +++ b/src/pages/(admin)/Lists/index.tsx @@ -9,10 +9,15 @@ import PackageList from "./PackageList"; import UserList from "./UserList"; import {checkAccess} from "@/utils/permissions"; import usePermissions from "@/hooks/usePermissions"; +import {PermissionType} from "@/interfaces/permissions"; -export default function Lists({user}: {user: User}) { - const {permissions} = usePermissions(user?.id || ""); +interface Props { + user: User; + users: User[]; + permissions: PermissionType[]; +} +export default function Lists({user, users, permissions}: Props) { return ( diff --git a/src/pages/(admin)/UserCreator.tsx b/src/pages/(admin)/UserCreator.tsx index a5a61db4..38dd4f3c 100644 --- a/src/pages/(admin)/UserCreator.tsx +++ b/src/pages/(admin)/UserCreator.tsx @@ -54,7 +54,14 @@ const USER_TYPE_PERMISSIONS: { }, }; -export default function UserCreator({user, onFinish}: {user: User; onFinish: () => void}) { +interface Props { + user: User; + users: User[]; + permissions: PermissionType[]; + onFinish: () => void; +} + +export default function UserCreator({user, users, permissions, onFinish}: Props) { const [name, setName] = useState(); const [email, setEmail] = useState(); const [phone, setPhone] = useState(); @@ -72,10 +79,9 @@ export default function UserCreator({user, onFinish}: {user: User; onFinish: () const [isExpiryDateEnabled, setIsExpiryDateEnabled] = useState(true); const [isLoading, setIsLoading] = useState(false); const [type, setType] = useState("student"); + const [position, setPosition] = useState(); - const {permissions} = usePermissions(user?.id || ""); const {groups} = useGroups({admin: ["developer", "admin"].includes(user?.type) ? undefined : user?.id, userType: user?.type}); - const {users} = useUsers(); useEffect(() => { if (!isExpiryDateEnabled) setExpiryDate(null); @@ -104,6 +110,7 @@ export default function UserCreator({user, onFinish}: {user: User; onFinish: () email, password, groupID: group, + corporate: selectedCorporate || user.id, type, studentID: type === "student" ? studentID : undefined, expiryDate, @@ -111,6 +118,7 @@ export default function UserCreator({user, onFinish}: {user: User; onFinish: () passport_id: type === "student" ? passportID : undefined, phone, country, + position, }, }; @@ -131,6 +139,7 @@ export default function UserCreator({user, onFinish}: {user: User; onFinish: () setExpiryDate(user?.subscriptionExpirationDate ? moment(user?.subscriptionExpirationDate).toDate() : null); setIsExpiryDateEnabled(true); setType("student"); + setPosition(undefined); }) .catch(() => toast.error("Something went wrong! Please try again later!")) .finally(() => setIsLoading(false)); @@ -186,10 +195,16 @@ export default function UserCreator({user, onFinish}: {user: User; onFinish: ()
)} + {["corporate", "mastercorporate"].includes(type) && ( + + )} +
({ - value: u, - label: USER_TYPE_LABELS[u], - }))} - value={{ - value: selectedScreen, - label: USER_TYPE_LABELS[selectedScreen], - }} - onChange={(value) => - value - ? setSelectedScreen(value.value) - : setSelectedScreen("admin") - } - /> + if (user && showDiagnostics) { + return ( + <> + + EnCoach + + + + + + setShowDiagnostics(false)} /> + + + ); + } - {selectedScreen === "student" && } - {selectedScreen === "teacher" && } - {selectedScreen === "corporate" && ( - - )} - {selectedScreen === "mastercorporate" && ( - - )} - {selectedScreen === "agent" && } - {selectedScreen === "admin" && } - - )} - - )} - - ); + return ( + <> + + EnCoach + + + + + + {user && ( + + {checkAccess(user, ["student"]) && } + {checkAccess(user, ["teacher"]) && } + {checkAccess(user, ["corporate"]) && } + {checkAccess(user, ["mastercorporate"]) && } + {checkAccess(user, ["agent"]) && } + {checkAccess(user, ["admin"]) && } + {checkAccess(user, ["developer"]) && ( + <> + setCorporateInformation((prev) => ({ ...prev!, @@ -499,9 +517,10 @@ function UserProfile({user, mutateUser}: Props) { onChange={setPosition} defaultValue={position} type="text" - label="Position" + label="Department" placeholder="CEO, Head of Marketing..." required + disabled={!!linkedCorporate} /> @@ -638,7 +657,7 @@ function UserProfile({user, mutateUser}: Props) { ); } -export default function Home() { +export default function Home(props: {linkedCorporate?: CorporateUser | MasterCorporateUser; groups: Group[]; users: User[]}) { const {user, mutateUser} = useUser({redirectTo: "/login"}); return ( @@ -653,7 +672,7 @@ export default function Home() { - {user && } + {user && } ); } diff --git a/src/pages/record.tsx b/src/pages/record.tsx index 798655ca..43d41d12 100644 --- a/src/pages/record.tsx +++ b/src/pages/record.tsx @@ -1,28 +1,31 @@ /* eslint-disable @next/next/no-img-element */ import Head from "next/head"; -import { withIronSessionSsr } from "iron-session/next"; -import { sessionOptions } from "@/lib/session"; -import { Stat, User } from "@/interfaces/user"; -import { useEffect, useRef, useState } from "react"; +import {withIronSessionSsr} from "iron-session/next"; +import {sessionOptions} from "@/lib/session"; +import {Stat, User} from "@/interfaces/user"; +import {useEffect, useRef, useState} from "react"; import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser"; -import { groupByDate } from "@/utils/stats"; +import {groupByDate} from "@/utils/stats"; import moment from "moment"; import useUsers from "@/hooks/useUsers"; import useExamStore from "@/stores/examStore"; -import { ToastContainer } from "react-toastify"; +import {ToastContainer} from "react-toastify"; import Layout from "@/components/High/Layout"; import clsx from "clsx"; -import { shouldRedirectHome } from "@/utils/navigation.disabled"; +import {shouldRedirectHome} from "@/utils/navigation.disabled"; import useAssignments from "@/hooks/useAssignments"; -import { uuidv4 } from "@firebase/util"; -import { usePDFDownload } from "@/hooks/usePDFDownload"; +import {uuidv4} from "@firebase/util"; +import {usePDFDownload} from "@/hooks/usePDFDownload"; import useRecordStore from "@/stores/recordStore"; import StatsGridItem from "@/components/Medium/StatGridItem"; import RecordFilter from "@/components/Medium/RecordFilter"; -import { useRouter } from "next/router"; +import {useRouter} from "next/router"; import useTrainingContentStore from "@/stores/trainingContentStore"; +import {Assignment} from "@/interfaces/results"; +import {getUsers} from "@/utils/users.be"; +import {getAssignments, getAssignmentsByAssigner} from "@/utils/assignments.be"; -export const getServerSideProps = withIronSessionSsr(({ req, res }) => { +export const getServerSideProps = withIronSessionSsr(async ({req, res}) => { const user = req.session.user; if (!user || !user.isVerified) { @@ -43,14 +46,23 @@ export const getServerSideProps = withIronSessionSsr(({ req, res }) => { }; } + const users = await getUsers(); + const assignments = await getAssignments(); + return { - props: { user: req.session.user }, + props: {user, users, assignments}, }; }, sessionOptions); type Filter = "months" | "weeks" | "days" | "assignments" | undefined; -export default function History({ user }: { user: User }) { +interface Props { + user: User; + users: User[]; + assignments: Assignment[]; +} + +export default function History({user, users, assignments}: Props) { const router = useRouter(); const [statsUserId, setStatsUserId, training, setTraining] = useRecordStore((state) => [ state.selectedUser, @@ -60,12 +72,10 @@ export default function History({ user }: { user: User }) { ]); // const [statsUserId, setStatsUserId] = useState(user.id); - const [groupedStats, setGroupedStats] = useState<{ [key: string]: Stat[] }>(); + const [groupedStats, setGroupedStats] = useState<{[key: string]: Stat[]}>(); const [filter, setFilter] = useState(); - const { assignments } = useAssignments({}); - const { users } = useUsers(); - const { data: stats, isLoading: isStatsLoading } = useFilterRecordsByUser(statsUserId || user?.id); + const {data: stats, isLoading: isStatsLoading} = useFilterRecordsByUser(statsUserId || user?.id); const setExams = useExamStore((state) => state.setExams); const setShowSolutions = useExamStore((state) => state.setShowSolutions); @@ -100,12 +110,12 @@ export default function History({ user }: { user: User }) { // if (!statsUserId) setStatsUserId(user.id); // }, []); - const filterStatsByDate = (stats: { [key: string]: Stat[] }) => { + const filterStatsByDate = (stats: {[key: string]: Stat[]}) => { if (filter && filter !== "assignments") { const filterDate = moment() - .subtract({ [filter as string]: 1 }) + .subtract({[filter as string]: 1}) .format("x"); - const filteredStats: { [key: string]: Stat[] } = {}; + const filteredStats: {[key: string]: Stat[]} = {}; Object.keys(stats).forEach((timestamp) => { if (timestamp >= filterDate) filteredStats[timestamp] = stats[timestamp]; @@ -114,7 +124,7 @@ export default function History({ user }: { user: User }) { } if (filter && filter === "assignments") { - const filteredStats: { [key: string]: Stat[] } = {}; + const filteredStats: {[key: string]: Stat[]} = {}; Object.keys(stats).forEach((timestamp) => { if (stats[timestamp].map((s) => s.assignment === undefined).includes(false)) @@ -127,7 +137,6 @@ export default function History({ user }: { user: User }) { return stats; }; - const MAX_TRAINING_EXAMS = 10; const [selectedTrainingExams, setSelectedTrainingExams] = useState([]); const setTrainingStats = useTrainingContentStore((state) => state.setStats); @@ -200,7 +209,7 @@ export default function History({ user }: { user: User }) { {user && ( - + {training && (
diff --git a/src/pages/settings.tsx b/src/pages/settings.tsx index 0276d94d..0e75039d 100644 --- a/src/pages/settings.tsx +++ b/src/pages/settings.tsx @@ -24,8 +24,12 @@ import UserCreator from "./(admin)/UserCreator"; import CorporateGradingSystem from "./(admin)/CorporateGradingSystem"; import useGradingSystem from "@/hooks/useGrading"; import {CEFR_STEPS} from "@/resources/grading"; +import {User} from "@/interfaces/user"; +import {getUserPermissions} from "@/utils/permissions.be"; +import {Permission, PermissionType} from "@/interfaces/permissions"; +import {getUsers} from "@/utils/users.be"; -export const getServerSideProps = withIronSessionSsr(({req, res}) => { +export const getServerSideProps = withIronSessionSsr(async ({req, res}) => { const user = req.session.user; if (!user || !user.isVerified) { return { @@ -45,14 +49,21 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => { }; } + const permissions = await getUserPermissions(user.id); + const users = await getUsers(); + return { - props: {user: req.session.user}, + props: {user, permissions, users}, }; }, sessionOptions); -export default function Admin() { - const {user} = useUser({redirectTo: "/login"}); - const {permissions} = usePermissions(user?.id || ""); +interface Props { + user: User; + users: User[]; + permissions: PermissionType[]; +} + +export default function Admin({user, users, permissions}: Props) { const {gradingSystem, mutate} = useGradingSystem(); const [modalOpen, setModalOpen] = useState(); @@ -69,80 +80,78 @@ export default function Admin() { - {user && ( - - setModalOpen(undefined)}> - setModalOpen(undefined)} /> - - setModalOpen(undefined)}> - setModalOpen(undefined)} /> - - setModalOpen(undefined)}> - setModalOpen(undefined)} /> - - setModalOpen(undefined)}> - setModalOpen(undefined)} /> - - setModalOpen(undefined)}> - { - mutate({user: user.id, steps}); - setModalOpen(undefined); - }} - /> - + + setModalOpen(undefined)}> + setModalOpen(undefined)} /> + + setModalOpen(undefined)}> + setModalOpen(undefined)} /> + + setModalOpen(undefined)}> + setModalOpen(undefined)} /> + + setModalOpen(undefined)}> + setModalOpen(undefined)} /> + + setModalOpen(undefined)}> + { + mutate({user: user.id, steps}); + setModalOpen(undefined); + }} + /> + -
- - {checkAccess(user, getTypesOfUser(["teacher"]), permissions, "viewCodes") && ( -
+
+ + {checkAccess(user, getTypesOfUser(["teacher"]), permissions, "viewCodes") && ( +
+ setModalOpen("createCode")} + /> + setModalOpen("batchCreateCode")} + /> + setModalOpen("createUser")} + /> + setModalOpen("batchCreateUser")} + /> + {checkAccess(user, ["admin", "corporate", "developer", "mastercorporate"]) && ( setModalOpen("createCode")} + className="w-full h-full col-span-2" + onClick={() => setModalOpen("gradingSystem")} /> - setModalOpen("batchCreateCode")} - /> - setModalOpen("createUser")} - /> - setModalOpen("batchCreateUser")} - /> - {checkAccess(user, ["admin", "corporate", "developer", "mastercorporate"]) && ( - setModalOpen("gradingSystem")} - /> - )} -
- )} -
-
- -
- - )} + )} +
+ )} +
+
+ +
+
); } diff --git a/src/utils/assignments.be.ts b/src/utils/assignments.be.ts index 7f46718f..03be8da3 100644 --- a/src/utils/assignments.be.ts +++ b/src/utils/assignments.be.ts @@ -1,58 +1,36 @@ -import { app } from "@/firebase"; -import { Assignment } from "@/interfaces/results"; -import { - collection, - getDocs, - getFirestore, - query, - where, -} from "firebase/firestore"; +import {app} from "@/firebase"; +import {Assignment} from "@/interfaces/results"; +import {collection, getDocs, getFirestore, query, where} from "firebase/firestore"; const db = getFirestore(app); -export const getAssignmentsByAssigner = async ( - id: string, - startDate?: Date, - endDate?: Date -) => { - const { docs } = await getDocs( - query( - collection(db, "assignments"), - ...[ - where("assigner", "==", id), - ...(startDate ? [where("startDate", ">=", startDate.toISOString())] : []), - // firebase doesnt accept compound queries so we have to filter on the server - // ...endDate ? [where("endDate", "<=", endDate)] : [], - ] - ) - ); - if (endDate) { - return docs - .map((x) => ({ ...(x.data() as Assignment), id: x.id })) - .filter((x) => new Date(x.endDate) <= endDate) as Assignment[]; - } - return docs.map((x) => ({ ...x.data(), id: x.id })) as Assignment[]; +export const getAssignmentsByAssigner = async (id: string, startDate?: Date, endDate?: Date) => { + const {docs} = await getDocs( + query( + collection(db, "assignments"), + ...[ + where("assigner", "==", id), + ...(startDate ? [where("startDate", ">=", startDate.toISOString())] : []), + // firebase doesnt accept compound queries so we have to filter on the server + // ...endDate ? [where("endDate", "<=", endDate)] : [], + ], + ), + ); + if (endDate) { + return docs.map((x) => ({...(x.data() as Assignment), id: x.id})).filter((x) => new Date(x.endDate) <= endDate) as Assignment[]; + } + return docs.map((x) => ({...x.data(), id: x.id})) as Assignment[]; +}; +export const getAssignments = async () => { + const {docs} = await getDocs(collection(db, "assignments")); + return docs.map((x) => ({...x.data(), id: x.id})) as Assignment[]; }; -export const getAssignmentsByAssignerBetweenDates = async ( - id: string, - startDate: Date, - endDate: Date -) => { - const { docs } = await getDocs( - query(collection(db, "assignments"), where("assigner", "==", id)) - ); - return docs.map((x) => ({ ...x.data(), id: x.id })) as Assignment[]; +export const getAssignmentsByAssignerBetweenDates = async (id: string, startDate: Date, endDate: Date) => { + const {docs} = await getDocs(query(collection(db, "assignments"), where("assigner", "==", id))); + return docs.map((x) => ({...x.data(), id: x.id})) as Assignment[]; }; -export const getAssignmentsByAssigners = async ( - ids: string[], - startDate?: Date, - endDate?: Date -) => { - return ( - await Promise.all( - ids.map((id) => getAssignmentsByAssigner(id, startDate, endDate)) - ) - ).flat(); +export const getAssignmentsByAssigners = async (ids: string[], startDate?: Date, endDate?: Date) => { + return (await Promise.all(ids.map((id) => getAssignmentsByAssigner(id, startDate, endDate)))).flat(); }; diff --git a/src/utils/groups.be.ts b/src/utils/groups.be.ts index 99929752..27ef4736 100644 --- a/src/utils/groups.be.ts +++ b/src/utils/groups.be.ts @@ -1,5 +1,5 @@ import {app} from "@/firebase"; -import {CorporateUser, Group, StudentUser, TeacherUser} from "@/interfaces/user"; +import {CorporateUser, Group, MasterCorporateUser, StudentUser, TeacherUser} from "@/interfaces/user"; import {collection, doc, getDoc, getDocs, getFirestore, query, setDoc, where} from "firebase/firestore"; import moment from "moment"; import {getUser} from "./users.be"; @@ -35,14 +35,14 @@ export const updateExpiryDateOnGroup = async (participantID: string, corporateID export const getUserCorporate = async (id: string) => { const user = await getUser(id); - if (user.type === "corporate" || user.type === "mastercorporate") return user; + if (user.type === "mastercorporate") return user; const groups = await getParticipantGroups(id); const admins = await Promise.all(groups.map((x) => x.admin).map(getUser)); - const corporates = admins.filter((x) => x.type === "corporate"); + const corporates = admins.filter((x) => x.type === "corporate" || x.type === "mastercorporate"); if (corporates.length === 0) return undefined; - return corporates.shift() as CorporateUser; + return corporates.shift() as CorporateUser | MasterCorporateUser; }; export const getGroups = async () => { diff --git a/src/utils/permissions.be.ts b/src/utils/permissions.be.ts index 1f03ffcc..93dffcba 100644 --- a/src/utils/permissions.be.ts +++ b/src/utils/permissions.be.ts @@ -1,99 +1,89 @@ -import { app, adminApp } from "@/firebase"; -import { getAuth } from "firebase-admin/auth"; +import {app, adminApp} from "@/firebase"; +import {getAuth} from "firebase-admin/auth"; -import { - collection, - deleteDoc, - doc, - getDoc, - getDocs, - getFirestore, - query, - setDoc, - where, -} from "firebase/firestore"; -import { - Permission, - PermissionType, - permissions, - PermissionTopic, -} from "@/interfaces/permissions"; -import { v4 } from "uuid"; +import {collection, deleteDoc, doc, getDoc, getDocs, getFirestore, query, setDoc, where} from "firebase/firestore"; +import {Permission, PermissionType, permissions, PermissionTopic} from "@/interfaces/permissions"; +import {v4} from "uuid"; const db = getFirestore(app); async function createPermission(topic: string, type: string) { - const permData = doc(db, "permissions", v4()); - const permDoc = await getDoc(permData); - if (permDoc.exists()) { - return true; - } + const permData = doc(db, "permissions", v4()); + const permDoc = await getDoc(permData); + if (permDoc.exists()) { + return true; + } - await setDoc(permData, { - type, - topic, - users: [], - }); + await setDoc(permData, { + type, + topic, + users: [], + }); } interface PermissionsHelperList { - topic: string; - type: string; + topic: string; + type: string; } export function getPermissions(userId: string | undefined, docs: Permission[]) { - if (!userId) { - return []; - } - // the concept is like a blacklist - // if the user exists in the list, he can't access this permission - // even if his profile allows - const permissions = docs.reduce((acc: PermissionType[], doc: Permission) => { - // typescript was complaining even with the validation on the top - if (doc.users.includes(userId)) { - return acc; - } + if (!userId) { + return []; + } + // the concept is like a blacklist + // if the user exists in the list, he can't access this permission + // even if his profile allows + const permissions = docs.reduce((acc: PermissionType[], doc: Permission) => { + // typescript was complaining even with the validation on the top + if (doc.users.includes(userId)) { + return acc; + } - return [...acc, doc.type]; - }, []) as PermissionType[]; - return permissions; + return [...acc, doc.type]; + }, []) as PermissionType[]; + return permissions; +} + +export async function getUserPermissions(id: string) { + const permissions = await getPermissionDocs(); + return getPermissions(id, permissions); } export async function bootstrap() { - await permissions - .reduce((accm: PermissionsHelperList[], permissionData) => { - return [ - ...accm, - ...permissionData.list.map((type) => ({ - topic: permissionData.topic, - type, - })), - ]; - }, []) - .forEach(async ({ topic, type }) => { - await createPermission(topic, type); - }); + await permissions + .reduce((accm: PermissionsHelperList[], permissionData) => { + return [ + ...accm, + ...permissionData.list.map((type) => ({ + topic: permissionData.topic, + type, + })), + ]; + }, []) + .forEach(async ({topic, type}) => { + await createPermission(topic, type); + }); } export async function getPermissionDoc(id: string) { - const docRef = doc(db, "permissions", id); - const docSnap = await getDoc(docRef); + const docRef = doc(db, "permissions", id); + const docSnap = await getDoc(docRef); - if (docSnap.exists()) { - return docSnap.data() as Permission; - } + if (docSnap.exists()) { + return docSnap.data() as Permission; + } - throw new Error("Permission not found"); + throw new Error("Permission not found"); } export async function getPermissionDocs() { - const q = query(collection(db, "permissions")); - // firebase is missing something like array-not-contains + const q = query(collection(db, "permissions")); + // firebase is missing something like array-not-contains - const snapshot = await getDocs(q); + const snapshot = await getDocs(q); - const docs = snapshot.docs.map((doc) => ({ - id: doc.id, - ...doc.data(), - })) as Permission[]; + const docs = snapshot.docs.map((doc) => ({ + id: doc.id, + ...doc.data(), + })) as Permission[]; - return docs; + return docs; } diff --git a/src/utils/users.be.ts b/src/utils/users.be.ts index b50467fb..be8d1f9b 100644 --- a/src/utils/users.be.ts +++ b/src/utils/users.be.ts @@ -5,21 +5,23 @@ import {CorporateUser, Group, User} from "@/interfaces/user"; import {getGroupsForUser} from "./groups.be"; import {uniq, uniqBy} from "lodash"; import {getUserCodes} from "./codes.be"; +import moment from "moment"; const db = getFirestore(app); export async function getUsers() { const snapshot = await getDocs(collection(db, "users")); return snapshot.docs.map((doc) => ({ - id: doc.id, ...doc.data(), - })) as User[]; + id: doc.id, + registrationDate: moment(doc.data().registrationDate).toISOString(), + })) as unknown as User[]; } export async function getUser(id: string) { const userDoc = await getDoc(doc(db, "users", id)); - return {...userDoc.data(), id} as User; + return {...userDoc.data(), id, registrationDate: moment(userDoc.data()?.registrationDate).toISOString()} as unknown as User; } export async function getSpecificUsers(ids: string[]) { @@ -28,9 +30,10 @@ export async function getSpecificUsers(ids: string[]) { const snapshot = await getDocs(query(collection(db, "users"), where("id", "in", ids))); const groups = snapshot.docs.map((doc) => ({ - id: doc.id, ...doc.data(), - })) as User[]; + id: doc.id, + registrationDate: moment(doc.data().registrationDate).toISOString(), + })) as unknown as User[]; return groups; } diff --git a/src/utils/users.ts b/src/utils/users.ts index 619d9ea9..bdcb262e 100644 --- a/src/utils/users.ts +++ b/src/utils/users.ts @@ -32,7 +32,7 @@ export const exportListToExcel = (rowUsers: User[], users: User[], groups: Group gender: user.demographicInformation?.gender ? capitalize(user.demographicInformation.gender) : "N/A", verified: user.isVerified?.toString() || "FALSE", })); - const header = "Name,Email,Type,Company Name,Expiry Date,Country,Phone,Employment/Position,Gender,Verification"; + const header = "Name,Email,Type,Company Name,Expiry Date,Country,Phone,Employment/Department,Gender,Verification"; const rowsString = rows.map((x) => Object.values(x).join(",")).join("\n"); return `${header}\n${rowsString}`; diff --git a/yarn.lock b/yarn.lock index 49406078..2590df19 100644 --- a/yarn.lock +++ b/yarn.lock @@ -191,7 +191,7 @@ "@emotion/utils" "0.11.3" "@emotion/weak-memoize" "0.2.5" -"@emotion/cache@^11.13.0": +"@emotion/cache@^11.13.0", "@emotion/cache@^11.4.0": version "11.13.1" resolved "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz" integrity sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw== @@ -202,27 +202,16 @@ "@emotion/weak-memoize" "^0.4.0" stylis "4.2.0" -"@emotion/cache@^11.4.0": - version "11.13.1" - resolved "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz" - integrity sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw== - dependencies: - "@emotion/memoize" "^0.9.0" - "@emotion/sheet" "^1.4.0" - "@emotion/utils" "^1.4.0" - "@emotion/weak-memoize" "^0.4.0" - stylis "4.2.0" - -"@emotion/hash@^0.9.2": - version "0.9.2" - resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz" - integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g== - "@emotion/hash@0.8.0": version "0.8.0" resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz" integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== +"@emotion/hash@^0.9.2": + version "0.9.2" + resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz" + integrity sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g== + "@emotion/is-prop-valid@^0.8.2": version "0.8.8" resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz" @@ -230,16 +219,16 @@ dependencies: "@emotion/memoize" "0.7.4" -"@emotion/memoize@^0.9.0": - version "0.9.0" - resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz" - integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== - "@emotion/memoize@0.7.4": version "0.7.4" resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz" integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== +"@emotion/memoize@^0.9.0": + version "0.9.0" + resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz" + integrity sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ== + "@emotion/react@^11.8.1": version "11.13.0" resolved "https://registry.npmjs.org/@emotion/react/-/react-11.13.0.tgz" @@ -265,7 +254,7 @@ "@emotion/utils" "0.11.3" csstype "^2.5.7" -"@emotion/serialize@^1.2.0": +"@emotion/serialize@^1.2.0", "@emotion/serialize@^1.3.0": version "1.3.0" resolved "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.0.tgz" integrity sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA== @@ -276,67 +265,56 @@ "@emotion/utils" "^1.4.0" csstype "^3.0.2" -"@emotion/serialize@^1.3.0": - version "1.3.0" - resolved "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.0.tgz" - integrity sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA== - dependencies: - "@emotion/hash" "^0.9.2" - "@emotion/memoize" "^0.9.0" - "@emotion/unitless" "^0.9.0" - "@emotion/utils" "^1.4.0" - csstype "^3.0.2" - -"@emotion/sheet@^1.4.0": - version "1.4.0" - resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz" - integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg== - "@emotion/sheet@0.9.4": version "0.9.4" resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.4.tgz" integrity sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA== +"@emotion/sheet@^1.4.0": + version "1.4.0" + resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz" + integrity sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg== + "@emotion/stylis@0.8.5": version "0.8.5" resolved "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz" integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== -"@emotion/unitless@^0.9.0": - version "0.9.0" - resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.9.0.tgz" - integrity sha512-TP6GgNZtmtFaFcsOgExdnfxLLpRDla4Q66tnenA9CktvVSdNKDvMVuUah4QvWPIpNjrWsGg3qeGo9a43QooGZQ== - "@emotion/unitless@0.7.5": version "0.7.5" resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz" integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== +"@emotion/unitless@^0.9.0": + version "0.9.0" + resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.9.0.tgz" + integrity sha512-TP6GgNZtmtFaFcsOgExdnfxLLpRDla4Q66tnenA9CktvVSdNKDvMVuUah4QvWPIpNjrWsGg3qeGo9a43QooGZQ== + "@emotion/use-insertion-effect-with-fallbacks@^1.1.0": version "1.1.0" resolved "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz" integrity sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw== -"@emotion/utils@^1.4.0": - version "1.4.0" - resolved "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.0.tgz" - integrity sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ== - "@emotion/utils@0.11.3": version "0.11.3" resolved "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz" integrity sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw== -"@emotion/weak-memoize@^0.4.0": - version "0.4.0" - resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz" - integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg== +"@emotion/utils@^1.4.0": + version "1.4.0" + resolved "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.0.tgz" + integrity sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ== "@emotion/weak-memoize@0.2.5": version "0.2.5" resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz" integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== +"@emotion/weak-memoize@^0.4.0": + version "0.4.0" + resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz" + integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg== + "@eslint/eslintrc@^1.4.1": version "1.4.1" resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz" @@ -511,7 +489,7 @@ "@firebase/util" "1.9.3" tslib "^2.1.0" -"@firebase/database-compat@^0.3.4", "@firebase/database-compat@0.3.4": +"@firebase/database-compat@0.3.4", "@firebase/database-compat@^0.3.4": version "0.3.4" resolved "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.3.4.tgz" integrity sha512-kuAW+l+sLMUKBThnvxvUZ+Q1ZrF/vFJ58iUY9kAcbX48U03nVzIF6Tmkf0p3WVQwMqiXguSgtOPIB6ZCeF+5Gg== @@ -523,7 +501,7 @@ "@firebase/util" "1.9.3" tslib "^2.1.0" -"@firebase/database-types@^0.10.4", "@firebase/database-types@0.10.4": +"@firebase/database-types@0.10.4", "@firebase/database-types@^0.10.4": version "0.10.4" resolved "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.10.4.tgz" integrity sha512-dPySn0vJ/89ZeBac70T+2tWWPiJXWbmRygYv0smT5TfE3hDrQ09eKMF3Y+vMlTdrMWq7mUdYW5REWPSGH4kAZQ== @@ -744,13 +722,6 @@ node-fetch "2.6.7" tslib "^2.1.0" -"@firebase/util@^1.9.7": - version "1.9.7" - resolved "https://registry.npmjs.org/@firebase/util/-/util-1.9.7.tgz" - integrity sha512-fBVNH/8bRbYjqlbIhZ+lBtdAAS4WqZumx03K06/u7fJSpz1TGjEMm1ImvKD47w+xaFKIP2ori6z8BrbakRfjJA== - dependencies: - tslib "^2.1.0" - "@firebase/util@1.9.3": version "1.9.3" resolved "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz" @@ -758,6 +729,13 @@ dependencies: tslib "^2.1.0" +"@firebase/util@^1.9.7": + version "1.9.7" + resolved "https://registry.npmjs.org/@firebase/util/-/util-1.9.7.tgz" + integrity sha512-fBVNH/8bRbYjqlbIhZ+lBtdAAS4WqZumx03K06/u7fJSpz1TGjEMm1ImvKD47w+xaFKIP2ori6z8BrbakRfjJA== + dependencies: + tslib "^2.1.0" + "@firebase/webchannel-wrapper@0.9.0": version "0.9.0" resolved "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.9.0.tgz" @@ -1013,6 +991,46 @@ dependencies: glob "7.1.7" +"@next/swc-darwin-arm64@14.2.5": + version "14.2.5" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.5.tgz#d0a160cf78c18731c51cc0bff131c706b3e9bb05" + integrity sha512-/9zVxJ+K9lrzSGli1///ujyRfon/ZneeZ+v4ptpiPoOU+GKZnm8Wj8ELWU1Pm7GHltYRBklmXMTUqM/DqQ99FQ== + +"@next/swc-darwin-x64@14.2.5": + version "14.2.5" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.5.tgz#eb832a992407f6e6352eed05a073379f1ce0589c" + integrity sha512-vXHOPCwfDe9qLDuq7U1OYM2wUY+KQ4Ex6ozwsKxp26BlJ6XXbHleOUldenM67JRyBfVjv371oneEvYd3H2gNSA== + +"@next/swc-linux-arm64-gnu@14.2.5": + version "14.2.5" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.5.tgz#098fdab57a4664969bc905f5801ef5a89582c689" + integrity sha512-vlhB8wI+lj8q1ExFW8lbWutA4M2ZazQNvMWuEDqZcuJJc78iUnLdPPunBPX8rC4IgT6lIx/adB+Cwrl99MzNaA== + +"@next/swc-linux-arm64-musl@14.2.5": + version "14.2.5" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.5.tgz#243a1cc1087fb75481726dd289c7b219fa01f2b5" + integrity sha512-NpDB9NUR2t0hXzJJwQSGu1IAOYybsfeB+LxpGsXrRIb7QOrYmidJz3shzY8cM6+rO4Aojuef0N/PEaX18pi9OA== + +"@next/swc-linux-x64-gnu@14.2.5": + version "14.2.5" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.5.tgz#b8a2e436387ee4a52aa9719b718992e0330c4953" + integrity sha512-8XFikMSxWleYNryWIjiCX+gU201YS+erTUidKdyOVYi5qUQo/gRxv/3N1oZFCgqpesN6FPeqGM72Zve+nReVXQ== + +"@next/swc-linux-x64-musl@14.2.5": + version "14.2.5" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.5.tgz#cb8a9adad5fb8df86112cfbd363aab5c6d32757b" + integrity sha512-6QLwi7RaYiQDcRDSU/os40r5o06b5ue7Jsk5JgdRBGGp8l37RZEh9JsLSM8QF0YDsgcosSeHjglgqi25+m04IQ== + +"@next/swc-win32-arm64-msvc@14.2.5": + version "14.2.5" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.5.tgz#81f996c1c38ea0900d4e7719cc8814be8a835da0" + integrity sha512-1GpG2VhbspO+aYoMOQPQiqc/tG3LzmsdBH0LhnDS3JrtDx2QmzXe0B6mSZZiN3Bq7IOMXxv1nlsjzoS1+9mzZw== + +"@next/swc-win32-ia32-msvc@14.2.5": + version "14.2.5" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.5.tgz#f61c74ce823e10b2bc150e648fc192a7056422e0" + integrity sha512-Igh9ZlxwvCDsu6438FXlQTHlRno4gFpJzqPjSIBZooD22tKeI4fE/YMRoHVJHmrQ2P5YL1DoZ0qaOKkbeFWeMg== + "@next/swc-win32-x64-msvc@14.2.5": version "14.2.5" resolved "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz" @@ -1026,7 +1044,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -1579,6 +1597,14 @@ resolved "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz" integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== +"@swc/helpers@0.5.5": + version "0.5.5" + resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz" + integrity sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A== + dependencies: + "@swc/counter" "^0.1.3" + tslib "^2.4.0" + "@swc/helpers@^0.4.2": version "0.4.14" resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz" @@ -1593,14 +1619,6 @@ dependencies: tslib "^2.4.0" -"@swc/helpers@0.5.5": - version "0.5.5" - resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz" - integrity sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A== - dependencies: - "@swc/counter" "^0.1.3" - tslib "^2.4.0" - "@tanstack/react-table@^8.10.1": version "8.19.3" resolved "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.19.3.tgz" @@ -1814,7 +1832,7 @@ resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz" integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== -"@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@>=8.1.0", "@types/node@18.13.0": +"@types/node@*", "@types/node@18.13.0", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@>=8.1.0": version "18.13.0" resolved "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz" integrity sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg== @@ -2624,20 +2642,16 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - clone@^2.1.2: version "2.1.2" resolved "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz" integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== +clsx@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz" + integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q== + clsx@^1.1.1: version "1.2.1" resolved "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz" @@ -2648,11 +2662,6 @@ clsx@^2.0.0, clsx@^2.1.1: resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz" integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== -clsx@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/clsx/-/clsx-2.0.0.tgz" - integrity sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q== - color-convert@^1.9.0: version "1.9.3" resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" @@ -2667,16 +2676,16 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - color-name@1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== +color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + color-string@^1.9.1: version "1.9.1" resolved "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz" @@ -2897,6 +2906,13 @@ dayjs@^1.8.34: resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz" integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== +debug@4, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + debug@^3.2.7: version "3.2.7" resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" @@ -2904,13 +2920,6 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@4: - version "4.3.4" - resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - decamelize@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" @@ -3085,7 +3094,7 @@ eastasianwidth@^0.2.0: resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== -ecdsa-sig-formatter@^1.0.11, ecdsa-sig-formatter@1.0.11: +ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11: version "1.0.11" resolved "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz" integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== @@ -3826,6 +3835,11 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + fstream@^1.0.12: version "1.0.12" resolved "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz" @@ -3928,7 +3942,7 @@ get-tsconfig@^4.2.0: resolved "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.4.0.tgz" integrity sha512-0Gdjo/9+FzsYhXCEFueo2aY1z1tpXrxWZzP7k8ul9qt1U5o8rYJwTJYmaeHdrVosYIVYkOy2iwCJ9FdpocJhPQ== -glob-parent@^5.1.2: +glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -3942,12 +3956,29 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== +glob@7.1.6: + version "7.1.6" + resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== dependencies: - is-glob "^4.0.1" + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +glob@7.1.7, glob@^7.1.3, glob@^7.1.4: + version "7.1.7" + resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" glob@^10.4.2: version "10.4.5" @@ -3961,18 +3992,6 @@ glob@^10.4.2: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" -glob@^7.1.3, glob@^7.1.4, glob@7.1.7: - version "7.1.7" - resolved "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz" - integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - glob@^7.2.3: version "7.2.3" resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" @@ -3996,18 +4015,6 @@ glob@^8.0.0: minimatch "^5.0.1" once "^1.3.0" -glob@7.1.6: - version "7.1.6" - resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - globals@^11.1.0: version "11.12.0" resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" @@ -4306,7 +4313,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3, inherits@2: +inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -4994,18 +5001,18 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" -lru-cache@^10.2.0: - version "10.4.3" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz" - integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== - -lru-cache@^6.0.0, lru-cache@6.0.0: +lru-cache@6.0.0, lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== dependencies: yallist "^4.0.0" +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + lru-memoizer@^2.2.0: version "2.3.0" resolved "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz" @@ -5076,7 +5083,7 @@ micromatch@^4.0.4, micromatch@^4.0.5: braces "^3.0.2" picomatch "^2.3.1" -"mime-db@>= 1.43.0 < 2", mime-db@1.52.0: +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": version "1.52.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== @@ -5100,14 +5107,7 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimatch@^5.0.1: - version "5.1.6" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz" - integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== - dependencies: - brace-expansion "^2.0.1" - -minimatch@^5.1.0: +minimatch@^5.0.1, minimatch@^5.1.0: version "5.1.6" resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz" integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== @@ -5151,11 +5151,6 @@ minizlib@^2.1.1: minipass "^3.0.0" yallist "^4.0.0" -mkdirp@^1.0.3, mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - "mkdirp@>=0.5 0": version "0.5.6" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz" @@ -5163,6 +5158,11 @@ mkdirp@^1.0.3, mkdirp@^1.0.4: dependencies: minimist "^1.2.6" +mkdirp@^1.0.3, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + moment-timezone@^0.5.44: version "0.5.45" resolved "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.45.tgz" @@ -5175,7 +5175,7 @@ moment@^2.29.4: resolved "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz" integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== -ms@^2.1.1, ms@2.1.2: +ms@2.1.2, ms@^2.1.1: version "2.1.2" resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== @@ -5237,21 +5237,14 @@ node-addon-api@^5.0.0: resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz" integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== -node-fetch@^2.6.1, node-fetch@^2.6.7, node-fetch@2.6.7: +node-fetch@2.6.7, node-fetch@^2.6.1, node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz" integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== dependencies: whatwg-url "^5.0.0" -node-fetch@^2.6.12: - version "2.7.0" - resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" - integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== - dependencies: - whatwg-url "^5.0.0" - -node-fetch@^2.6.9: +node-fetch@^2.6.12, node-fetch@^2.6.9: version "2.7.0" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -5607,7 +5600,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^ resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8, postcss@^8.0.9, postcss@^8.4.21, postcss@8.4.31: +postcss@8.4.31, postcss@^8, postcss@^8.0.9, postcss@^8.4.21: version "8.4.31" resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz" integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== @@ -5649,15 +5642,6 @@ promise-polyfill@^8.3.0: resolved "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz" integrity sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg== -prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: - version "15.8.1" - resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" - integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== - dependencies: - loose-envify "^1.4.0" - object-assign "^4.1.1" - react-is "^16.13.1" - prop-types@15.7.2: version "15.7.2" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz" @@ -5667,6 +5651,15 @@ prop-types@15.7.2: object-assign "^4.1.1" react-is "^16.8.1" +prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + proto3-json-serializer@^1.0.0: version "1.1.1" resolved "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.1.tgz" @@ -5690,6 +5683,24 @@ protobufjs-cli@1.1.1: tmp "^0.2.1" uglify-js "^3.7.7" +protobufjs@7.2.4: + version "7.2.4" + resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz" + integrity sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + protobufjs@^6.11.3: version "6.11.3" resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz" @@ -5745,24 +5756,6 @@ protobufjs@^7.2.5: "@types/node" ">=13.7.0" long "^5.0.0" -protobufjs@7.2.4: - version "7.2.4" - resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz" - integrity sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ== - dependencies: - "@protobufjs/aspromise" "^1.1.2" - "@protobufjs/base64" "^1.1.2" - "@protobufjs/codegen" "^2.0.4" - "@protobufjs/eventemitter" "^1.1.0" - "@protobufjs/fetch" "^1.1.0" - "@protobufjs/float" "^1.0.2" - "@protobufjs/inquire" "^1.1.0" - "@protobufjs/path" "^1.1.2" - "@protobufjs/pool" "^1.1.0" - "@protobufjs/utf8" "^1.1.0" - "@types/node" ">=13.7.0" - long "^5.0.0" - proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" @@ -5893,10 +5886,10 @@ react-firebase-hooks@^5.1.1: resolved "https://registry.npmjs.org/react-firebase-hooks/-/react-firebase-hooks-5.1.1.tgz" integrity sha512-y2UpWs82xs+39q5Rc/wq316ca52QsC0n8m801V+yM4IC4hbfOL4yQPVSh7w+ydstdvjN9F+lvs1WrO2VYxpmdA== -react-icons@^4.8.0: - version "4.10.1" - resolved "https://registry.npmjs.org/react-icons/-/react-icons-4.10.1.tgz" - integrity sha512-/ngzDP/77tlCfqthiiGNZeYFACw85fUjZtLbedmJ5DTlNDIwETxhwBzdOJ21zj4iJdvc0J3y7yOsX3PpxAJzrw== +react-icons@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.3.0.tgz#ccad07a30aebd40a89f8cfa7d82e466019203f1c" + integrity sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg== react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1: version "16.13.1" @@ -6064,33 +6057,7 @@ read-excel-file@^5.7.1: fflate "^0.7.3" unzipper "^0.12.2" -readable-stream@^2.0.0: - version "2.3.8" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^2.0.2: - version "2.3.8" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^2.0.5: +readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@~2.3.6: version "2.3.8" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz" integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== @@ -6112,19 +6079,6 @@ readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@~2.3.6: - version "2.3.8" - resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - readdir-glob@^1.1.2: version "1.1.3" resolved "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz" @@ -6226,13 +6180,6 @@ reusify@^1.0.4: resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - rimraf@2: version "2.7.1" resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz" @@ -6240,6 +6187,13 @@ rimraf@2: dependencies: glob "^7.1.3" +rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + run-parallel@^1.1.9: version "1.2.0" resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" @@ -6247,7 +6201,7 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -safe-buffer@^5.0.1, safe-buffer@>=5.1.0, safe-buffer@~5.2.0: +safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -6447,30 +6401,7 @@ streamsearch@^1.1.0: resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz" integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -6520,14 +6451,21 @@ string.prototype.trimstart@^1.0.6: define-properties "^1.1.4" es-abstract "^1.20.4" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== dependencies: - ansi-regex "^5.0.1" + safe-buffer "~5.2.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -6966,7 +6904,7 @@ use-sidecar@^1.1.2: detect-node-es "^1.1.0" tslib "^2.0.0" -use-sync-external-store@^1.2.0, use-sync-external-store@1.2.0: +use-sync-external-store@1.2.0, use-sync-external-store@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz" integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== @@ -6976,12 +6914,7 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -uuid@^8.0.0: - version "8.3.2" - resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -uuid@^8.3.0: +uuid@^8.0.0, uuid@^8.3.0: version "8.3.2" resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== @@ -7119,7 +7052,7 @@ wordwrap@^1.0.0: resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -7137,15 +7070,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz" @@ -7203,11 +7127,6 @@ yargs-parser@^20.2.2: resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - yargs@^15.3.1: version "15.4.1" resolved "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz" @@ -7238,19 +7157,6 @@ yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^17.7.2: - version "17.7.2" - resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"