Compare commits

..

35 Commits

Author SHA1 Message Date
Tiago Ribeiro
3ce97b4dcd Removed sorting in exchange for filtering 2024-09-11 16:33:05 +01:00
Tiago Ribeiro
7bfd000213 Solved a problem where it was only getting the first 25 students 2024-09-11 11:29:48 +01:00
Tiago Ribeiro
2a10933206 Solved some problems with the excel of master statistical 2024-09-10 12:00:14 +01:00
Tiago Ribeiro
33a46c227b Solved a bug for the master statistical 2024-09-10 10:55:02 +01:00
Tiago Ribeiro
85c8f622ee Added Student ID to the Master Statistical 2024-09-09 14:43:05 +01:00
Tiago Ribeiro
b9c097d42c Had a bug in pagination 2024-09-09 09:07:30 +01:00
Tiago Ribeiro
192132559b Solved a problem with the download of the excel 2024-09-09 08:19:32 +01:00
Tiago Ribeiro
6d1e8a9788 Had some errors on updating groups 2024-09-09 00:06:51 +01:00
Tiago Ribeiro
1c61d50a5c Improved some of the querying for the assignments 2024-09-09 00:02:34 +01:00
Tiago Ribeiro
9f0ba418e5 Added filtering and pagination for the assignment creator 2024-09-08 23:24:27 +01:00
Tiago Ribeiro
6fd2e64e04 Merge branch 'main' of bitbucket.org:ecropdev/ielts-ui 2024-09-08 23:09:25 +01:00
carlos.mesquita
2c01e6b460 Merged in feature/training-content (pull request #101)
Feature/training content

Approved-by: Tiago Ribeiro
2024-09-08 22:08:32 +00:00
Tiago Ribeiro
6e0c4d4361 Added search per exam 2024-09-08 23:07:47 +01:00
Tiago Ribeiro
745eef981f Added some more pagination 2024-09-08 23:02:48 +01:00
carlos.mesquita
7a33f42bcd Merged main into feature/training-content 2024-09-08 21:49:22 +00:00
Carlos Mesquita
02564c8426 Had the type hardcoded 2024-09-08 22:47:43 +01:00
Tiago Ribeiro
eab6ab03b7 Not shown when not completed 2024-09-08 22:46:09 +01:00
Carlos Mesquita
6f534662e1 Shuffles bugfix 2024-09-08 22:45:24 +01:00
Tiago Ribeiro
fbc7abdabb Solved a bug 2024-09-08 22:35:14 +01:00
Tiago Ribeiro
b7349b5df8 Tried to solve some more issues with counts 2024-09-08 19:56:44 +01:00
Tiago Ribeiro
298901a642 Updated the UserList to show the corporates 2024-09-08 19:50:59 +01:00
Tiago Ribeiro
88eafafe12 Stopped sessions from being cached 2024-09-08 19:36:04 +01:00
Tiago Ribeiro
31a01a3157 Allow admins create other admins 2024-09-08 19:27:12 +01:00
Tiago Ribeiro
a5b3a7e94d Solved a problem with the _id 2024-09-08 19:25:14 +01:00
Tiago Ribeiro
49e8237e99 A bit of error handling I guess 2024-09-08 18:54:44 +01:00
Tiago Ribeiro
d5769c2cb9 Updated more of the page 2024-09-08 18:39:52 +01:00
Tiago Ribeiro
e49a325074 Had a small error on the groups 2024-09-08 18:00:47 +01:00
Tiago Ribeiro
e6528392a2 Changed the totals of the admin pretty much 2024-09-08 16:06:54 +01:00
Tiago Ribeiro
620e4dd787 Solved some problems, bypassed some stuff 2024-09-08 11:35:09 +01:00
João Ramos
e3847baadb Merged in bug-fixing-8-sep-24 (pull request #99)
Bug fixing 8 sep 24

Approved-by: Tiago Ribeiro
2024-09-08 09:21:53 +00:00
Joao Ramos
5b8631ab6a Readded master statistical to corporate 2024-09-08 01:30:12 +01:00
Joao Ramos
f9f29eabb3 Fixed an issue after merging 2024-09-08 01:23:56 +01:00
Tiago Ribeiro
898edb152f Moved the Dockerfile env 2024-09-07 23:59:58 +01:00
Tiago Ribeiro
bf0d696b2f Some hard coding at least for now 2024-09-07 23:51:04 +01:00
Tiago Ribeiro
d91b1c14e7 Removed an error 2024-09-07 23:41:01 +01:00
54 changed files with 2210 additions and 2378 deletions

View File

@@ -23,6 +23,8 @@ COPY . .
# Uncomment the following line in case you want to disable telemetry during the build. # Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1 # ENV NEXT_TELEMETRY_DISABLED 1
ENV MONGODB_URI "mongodb+srv://user:JKpFBymv0WLv3STj@encoach.lz18a.mongodb.net/?retryWrites=true&w=majority&appName=EnCoach"
RUN yarn build RUN yarn build
# If using npm comment out above and use below instead # If using npm comment out above and use below instead

View File

@@ -17,7 +17,7 @@ import moment from "moment";
interface Props { interface Props {
user: User; user: User;
mutateUser: KeyedMutator<User>; mutateUser: (user: User) => void;
} }
export default function DemographicInformationInput({user, mutateUser}: Props) { export default function DemographicInformationInput({user, mutateUser}: Props) {
@@ -42,7 +42,7 @@ export default function DemographicInformationInput({user, mutateUser}: Props) {
setIsLoading(true); setIsLoading(true);
axios axios
.patch("/api/users/update", { .patch<{user: User}>("/api/users/update", {
demographicInformation: { demographicInformation: {
country, country,
phone: `+${countryCodes.findOne("countryCode" as any, country!).countryCallingCode}${phone}`, phone: `+${countryCodes.findOne("countryCode" as any, country!).countryCallingCode}${phone}`,
@@ -54,7 +54,7 @@ export default function DemographicInformationInput({user, mutateUser}: Props) {
}, },
agentInformation: user.type === "agent" ? {companyName, commercialRegistration} : undefined, agentInformation: user.type === "agent" ? {companyName, commercialRegistration} : undefined,
}) })
.then((response) => mutateUser((response.data as {user: User}).user)) .then((response) => mutateUser(response.data.user))
.catch(() => { .catch(() => {
toast.error("Something went wrong, please try again later!", {toastId: "user-update-error"}); toast.error("Something went wrong, please try again later!", {toastId: "user-update-error"});
}) })
@@ -89,7 +89,15 @@ export default function DemographicInformationInput({user, mutateUser}: Props) {
<label className="font-normal text-base text-mti-gray-dim">Country *</label> <label className="font-normal text-base text-mti-gray-dim">Country *</label>
<CountrySelect value={country} onChange={setCountry} /> <CountrySelect value={country} onChange={setCountry} />
</div> </div>
<Input type="tel" name="phone" label="Phone number" onChange={(e) => setPhone(e)} value={phone} placeholder="Enter phone number" required /> <Input
type="tel"
name="phone"
label="Phone number"
onChange={(e) => setPhone(e)}
value={phone}
placeholder="Enter phone number"
required
/>
</div> </div>
{user.type === "student" && ( {user.type === "student" && (
<Input <Input

View File

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

View File

@@ -65,28 +65,28 @@ export default function Navbar({user, path, navDisabled = false, focusMode = fal
{ {
module: "reading", module: "reading",
icon: () => <BsBook className="h-4 w-4 text-white" />, icon: () => <BsBook className="h-4 w-4 text-white" />,
achieved: user.levels.reading >= user.desiredLevels.reading, achieved: user.levels?.reading || 0 >= user.desiredLevels?.reading || 9,
}, },
{ {
module: "listening", module: "listening",
icon: () => <BsHeadphones className="h-4 w-4 text-white" />, icon: () => <BsHeadphones className="h-4 w-4 text-white" />,
achieved: user.levels.listening >= user.desiredLevels.listening, achieved: user.levels?.listening || 0 >= user.desiredLevels?.listening || 9,
}, },
{ {
module: "writing", module: "writing",
icon: () => <BsPen className="h-4 w-4 text-white" />, icon: () => <BsPen className="h-4 w-4 text-white" />,
achieved: user.levels.writing >= user.desiredLevels.writing, achieved: user.levels?.writing || 0 >= user.desiredLevels?.writing || 9,
}, },
{ {
module: "speaking", module: "speaking",
icon: () => <BsMegaphone className="h-4 w-4 text-white" />, icon: () => <BsMegaphone className="h-4 w-4 text-white" />,
achieved: user.levels.speaking >= user.desiredLevels.speaking, achieved: user.levels?.speaking || 0 >= user.desiredLevels?.speaking || 9,
}, },
{ {
module: "level", module: "level",
icon: () => <BsClipboard className="h-4 w-4 text-white" />, icon: () => <BsClipboard className="h-4 w-4 text-white" />,
achieved: user.levels.level >= user.desiredLevels.level, achieved: user.levels?.level || 0 >= user.desiredLevels?.level || 9,
}, },
]; ];

View File

@@ -31,12 +31,40 @@ interface Props {
user: User; user: User;
} }
const studentHash = {
type: "student",
size: 25,
orderBy: "registrationDate",
};
const teacherHash = {
type: "teacher",
size: 25,
orderBy: "registrationDate",
};
const corporateHash = {
type: "corporate",
size: 25,
orderBy: "registrationDate",
};
const agentsHash = {
type: "agent",
size: 25,
orderBy: "registrationDate",
};
export default function AdminDashboard({user}: Props) { export default function AdminDashboard({user}: Props) {
const [page, setPage] = useState(""); const [page, setPage] = useState("");
const [selectedUser, setSelectedUser] = useState<User>(); const [selectedUser, setSelectedUser] = useState<User>();
const [showModal, setShowModal] = useState(false); const [showModal, setShowModal] = useState(false);
const {users, reload, isLoading} = useUsers(); const {users: students, total: totalStudents, reload: reloadStudents, isLoading: isStudentsLoading} = useUsers(studentHash);
const {users: teachers, total: totalTeachers, reload: reloadTeachers, isLoading: isTeachersLoading} = useUsers(teacherHash);
const {users: corporates, total: totalCorporate, reload: reloadCorporates, isLoading: isCorporatesLoading} = useUsers(corporateHash);
const {users: agents, total: totalAgents, reload: reloadAgents, isLoading: isAgentsLoading} = useUsers(agentsHash);
const {groups} = useGroups({}); const {groups} = useGroups({});
const {pending, done} = usePaymentStatusUsers(); const {pending, done} = usePaymentStatusUsers();
@@ -47,9 +75,6 @@ export default function AdminDashboard({user}: Props) {
setShowModal(!!selectedUser && router.asPath === "/#"); setShowModal(!!selectedUser && router.asPath === "/#");
}, [selectedUser, router.asPath]); }, [selectedUser, router.asPath]);
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(reload, [page]);
const inactiveCountryManagerFilter = (x: User) => x.status === "disabled" || moment().isAfter(x.subscriptionExpirationDate); const inactiveCountryManagerFilter = (x: User) => x.status === "disabled" || moment().isAfter(x.subscriptionExpirationDate);
const UserDisplay = (displayUser: User) => ( const UserDisplay = (displayUser: User) => (
@@ -279,50 +304,50 @@ export default function AdminDashboard({user}: Props) {
<section className="w-full grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 place-items-center items-center justify-between"> <section className="w-full grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 place-items-center items-center justify-between">
<IconCard <IconCard
Icon={BsPersonFill} Icon={BsPersonFill}
isLoading={isLoading} isLoading={isStudentsLoading}
label="Students" label="Students"
value={users.filter((x) => x.type === "student").length} value={totalStudents}
onClick={() => router.push("/#students")} onClick={() => router.push("/#students")}
color="purple" color="purple"
/> />
<IconCard <IconCard
Icon={BsPencilSquare} Icon={BsPencilSquare}
isLoading={isLoading} isLoading={isTeachersLoading}
label="Teachers" label="Teachers"
value={users.filter((x) => x.type === "teacher").length} value={totalTeachers}
onClick={() => router.push("/#teachers")} onClick={() => router.push("/#teachers")}
color="purple" color="purple"
/> />
<IconCard <IconCard
Icon={BsBank} Icon={BsBank}
isLoading={isLoading} isLoading={isCorporatesLoading}
label="Corporate" label="Corporate"
value={users.filter((x) => x.type === "corporate").length} value={totalCorporate}
onClick={() => router.push("/#corporate")} onClick={() => router.push("/#corporate")}
color="purple" color="purple"
/> />
<IconCard <IconCard
Icon={BsBriefcaseFill} Icon={BsBriefcaseFill}
isLoading={isLoading} isLoading={isAgentsLoading}
label="Country Managers" label="Country Managers"
value={users.filter((x) => x.type === "agent").length} value={totalAgents}
onClick={() => router.push("/#agents")} onClick={() => router.push("/#agents")}
color="purple" color="purple"
/> />
<IconCard <IconCard
Icon={BsGlobeCentralSouthAsia} Icon={BsGlobeCentralSouthAsia}
isLoading={isLoading} isLoading={isAgentsLoading}
label="Countries" label="Countries"
value={[...new Set(users.filter((x) => x.demographicInformation).map((x) => x.demographicInformation?.country))].length} value={[...new Set(agents.filter((x) => x.demographicInformation).map((x) => x.demographicInformation?.country))].length}
color="purple" color="purple"
/> />
<IconCard <IconCard
onClick={() => router.push("/#inactiveStudents")} onClick={() => router.push("/#inactiveStudents")}
Icon={BsPersonFill} Icon={BsPersonFill}
isLoading={isLoading} isLoading={isStudentsLoading}
label="Inactive Students" label="Inactive Students"
value={ value={
users.filter((x) => x.type === "student" && (x.status === "disabled" || moment().isAfter(x.subscriptionExpirationDate))) students.filter((x) => x.type === "student" && (x.status === "disabled" || moment().isAfter(x.subscriptionExpirationDate)))
.length .length
} }
color="rose" color="rose"
@@ -330,26 +355,26 @@ export default function AdminDashboard({user}: Props) {
<IconCard <IconCard
onClick={() => router.push("/#inactiveCountryManagers")} onClick={() => router.push("/#inactiveCountryManagers")}
Icon={BsBriefcaseFill} Icon={BsBriefcaseFill}
isLoading={isLoading} isLoading={isAgentsLoading}
label="Inactive Country Managers" label="Inactive Country Managers"
value={users.filter(inactiveCountryManagerFilter).length} value={agents.filter(inactiveCountryManagerFilter).length}
color="rose" color="rose"
/> />
<IconCard <IconCard
onClick={() => router.push("/#inactiveCorporate")} onClick={() => router.push("/#inactiveCorporate")}
Icon={BsBank} Icon={BsBank}
isLoading={isLoading} isLoading={isCorporatesLoading}
label="Inactive Corporate" label="Inactive Corporate"
value={ value={
users.filter((x) => x.type === "corporate" && (x.status === "disabled" || moment().isAfter(x.subscriptionExpirationDate))) corporates.filter(
.length (x) => x.type === "corporate" && (x.status === "disabled" || moment().isAfter(x.subscriptionExpirationDate)),
).length
} }
color="rose" color="rose"
/> />
<IconCard <IconCard
onClick={() => router.push("/#paymentdone")} onClick={() => router.push("/#paymentdone")}
Icon={BsCurrencyDollar} Icon={BsCurrencyDollar}
isLoading={isLoading}
label="Payment Done" label="Payment Done"
value={done.length} value={done.length}
color="purple" color="purple"
@@ -357,7 +382,6 @@ export default function AdminDashboard({user}: Props) {
<IconCard <IconCard
onClick={() => router.push("/#paymentpending")} onClick={() => router.push("/#paymentpending")}
Icon={BsCurrencyDollar} Icon={BsCurrencyDollar}
isLoading={isLoading}
label="Pending Payment" label="Pending Payment"
value={pending.length} value={pending.length}
color="rose" color="rose"
@@ -365,14 +389,13 @@ export default function AdminDashboard({user}: Props) {
<IconCard <IconCard
onClick={() => router.push("https://cms.encoach.com/admin")} onClick={() => router.push("https://cms.encoach.com/admin")}
Icon={BsLayoutSidebar} Icon={BsLayoutSidebar}
isLoading={isLoading}
label="Content Management System (CMS)" label="Content Management System (CMS)"
color="green" color="green"
/> />
<IconCard <IconCard
onClick={() => router.push("/#corporatestudentslevels")} onClick={() => router.push("/#corporatestudentslevels")}
Icon={BsPersonFill} Icon={BsPersonFill}
isLoading={isLoading} isLoading={isStudentsLoading}
label="Corporate Students Levels" label="Corporate Students Levels"
color="purple" color="purple"
/> />
@@ -382,8 +405,7 @@ export default function AdminDashboard({user}: Props) {
<div className="bg-white shadow flex flex-col rounded-xl w-full"> <div className="bg-white shadow flex flex-col rounded-xl w-full">
<span className="p-4">Latest students</span> <span className="p-4">Latest students</span>
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide"> <div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
{users {students
.filter((x) => x.type === "student")
.sort((a, b) => dateSorter(a, b, "desc", "registrationDate")) .sort((a, b) => dateSorter(a, b, "desc", "registrationDate"))
.map((x) => ( .map((x) => (
<UserDisplay key={x.id} {...x} /> <UserDisplay key={x.id} {...x} />
@@ -393,8 +415,7 @@ export default function AdminDashboard({user}: Props) {
<div className="bg-white shadow flex flex-col rounded-xl w-full"> <div className="bg-white shadow flex flex-col rounded-xl w-full">
<span className="p-4">Latest teachers</span> <span className="p-4">Latest teachers</span>
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide"> <div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
{users {teachers
.filter((x) => x.type === "teacher")
.sort((a, b) => { .sort((a, b) => {
return dateSorter(a, b, "desc", "registrationDate"); return dateSorter(a, b, "desc", "registrationDate");
}) })
@@ -406,8 +427,7 @@ export default function AdminDashboard({user}: Props) {
<div className="bg-white shadow flex flex-col rounded-xl w-full"> <div className="bg-white shadow flex flex-col rounded-xl w-full">
<span className="p-4">Latest corporate</span> <span className="p-4">Latest corporate</span>
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide"> <div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
{users {corporates
.filter((x) => x.type === "corporate")
.sort((a, b) => { .sort((a, b) => {
return dateSorter(a, b, "desc", "registrationDate"); return dateSorter(a, b, "desc", "registrationDate");
}) })
@@ -419,8 +439,8 @@ export default function AdminDashboard({user}: Props) {
<div className="bg-white shadow flex flex-col rounded-xl w-full"> <div className="bg-white shadow flex flex-col rounded-xl w-full">
<span className="p-4">Unpaid Corporate</span> <span className="p-4">Unpaid Corporate</span>
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide"> <div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
{users {corporates
.filter((x) => x.type === "corporate" && x.status === "paymentDue") .filter((x) => x.status === "paymentDue")
.map((x) => ( .map((x) => (
<UserDisplay key={x.id} {...x} /> <UserDisplay key={x.id} {...x} />
))} ))}
@@ -429,10 +449,9 @@ export default function AdminDashboard({user}: Props) {
<div className="bg-white shadow flex flex-col rounded-xl w-full"> <div className="bg-white shadow flex flex-col rounded-xl w-full">
<span className="p-4">Students expiring in 1 month</span> <span className="p-4">Students expiring in 1 month</span>
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide"> <div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
{users {students
.filter( .filter(
(x) => (x) =>
x.type === "student" &&
x.subscriptionExpirationDate && x.subscriptionExpirationDate &&
moment().isAfter(moment(x.subscriptionExpirationDate).subtract(30, "days")) && moment().isAfter(moment(x.subscriptionExpirationDate).subtract(30, "days")) &&
moment().isBefore(moment(x.subscriptionExpirationDate)), moment().isBefore(moment(x.subscriptionExpirationDate)),
@@ -445,10 +464,9 @@ export default function AdminDashboard({user}: Props) {
<div className="bg-white shadow flex flex-col rounded-xl w-full"> <div className="bg-white shadow flex flex-col rounded-xl w-full">
<span className="p-4">Teachers expiring in 1 month</span> <span className="p-4">Teachers expiring in 1 month</span>
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide"> <div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
{users {teachers
.filter( .filter(
(x) => (x) =>
x.type === "teacher" &&
x.subscriptionExpirationDate && x.subscriptionExpirationDate &&
moment().isAfter(moment(x.subscriptionExpirationDate).subtract(30, "days")) && moment().isAfter(moment(x.subscriptionExpirationDate).subtract(30, "days")) &&
moment().isBefore(moment(x.subscriptionExpirationDate)), moment().isBefore(moment(x.subscriptionExpirationDate)),
@@ -461,10 +479,9 @@ export default function AdminDashboard({user}: Props) {
<div className="bg-white shadow flex flex-col rounded-xl w-full"> <div className="bg-white shadow flex flex-col rounded-xl w-full">
<span className="p-4">Country Manager expiring in 1 month</span> <span className="p-4">Country Manager expiring in 1 month</span>
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide"> <div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
{users {agents
.filter( .filter(
(x) => (x) =>
x.type === "agent" &&
x.subscriptionExpirationDate && x.subscriptionExpirationDate &&
moment().isAfter(moment(x.subscriptionExpirationDate).subtract(30, "days")) && moment().isAfter(moment(x.subscriptionExpirationDate).subtract(30, "days")) &&
moment().isBefore(moment(x.subscriptionExpirationDate)), moment().isBefore(moment(x.subscriptionExpirationDate)),
@@ -477,10 +494,9 @@ export default function AdminDashboard({user}: Props) {
<div className="bg-white shadow flex flex-col rounded-xl w-full"> <div className="bg-white shadow flex flex-col rounded-xl w-full">
<span className="p-4">Corporate expiring in 1 month</span> <span className="p-4">Corporate expiring in 1 month</span>
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide"> <div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
{users {corporates
.filter( .filter(
(x) => (x) =>
x.type === "corporate" &&
x.subscriptionExpirationDate && x.subscriptionExpirationDate &&
moment().isAfter(moment(x.subscriptionExpirationDate).subtract(30, "days")) && moment().isAfter(moment(x.subscriptionExpirationDate).subtract(30, "days")) &&
moment().isBefore(moment(x.subscriptionExpirationDate)), moment().isBefore(moment(x.subscriptionExpirationDate)),
@@ -493,10 +509,8 @@ export default function AdminDashboard({user}: Props) {
<div className="bg-white shadow flex flex-col rounded-xl w-full"> <div className="bg-white shadow flex flex-col rounded-xl w-full">
<span className="p-4">Expired Students</span> <span className="p-4">Expired Students</span>
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide"> <div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
{users {students
.filter( .filter((x) => x.subscriptionExpirationDate && moment().isAfter(moment(x.subscriptionExpirationDate)))
(x) => x.type === "student" && x.subscriptionExpirationDate && moment().isAfter(moment(x.subscriptionExpirationDate)),
)
.map((x) => ( .map((x) => (
<UserDisplay key={x.id} {...x} /> <UserDisplay key={x.id} {...x} />
))} ))}
@@ -505,10 +519,8 @@ export default function AdminDashboard({user}: Props) {
<div className="bg-white shadow flex flex-col rounded-xl w-full"> <div className="bg-white shadow flex flex-col rounded-xl w-full">
<span className="p-4">Expired Teachers</span> <span className="p-4">Expired Teachers</span>
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide"> <div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
{users {teachers
.filter( .filter((x) => x.subscriptionExpirationDate && moment().isAfter(moment(x.subscriptionExpirationDate)))
(x) => x.type === "teacher" && x.subscriptionExpirationDate && moment().isAfter(moment(x.subscriptionExpirationDate)),
)
.map((x) => ( .map((x) => (
<UserDisplay key={x.id} {...x} /> <UserDisplay key={x.id} {...x} />
))} ))}
@@ -517,10 +529,8 @@ export default function AdminDashboard({user}: Props) {
<div className="bg-white shadow flex flex-col rounded-xl w-full"> <div className="bg-white shadow flex flex-col rounded-xl w-full">
<span className="p-4">Expired Country Manager</span> <span className="p-4">Expired Country Manager</span>
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide"> <div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
{users {agents
.filter( .filter((x) => x.subscriptionExpirationDate && moment().isAfter(moment(x.subscriptionExpirationDate)))
(x) => x.type === "agent" && x.subscriptionExpirationDate && moment().isAfter(moment(x.subscriptionExpirationDate)),
)
.map((x) => ( .map((x) => (
<UserDisplay key={x.id} {...x} /> <UserDisplay key={x.id} {...x} />
))} ))}
@@ -529,11 +539,8 @@ export default function AdminDashboard({user}: Props) {
<div className="bg-white shadow flex flex-col rounded-xl w-full"> <div className="bg-white shadow flex flex-col rounded-xl w-full">
<span className="p-4">Expired Corporate</span> <span className="p-4">Expired Corporate</span>
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide"> <div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
{users {corporates
.filter( .filter((x) => x.subscriptionExpirationDate && moment().isAfter(moment(x.subscriptionExpirationDate)))
(x) =>
x.type === "corporate" && x.subscriptionExpirationDate && moment().isAfter(moment(x.subscriptionExpirationDate)),
)
.map((x) => ( .map((x) => (
<UserDisplay key={x.id} {...x} /> <UserDisplay key={x.id} {...x} />
))} ))}
@@ -553,7 +560,10 @@ export default function AdminDashboard({user}: Props) {
loggedInUser={user} loggedInUser={user}
onClose={(shouldReload) => { onClose={(shouldReload) => {
setSelectedUser(undefined); setSelectedUser(undefined);
if (shouldReload) reload(); if (shouldReload && selectedUser!.type === "student") reloadStudents();
if (shouldReload && selectedUser!.type === "teacher") reloadTeachers();
if (shouldReload && selectedUser!.type === "corporate") reloadCorporates();
if (shouldReload && selectedUser!.type === "agent") reloadAgents();
}} }}
onViewStudents={ onViewStudents={
selectedUser.type === "corporate" || selectedUser.type === "teacher" selectedUser.type === "corporate" || selectedUser.type === "teacher"

View File

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

View File

@@ -26,6 +26,7 @@ import {
BsArrowRepeat, BsArrowRepeat,
BsPlus, BsPlus,
BsEnvelopePaper, BsEnvelopePaper,
BsDatabase,
} from "react-icons/bs"; } from "react-icons/bs";
import UserCard from "@/components/UserCard"; import UserCard from "@/components/UserCard";
import useGroups from "@/hooks/useGroups"; import useGroups from "@/hooks/useGroups";
@@ -54,6 +55,7 @@ import useUserBalance from "@/hooks/useUserBalance";
import AssignmentsPage from "../views/AssignmentsPage"; import AssignmentsPage from "../views/AssignmentsPage";
import StudentPerformancePage from "./StudentPerformancePage"; import StudentPerformancePage from "./StudentPerformancePage";
import MasterStatistical from "../MasterCorporate/MasterStatistical"; import MasterStatistical from "../MasterCorporate/MasterStatistical";
import MasterStatisticalPage from "./MasterStatisticalPage";
interface Props { interface Props {
user: CorporateUser; user: CorporateUser;
@@ -218,6 +220,8 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
/> />
); );
if (router.asPath === "/#statistical") return <MasterStatisticalPage user={user} />;
return ( return (
<> <>
<Modal isOpen={showModal} onClose={() => setSelectedUser(undefined)}> <Modal isOpen={showModal} onClose={() => setSelectedUser(undefined)}>
@@ -335,6 +339,7 @@ export default function CorporateDashboard({user, linkedCorporate}: Props) {
color="purple" color="purple"
onClick={() => router.push("/#studentsPerformance")} onClick={() => router.push("/#studentsPerformance")}
/> />
<IconCard Icon={BsDatabase} label="Master Statistical" color="purple" onClick={() => router.push("/#statistical")} />
<button <button
disabled={isAssignmentsLoading} disabled={isAssignmentsLoading}
onClick={() => router.push("/#assignments")} onClick={() => router.push("/#assignments")}

View File

@@ -1,5 +1,5 @@
import React from "react"; import React, {useEffect, useMemo, useState} from "react";
import {CorporateUser, User} from "@/interfaces/user"; import {CorporateUser, StudentUser, User} from "@/interfaces/user";
import {BsFileExcel, BsBank, BsPersonFill} from "react-icons/bs"; import {BsFileExcel, BsBank, BsPersonFill} from "react-icons/bs";
import IconCard from "../IconCard"; import IconCard from "../IconCard";
@@ -14,6 +14,7 @@ import {useListSearch} from "@/hooks/useListSearch";
import axios from "axios"; import axios from "axios";
import {toast} from "react-toastify"; import {toast} from "react-toastify";
import Button from "@/components/Low/Button"; import Button from "@/components/Low/Button";
import {getUserName} from "@/utils/users";
interface GroupedCorporateUsers { interface GroupedCorporateUsers {
// list of user Ids // list of user Ids
@@ -27,7 +28,7 @@ interface Props {
} }
interface TableData { interface TableData {
user: string; user: User | undefined;
email: string; email: string;
correct: number; correct: number;
corporate: string; corporate: string;
@@ -42,10 +43,13 @@ interface UserCount {
maxUserCount: number; maxUserCount: number;
} }
const searchFilters = [["email"], ["user"], ["userId"]]; const searchFilters = [["email"], ["user"], ["userId"], ["exams"], ["assignment"]];
const SIZE = 16;
const MasterStatistical = (props: Props) => { const MasterStatistical = (props: Props) => {
const {users, corporateUsers, displaySelection = true} = props; const {users, corporateUsers, displaySelection = true} = props;
const [page, setPage] = useState(0);
// const corporateRelevantUsers = React.useMemo( // const corporateRelevantUsers = React.useMemo(
// () => corporateUsers.filter((x) => x.type !== "student") as CorporateUser[], // () => corporateUsers.filter((x) => x.type !== "student") as CorporateUser[],
@@ -58,8 +62,7 @@ const MasterStatistical = (props: Props) => {
const [startDate, setStartDate] = React.useState<Date | null>(moment("01/01/2023").toDate()); const [startDate, setStartDate] = React.useState<Date | null>(moment("01/01/2023").toDate());
const [endDate, setEndDate] = React.useState<Date | null>(moment().endOf("year").toDate()); const [endDate, setEndDate] = React.useState<Date | null>(moment().endOf("year").toDate());
const {assignments} = useAssignmentsCorporates({ const {assignments, isLoading} = useAssignmentsCorporates({
// corporates: [...corporates, "tYU0HTiJdjMsS8SB7XJsUdMMP892"],
corporates: selectedCorporates, corporates: selectedCorporates,
startDate, startDate,
endDate, endDate,
@@ -71,23 +74,25 @@ const MasterStatistical = (props: Props) => {
() => () =>
assignments.reduce((accmA: TableData[], a: AssignmentWithCorporateId) => { assignments.reduce((accmA: TableData[], a: AssignmentWithCorporateId) => {
const userResults = a.assignees.map((assignee) => { const userResults = a.assignees.map((assignee) => {
const userStats = a.results.find((r) => r.user === assignee)?.stats || [];
const userData = users.find((u) => u.id === assignee); const userData = users.find((u) => u.id === assignee);
const corporate = users.find((u) => u.id === a.assigner)?.name || ""; const userStats = a.results.find((r) => r.user === assignee)?.stats || [];
const corporate = getUserName(users.find((u) => u.id === a.assigner));
const commonData = { const commonData = {
user: userData?.name || "", user: userData,
email: userData?.email || "", email: userData?.email || "N/A",
userId: assignee, userId: assignee,
corporateId: a.corporateId, corporateId: a.corporateId,
exams: a.exams.map((x) => x.id).join(", "),
corporate, corporate,
assignment: a.name, assignment: a.name,
}; };
if (userStats.length === 0) { if (userStats.length === 0) {
return { return {
...commonData, ...commonData,
correct: 0, correct: 0,
submitted: false, submitted: false,
// date: moment(), date: null,
}; };
} }
@@ -145,7 +150,7 @@ const MasterStatistical = (props: Props) => {
header: "User", header: "User",
id: "user", id: "user",
cell: (info) => { cell: (info) => {
return <span>{info.getValue()}</span>; return <span>{info.getValue()?.name || "N/A"}</span>;
}, },
}), }),
columnHelper.accessor("email", { columnHelper.accessor("email", {
@@ -155,6 +160,13 @@ const MasterStatistical = (props: Props) => {
return <span>{info.getValue()}</span>; return <span>{info.getValue()}</span>;
}, },
}), }),
columnHelper.accessor("user", {
header: "Student ID",
id: "studentID",
cell: (info) => {
return <span>{(info.getValue() as StudentUser)?.studentID || "N/A"}</span>;
},
}),
...(displaySelection ...(displaySelection
? [ ? [
columnHelper.accessor("corporate", { columnHelper.accessor("corporate", {
@@ -166,13 +178,6 @@ const MasterStatistical = (props: Props) => {
}), }),
] ]
: []), : []),
columnHelper.accessor("corporate", {
header: "Corporate",
id: "corporate",
cell: (info) => {
return <span>{info.getValue()}</span>;
},
}),
columnHelper.accessor("assignment", { columnHelper.accessor("assignment", {
header: "Assignment", header: "Assignment",
id: "assignment", id: "assignment",
@@ -192,7 +197,7 @@ const MasterStatistical = (props: Props) => {
}, },
}), }),
columnHelper.accessor("correct", { columnHelper.accessor("correct", {
header: "Correct", header: "Score",
id: "correct", id: "correct",
cell: (info) => { cell: (info) => {
return <span>{info.getValue()}</span>; return <span>{info.getValue()}</span>;
@@ -204,7 +209,7 @@ const MasterStatistical = (props: Props) => {
cell: (info) => { cell: (info) => {
const date = info.getValue(); const date = info.getValue();
if (date) { if (date) {
return <span>{date.format("DD/MM/YYYY")}</span>; return <span>{!!date ? date.format("DD/MM/YYYY") : "N/A"}</span>;
} }
return <span>{""}</span>; return <span>{""}</span>;
@@ -214,8 +219,14 @@ const MasterStatistical = (props: Props) => {
const {rows: filteredRows, renderSearch, text: searchText} = useListSearch(searchFilters, tableResults); const {rows: filteredRows, renderSearch, text: searchText} = useListSearch(searchFilters, tableResults);
useEffect(() => setPage(0), [searchText]);
const rows = useMemo(
() => filteredRows.slice(page * SIZE, (page + 1) * SIZE > filteredRows.length ? filteredRows.length : (page + 1) * SIZE),
[filteredRows, page],
);
const table = useReactTable({ const table = useReactTable({
data: filteredRows, data: rows,
columns: defaultColumns, columns: defaultColumns,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
}); });
@@ -276,6 +287,7 @@ const MasterStatistical = (props: Props) => {
<IconCard <IconCard
Icon={BsBank} Icon={BsBank}
label="Consolidate" label="Consolidate"
isLoading={isLoading}
value={getConsolidateScoreStr(consolidateScore)} value={getConsolidateScoreStr(consolidateScore)}
color="purple" color="purple"
onClick={() => { onClick={() => {
@@ -297,6 +309,7 @@ const MasterStatistical = (props: Props) => {
<IconCard <IconCard
key={corporateName} key={corporateName}
Icon={BsBank} Icon={BsBank}
isLoading={isLoading}
label={corporateName} label={corporateName}
value={value} value={value}
color="purple" color="purple"
@@ -338,13 +351,28 @@ const MasterStatistical = (props: Props) => {
</div> </div>
{renderSearch()} {renderSearch()}
<div className="flex flex-col gap-3 justify-end"> <div className="flex flex-col gap-3 justify-end">
<Button className="max-w-[200px] h-[70px]" variant="outline" onClick={triggerDownload}> <Button className="w-[200px] h-[70px]" variant="outline" isLoading={downloading} onClick={triggerDownload}>
Download Download
</Button> </Button>
</div> </div>
</div> </div>
<div> <div className="w-full h-full flex flex-col gap-4">
<div className="w-full flex gap-2 justify-between">
<Button className="w-full max-w-[200px]" disabled={page === 0} onClick={() => setPage((prev) => prev - 1)}>
Previous Page
</Button>
<div className="flex items-center gap-4 w-fit">
<span className="opacity-80">
{page * SIZE + 1} - {(page + 1) * SIZE > filteredRows.length ? filteredRows.length : (page + 1) * SIZE} /{" "}
{filteredRows.length}
</span>
<Button className="w-[200px]" disabled={(page + 1) * SIZE >= filteredRows.length} onClick={() => setPage((prev) => prev + 1)}>
Next Page
</Button>
</div>
</div>
<table className="rounded-xl h-full bg-mti-purple-ultralight/40 w-full"> <table className="rounded-xl h-full bg-mti-purple-ultralight/40 w-full">
<thead> <thead>
{table.getHeaderGroups().map((headerGroup) => ( {table.getHeaderGroups().map((headerGroup) => (

View File

@@ -7,23 +7,14 @@ import {BsArrowLeft} from "react-icons/bs";
import MasterStatistical from "./MasterStatistical"; import MasterStatistical from "./MasterStatistical";
interface Props { interface Props {
user: User; groupedByNameCorporates: Record<string, CorporateUser[]>;
} }
const MasterStatisticalPage = () => { const MasterStatisticalPage = ({ groupedByNameCorporates }: Props) => {
const {users} = useUsers(); const {users} = useUsers();
const router = useRouter(); const router = useRouter();
const groupedByNameCorporates = useMemo(
() =>
groupBy(
users.filter((x) => x.type === "corporate"),
(x: CorporateUser) => x.corporateInformation?.companyInformation?.name || "N/A",
),
[users],
);
const groupedByNameCorporateIds = useMemo( const groupedByNameCorporateIds = useMemo(
() => () =>
Object.keys(groupedByNameCorporates).reduce((accm, x) => { Object.keys(groupedByNameCorporates).reduce((accm, x) => {

View File

@@ -83,18 +83,6 @@ export default function MasterCorporateDashboard({user}: Props) {
const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({corporate: user.id}); const {assignments, isLoading: isAssignmentsLoading, reload: reloadAssignments} = useAssignments({corporate: user.id});
const assignmentsGroups = useMemo(() => groups.filter((x) => x.admin === user.id || x.participants.includes(user.id)), [groups, user.id]); const assignmentsGroups = useMemo(() => groups.filter((x) => x.admin === user.id || x.participants.includes(user.id)), [groups, user.id]);
const assignmentsUsers = useMemo(
() =>
[...students, ...teachers].filter((x) =>
!!selectedUser
? groups
.filter((g) => g.admin === selectedUser.id)
.flatMap((g) => g.participants)
.includes(x.id) || false
: groups.flatMap((g) => g.participants).includes(x.id),
),
[groups, selectedUser, teachers, students],
);
const appendUserFilters = useFilterStore((state) => state.appendUserFilter); const appendUserFilters = useFilterStore((state) => state.appendUserFilter);
const router = useRouter(); const router = useRouter();
@@ -129,6 +117,19 @@ export default function MasterCorporateDashboard({user}: Props) {
</div> </div>
); );
const {users} = useUsers();
const groupedByNameCorporates = useMemo(
() =>
groupBy(
users.filter((x) => x.type === "corporate"),
(x: CorporateUser) => x.corporateInformation?.companyInformation?.name || "N/A",
) as Record<string, CorporateUser[]>,
[users],
);
const groupedByNameCorporatesKeys = Object.keys(groupedByNameCorporates);
const GroupsList = () => { const GroupsList = () => {
return ( return (
<> <>
@@ -148,7 +149,7 @@ export default function MasterCorporateDashboard({user}: Props) {
}; };
if (router.asPath === "/#studentsPerformance") return <StudentPerformancePage user={user} />; if (router.asPath === "/#studentsPerformance") return <StudentPerformancePage user={user} />;
if (router.asPath === "/#statistical") return <MasterStatisticalPage />; if (router.asPath === "/#statistical") return <MasterStatisticalPage groupedByNameCorporates={groupedByNameCorporates} />;
if (router.asPath === "/#groups") return <GroupsList />; if (router.asPath === "/#groups") return <GroupsList />;
if (router.asPath === "/#students") if (router.asPath === "/#students")
@@ -360,12 +361,18 @@ export default function MasterCorporateDashboard({user}: Props) {
color="purple" color="purple"
onClick={() => router.push("/#corporate")} onClick={() => router.push("/#corporate")}
/> />
<IconCard Icon={BsBank} label="Corporate" value={totalCorporate} isLoading={isCorporatesLoading} color="purple" /> <IconCard
Icon={BsBank}
label="Corporate"
value={groupedByNameCorporatesKeys.length}
isLoading={isCorporatesLoading}
color="purple"
/>
<IconCard <IconCard
Icon={BsPersonFillGear} Icon={BsPersonFillGear}
isLoading={isStudentsLoading} isLoading={isStudentsLoading}
label="Student Performance" label="Student Performance"
value={students.length} value={totalStudents}
color="purple" color="purple"
onClick={() => router.push("/#studentsPerformance")} onClick={() => router.push("/#studentsPerformance")}
/> />

View File

@@ -55,7 +55,7 @@ export default function AssignmentsPage({assignments, corporateAssignments, user
<AssignmentCreator <AssignmentCreator
assignment={selectedAssignment} assignment={selectedAssignment}
groups={groups} groups={groups}
users={users} users={[...users, user]}
user={user} user={user}
isCreating={isCreatingAssignment} isCreating={isCreatingAssignment}
cancelCreation={() => { cancelCreation={() => {

View File

@@ -12,7 +12,7 @@ import {calculateAverageLevel} from "@/utils/score";
import {sortByModuleName} from "@/utils/moduleUtils"; import {sortByModuleName} from "@/utils/moduleUtils";
import {capitalize} from "lodash"; import {capitalize} from "lodash";
import ProfileSummary from "@/components/ProfileSummary"; import ProfileSummary from "@/components/ProfileSummary";
import {Variant} from "@/interfaces/exam"; import {ShuffleMap, Shuffles, Variant} from "@/interfaces/exam";
import useSessions, {Session} from "@/hooks/useSessions"; import useSessions, {Session} from "@/hooks/useSessions";
import SessionCard from "@/components/Medium/SessionCard"; import SessionCard from "@/components/Medium/SessionCard";
import useExamStore from "@/stores/examStore"; import useExamStore from "@/stores/examStore";
@@ -41,6 +41,7 @@ export default function Selection({user, page, onStart, disableSelection = false
}; };
const loadSession = async (session: Session) => { const loadSession = async (session: Session) => {
state.setShuffles(session.userSolutions.map((x) => ({exerciseID: x.exercise, shuffles: x.shuffleMaps ? x.shuffleMaps : []})));
state.setSelectedModules(session.selectedModules); state.setSelectedModules(session.selectedModules);
state.setExam(session.exam); state.setExam(session.exam);
state.setExams(session.exams); state.setExams(session.exams);

View File

@@ -8,7 +8,8 @@ export function useListSearch<T>(fields: string[][], rows: T[]) {
const renderSearch = () => <Input label="Search" type="text" name="search" onChange={setText} placeholder="Enter search text" value={text} />; const renderSearch = () => <Input label="Search" type="text" name="search" onChange={setText} placeholder="Enter search text" value={text} />;
const updatedRows = useMemo(() => { const updatedRows = useMemo(() => {
return search(text, fields, rows); if (text.length > 0) return search(text, fields, rows);
return rows;
}, [fields, rows, text]); }, [fields, rows, text]);
return { return {

View File

@@ -0,0 +1,28 @@
import Button from "@/components/Low/Button";
import {useMemo, useState} from "react";
export default function usePagination<T>(list: T[], size = 25) {
const [page, setPage] = useState(0);
const items = useMemo(() => list.slice(page * size, (page + 1) * size), [page, size, list]);
const render = () => (
<div className="w-full flex gap-2 justify-between items-center">
<div className="flex items-center gap-4 w-fit">
<Button className="w-[200px] h-fit" disabled={page === 0} onClick={() => setPage((prev) => prev - 1)}>
Previous Page
</Button>
</div>
<div className="flex items-center gap-4 w-fit">
<span className="opacity-80">
{page * size + 1} - {(page + 1) * size > list.length ? list.length : (page + 1) * size} / {list.length}
</span>
<Button className="w-[200px]" disabled={(page + 1) * size >= list.length} onClick={() => setPage((prev) => prev + 1)}>
Next Page
</Button>
</div>
</div>
);
return {page, items, setPage, render};
}

View File

@@ -1,12 +1,9 @@
import {Exam} from "@/interfaces/exam"; import {Exam} from "@/interfaces/exam";
import {ExamState} from "@/stores/examStore"; import {ExamState} from "@/stores/examStore";
import Axios from "axios"; import axios from "axios";
import {setupCache} from "axios-cache-interceptor"; import {setupCache} from "axios-cache-interceptor";
import {useEffect, useState} from "react"; import {useEffect, useState} from "react";
const instance = Axios.create();
const axios = setupCache(instance);
export type Session = ExamState & {user: string; id: string; date: string}; export type Session = ExamState & {user: string; id: string; date: string};
export default function useSessions(user?: string) { export default function useSessions(user?: string) {

View File

@@ -16,9 +16,9 @@ export default function useUser({redirectTo = "", redirectIfFound = false} = {})
if ( if (
// If redirectIfFound is also set, redirect if the user was found // If redirectIfFound is also set, redirect if the user was found
(redirectIfFound && user && user.isVerified) || (redirectIfFound && user) ||
// If redirectTo is set, redirect if the user was not found. // If redirectTo is set, redirect if the user was not found.
(redirectTo && !redirectIfFound && (!user || (user && !user.isVerified))) (redirectTo && !redirectIfFound && !user)
) { ) {
Router.push(redirectTo); Router.push(redirectTo);
} }

View File

@@ -23,8 +23,6 @@ export default function useUsers(props?: {type?: string; page?: number; size?: n
if (props[key as keyof typeof props] !== undefined) params.append(key, props[key as keyof typeof props]!.toString()); if (props[key as keyof typeof props] !== undefined) params.append(key, props[key as keyof typeof props]!.toString());
}); });
console.log(params.toString());
setIsLoading(true); setIsLoading(true);
axios axios
.get<{users: User[]; total: number}>(`/api/users/list?${params.toString()}`, {headers: {page: "register"}}) .get<{users: User[]; total: number}>(`/api/users/list?${params.toString()}`, {headers: {page: "register"}})

View File

@@ -4,7 +4,7 @@ if (!process.env.MONGODB_URI) {
throw new Error('Invalid/Missing environment variable: "MONGODB_URI"'); throw new Error('Invalid/Missing environment variable: "MONGODB_URI"');
} }
const uri = process.env.MONGODB_URI; const uri = process.env.MONGODB_URI || "";
const options = {}; const options = {};
let client: MongoClient; let client: MongoClient;

View File

@@ -53,7 +53,7 @@ const USER_TYPE_PERMISSIONS: {
}, },
admin: { admin: {
perm: "createCodeAdmin", perm: "createCodeAdmin",
list: ["student", "teacher", "agent", "corporate", "admin", "mastercorporate"], list: ["student", "teacher", "agent", "corporate", "mastercorporate"],
}, },
developer: { developer: {
perm: undefined, perm: undefined,
@@ -161,7 +161,7 @@ export default function BatchCreateUser({user, users, permissions, onFinish}: Pr
setIsLoading(true); setIsLoading(true);
try { try {
await axios.post("/api/batch_users", { users: newUsers.map(user => ({...user, type, expiryDate})) }); await axios.post("/api/batch_users", {users: newUsers.map((user) => ({...user, type, expiryDate}))});
toast.success(`Successfully added ${newUsers.length} user(s)!`); toast.success(`Successfully added ${newUsers.length} user(s)!`);
onFinish(); onFinish();
} catch { } catch {

View File

@@ -28,9 +28,14 @@ import {checkAccess} from "@/utils/permissions";
import {PermissionType} from "@/interfaces/permissions"; import {PermissionType} from "@/interfaces/permissions";
import usePermissions from "@/hooks/usePermissions"; import usePermissions from "@/hooks/usePermissions";
import useUserBalance from "@/hooks/useUserBalance"; import useUserBalance from "@/hooks/useUserBalance";
import usePagination from "@/hooks/usePagination";
const columnHelper = createColumnHelper<User>(); const columnHelper = createColumnHelper<User>();
const searchFields = [["name"], ["email"], ["corporateInformation", "companyInformation", "name"]]; const searchFields = [["name"], ["email"], ["corporateInformation", "companyInformation", "name"]];
const corporatesHash = {
type: "corporate",
};
const CompanyNameCell = ({users, user, groups}: {user: User; users: User[]; groups: Group[]}) => { const CompanyNameCell = ({users, user, groups}: {user: User; users: User[]; groups: Group[]}) => {
const [companyName, setCompanyName] = useState(""); const [companyName, setCompanyName] = useState("");
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@@ -58,18 +63,19 @@ export default function UserList({
const [sorter, setSorter] = useState<string>(); const [sorter, setSorter] = useState<string>();
const [displayUsers, setDisplayUsers] = useState<User[]>([]); const [displayUsers, setDisplayUsers] = useState<User[]>([]);
const [selectedUser, setSelectedUser] = useState<User>(); const [selectedUser, setSelectedUser] = useState<User>();
const [page, setPage] = useState(0);
const userHash = useMemo( const userHash = useMemo(
() => ({ () => ({
type, type,
size: 16,
page,
}), }),
[type, page], [type],
); );
const {users, total, isLoading, reload} = useUsers(userHash); const {users, total, isLoading, reload} = useUsers(userHash);
const {users: corporates} = useUsers(corporatesHash);
const totalUsers = useMemo(() => [...users, ...corporates], [users, corporates]);
const {permissions} = usePermissions(user?.id || ""); const {permissions} = usePermissions(user?.id || "");
const {balance} = useUserBalance(); const {balance} = useUserBalance();
const {groups} = useGroups({ const {groups} = useGroups({
@@ -94,9 +100,9 @@ export default function UserList({
(async () => { (async () => {
if (users && users.length > 0) { if (users && users.length > 0) {
const filteredUsers = filters.reduce((d, f) => d.filter(f), users); const filteredUsers = filters.reduce((d, f) => d.filter(f), users);
const sortedUsers = await asyncSorter<User>(filteredUsers, sortFunction); // const sortedUsers = await asyncSorter<User>(filteredUsers, sortFunction);
setDisplayUsers([...sortedUsers]); setDisplayUsers([...filteredUsers]);
} }
})(); })();
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -354,7 +360,7 @@ export default function UserList({
<SorterArrow name="companyName" /> <SorterArrow name="companyName" />
</button> </button>
) as any, ) as any,
cell: (info) => <CompanyNameCell user={info.row.original} users={users} groups={groups} />, cell: (info) => <CompanyNameCell user={info.row.original} users={totalUsers} groups={groups} />,
}), }),
columnHelper.accessor("subscriptionExpirationDate", { columnHelper.accessor("subscriptionExpirationDate", {
header: ( header: (
@@ -505,10 +511,14 @@ export default function UserList({
return a.id.localeCompare(b.id); return a.id.localeCompare(b.id);
}; };
const {rows: filteredRows, renderSearch} = useListSearch<User>(searchFields, displayUsers); const {rows: filteredRows, renderSearch, text: searchText} = useListSearch<User>(searchFields, displayUsers);
const {items, setPage, render: renderPagination} = usePagination<User>(filteredRows, 16);
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => setPage(0), [searchText]);
const table = useReactTable({ const table = useReactTable({
data: filteredRows, data: items,
columns: (!showDemographicInformation ? defaultColumns : demographicColumns) as any, columns: (!showDemographicInformation ? defaultColumns : demographicColumns) as any,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
}); });
@@ -624,27 +634,7 @@ export default function UserList({
Download List Download List
</Button> </Button>
</div> </div>
<div className="w-full flex gap-2 justify-between"> {renderPagination()}
<Button
isLoading={isLoading}
className="w-full max-w-[200px]"
disabled={page === 0}
onClick={() => setPage((prev) => prev - 1)}>
Previous Page
</Button>
<div className="flex items-center gap-4 w-fit">
<span className="opacity-80">
{page * 16 + 1} - {(page + 1) * 16 > total ? total : (page + 1) * 16} / {total}
</span>
<Button
isLoading={isLoading}
className="w-[200px]"
disabled={page * 16 >= total}
onClick={() => setPage((prev) => prev + 1)}>
Next Page
</Button>
</div>
</div>
<table className="rounded-xl bg-mti-purple-ultralight/40 w-full"> <table className="rounded-xl bg-mti-purple-ultralight/40 w-full">
<thead> <thead>
{table.getHeaderGroups().map((headerGroup) => ( {table.getHeaderGroups().map((headerGroup) => (

View File

@@ -141,7 +141,11 @@ export default function UserCreator({user, users, permissions, onFinish}: Props)
setType("student"); setType("student");
setPosition(undefined); setPosition(undefined);
}) })
.catch(() => toast.error("Something went wrong! Please try again later!")) .catch((error) => {
const data = error?.response?.data;
if (!!data?.message) return toast.error(data.message);
toast.error("Something went wrong! Please try again later!");
})
.finally(() => setIsLoading(false)); .finally(() => setIsLoading(false));
}; };

View File

@@ -22,7 +22,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
async function GET(req: NextApiRequest, res: NextApiResponse) { async function GET(req: NextApiRequest, res: NextApiResponse) {
const {id} = req.query as {id: string}; const {id} = req.query as {id: string};
const assigners = await getAllAssignersByCorporate(id); const assigners = await getAllAssignersByCorporate(id, req.session.user!.type);
const assignments = await getAssignmentsByAssigners([...assigners, id]); const assignments = await getAssignmentsByAssigners([...assigners, id]);
res.status(200).json(uniqBy(assignments, "id")); res.status(200).json(uniqBy(assignments, "id"));

View File

@@ -29,7 +29,7 @@ async function GET(req: NextApiRequest, res: NextApiResponse) {
try { try {
const idsList = ids.split(","); const idsList = ids.split(",");
const assignments = await getAssignmentsForCorporates(idsList, startDateParsed, endDateParsed); const assignments = await getAssignmentsForCorporates(req.session.user!.type, idsList, startDateParsed, endDateParsed);
res.status(200).json(assignments); res.status(200).json(assignments);
} catch (err: any) { } catch (err: any) {
res.status(500).json({error: err.message}); res.status(500).json({error: err.message});

View File

@@ -11,14 +11,21 @@ import { checkAccess } from "@/utils/permissions";
import {getAssignmentsForCorporates} from "@/utils/assignments.be"; import {getAssignmentsForCorporates} from "@/utils/assignments.be";
import {search} from "@/utils/search"; import {search} from "@/utils/search";
import {getGradingSystem} from "@/utils/grading.be"; import {getGradingSystem} from "@/utils/grading.be";
import { User } from "@/interfaces/user"; import {StudentUser, User} from "@/interfaces/user";
import {calculateBandScore, getGradingLabel} from "@/utils/score"; import {calculateBandScore, getGradingLabel} from "@/utils/score";
import {Module} from "@/interfaces"; import {Module} from "@/interfaces";
import {uniq} from "lodash";
import {getUserName} from "@/utils/users";
import {LevelExam} from "@/interfaces/exam";
import {getSpecificExams} from "@/utils/exams.be";
export default withIronSessionApiRoute(handler, sessionOptions); export default withIronSessionApiRoute(handler, sessionOptions);
interface TableData { interface TableData {
user: string; user: string;
studentID: string;
passportID: string;
exams: string;
email: string; email: string;
correct: number; correct: number;
corporate: string; corporate: string;
@@ -40,14 +47,12 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "POST") return await post(req, res); if (req.method === "POST") return await post(req, res);
} }
const searchFilters = [["email"], ["user"], ["userId"]]; const searchFilters = [["email"], ["user"], ["userId"], ["assignment"], ["exams"]];
async function post(req: NextApiRequest, res: NextApiResponse) { async function post(req: NextApiRequest, res: NextApiResponse) {
// verify if it's a logged user that is trying to export // verify if it's a logged user that is trying to export
if (req.session.user) { if (req.session.user) {
if ( if (!checkAccess(req.session.user, ["mastercorporate", "corporate", "developer", "admin"])) {
!checkAccess(req.session.user, ["mastercorporate", "corporate", "developer", "admin"])
) {
return res.status(403).json({error: "Unauthorized"}); return res.status(403).json({error: "Unauthorized"});
} }
const { const {
@@ -65,25 +70,20 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
}; };
const startDateParsed = startDate ? new Date(startDate) : undefined; const startDateParsed = startDate ? new Date(startDate) : undefined;
const endDateParsed = endDate ? new Date(endDate) : undefined; const endDateParsed = endDate ? new Date(endDate) : undefined;
const assignments = await getAssignmentsForCorporates( const assignments = await getAssignmentsForCorporates(req.session.user.type, ids, startDateParsed, endDateParsed);
ids,
startDateParsed,
endDateParsed
);
const assignmentUsers = [ const assignmentUsers = uniq([...assignments.flatMap((x) => x.assignees), ...assignments.flatMap((x) => x.assigner)]);
...new Set(assignments.flatMap((a) => a.assignees)),
];
const assigners = [...new Set(assignments.map((a) => a.assigner))]; const assigners = [...new Set(assignments.map((a) => a.assigner))];
const users = await getSpecificUsers(assignmentUsers); const users = await getSpecificUsers(assignmentUsers);
const assignerUsers = await getSpecificUsers(assigners); const assignerUsers = await getSpecificUsers(assigners);
const exams = await getSpecificExams(uniq(assignments.flatMap((x) => x.exams.map((x) => x.id))));
const assignerUsersGradingSystems = await Promise.all( const assignerUsersGradingSystems = await Promise.all(
assignerUsers.map(async (user: User) => { assignerUsers.map(async (user: User) => {
const data = await getGradingSystem(user); const data = await getGradingSystem(user);
// in this context I need to override as I'll have to match to the assigner // in this context I need to override as I'll have to match to the assigner
return {...data, user: user.id}; return {...data, user: user.id};
}) }),
); );
const getGradingSystemHelper = ( const getGradingSystemHelper = (
@@ -91,21 +91,14 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
assigner: string, assigner: string,
user: User, user: User,
correct: number, correct: number,
total: number total: number,
) => { ) => {
if (exams.some((e) => e.module === "level")) { if (exams.some((e) => e.module === "level")) {
const gradingSystem = assignerUsersGradingSystems.find( const gradingSystem = assignerUsersGradingSystems.find((gs) => gs.user === assigner);
(gs) => gs.user === assigner
);
if (gradingSystem) { if (gradingSystem) {
const bandScore = calculateBandScore( const bandScore = calculateBandScore(correct, total, "level", user?.focus || "academic");
correct,
total,
"level",
user.focus
);
return { return {
label: getGradingLabel(bandScore, gradingSystem?.steps || []), label: getGradingLabel(bandScore, gradingSystem.steps || []),
score: bandScore, score: bandScore,
}; };
} }
@@ -117,27 +110,22 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
const tableResults = assignments const tableResults = assignments
.reduce((accmA: TableData[], a: AssignmentWithCorporateId) => { .reduce((accmA: TableData[], a: AssignmentWithCorporateId) => {
const userResults = a.assignees.map((assignee) => { const userResults = a.assignees.map((assignee) => {
const userStats = const userStats = a.results.find((r) => r.user === assignee)?.stats || [];
a.results.find((r) => r.user === assignee)?.stats || [];
const userData = users.find((u) => u.id === assignee); const userData = users.find((u) => u.id === assignee);
const corporateUser = users.find((u) => u.id === a.assigner); const corporateUser = users.find((u) => u.id === a.assigner);
const correct = userStats.reduce((n, e) => n + e.score.correct, 0); const correct = userStats.reduce((n, e) => n + e.score.correct, 0);
const total = userStats.reduce((n, e) => n + e.score.total, 0); const total = userStats.reduce((n, e) => n + e.score.total, 0);
const { label: level, score } = getGradingSystemHelper( const {label: level, score} = getGradingSystemHelper(a.exams, a.assigner, userData!, correct, total);
a.exams,
a.assigner,
userData!,
correct,
total
);
console.log("Level", level);
const commonData = { const commonData = {
user: userData?.name || "", user: userData?.name || "",
email: userData?.email || "", email: userData?.email || "",
studentID: (userData as StudentUser)?.studentID || "",
passportID: (userData as StudentUser)?.demographicInformation?.passport_id || "",
userId: assignee, userId: assignee,
exams: a.exams.map((x) => x.id).join(", "),
corporateId: a.corporateId, corporateId: a.corporateId,
corporate: corporateUser?.name || "", corporate: !corporateUser ? "" : getUserName(corporateUser),
assignment: a.name, assignment: a.name,
level, level,
score, score,
@@ -151,14 +139,22 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
}; };
} }
const partsData = userStats.every((e) => e.module === "level") let data: {total: number; correct: number}[] = [];
? userStats.reduce((acc, e, index) => { if (a.exams.every((x) => x.module === "level")) {
return { const exam = exams.find((x) => x.id === a.exams.find((x) => x.assignee === assignee)?.id) as LevelExam;
...acc, data = exam.parts.map((x) => {
[`part${index}`]: `${e.score.correct}/${e.score.total}`, const exerciseIDs = x.exercises.map((x) => x.id);
}; const stats = userStats.filter((x) => exerciseIDs.includes(x.exercise));
}, {})
: {}; const total = stats.reduce((acc, curr) => acc + curr.score.total, 0);
const correct = stats.reduce((acc, curr) => acc + curr.score.correct, 0);
return {total, correct};
});
}
const partsData =
data.length > 0 ? data.reduce((acc, e, index) => ({...acc, [`part${index}`]: `${e.correct}/${e.total}`}), {}) : {};
return { return {
...commonData, ...commonData,
@@ -186,6 +182,14 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
label: "Email", label: "Email",
value: (entry: TableData) => entry.email, value: (entry: TableData) => entry.email,
}, },
{
label: "Student ID",
value: (entry: TableData) => entry.studentID,
},
{
label: "Passport ID",
value: (entry: TableData) => entry.passportID,
},
...(displaySelection ...(displaySelection
? [ ? [
{ {
@@ -203,7 +207,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
value: (entry: TableData) => (entry.submitted ? "Yes" : "No"), value: (entry: TableData) => (entry.submitted ? "Yes" : "No"),
}, },
{ {
label: "Correct", label: "Score",
value: (entry: TableData) => entry.correct, value: (entry: TableData) => entry.correct,
}, },
{ {
@@ -223,9 +227,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
})), })),
]; ];
const filteredSearch = searchText const filteredSearch = !!searchText ? search(searchText, searchFilters, tableResults) : tableResults;
? search(searchText, searchFilters, tableResults)
: tableResults;
worksheet.addRow(headers.map((h) => h.label)); worksheet.addRow(headers.map((h) => h.label));
(filteredSearch as TableData[]).forEach((entry) => { (filteredSearch as TableData[]).forEach((entry) => {
@@ -241,8 +243,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
const fileRef = ref(storage, refName); const fileRef = ref(storage, refName);
// upload the pdf to storage // upload the pdf to storage
await uploadBytes(fileRef, buffer, { await uploadBytes(fileRef, buffer, {
contentType: contentType: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
}); });
const url = await getDownloadURL(fileRef); const url = await getDownloadURL(fileRef);

View File

@@ -50,11 +50,7 @@ async function del(req: NextApiRequest, res: NextApiResponse) {
} }
const user = req.session.user; const user = req.session.user;
if ( if (user.type === "admin" || user.type === "developer" || user.id === group.admin) {
user.type === "admin" ||
user.type === "developer" ||
user.id === group.admin
) {
await db.collection("groups").deleteOne({id: id}); await db.collection("groups").deleteOne({id: id});
res.status(200).json({ok: true}); res.status(200).json({ok: true});
@@ -79,27 +75,13 @@ async function patch(req: NextApiRequest, res: NextApiResponse) {
} }
const user = req.session.user; const user = req.session.user;
if ( if (user.type === "admin" || user.type === "developer" || user.id === group.admin) {
user.type === "admin" ||
user.type === "developer" ||
user.id === group.admin
) {
if ("participants" in req.body) { if ("participants" in req.body) {
const newParticipants = (req.body.participants as string[]).filter( const newParticipants = (req.body.participants as string[]).filter((x) => !group.participants.includes(x));
(x) => !group.participants.includes(x), await Promise.all(newParticipants.map(async (p) => await updateExpiryDateOnGroup(p, group.admin)));
);
await Promise.all(
newParticipants.map(
async (p) => await updateExpiryDateOnGroup(p, group.admin),
),
);
} }
await db.collection("grading").updateOne( await db.collection("groups").updateOne({id: req.session.user.id}, {$set: {id, ...req.body}}, {upsert: true});
{ id: req.session.user.id },
{ $set: {id: id, ...req.body} },
{ upsert: true }
);
res.status(200).json({ok: true}); res.status(200).json({ok: true});
return; return;

View File

@@ -218,6 +218,8 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
return res.status(200).json({ok: true}); return res.status(200).json({ok: true});
}) })
.catch((error) => { .catch((error) => {
if (error.code.includes("email-already-in-use")) return res.status(403).json({error, message: "E-mail is already in the platform."});
console.log(`Failing - ${email}`); console.log(`Failing - ${email}`);
console.log(error); console.log(error);
return res.status(401).json({error}); return res.status(401).json({error});

View File

@@ -24,7 +24,7 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
const {user} = req.query as {user?: string}; const {user} = req.query as {user?: string};
const q = user ? {user: user} : {}; const q = user ? {user: user} : {};
const sessions = await db.collection("sessions").find<Session>(q).toArray(); const sessions = await db.collection("sessions").find<Session>(q).limit(10).toArray();
res.status(200).json( res.status(200).json(
sessions.filter((x) => { sessions.filter((x) => {
@@ -42,11 +42,7 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
} }
const session = req.body; const session = req.body;
await db.collection("sessions").updateOne( await db.collection("sessions").updateOne({id: session.id}, {$set: session}, {upsert: true});
{ id: session.id},
{ $set: session },
{ upsert: true }
);
res.status(200).json({ok: true}); res.status(200).json({ok: true});
} }

View File

@@ -4,6 +4,7 @@ import {withIronSessionApiRoute} from "iron-session/next";
import {sessionOptions} from "@/lib/session"; import {sessionOptions} from "@/lib/session";
import {getLinkedUsers} from "@/utils/users.be"; import {getLinkedUsers} from "@/utils/users.be";
import {Type} from "@/interfaces/user"; import {Type} from "@/interfaces/user";
import {uniqBy} from "lodash";
export default withIronSessionApiRoute(handler, sessionOptions); export default withIronSessionApiRoute(handler, sessionOptions);
@@ -31,5 +32,5 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
direction, direction,
); );
res.status(200).json({users, total}); res.status(200).json({users: uniqBy([...users], "id"), total});
} }

View File

@@ -81,10 +81,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
const updatedUser = req.body as User & {password?: string; newPassword?: string}; const updatedUser = req.body as User & {password?: string; newPassword?: string};
if (!!queryId) { if (!!queryId) {
await db.collection("users").updateOne( await db.collection("users").updateOne({id: queryId}, {$set: updatedUser});
{ id: queryId },
{ $set: updatedUser }
);
await managePaymentRecords(updatedUser, updatedUser.id); await managePaymentRecords(updatedUser, updatedUser.id);
if (updatedUser.status || updatedUser.type === "corporate") { if (updatedUser.status || updatedUser.type === "corporate") {
@@ -133,16 +130,18 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.session.user.type === "student") { if (req.session.user.type === "student") {
const corporateAdmins = (await db.collection("users").find<User>({type: "corporate"}).toArray()).map((x) => x.id); const corporateAdmins = (await db.collection("users").find<User>({type: "corporate"}).toArray()).map((x) => x.id);
const groups = await db.collection("groups").find<Group>({ const groups = await db
.collection("groups")
.find<Group>({
participants: req.session.user!.id, participants: req.session.user!.id,
admin: { $in: corporateAdmins } admin: {$in: corporateAdmins},
}).toArray(); })
.toArray();
groups.forEach(async (group) => { groups.forEach(async (group) => {
await db.collection("groups").updateOne( await db
{ id: group.id }, .collection("groups")
{ $set: { participants: group.participants.filter((x) => x !== req.session.user!.id) } } .updateOne({id: group.id}, {$set: {participants: group.participants.filter((x) => x !== req.session.user!.id)}});
);
}); });
} }
} catch { } catch {
@@ -159,22 +158,18 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
delete updatedUser.password; delete updatedUser.password;
delete updatedUser.newPassword; delete updatedUser.newPassword;
await db.collection("users").updateOne( await db.collection("users").updateOne({id: queryId}, {$set: updatedUser});
{ id: queryId },
{ $set: updatedUser }
);
user = await db.collection("users").findOne<User>({ id: req.session.user.id });
if (!queryId) { if (!queryId) {
req.session.user = user ? user : null; req.session.user = updatedUser ? {...updatedUser, id: req.session.user.id} : null;
await req.session.save(); await req.session.save();
} }
if (user) { if ({...updatedUser, id: req.session.user!.id}) {
await managePaymentRecords(user, queryId); await managePaymentRecords({...updatedUser, id: req.session.user!.id}, queryId);
} }
res.status(200).json({ user });
res.status(200).json({user: {...updatedUser, id: req.session.user!.id}});
} }
export const config = { export const config = {

View File

@@ -10,7 +10,7 @@ import {User} from "@/interfaces/user";
export const getServerSideProps = withIronSessionSsr(({req, res}) => { export const getServerSideProps = withIronSessionSsr(({req, res}) => {
const user = req.session.user; const user = req.session.user;
if (!user || !user.isVerified) { if (!user) {
return { return {
redirect: { redirect: {
destination: "/login", destination: "/login",

View File

@@ -10,7 +10,7 @@ import {User} from "@/interfaces/user";
export const getServerSideProps = withIronSessionSsr(({req, res}) => { export const getServerSideProps = withIronSessionSsr(({req, res}) => {
const user = req.session.user; const user = req.session.user;
if (!user || !user.isVerified) { if (!user) {
return { return {
redirect: { redirect: {
destination: "/login", destination: "/login",

View File

@@ -27,7 +27,7 @@ import {User} from "@/interfaces/user";
export const getServerSideProps = withIronSessionSsr(({req, res}) => { export const getServerSideProps = withIronSessionSsr(({req, res}) => {
const user = req.session.user; const user = req.session.user;
if (!user || !user.isVerified) { if (!user) {
return { return {
redirect: { redirect: {
destination: "/login", destination: "/login",

View File

@@ -43,7 +43,7 @@ import {getUsers} from "@/utils/users.be";
export const getServerSideProps = withIronSessionSsr(async ({req, res}) => { export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
const user = req.session.user; const user = req.session.user;
if (!user || !user.isVerified) { if (!user) {
return { return {
redirect: { redirect: {
destination: "/login", destination: "/login",

View File

@@ -46,7 +46,7 @@ export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
envVariables[x] = process.env[x]!; envVariables[x] = process.env[x]!;
}); });
if (!user || !user.isVerified) { if (!user) {
return { return {
redirect: { redirect: {
destination: "/login", destination: "/login",
@@ -68,22 +68,18 @@ interface Props {
linkedCorporate?: CorporateUser | MasterCorporateUser; linkedCorporate?: CorporateUser | MasterCorporateUser;
} }
export default function Home({linkedCorporate}: Props) { export default function Home({user: propsUser, linkedCorporate}: Props) {
const [user, setUser] = useState(propsUser);
const [showDiagnostics, setShowDiagnostics] = useState(false); const [showDiagnostics, setShowDiagnostics] = useState(false);
const [showDemographicInput, setShowDemographicInput] = useState(false); const [showDemographicInput, setShowDemographicInput] = useState(false);
const [selectedScreen, setSelectedScreen] = useState<Type>("admin"); const [selectedScreen, setSelectedScreen] = useState<Type>("admin");
const {user, mutateUser} = useUser({redirectTo: "/login"}); const {mutateUser} = useUser({redirectTo: "/login"});
const router = useRouter(); const router = useRouter();
useEffect(() => { useEffect(() => {
if (user) { if (user) {
setShowDemographicInput( // setShowDemographicInput(!user.demographicInformation || !user.demographicInformation.country || !user.demographicInformation.phone);
!user.demographicInformation ||
!user.demographicInformation.country ||
!user.demographicInformation.gender ||
!user.demographicInformation.phone,
);
setShowDiagnostics(user.isFirstLogin && user.type === "student"); setShowDiagnostics(user.isFirstLogin && user.type === "student");
} }
}, [user]); }, [user]);
@@ -135,7 +131,13 @@ export default function Home({linkedCorporate}: Props) {
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
<Layout user={user} navDisabled> <Layout user={user} navDisabled>
<DemographicInformationInput mutateUser={mutateUser} user={user} /> <DemographicInformationInput
mutateUser={(user) => {
setUser(user);
mutateUser(user);
}}
user={user}
/>
</Layout> </Layout>
</> </>
); );

View File

@@ -21,7 +21,7 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
envVariables[x] = process.env[x]!; envVariables[x] = process.env[x]!;
}); });
if (!user || !user.isVerified) { if (!user) {
return { return {
redirect: { redirect: {
destination: "/login", destination: "/login",

View File

@@ -28,7 +28,7 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
envVariables[x] = process.env[x]!; envVariables[x] = process.env[x]!;
}); });
if (user && user.isVerified) { if (user) {
return { return {
redirect: { redirect: {
destination: "/", destination: "/",
@@ -56,7 +56,7 @@ export default function Login() {
}); });
useEffect(() => { useEffect(() => {
if (user && user.isVerified) router.push("/"); if (user) router.push("/");
}, [router, user]); }, [router, user]);
const forgotPassword = () => { const forgotPassword = () => {
@@ -173,7 +173,7 @@ export default function Login() {
</span> </span>
</> </>
)} )}
{user && !user.isVerified && <EmailVerification user={user} isLoading={isLoading} setIsLoading={setIsLoading} />} {/* {user && !user.isVerified && <EmailVerification user={user} isLoading={isLoading} setIsLoading={setIsLoading} />} */}
</section> </section>
</main> </main>
</> </>

View File

@@ -9,15 +9,7 @@ import { shouldRedirectHome } from "@/utils/navigation.disabled";
import usePayments from "@/hooks/usePayments"; import usePayments from "@/hooks/usePayments";
import usePaypalPayments from "@/hooks/usePaypalPayments"; import usePaypalPayments from "@/hooks/usePaypalPayments";
import {Payment, PaypalPayment} from "@/interfaces/paypal"; import {Payment, PaypalPayment} from "@/interfaces/paypal";
import { import {CellContext, createColumnHelper, flexRender, getCoreRowModel, HeaderGroup, Table, useReactTable} from "@tanstack/react-table";
CellContext,
createColumnHelper,
flexRender,
getCoreRowModel,
HeaderGroup,
Table,
useReactTable,
} from "@tanstack/react-table";
import {CURRENCIES} from "@/resources/paypal"; import {CURRENCIES} from "@/resources/paypal";
import {BsTrash} from "react-icons/bs"; import {BsTrash} from "react-icons/bs";
import axios from "axios"; import axios from "axios";
@@ -43,7 +35,7 @@ import { checkAccess, getTypesOfUser } from "@/utils/permissions";
export const getServerSideProps = withIronSessionSsr(({req, res}) => { export const getServerSideProps = withIronSessionSsr(({req, res}) => {
const user = req.session.user; const user = req.session.user;
if (!user || !user.isVerified) { if (!user) {
return { return {
redirect: { redirect: {
destination: "/login", destination: "/login",
@@ -52,19 +44,7 @@ export const getServerSideProps = withIronSessionSsr(({ req, res }) => {
}; };
} }
if ( if (shouldRedirectHome(user) || checkAccess(user, getTypesOfUser(["admin", "developer", "agent", "corporate", "mastercorporate"]))) {
shouldRedirectHome(user) ||
checkAccess(
user,
getTypesOfUser([
"admin",
"developer",
"agent",
"corporate",
"mastercorporate",
])
)
) {
return { return {
redirect: { redirect: {
destination: "/", destination: "/",
@@ -81,15 +61,7 @@ export const getServerSideProps = withIronSessionSsr(({ req, res }) => {
const columnHelper = createColumnHelper<Payment>(); const columnHelper = createColumnHelper<Payment>();
const paypalColumnHelper = createColumnHelper<PaypalPaymentWithUserData>(); const paypalColumnHelper = createColumnHelper<PaypalPaymentWithUserData>();
const PaymentCreator = ({ const PaymentCreator = ({onClose, reload, showComission = false}: {onClose: () => void; reload: () => void; showComission: boolean}) => {
onClose,
reload,
showComission = false,
}: {
onClose: () => void;
reload: () => void;
showComission: boolean;
}) => {
const [corporate, setCorporate] = useState<CorporateUser>(); const [corporate, setCorporate] = useState<CorporateUser>();
const [date, setDate] = useState<Date>(new Date()); const [date, setDate] = useState<Date>(new Date());
@@ -101,9 +73,7 @@ const PaymentCreator = ({
const referralAgent = useMemo(() => { const referralAgent = useMemo(() => {
if (corporate?.corporateInformation?.referralAgent) { if (corporate?.corporateInformation?.referralAgent) {
return users.find( return users.find((u) => u.id === corporate.corporateInformation.referralAgent);
(u) => u.id === corporate.corporateInformation.referralAgent
);
} }
return undefined; return undefined;
@@ -136,24 +106,16 @@ const PaymentCreator = ({
<h1 className="text-2xl font-semibold">New Payment</h1> <h1 className="text-2xl font-semibold">New Payment</h1>
<div className="flex flex-col gap-4"> <div className="flex flex-col gap-4">
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">
<label className="font-normal text-base text-mti-gray-dim"> <label className="font-normal text-base text-mti-gray-dim">Corporate account *</label>
Corporate account *
</label>
<Select <Select
className="px-4 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed bg-white rounded-full border border-mti-gray-platinum focus:outline-none" className="px-4 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed bg-white rounded-full border border-mti-gray-platinum focus:outline-none"
options={( options={(users.filter((u) => u.type === "corporate") as CorporateUser[]).map((user) => ({
users.filter((u) => u.type === "corporate") as CorporateUser[]
).map((user) => ({
value: user.id, value: user.id,
meta: user, meta: user,
label: `${ label: `${user.corporateInformation?.companyInformation?.name || user.name} - ${user.email}`,
user.corporateInformation?.companyInformation?.name || user.name
} - ${user.email}`,
}))} }))}
defaultValue={{value: "undefined", label: "Select an account"}} defaultValue={{value: "undefined", label: "Select an account"}}
onChange={(value) => onChange={(value) => setCorporate((value as any)?.meta ?? undefined)}
setCorporate((value as any)?.meta ?? undefined)
}
menuPortalTarget={document?.body} menuPortalTarget={document?.body}
styles={{ styles={{
menuPortal: (base) => ({...base, zIndex: 9999}), menuPortal: (base) => ({...base, zIndex: 9999}),
@@ -168,11 +130,7 @@ const PaymentCreator = ({
}), }),
option: (styles, state) => ({ option: (styles, state) => ({
...styles, ...styles,
backgroundColor: state.isFocused backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
? "#D5D9F0"
: state.isSelected
? "#7872BF"
: "white",
color: state.isFocused ? "black" : styles.color, color: state.isFocused ? "black" : styles.color,
}), }),
}} }}
@@ -180,19 +138,9 @@ const PaymentCreator = ({
</div> </div>
<div className="flex flex-col gap-3 w-full"> <div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim"> <label className="font-normal text-base text-mti-gray-dim">Price *</label>
Price *
</label>
<div className="w-full grid grid-cols-5 gap-2"> <div className="w-full grid grid-cols-5 gap-2">
<Input <Input name="paymentValue" onChange={() => {}} type="number" value={price} defaultValue={0} className="col-span-3" disabled />
name="paymentValue"
onChange={() => {}}
type="number"
value={price}
defaultValue={0}
className="col-span-3"
disabled
/>
<Select <Select
className="px-4 col-span-2 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool bg-mti-gray-platinum/40 text-mti-gray-dim cursor-not-allowed rounded-full border border-mti-gray-platinum focus:outline-none" className="px-4 col-span-2 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool bg-mti-gray-platinum/40 text-mti-gray-dim cursor-not-allowed rounded-full border border-mti-gray-platinum focus:outline-none"
options={CURRENCIES.map(({label, currency}) => ({ options={CURRENCIES.map(({label, currency}) => ({
@@ -201,16 +149,12 @@ const PaymentCreator = ({
}))} }))}
defaultValue={{ defaultValue={{
value: currency || "EUR", value: currency || "EUR",
label: label: CURRENCIES.find((c) => c.currency === currency)?.label || "Euro",
CURRENCIES.find((c) => c.currency === currency)?.label ||
"Euro",
}} }}
onChange={() => {}} onChange={() => {}}
value={{ value={{
value: currency || "EUR", value: currency || "EUR",
label: label: CURRENCIES.find((c) => c.currency === currency)?.label || "Euro",
CURRENCIES.find((c) => c.currency === currency)?.label ||
"Euro",
}} }}
menuPortalTarget={document?.body} menuPortalTarget={document?.body}
styles={{ styles={{
@@ -226,11 +170,7 @@ const PaymentCreator = ({
}), }),
option: (styles, state) => ({ option: (styles, state) => ({
...styles, ...styles,
backgroundColor: state.isFocused backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
? "#D5D9F0"
: state.isSelected
? "#7872BF"
: "white",
color: state.isFocused ? "black" : styles.color, color: state.isFocused ? "black" : styles.color,
}), }),
}} }}
@@ -241,27 +181,14 @@ const PaymentCreator = ({
{showComission && ( {showComission && (
<div className="flex gap-4 w-full"> <div className="flex gap-4 w-full">
<div className="flex flex-col w-full gap-3"> <div className="flex flex-col w-full gap-3">
<label className="font-normal text-base text-mti-gray-dim"> <label className="font-normal text-base text-mti-gray-dim">Commission *</label>
Commission * <Input name="commission" onChange={() => {}} type="number" defaultValue={0} value={commission} disabled />
</label>
<Input
name="commission"
onChange={() => {}}
type="number"
defaultValue={0}
value={commission}
disabled
/>
</div> </div>
<div className="flex flex-col w-full gap-3"> <div className="flex flex-col w-full gap-3">
<label className="font-normal text-base text-mti-gray-dim"> <label className="font-normal text-base text-mti-gray-dim">Commission Value*</label>
Commission Value*
</label>
<Input <Input
name="commissionValue" name="commissionValue"
value={`${(commission! / 100) * price!} ${ value={`${(commission! / 100) * price!} ${CURRENCIES.find((c) => c.currency === currency)?.label}`}
CURRENCIES.find((c) => c.currency === currency)?.label
}`}
onChange={() => null} onChange={() => null}
type="text" type="text"
defaultValue={0} defaultValue={0}
@@ -273,14 +200,12 @@ const PaymentCreator = ({
<div className="flex gap-4"> <div className="flex gap-4">
<div className="flex flex-col w-full gap-3"> <div className="flex flex-col w-full gap-3">
<label className="font-normal text-base text-mti-gray-dim"> <label className="font-normal text-base text-mti-gray-dim">Date *</label>
Date *
</label>
<ReactDatePicker <ReactDatePicker
className={clsx( className={clsx(
"p-6 w-full min-h-[70px] flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer", "p-6 w-full min-h-[70px] flex justify-center text-sm font-normal rounded-full border focus:outline-none cursor-pointer",
"hover:border-mti-purple tooltip", "hover:border-mti-purple tooltip",
"transition duration-300 ease-in-out" "transition duration-300 ease-in-out",
)} )}
dateFormat="dd/MM/yyyy" dateFormat="dd/MM/yyyy"
selected={moment(date).toDate()} selected={moment(date).toDate()}
@@ -289,16 +214,10 @@ const PaymentCreator = ({
</div> </div>
<div className="flex flex-col w-full gap-3"> <div className="flex flex-col w-full gap-3">
<label className="font-normal text-base text-mti-gray-dim"> <label className="font-normal text-base text-mti-gray-dim">Country Manager *</label>
Country Manager *
</label>
<Input <Input
name="referralAgent" name="referralAgent"
value={ value={referralAgent ? `${referralAgent.name} - ${referralAgent.email}` : "No country manager"}
referralAgent
? `${referralAgent.name} - ${referralAgent.email}`
: "No country manager"
}
onChange={() => null} onChange={() => null}
type="text" type="text"
defaultValue={"No country manager"} defaultValue={"No country manager"}
@@ -307,19 +226,10 @@ const PaymentCreator = ({
</div> </div>
</div> </div>
<div className="flex w-full justify-end items-center gap-8 mt-4"> <div className="flex w-full justify-end items-center gap-8 mt-4">
<Button <Button variant="outline" color="red" className="w-full max-w-[200px]" onClick={onClose}>
variant="outline"
color="red"
className="w-full max-w-[200px]"
onClick={onClose}
>
Cancel Cancel
</Button> </Button>
<Button <Button className="w-full max-w-[200px]" onClick={submit} disabled={!corporate || !price}>
className="w-full max-w-[200px]"
onClick={submit}
disabled={!corporate || !price}
>
Submit Submit
</Button> </Button>
</div> </div>
@@ -358,26 +268,9 @@ const IS_FILE_SUBMITTED_OPTIONS = [
}, },
]; ];
const CSV_PAYMENTS_WHITELISTED_KEYS = [ const CSV_PAYMENTS_WHITELISTED_KEYS = ["corporateId", "corporate", "date", "amount", "agent", "agentCommission", "agentValue", "isPaid"];
"corporateId",
"corporate",
"date",
"amount",
"agent",
"agentCommission",
"agentValue",
"isPaid",
];
const CSV_PAYPAL_WHITELISTED_KEYS = [ const CSV_PAYPAL_WHITELISTED_KEYS = ["orderId", "status", "name", "email", "value", "createdAt", "subscriptionExpirationDate"];
"orderId",
"status",
"name",
"email",
"value",
"createdAt",
"subscriptionExpirationDate",
];
interface SimpleCSVColumn { interface SimpleCSVColumn {
key: string; key: string;
@@ -395,9 +288,7 @@ export default function PaymentRecord() {
const [selectedCorporateUser, setSelectedCorporateUser] = useState<User>(); const [selectedCorporateUser, setSelectedCorporateUser] = useState<User>();
const [selectedAgentUser, setSelectedAgentUser] = useState<User>(); const [selectedAgentUser, setSelectedAgentUser] = useState<User>();
const [isCreatingPayment, setIsCreatingPayment] = useState(false); const [isCreatingPayment, setIsCreatingPayment] = useState(false);
const [filters, setFilters] = useState< const [filters, setFilters] = useState<{filter: (p: Payment) => boolean; id: string}[]>([]);
{ filter: (p: Payment) => boolean; id: string }[]
>([]);
const [displayPayments, setDisplayPayments] = useState<Payment[]>([]); const [displayPayments, setDisplayPayments] = useState<Payment[]>([]);
const [corporate, setCorporate] = useState<User>(); const [corporate, setCorporate] = useState<User>();
@@ -406,29 +297,16 @@ export default function PaymentRecord() {
const {user} = useUser({redirectTo: "/login"}); const {user} = useUser({redirectTo: "/login"});
const {users, reload: reloadUsers} = useUsers(); const {users, reload: reloadUsers} = useUsers();
const {payments: originalPayments, reload: reloadPayment} = usePayments(); const {payments: originalPayments, reload: reloadPayment} = usePayments();
const { payments: paypalPayments, reload: reloadPaypalPayment } = const {payments: paypalPayments, reload: reloadPaypalPayment} = usePaypalPayments();
usePaypalPayments(); const [startDate, setStartDate] = useState<Date | null>(moment("01/01/2023").toDate());
const [startDate, setStartDate] = useState<Date | null>( const [endDate, setEndDate] = useState<Date | null>(moment().endOf("day").toDate());
moment("01/01/2023").toDate()
);
const [endDate, setEndDate] = useState<Date | null>(
moment().endOf("day").toDate()
);
const [startDatePaymob, setStartDatePaymob] = useState<Date | null>( const [startDatePaymob, setStartDatePaymob] = useState<Date | null>(moment("01/01/2023").toDate());
moment("01/01/2023").toDate() const [endDatePaymob, setEndDatePaymob] = useState<Date | null>(moment().endOf("day").toDate());
);
const [endDatePaymob, setEndDatePaymob] = useState<Date | null>(
moment().endOf("day").toDate()
);
const [paid, setPaid] = useState<Boolean | null>(IS_PAID_OPTIONS[0].value); const [paid, setPaid] = useState<Boolean | null>(IS_PAID_OPTIONS[0].value);
const [commissionTransfer, setCommissionTransfer] = useState<Boolean | null>( const [commissionTransfer, setCommissionTransfer] = useState<Boolean | null>(IS_FILE_SUBMITTED_OPTIONS[0].value);
IS_FILE_SUBMITTED_OPTIONS[0].value const [corporateTransfer, setCorporateTransfer] = useState<Boolean | null>(IS_FILE_SUBMITTED_OPTIONS[0].value);
);
const [corporateTransfer, setCorporateTransfer] = useState<Boolean | null>(
IS_FILE_SUBMITTED_OPTIONS[0].value
);
const reload = () => { const reload = () => {
reloadUsers(); reloadUsers();
reloadPayment(); reloadPayment();
@@ -448,7 +326,7 @@ export default function PaymentRecord() {
filters filters
.map((f) => f.filter) .map((f) => f.filter)
.reduce((d, f) => d.filter(f), payments) .reduce((d, f) => d.filter(f), payments)
.sort((a, b) => moment(b.date).diff(moment(a.date))) .sort((a, b) => moment(b.date).diff(moment(a.date))),
); );
}, [payments, filters]); }, [payments, filters]);
@@ -489,9 +367,7 @@ export default function PaymentRecord() {
useEffect(() => { useEffect(() => {
setFilters((prev) => [ setFilters((prev) => [
...prev.filter((x) => x.id !== "paid"), ...prev.filter((x) => x.id !== "paid"),
...(typeof paid !== "boolean" ...(typeof paid !== "boolean" ? [] : [{id: "paid", filter: (p: Payment) => p.isPaid === paid}]),
? []
: [{ id: "paid", filter: (p: Payment) => p.isPaid === paid }]),
]); ]);
}, [paid]); }, [paid]);
@@ -503,8 +379,7 @@ export default function PaymentRecord() {
: [ : [
{ {
id: "commissionTransfer", id: "commissionTransfer",
filter: (p: Payment) => filter: (p: Payment) => !p.commissionTransfer === commissionTransfer,
!p.commissionTransfer === commissionTransfer,
}, },
]), ]),
]); ]);
@@ -518,8 +393,7 @@ export default function PaymentRecord() {
: [ : [
{ {
id: "corporateTransfer", id: "corporateTransfer",
filter: (p: Payment) => filter: (p: Payment) => !p.corporateTransfer === corporateTransfer,
!p.corporateTransfer === corporateTransfer,
}, },
]), ]),
]); ]);
@@ -550,9 +424,7 @@ export default function PaymentRecord() {
} }
if (reason.response.status === 403) { if (reason.response.status === 403) {
toast.error( toast.error("You do not have permission to delete an approved payment record!");
"You do not have permission to delete an approved payment record!"
);
return; return;
} }
@@ -563,8 +435,7 @@ export default function PaymentRecord() {
const getFileAssetsColumns = () => { const getFileAssetsColumns = () => {
if (user) { if (user) {
const containerClassName = const containerClassName = "flex gap-2 text-mti-purple-light hover:text-mti-purple-dark ease-in-out duration-300 cursor-pointer";
"flex gap-2 text-mti-purple-light hover:text-mti-purple-dark ease-in-out duration-300 cursor-pointer";
switch (user.type) { switch (user.type) {
case "corporate": case "corporate":
return [ return [
@@ -683,9 +554,7 @@ export default function PaymentRecord() {
return {value: `${value}%`}; return {value: `${value}%`};
} }
case "agent": { case "agent": {
const user = users.find( const user = users.find((x) => x.id === info.row.original.agent) as AgentUser;
(x) => x.id === info.row.original.agent
) as AgentUser;
return { return {
value: user?.name, value: user?.name,
user, user,
@@ -706,8 +575,7 @@ export default function PaymentRecord() {
const user = users.find((x) => x.id === specificValue) as CorporateUser; const user = users.find((x) => x.id === specificValue) as CorporateUser;
return { return {
user, user,
value: value: user?.corporateInformation.companyInformation.name || user?.name,
user?.corporateInformation.companyInformation.name || user?.name,
}; };
} }
case "currency": { case "currency": {
@@ -735,10 +603,9 @@ export default function PaymentRecord() {
return ( return (
<div <div
className={clsx( className={clsx(
"underline text-mti-purple-light hover:text-mti-purple-dark transition ease-in-out duration-300 cursor-pointer" "underline text-mti-purple-light hover:text-mti-purple-dark transition ease-in-out duration-300 cursor-pointer",
)} )}
onClick={() => setSelectedAgentUser(user)} onClick={() => setSelectedAgentUser(user)}>
>
{value} {value}
</div> </div>
); );
@@ -782,10 +649,9 @@ export default function PaymentRecord() {
return ( return (
<div <div
className={clsx( className={clsx(
"underline text-mti-purple-light hover:text-mti-purple-dark transition ease-in-out duration-300 cursor-pointer" "underline text-mti-purple-light hover:text-mti-purple-dark transition ease-in-out duration-300 cursor-pointer",
)} )}
onClick={() => setSelectedCorporateUser(user)} onClick={() => setSelectedCorporateUser(user)}>
>
{value} {value}
</div> </div>
); );
@@ -804,9 +670,7 @@ export default function PaymentRecord() {
id: "amount", id: "amount",
cell: (info) => { cell: (info) => {
const {value} = columHelperValue(info.column.id, info); const {value} = columHelperValue(info.column.id, info);
const currency = CURRENCIES.find( const currency = CURRENCIES.find((x) => x.currency === info.row.original.currency)?.label;
(x) => x.currency === info.row.original.currency
)?.label;
const finalValue = `${value} ${currency}`; const finalValue = `${value} ${currency}`;
return <span>{finalValue}</span>; return <span>{finalValue}</span>;
}, },
@@ -822,23 +686,13 @@ export default function PaymentRecord() {
<Checkbox <Checkbox
isChecked={value} isChecked={value}
onChange={(e) => { onChange={(e) => {
if (user?.type === agent || user?.type === "corporate" || value) if (user?.type === agent || user?.type === "corporate" || value) return null;
return null; if (!info.row.original.commissionTransfer || !info.row.original.corporateTransfer)
if ( return alert("All files need to be uploaded to consider it paid!");
!info.row.original.commissionTransfer || if (!confirm(`Are you sure you want to consider this payment paid?`)) return null;
!info.row.original.corporateTransfer
)
return alert(
"All files need to be uploaded to consider it paid!"
);
if (
!confirm(`Are you sure you want to consider this payment paid?`)
)
return null;
return updatePayment(info.row.original, "isPaid", e); return updatePayment(info.row.original, "isPaid", e);
}} }}>
>
<span></span> <span></span>
</Checkbox> </Checkbox>
); );
@@ -852,11 +706,7 @@ export default function PaymentRecord() {
return ( return (
<div className="flex gap-4"> <div className="flex gap-4">
{user?.type !== "agent" && ( {user?.type !== "agent" && (
<div <div data-tip="Delete" className="cursor-pointer tooltip" onClick={() => deletePayment(row.original.id)}>
data-tip="Delete"
className="cursor-pointer tooltip"
onClick={() => deletePayment(row.original.id)}
>
<BsTrash className="hover:text-mti-purple-light transition ease-in-out duration-300" /> <BsTrash className="hover:text-mti-purple-light transition ease-in-out duration-300" />
</div> </div>
)} )}
@@ -883,7 +733,7 @@ export default function PaymentRecord() {
const user = users.find((x) => x.id === p.userId) as User; const user = users.find((x) => x.id === p.userId) as User;
return {...p, name: user?.name, email: user?.email}; return {...p, name: user?.name, email: user?.email};
}), }),
[paypalPayments, users, startDatePaymob, endDatePaymob] [paypalPayments, users, startDatePaymob, endDatePaymob],
); );
const paypalColumns = [ const paypalColumns = [
@@ -946,15 +796,10 @@ export default function PaymentRecord() {
}), }),
]; ];
const { rows: filteredRows, renderSearch } = useListSearch( const {rows: filteredRows, renderSearch} = useListSearch(paypalFilterRows, updatedPaypalPayments);
paypalFilterRows,
updatedPaypalPayments
);
const paypalTable = useReactTable({ const paypalTable = useReactTable({
data: filteredRows.sort((a, b) => data: filteredRows.sort((a, b) => moment(b.createdAt).diff(moment(a.createdAt), "second")),
moment(b.createdAt).diff(moment(a.createdAt), "second")
),
columns: paypalColumns, columns: paypalColumns,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
}); });
@@ -963,10 +808,7 @@ export default function PaymentRecord() {
if (user) { if (user) {
if (selectedCorporateUser) { if (selectedCorporateUser) {
return ( return (
<Modal <Modal isOpen={!!selectedCorporateUser} onClose={() => setSelectedCorporateUser(undefined)}>
isOpen={!!selectedCorporateUser}
onClose={() => setSelectedCorporateUser(undefined)}
>
<> <>
{selectedCorporateUser && ( {selectedCorporateUser && (
<div className="w-full flex flex-col gap-8"> <div className="w-full flex flex-col gap-8">
@@ -989,10 +831,7 @@ export default function PaymentRecord() {
if (selectedAgentUser) { if (selectedAgentUser) {
return ( return (
<Modal <Modal isOpen={!!selectedAgentUser} onClose={() => setSelectedAgentUser(undefined)}>
isOpen={!!selectedAgentUser}
onClose={() => setSelectedAgentUser(undefined)}
>
<> <>
{selectedAgentUser && ( {selectedAgentUser && (
<div className="w-full flex flex-col gap-8"> <div className="w-full flex flex-col gap-8">
@@ -1017,17 +856,11 @@ export default function PaymentRecord() {
const getCSVData = () => { const getCSVData = () => {
const tables = [table, paypalTable]; const tables = [table, paypalTable];
const whitelists = [ const whitelists = [CSV_PAYMENTS_WHITELISTED_KEYS, CSV_PAYPAL_WHITELISTED_KEYS];
CSV_PAYMENTS_WHITELISTED_KEYS,
CSV_PAYPAL_WHITELISTED_KEYS,
];
const currentTable = tables[selectedIndex]; const currentTable = tables[selectedIndex];
const whitelist = whitelists[selectedIndex]; const whitelist = whitelists[selectedIndex];
const columns = (currentTable.getHeaderGroups() as any[]).reduce( const columns = (currentTable.getHeaderGroups() as any[]).reduce((accm: any[], group: any) => {
(accm: any[], group: any) => { const whitelistedColumns = group.headers.filter((header: any) => whitelist.includes(header.id));
const whitelistedColumns = group.headers.filter((header: any) =>
whitelist.includes(header.id)
);
const data = whitelistedColumns.map((data: any) => ({ const data = whitelistedColumns.map((data: any) => ({
key: data.column.columnDef.id, key: data.column.columnDef.id,
@@ -1035,9 +868,7 @@ export default function PaymentRecord() {
})) as SimpleCSVColumn[]; })) as SimpleCSVColumn[];
return [...accm, ...data]; return [...accm, ...data];
}, }, []);
[]
);
const {rows} = currentTable.getRowModel(); const {rows} = currentTable.getRowModel();
@@ -1075,12 +906,7 @@ export default function PaymentRecord() {
<tr key={headerGroup.id}> <tr key={headerGroup.id}>
{headerGroup.headers.map((header) => ( {headerGroup.headers.map((header) => (
<th className="p-4 text-left" key={header.id}> <th className="p-4 text-left" key={header.id}>
{header.isPlaceholder {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
? null
: flexRender(
header.column.columnDef.header,
header.getContext()
)}
</th> </th>
))} ))}
</tr> </tr>
@@ -1088,10 +914,7 @@ export default function PaymentRecord() {
</thead> </thead>
<tbody className="px-2"> <tbody className="px-2">
{table.getRowModel().rows.map((row) => ( {table.getRowModel().rows.map((row) => (
<tr <tr className="odd:bg-white even:bg-mti-purple-ultralight/40 rounded-lg py-2" key={row.id}>
className="odd:bg-white even:bg-mti-purple-ultralight/40 rounded-lg py-2"
key={row.id}
>
{row.getVisibleCells().map((cell) => ( {row.getVisibleCells().map((cell) => (
<td className="px-4 py-2" key={cell.id}> <td className="px-4 py-2" key={cell.id}>
{flexRender(cell.column.columnDef.cell, cell.getContext())} {flexRender(cell.column.columnDef.cell, cell.getContext())}
@@ -1118,10 +941,7 @@ export default function PaymentRecord() {
{user && ( {user && (
<Layout user={user} className="gap-6"> <Layout user={user} className="gap-6">
{getUserModal()} {getUserModal()}
<Modal <Modal isOpen={isCreatingPayment} onClose={() => setIsCreatingPayment(false)}>
isOpen={isCreatingPayment}
onClose={() => setIsCreatingPayment(false)}
>
<PaymentCreator <PaymentCreator
onClose={() => setIsCreatingPayment(false)} onClose={() => setIsCreatingPayment(false)}
reload={reload} reload={reload}
@@ -1132,29 +952,15 @@ export default function PaymentRecord() {
<div className="w-full flex flex-end justify-between p-2"> <div className="w-full flex flex-end justify-between p-2">
<h1 className="text-2xl font-semibold">Payment Record</h1> <h1 className="text-2xl font-semibold">Payment Record</h1>
<div className="flex justify-end gap-2"> <div className="flex justify-end gap-2">
{checkAccess(user, [ {checkAccess(user, ["developer", "admin", "agent", "corporate", "mastercorporate"]) && (
"developer",
"admin",
"agent",
"corporate",
"mastercorporate",
]) && (
<Button className="max-w-[200px]" variant="outline"> <Button className="max-w-[200px]" variant="outline">
<CSVLink <CSVLink data={csvRows} headers={csvColumns} filename="payment-records.csv">
data={csvRows}
headers={csvColumns}
filename="payment-records.csv"
>
Download CSV Download CSV
</CSVLink> </CSVLink>
</Button> </Button>
)} )}
{checkAccess(user, ["developer", "admin"]) && ( {checkAccess(user, ["developer", "admin"]) && (
<Button <Button className="max-w-[200px]" variant="outline" onClick={() => setIsCreatingPayment(true)}>
className="max-w-[200px]"
variant="outline"
onClick={() => setIsCreatingPayment(true)}
>
New Payment New Payment
</Button> </Button>
)} )}
@@ -1168,12 +974,9 @@ export default function PaymentRecord() {
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-mti-purple-light", "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-mti-purple-light",
"ring-white ring-opacity-60 ring-offset-2 ring-offset-mti-purple-light focus:outline-none focus:ring-2", "ring-white ring-opacity-60 ring-offset-2 ring-offset-mti-purple-light focus:outline-none focus:ring-2",
"transition duration-300 ease-in-out", "transition duration-300 ease-in-out",
selected selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-mti-purple-dark",
? "bg-white shadow"
: "text-blue-100 hover:bg-white/[0.12] hover:text-mti-purple-dark"
) )
} }>
>
Payments Payments
</Tab> </Tab>
{checkAccess(user, ["developer", "admin"]) && ( {checkAccess(user, ["developer", "admin"]) && (
@@ -1183,63 +986,42 @@ export default function PaymentRecord() {
"w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-mti-purple-light", "w-full rounded-lg py-2.5 text-sm font-medium leading-5 text-mti-purple-light",
"ring-white ring-opacity-60 ring-offset-2 ring-offset-mti-purple-light focus:outline-none focus:ring-2", "ring-white ring-opacity-60 ring-offset-2 ring-offset-mti-purple-light focus:outline-none focus:ring-2",
"transition duration-300 ease-in-out", "transition duration-300 ease-in-out",
selected selected ? "bg-white shadow" : "text-blue-100 hover:bg-white/[0.12] hover:text-mti-purple-dark",
? "bg-white shadow"
: "text-blue-100 hover:bg-white/[0.12] hover:text-mti-purple-dark"
) )
} }>
>
Paymob Paymob
</Tab> </Tab>
)} )}
</Tab.List> </Tab.List>
<Tab.Panels> <Tab.Panels>
<Tab.Panel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide gap-8 flex flex-col"> <Tab.Panel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide gap-8 flex flex-col">
<div <div className={clsx("grid grid-cols-1 md:grid-cols-2 gap-8 w-full", user.type !== "corporate" && "lg:grid-cols-3")}>
className={clsx(
"grid grid-cols-1 md:grid-cols-2 gap-8 w-full",
user.type !== "corporate" && "lg:grid-cols-3"
)}
>
<div className="flex flex-col gap-3 w-full"> <div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim"> <label className="font-normal text-base text-mti-gray-dim">Corporate account *</label>
Corporate account *
</label>
<Select <Select
isClearable={user.type !== "corporate"} isClearable={user.type !== "corporate"}
className={clsx( className={clsx(
"px-4 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool bg-white rounded-full border border-mti-gray-platinum focus:outline-none", "px-4 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool bg-white rounded-full border border-mti-gray-platinum focus:outline-none",
user.type === "corporate" && user.type === "corporate" && "!bg-mti-gray-platinum/40 !text-mti-gray-dim !cursor-not-allowed",
"!bg-mti-gray-platinum/40 !text-mti-gray-dim !cursor-not-allowed"
)} )}
options={( options={(users.filter((u) => u.type === "corporate") as CorporateUser[]).map((user) => ({
users.filter(
(u) => u.type === "corporate"
) as CorporateUser[]
).map((user) => ({
value: user.id, value: user.id,
meta: user, meta: user,
label: `${ label: `${user.corporateInformation?.companyInformation?.name || user.name} - ${user.email}`,
user.corporateInformation?.companyInformation?.name ||
user.name
} - ${user.email}`,
}))} }))}
defaultValue={ defaultValue={
user.type === "corporate" user.type === "corporate"
? { ? {
value: user.id, value: user.id,
meta: user, meta: user,
label: `${ label: `${user.corporateInformation?.companyInformation?.name || user.name} - ${
user.corporateInformation?.companyInformation user.email
?.name || user.name }`,
} - ${user.email}`,
} }
: undefined : undefined
} }
isDisabled={user.type === "corporate"} isDisabled={user.type === "corporate"}
onChange={(value) => onChange={(value) => setCorporate((value as any)?.meta ?? undefined)}
setCorporate((value as any)?.meta ?? undefined)
}
menuPortalTarget={document?.body} menuPortalTarget={document?.body}
styles={{ styles={{
menuPortal: (base) => ({...base, zIndex: 9999}), menuPortal: (base) => ({...base, zIndex: 9999}),
@@ -1254,11 +1036,7 @@ export default function PaymentRecord() {
}), }),
option: (styles, state) => ({ option: (styles, state) => ({
...styles, ...styles,
backgroundColor: state.isFocused backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
? "#D5D9F0"
: state.isSelected
? "#7872BF"
: "white",
color: state.isFocused ? "black" : styles.color, color: state.isFocused ? "black" : styles.color,
}), }),
}} }}
@@ -1266,21 +1044,15 @@ export default function PaymentRecord() {
</div> </div>
{user.type !== "corporate" && ( {user.type !== "corporate" && (
<div className="flex flex-col gap-3 w-full"> <div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim"> <label className="font-normal text-base text-mti-gray-dim">Country manager *</label>
Country manager *
</label>
<Select <Select
isClearable isClearable
isDisabled={user.type === "agent"} isDisabled={user.type === "agent"}
className={clsx( className={clsx(
"px-4 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed rounded-full border border-mti-gray-platinum focus:outline-none", "px-4 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed rounded-full border border-mti-gray-platinum focus:outline-none",
user.type === "agent" user.type === "agent" ? "bg-mti-gray-platinum/40" : "bg-white",
? "bg-mti-gray-platinum/40"
: "bg-white"
)} )}
options={( options={(users.filter((u) => u.type === "agent") as AgentUser[]).map((user) => ({
users.filter((u) => u.type === "agent") as AgentUser[]
).map((user) => ({
value: user.id, value: user.id,
meta: user, meta: user,
label: `${user.name} - ${user.email}`, label: `${user.name} - ${user.email}`,
@@ -1293,11 +1065,7 @@ export default function PaymentRecord() {
} }
: undefined : undefined
} }
onChange={(value) => onChange={(value) => setAgent(value !== null ? (value as any).meta : undefined)}
setAgent(
value !== null ? (value as any).meta : undefined
)
}
menuPortalTarget={document?.body} menuPortalTarget={document?.body}
styles={{ styles={{
menuPortal: (base) => ({...base, zIndex: 9999}), menuPortal: (base) => ({...base, zIndex: 9999}),
@@ -1312,11 +1080,7 @@ export default function PaymentRecord() {
}), }),
option: (styles, state) => ({ option: (styles, state) => ({
...styles, ...styles,
backgroundColor: state.isFocused backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
? "#D5D9F0"
: state.isSelected
? "#7872BF"
: "white",
color: state.isFocused ? "black" : styles.color, color: state.isFocused ? "black" : styles.color,
}), }),
}} }}
@@ -1324,16 +1088,12 @@ export default function PaymentRecord() {
</div> </div>
)} )}
<div className="flex flex-col gap-3 w-full"> <div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim"> <label className="font-normal text-base text-mti-gray-dim">Paid</label>
Paid
</label>
<Select <Select
isClearable isClearable
className={clsx( className={clsx(
"px-4 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed rounded-full border border-mti-gray-platinum focus:outline-none", "px-4 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed rounded-full border border-mti-gray-platinum focus:outline-none",
user.type === "agent" user.type === "agent" ? "bg-mti-gray-platinum/40" : "bg-white",
? "bg-mti-gray-platinum/40"
: "bg-white"
)} )}
options={IS_PAID_OPTIONS} options={IS_PAID_OPTIONS}
value={IS_PAID_OPTIONS.find((e) => e.value === paid)} value={IS_PAID_OPTIONS.find((e) => e.value === paid)}
@@ -1355,20 +1115,14 @@ export default function PaymentRecord() {
}), }),
option: (styles, state) => ({ option: (styles, state) => ({
...styles, ...styles,
backgroundColor: state.isFocused backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
? "#D5D9F0"
: state.isSelected
? "#7872BF"
: "white",
color: state.isFocused ? "black" : styles.color, color: state.isFocused ? "black" : styles.color,
}), }),
}} }}
/> />
</div> </div>
<div className="flex flex-col gap-3 w-full"> <div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim"> <label className="font-normal text-base text-mti-gray-dim">Date</label>
Date
</label>
<ReactDatePicker <ReactDatePicker
dateFormat="dd/MM/yyyy" dateFormat="dd/MM/yyyy"
className="px-4 py-6 w-full text-sm text-center font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed rounded-full border border-mti-gray-platinum focus:outline-none" className="px-4 py-6 w-full text-sm text-center font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed rounded-full border border-mti-gray-platinum focus:outline-none"
@@ -1377,13 +1131,9 @@ export default function PaymentRecord() {
endDate={endDate} endDate={endDate}
selectsRange selectsRange
showMonthDropdown showMonthDropdown
filterDate={(date: Date) => filterDate={(date: Date) => moment(date).isSameOrBefore(moment(new Date()))}
moment(date).isSameOrBefore(moment(new Date()))
}
onChange={([initialDate, finalDate]: [Date, Date]) => { onChange={([initialDate, finalDate]: [Date, Date]) => {
setStartDate( setStartDate(initialDate ?? moment("01/01/2023").toDate());
initialDate ?? moment("01/01/2023").toDate()
);
if (finalDate) { if (finalDate) {
// basicly selecting a final day works as if I'm selecting the first // basicly selecting a final day works as if I'm selecting the first
// minute of that day. this way it covers the whole day // minute of that day. this way it covers the whole day
@@ -1396,18 +1146,14 @@ export default function PaymentRecord() {
</div> </div>
{user.type !== "corporate" && ( {user.type !== "corporate" && (
<div className="flex flex-col gap-3 w-full"> <div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim"> <label className="font-normal text-base text-mti-gray-dim">Commission transfer</label>
Commission transfer
</label>
<Select <Select
isClearable isClearable
className={clsx( className={clsx(
"px-4 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed rounded-full border border-mti-gray-platinum focus:outline-none" "px-4 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed rounded-full border border-mti-gray-platinum focus:outline-none",
)} )}
options={IS_FILE_SUBMITTED_OPTIONS} options={IS_FILE_SUBMITTED_OPTIONS}
value={IS_FILE_SUBMITTED_OPTIONS.find( value={IS_FILE_SUBMITTED_OPTIONS.find((e) => e.value === commissionTransfer)}
(e) => e.value === commissionTransfer
)}
onChange={(value) => { onChange={(value) => {
if (value) return setCommissionTransfer(value.value); if (value) return setCommissionTransfer(value.value);
setCommissionTransfer(null); setCommissionTransfer(null);
@@ -1426,11 +1172,7 @@ export default function PaymentRecord() {
}), }),
option: (styles, state) => ({ option: (styles, state) => ({
...styles, ...styles,
backgroundColor: state.isFocused backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
? "#D5D9F0"
: state.isSelected
? "#7872BF"
: "white",
color: state.isFocused ? "black" : styles.color, color: state.isFocused ? "black" : styles.color,
}), }),
}} }}
@@ -1438,18 +1180,14 @@ export default function PaymentRecord() {
</div> </div>
)} )}
<div className="flex flex-col gap-3 w-full"> <div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim"> <label className="font-normal text-base text-mti-gray-dim">Corporate transfer</label>
Corporate transfer
</label>
<Select <Select
isClearable isClearable
className={clsx( className={clsx(
"px-4 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed rounded-full border border-mti-gray-platinum focus:outline-none" "px-4 py-4 w-full text-sm font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed rounded-full border border-mti-gray-platinum focus:outline-none",
)} )}
options={IS_FILE_SUBMITTED_OPTIONS} options={IS_FILE_SUBMITTED_OPTIONS}
value={IS_FILE_SUBMITTED_OPTIONS.find( value={IS_FILE_SUBMITTED_OPTIONS.find((e) => e.value === corporateTransfer)}
(e) => e.value === corporateTransfer
)}
onChange={(value) => { onChange={(value) => {
if (value) return setCorporateTransfer(value.value); if (value) return setCorporateTransfer(value.value);
setCorporateTransfer(null); setCorporateTransfer(null);
@@ -1468,11 +1206,7 @@ export default function PaymentRecord() {
}), }),
option: (styles, state) => ({ option: (styles, state) => ({
...styles, ...styles,
backgroundColor: state.isFocused backgroundColor: state.isFocused ? "#D5D9F0" : state.isSelected ? "#7872BF" : "white",
? "#D5D9F0"
: state.isSelected
? "#7872BF"
: "white",
color: state.isFocused ? "black" : styles.color, color: state.isFocused ? "black" : styles.color,
}), }),
}} }}
@@ -1482,16 +1216,9 @@ export default function PaymentRecord() {
{renderTable(table as Table<Payment>)} {renderTable(table as Table<Payment>)}
</Tab.Panel> </Tab.Panel>
<Tab.Panel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide flex flex-col gap-8"> <Tab.Panel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide flex flex-col gap-8">
<div <div className={clsx("grid grid-cols-1 md:grid-cols-2 gap-8 w-full", user.type !== "corporate" && "lg:grid-cols-3")}>
className={clsx(
"grid grid-cols-1 md:grid-cols-2 gap-8 w-full",
user.type !== "corporate" && "lg:grid-cols-3"
)}
>
<div className="flex flex-col gap-3 w-full"> <div className="flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim"> <label className="font-normal text-base text-mti-gray-dim">Date</label>
Date
</label>
<ReactDatePicker <ReactDatePicker
dateFormat="dd/MM/yyyy" dateFormat="dd/MM/yyyy"
className="px-4 py-6 w-full text-sm text-center font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed rounded-full border border-mti-gray-platinum focus:outline-none" className="px-4 py-6 w-full text-sm text-center font-normal placeholder:text-mti-gray-cool disabled:bg-mti-gray-platinum/40 disabled:text-mti-gray-dim disabled:cursor-not-allowed rounded-full border border-mti-gray-platinum focus:outline-none"
@@ -1500,19 +1227,13 @@ export default function PaymentRecord() {
endDate={endDatePaymob} endDate={endDatePaymob}
selectsRange selectsRange
showMonthDropdown showMonthDropdown
filterDate={(date: Date) => filterDate={(date: Date) => moment(date).isSameOrBefore(moment(new Date()))}
moment(date).isSameOrBefore(moment(new Date()))
}
onChange={([initialDate, finalDate]: [Date, Date]) => { onChange={([initialDate, finalDate]: [Date, Date]) => {
setStartDatePaymob( setStartDatePaymob(initialDate ?? moment("01/01/2023").toDate());
initialDate ?? moment("01/01/2023").toDate()
);
if (finalDate) { if (finalDate) {
// basicly selecting a final day works as if I'm selecting the first // basicly selecting a final day works as if I'm selecting the first
// minute of that day. this way it covers the whole day // minute of that day. this way it covers the whole day
setEndDatePaymob( setEndDatePaymob(moment(finalDate).endOf("day").toDate());
moment(finalDate).endOf("day").toDate()
);
return; return;
} }
setEndDatePaymob(null); setEndDatePaymob(null);

View File

@@ -16,7 +16,7 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
envVariables[x] = process.env[x]!; envVariables[x] = process.env[x]!;
}); });
if (!user || !user.isVerified) { if (!user) {
return { return {
redirect: { redirect: {
destination: "/login", destination: "/login",

View File

@@ -32,7 +32,7 @@ export const getServerSideProps = withIronSessionSsr(async (context) => {
const {req, params} = context; const {req, params} = context;
const user = req.session.user; const user = req.session.user;
if (!user || !user.isVerified) { if (!user) {
return { return {
redirect: { redirect: {
destination: "/login", destination: "/login",

View File

@@ -12,7 +12,7 @@ import PermissionList from "@/components/PermissionList";
export const getServerSideProps = withIronSessionSsr(async ({req}) => { export const getServerSideProps = withIronSessionSsr(async ({req}) => {
const user = req.session.user; const user = req.session.user;
if (!user || !user.isVerified) { if (!user) {
return { return {
redirect: { redirect: {
destination: "/login", destination: "/login",

View File

@@ -50,7 +50,7 @@ import {getUsers} from "@/utils/users.be";
export const getServerSideProps = withIronSessionSsr(async ({req, res}) => { export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
const user = req.session.user; const user = req.session.user;
if (!user || !user.isVerified) { if (!user) {
return { return {
redirect: { redirect: {
destination: "/login", destination: "/login",

View File

@@ -29,7 +29,7 @@ import useGradingSystem from "@/hooks/useGrading";
export const getServerSideProps = withIronSessionSsr(async ({req, res}) => { export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
const user = req.session.user; const user = req.session.user;
if (!user || !user.isVerified) { if (!user) {
return { return {
redirect: { redirect: {
destination: "/login", destination: "/login",

View File

@@ -121,7 +121,7 @@ export default function Register({code: queryCode}: {code: string}) {
</Link> </Link>
</> </>
)} )}
{user && !user.isVerified && <EmailVerification user={user} isLoading={isLoading} setIsLoading={setIsLoading} />} {/* {user && !user.isVerified && <EmailVerification user={user} isLoading={isLoading} setIsLoading={setIsLoading} />} */}
</section> </section>
</main> </main>
</> </>

View File

@@ -31,7 +31,7 @@ import useUsers from "@/hooks/useUsers";
export const getServerSideProps = withIronSessionSsr(async ({req, res}) => { export const getServerSideProps = withIronSessionSsr(async ({req, res}) => {
const user = req.session.user; const user = req.session.user;
if (!user || !user.isVerified) { if (!user) {
return { return {
redirect: { redirect: {
destination: "/login", destination: "/login",

View File

@@ -34,7 +34,7 @@ const COLORS = ["#1EB3FF", "#FF790A", "#3D9F11", "#EF5DA8", "#414288"];
export const getServerSideProps = withIronSessionSsr(({req, res}) => { export const getServerSideProps = withIronSessionSsr(({req, res}) => {
const user = req.session.user; const user = req.session.user;
if (!user || !user.isVerified) { if (!user) {
return { return {
redirect: { redirect: {
destination: "/login", destination: "/login",

View File

@@ -22,7 +22,7 @@ const columnHelper = createColumnHelper<TicketWithCorporate>();
export const getServerSideProps = withIronSessionSsr(({req, res}) => { export const getServerSideProps = withIronSessionSsr(({req, res}) => {
const user = req.session.user; const user = req.session.user;
if (!user || !user.isVerified) { if (!user) {
return { return {
redirect: { redirect: {
destination: "/login", destination: "/login",

View File

@@ -35,7 +35,7 @@ import {sortByModule} from "@/utils/moduleUtils";
export const getServerSideProps = withIronSessionSsr(({req, res}) => { export const getServerSideProps = withIronSessionSsr(({req, res}) => {
const user = req.session.user; const user = req.session.user;
if (!user || !user.isVerified) { if (!user) {
return { return {
redirect: { redirect: {
destination: "/login", destination: "/login",

View File

@@ -24,7 +24,7 @@ import useFilterRecordsByUser from "@/hooks/useFilterRecordsByUser";
export const getServerSideProps = withIronSessionSsr(({req, res}) => { export const getServerSideProps = withIronSessionSsr(({req, res}) => {
const user = req.session.user; const user = req.session.user;
if (!user || !user.isVerified) { if (!user) {
return { return {
redirect: { redirect: {
destination: "/login", destination: "/login",
@@ -48,17 +48,18 @@ export const getServerSideProps = withIronSessionSsr(({ req, res }) => {
}, sessionOptions); }, sessionOptions);
const Training: React.FC<{user: User}> = ({user}) => { const Training: React.FC<{user: User}> = ({user}) => {
const [recordUserId, setRecordTraining] = useRecordStore((state) => [ const [recordUserId, setRecordTraining] = useRecordStore((state) => [state.selectedUser, state.setTraining]);
state.selectedUser,
state.setTraining,
]);
const [filter, setFilter] = useState<"months" | "weeks" | "days" | "assignments">(); const [filter, setFilter] = useState<"months" | "weeks" | "days" | "assignments">();
const [stats, setTrainingStats] = useTrainingContentStore((state) => [state.stats, state.setStats]); const [stats, setTrainingStats] = useTrainingContentStore((state) => [state.stats, state.setStats]);
const [isNewContentLoading, setIsNewContentLoading] = useState(stats.length != 0); const [isNewContentLoading, setIsNewContentLoading] = useState(stats.length != 0);
const [groupedByTrainingContent, setGroupedByTrainingContent] = useState<{[key: string]: ITrainingContent}>(); const [groupedByTrainingContent, setGroupedByTrainingContent] = useState<{[key: string]: ITrainingContent}>();
const { data: trainingContent, isLoading: areRecordsLoading } = useFilterRecordsByUser<ITrainingContent[]>(recordUserId || user?.id, undefined, "training"); const {data: trainingContent, isLoading: areRecordsLoading} = useFilterRecordsByUser<ITrainingContent[]>(
recordUserId || user?.id,
undefined,
"training",
);
useEffect(() => { useEffect(() => {
const handleRouteChange = (url: string) => { const handleRouteChange = (url: string) => {

View File

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

View File

@@ -6,6 +6,28 @@ import {Module} from "@/interfaces";
import {getCorporateUser} from "@/resources/user"; import {getCorporateUser} from "@/resources/user";
import {getUserCorporate} from "./groups.be"; import {getUserCorporate} from "./groups.be";
import {Db, ObjectId} from "mongodb"; import {Db, ObjectId} from "mongodb";
import client from "@/lib/mongodb";
import {MODULE_ARRAY} from "./moduleUtils";
const db = client.db(process.env.MONGODB_DB);
export async function getSpecificExams(ids: string[]) {
if (ids.length === 0) return [];
const exams: Exam[] = (
await Promise.all(
MODULE_ARRAY.flatMap(
async (module) =>
await db
.collection(module)
.find<Exam>({id: {$in: ids}})
.toArray(),
),
)
).flat();
return exams;
}
export const getExams = async ( export const getExams = async (
db: Db, db: Db,
@@ -18,18 +40,18 @@ export const getExams = async (
variant?: Variant, variant?: Variant,
instructorGender?: InstructorGender, instructorGender?: InstructorGender,
): Promise<Exam[]> => { ): Promise<Exam[]> => {
const allExams = await db
.collection(module)
.find<Exam>({
isDiagnostic: false,
})
.toArray();
const allExams = await db.collection(module).find<Exam>({ const shuffledPublicExams = shuffle(
isDiagnostic: false
}).toArray();
const shuffledPublicExams = (
shuffle(
allExams.map((doc) => ({ allExams.map((doc) => ({
...doc, ...doc,
module, module,
})) as Exam[], })) as Exam[],
)
).filter((x) => !x.private); ).filter((x) => !x.private);
let exams: Exam[] = await filterByOwners(shuffledPublicExams, userId); let exams: Exam[] = await filterByOwners(shuffledPublicExams, userId);
@@ -39,9 +61,12 @@ export const getExams = async (
exams = await filterByPreference(db, exams, module, userId); exams = await filterByPreference(db, exams, module, userId);
if (avoidRepeated === "true") { if (avoidRepeated === "true") {
const stats = await db.collection("stats").find<Stat>({ const stats = await db
user: userId .collection("stats")
}).toArray(); .find<Stat>({
user: userId,
})
.toArray();
const filteredExams = exams.filter((x) => !stats.map((s) => s.exam).includes(x.id)); const filteredExams = exams.filter((x) => !stats.map((s) => s.exam).includes(x.id));
@@ -78,7 +103,7 @@ const filterByOwners = async (exams: Exam[], userID?: string) => {
const filterByDifficulty = async (db: Db, exams: Exam[], module: Module, userID?: string) => { const filterByDifficulty = async (db: Db, exams: Exam[], module: Module, userID?: string) => {
if (!userID) return exams; if (!userID) return exams;
const user = await db.collection("users").findOne<User>({ _id: new ObjectId(userID) }); const user = await db.collection("users").findOne<User>({id: userID});
if (!user) return exams; if (!user) return exams;
const difficulty = user.levels[module] <= 3 ? "easy" : user.levels[module] <= 6 ? "medium" : "hard"; const difficulty = user.levels[module] <= 3 ? "easy" : user.levels[module] <= 6 ? "medium" : "hard";
@@ -92,7 +117,7 @@ const filterByPreference = async (db: Db, exams: Exam[], module: Module, userID?
if (!userID) return exams; if (!userID) return exams;
const user = await db.collection("users").findOne<StudentUser | DeveloperUser>({ _id: new ObjectId(userID) }); const user = await db.collection("users").findOne<StudentUser | DeveloperUser>({id: userID});
if (!user) return exams; if (!user) return exams;
if (!["developer", "student"].includes(user.type)) return exams; if (!["developer", "student"].includes(user.type)) return exams;

View File

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

View File

@@ -1,4 +1,3 @@
/*fields example = [ /*fields example = [
['id'], ['id'],
['companyInformation', 'companyInformation', 'name'] ['companyInformation', 'companyInformation', 'name']
@@ -26,4 +25,4 @@ export const search = (text: string, fields: string[][], rows: any[]) => {
} }
}); });
}); });
} };

View File

@@ -56,7 +56,7 @@ export async function getLinkedUsers(
const participants = uniq([ const participants = uniq([
...adminGroups.flatMap((x) => x.participants), ...adminGroups.flatMap((x) => x.participants),
...groups.flat().flatMap((x) => x.participants), ...(userType === "mastercorporate" ? groups.flat().flatMap((x) => x.participants) : []),
...(userType === "teacher" ? belongingGroups.flatMap((x) => x.participants) : []), ...(userType === "teacher" ? belongingGroups.flatMap((x) => x.participants) : []),
]); ]);

483
yarn.lock
View File

@@ -191,7 +191,7 @@
"@emotion/utils" "0.11.3" "@emotion/utils" "0.11.3"
"@emotion/weak-memoize" "0.2.5" "@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" version "11.13.1"
resolved "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz" resolved "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.1.tgz"
integrity sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw== integrity sha512-iqouYkuEblRcXmylXIwwOodiEK5Ifl7JcX7o6V4jI3iW4mLXX3dmt5xwBtIkJiQEXFAI+pC8X0i67yiPkH9Ucw==
@@ -202,27 +202,16 @@
"@emotion/weak-memoize" "^0.4.0" "@emotion/weak-memoize" "^0.4.0"
stylis "4.2.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": "@emotion/hash@0.8.0":
version "0.8.0" version "0.8.0"
resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz" resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz"
integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== 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": "@emotion/is-prop-valid@^0.8.2":
version "0.8.8" version "0.8.8"
resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz" resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz"
@@ -230,16 +219,16 @@
dependencies: dependencies:
"@emotion/memoize" "0.7.4" "@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": "@emotion/memoize@0.7.4":
version "0.7.4" version "0.7.4"
resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz" resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz"
integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== 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": "@emotion/react@^11.8.1":
version "11.13.0" version "11.13.0"
resolved "https://registry.npmjs.org/@emotion/react/-/react-11.13.0.tgz" resolved "https://registry.npmjs.org/@emotion/react/-/react-11.13.0.tgz"
@@ -265,7 +254,7 @@
"@emotion/utils" "0.11.3" "@emotion/utils" "0.11.3"
csstype "^2.5.7" csstype "^2.5.7"
"@emotion/serialize@^1.2.0": "@emotion/serialize@^1.2.0", "@emotion/serialize@^1.3.0":
version "1.3.0" version "1.3.0"
resolved "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.0.tgz" resolved "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.0.tgz"
integrity sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA== integrity sha512-jACuBa9SlYajnpIVXB+XOXnfJHyckDfe6fOpORIM6yhBDlqGuExvDdZYHDQGoDf3bZXGv7tNr+LpLjJqiEQ6EA==
@@ -276,67 +265,56 @@
"@emotion/utils" "^1.4.0" "@emotion/utils" "^1.4.0"
csstype "^3.0.2" 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": "@emotion/sheet@0.9.4":
version "0.9.4" version "0.9.4"
resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.4.tgz" resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-0.9.4.tgz"
integrity sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA== 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": "@emotion/stylis@0.8.5":
version "0.8.5" version "0.8.5"
resolved "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz" resolved "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz"
integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== 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": "@emotion/unitless@0.7.5":
version "0.7.5" version "0.7.5"
resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz" resolved "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz"
integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== 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": "@emotion/use-insertion-effect-with-fallbacks@^1.1.0":
version "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" 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== 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": "@emotion/utils@0.11.3":
version "0.11.3" version "0.11.3"
resolved "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz" resolved "https://registry.npmjs.org/@emotion/utils/-/utils-0.11.3.tgz"
integrity sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw== integrity sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw==
"@emotion/weak-memoize@^0.4.0": "@emotion/utils@^1.4.0":
version "0.4.0" version "1.4.0"
resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz" resolved "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.0.tgz"
integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg== integrity sha512-spEnrA1b6hDR/C68lC2M7m6ALPUHZC0lIY7jAS/B/9DuuO1ZP04eov8SMv/6fwRd8pzmsn2AuJEznRREWlQrlQ==
"@emotion/weak-memoize@0.2.5": "@emotion/weak-memoize@0.2.5":
version "0.2.5" version "0.2.5"
resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz" resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz"
integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== 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": "@eslint/eslintrc@^1.4.1":
version "1.4.1" version "1.4.1"
resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz" resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz"
@@ -511,7 +489,7 @@
"@firebase/util" "1.9.3" "@firebase/util" "1.9.3"
tslib "^2.1.0" 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" version "0.3.4"
resolved "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.3.4.tgz" resolved "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.3.4.tgz"
integrity sha512-kuAW+l+sLMUKBThnvxvUZ+Q1ZrF/vFJ58iUY9kAcbX48U03nVzIF6Tmkf0p3WVQwMqiXguSgtOPIB6ZCeF+5Gg== integrity sha512-kuAW+l+sLMUKBThnvxvUZ+Q1ZrF/vFJ58iUY9kAcbX48U03nVzIF6Tmkf0p3WVQwMqiXguSgtOPIB6ZCeF+5Gg==
@@ -523,7 +501,7 @@
"@firebase/util" "1.9.3" "@firebase/util" "1.9.3"
tslib "^2.1.0" 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" version "0.10.4"
resolved "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.10.4.tgz" resolved "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.10.4.tgz"
integrity sha512-dPySn0vJ/89ZeBac70T+2tWWPiJXWbmRygYv0smT5TfE3hDrQ09eKMF3Y+vMlTdrMWq7mUdYW5REWPSGH4kAZQ== integrity sha512-dPySn0vJ/89ZeBac70T+2tWWPiJXWbmRygYv0smT5TfE3hDrQ09eKMF3Y+vMlTdrMWq7mUdYW5REWPSGH4kAZQ==
@@ -744,13 +722,6 @@
node-fetch "2.6.7" node-fetch "2.6.7"
tslib "^2.1.0" 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": "@firebase/util@1.9.3":
version "1.9.3" version "1.9.3"
resolved "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz" resolved "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz"
@@ -758,6 +729,13 @@
dependencies: dependencies:
tslib "^2.1.0" 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": "@firebase/webchannel-wrapper@0.9.0":
version "0.9.0" version "0.9.0"
resolved "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.9.0.tgz" resolved "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-0.9.0.tgz"
@@ -1020,6 +998,46 @@
dependencies: dependencies:
glob "7.1.7" 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": "@next/swc-win32-x64-msvc@14.2.5":
version "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" resolved "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.5.tgz"
@@ -1033,7 +1051,7 @@
"@nodelib/fs.stat" "2.0.5" "@nodelib/fs.stat" "2.0.5"
run-parallel "^1.1.9" 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" version "2.0.5"
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
@@ -1591,6 +1609,14 @@
resolved "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz" resolved "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz"
integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== 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": "@swc/helpers@^0.4.2":
version "0.4.14" version "0.4.14"
resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz" resolved "https://registry.npmjs.org/@swc/helpers/-/helpers-0.4.14.tgz"
@@ -1605,14 +1631,6 @@
dependencies: dependencies:
tslib "^2.4.0" 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": "@tanstack/react-table@^8.10.1":
version "8.19.3" version "8.19.3"
resolved "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.19.3.tgz" resolved "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.19.3.tgz"
@@ -1826,7 +1844,7 @@
resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz" resolved "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz"
integrity sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA== 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" version "18.13.0"
resolved "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz" resolved "https://registry.npmjs.org/@types/node/-/node-18.13.0.tgz"
integrity sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg== integrity sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==
@@ -2652,7 +2670,7 @@ classnames@^2.2.6, classnames@^2.3.0, classnames@^2.5.1:
resolved "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz" resolved "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz"
integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
client-only@^0.0.1, client-only@0.0.1: client-only@0.0.1, client-only@^0.0.1:
version "0.0.1" version "0.0.1"
resolved "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz" resolved "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz"
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
@@ -2675,20 +2693,16 @@ cliui@^7.0.2:
strip-ansi "^6.0.0" strip-ansi "^6.0.0"
wrap-ansi "^7.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: clone@^2.1.2:
version "2.1.2" version "2.1.2"
resolved "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz" resolved "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz"
integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== 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: clsx@^1.1.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz" resolved "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz"
@@ -2699,11 +2713,6 @@ clsx@^2.0.0, clsx@^2.1.1:
resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz" resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz"
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== 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: color-convert@^1.9.0:
version "1.9.3" version "1.9.3"
resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" resolved "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz"
@@ -2718,16 +2727,16 @@ color-convert@^2.0.1:
dependencies: dependencies:
color-name "~1.1.4" 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: color-name@1.1.3:
version "1.1.3" version "1.1.3"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== 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: color-string@^1.9.1:
version "1.9.1" version "1.9.1"
resolved "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz" resolved "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz"
@@ -2953,6 +2962,13 @@ dayjs@^1.8.34:
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz" resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz"
integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== 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: debug@^3.2.7:
version "3.2.7" version "3.2.7"
resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz"
@@ -2960,13 +2976,6 @@ debug@^3.2.7:
dependencies: dependencies:
ms "^2.1.1" 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: decamelize@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" resolved "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz"
@@ -3141,7 +3150,7 @@ eastasianwidth@^0.2.0:
resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz" resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz"
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== 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" version "1.0.11"
resolved "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz" resolved "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz"
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
@@ -3894,6 +3903,11 @@ fs.realpath@^1.0.0:
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== 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: fstream@^1.0.12:
version "1.0.12" version "1.0.12"
resolved "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz" resolved "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz"
@@ -3996,7 +4010,7 @@ get-tsconfig@^4.2.0:
resolved "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.4.0.tgz" resolved "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.4.0.tgz"
integrity sha512-0Gdjo/9+FzsYhXCEFueo2aY1z1tpXrxWZzP7k8ul9qt1U5o8rYJwTJYmaeHdrVosYIVYkOy2iwCJ9FdpocJhPQ== integrity sha512-0Gdjo/9+FzsYhXCEFueo2aY1z1tpXrxWZzP7k8ul9qt1U5o8rYJwTJYmaeHdrVosYIVYkOy2iwCJ9FdpocJhPQ==
glob-parent@^5.1.2: glob-parent@^5.1.2, glob-parent@~5.1.2:
version "5.1.2" version "5.1.2"
resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
@@ -4010,12 +4024,29 @@ glob-parent@^6.0.2:
dependencies: dependencies:
is-glob "^4.0.3" is-glob "^4.0.3"
glob-parent@~5.1.2: glob@7.1.6:
version "5.1.2" version "7.1.6"
resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" resolved "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz"
integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies: 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: glob@^10.4.2:
version "10.4.5" version "10.4.5"
@@ -4029,18 +4060,6 @@ glob@^10.4.2:
package-json-from-dist "^1.0.0" package-json-from-dist "^1.0.0"
path-scurry "^1.11.1" 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: glob@^7.2.3:
version "7.2.3" version "7.2.3"
resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz"
@@ -4064,18 +4083,6 @@ glob@^8.0.0:
minimatch "^5.0.1" minimatch "^5.0.1"
once "^1.3.0" 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: globals@^11.1.0:
version "11.12.0" version "11.12.0"
resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz" resolved "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz"
@@ -4374,7 +4381,7 @@ inflight@^1.0.4:
once "^1.3.0" once "^1.3.0"
wrappy "1" 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" version "2.0.4"
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
@@ -5062,18 +5069,18 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
dependencies: dependencies:
js-tokens "^3.0.0 || ^4.0.0" js-tokens "^3.0.0 || ^4.0.0"
lru-cache@^10.2.0: lru-cache@6.0.0, lru-cache@^6.0.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:
version "6.0.0" version "6.0.0"
resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz"
integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
dependencies: dependencies:
yallist "^4.0.0" 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: lru-memoizer@^2.2.0:
version "2.3.0" version "2.3.0"
resolved "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz" resolved "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz"
@@ -5149,7 +5156,7 @@ micromatch@^4.0.4, micromatch@^4.0.5:
braces "^3.0.2" braces "^3.0.2"
picomatch "^2.3.1" 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" version "1.52.0"
resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
@@ -5173,14 +5180,7 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
dependencies: dependencies:
brace-expansion "^1.1.7" brace-expansion "^1.1.7"
minimatch@^5.0.1: 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==
dependencies:
brace-expansion "^2.0.1"
minimatch@^5.1.0:
version "5.1.6" version "5.1.6"
resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz" resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz"
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
@@ -5224,11 +5224,6 @@ minizlib@^2.1.1:
minipass "^3.0.0" minipass "^3.0.0"
yallist "^4.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": "mkdirp@>=0.5 0":
version "0.5.6" version "0.5.6"
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz" resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz"
@@ -5236,6 +5231,11 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
dependencies: dependencies:
minimist "^1.2.6" 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: moment-timezone@^0.5.44:
version "0.5.45" version "0.5.45"
resolved "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.45.tgz" resolved "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.45.tgz"
@@ -5265,7 +5265,7 @@ mongodb@^6.8.1:
bson "^6.7.0" bson "^6.7.0"
mongodb-connection-string-url "^3.0.0" mongodb-connection-string-url "^3.0.0"
ms@^2.1.1, ms@2.1.2: ms@2.1.2, ms@^2.1.1:
version "2.1.2" version "2.1.2"
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
@@ -5327,21 +5327,14 @@ node-addon-api@^5.0.0:
resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz" resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz"
integrity sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA== 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" version "2.6.7"
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
dependencies: dependencies:
whatwg-url "^5.0.0" whatwg-url "^5.0.0"
node-fetch@^2.6.12: 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==
dependencies:
whatwg-url "^5.0.0"
node-fetch@^2.6.9:
version "2.7.0" version "2.7.0"
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz" resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz"
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
@@ -5702,7 +5695,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" resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== 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" version "8.4.31"
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz" resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz"
integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==
@@ -5744,15 +5737,6 @@ promise-polyfill@^8.3.0:
resolved "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz" resolved "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz"
integrity sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg== 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: prop-types@15.7.2:
version "15.7.2" version "15.7.2"
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz"
@@ -5762,6 +5746,15 @@ prop-types@15.7.2:
object-assign "^4.1.1" object-assign "^4.1.1"
react-is "^16.8.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: proto3-json-serializer@^1.0.0:
version "1.1.1" version "1.1.1"
resolved "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.1.tgz" resolved "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.1.tgz"
@@ -5785,6 +5778,24 @@ protobufjs-cli@1.1.1:
tmp "^0.2.1" tmp "^0.2.1"
uglify-js "^3.7.7" 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: protobufjs@^6.11.3:
version "6.11.3" version "6.11.3"
resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz" resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz"
@@ -5840,24 +5851,6 @@ protobufjs@^7.2.5:
"@types/node" ">=13.7.0" "@types/node" ">=13.7.0"
long "^5.0.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: proxy-from-env@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz"
@@ -6159,33 +6152,7 @@ read-excel-file@^5.7.1:
fflate "^0.7.3" fflate "^0.7.3"
unzipper "^0.12.2" unzipper "^0.12.2"
readable-stream@^2.0.0: 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==
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:
version "2.3.8" version "2.3.8"
resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz" resolved "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz"
integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==
@@ -6207,19 +6174,6 @@ readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0:
string_decoder "^1.1.1" string_decoder "^1.1.1"
util-deprecate "^1.0.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: readdir-glob@^1.1.2:
version "1.1.3" version "1.1.3"
resolved "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz" resolved "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz"
@@ -6326,13 +6280,6 @@ reusify@^1.0.4:
resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz" resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== 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: rimraf@2:
version "2.7.1" version "2.7.1"
resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz" resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz"
@@ -6340,6 +6287,13 @@ rimraf@2:
dependencies: dependencies:
glob "^7.1.3" 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: run-parallel@^1.1.9:
version "1.2.0" version "1.2.0"
resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz" resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz"
@@ -6347,7 +6301,7 @@ run-parallel@^1.1.9:
dependencies: dependencies:
queue-microtask "^1.2.2" 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" version "5.2.1"
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
@@ -6554,20 +6508,6 @@ streamsearch@^1.1.0:
resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz" resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz"
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== 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": "string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3" version "4.2.3"
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
@@ -6627,6 +6567,20 @@ string.prototype.trimstart@^1.0.6:
define-properties "^1.1.4" define-properties "^1.1.4"
es-abstract "^1.20.4" es-abstract "^1.20.4"
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"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1": "strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1" version "6.0.1"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
@@ -7081,7 +7035,7 @@ use-sidecar@^1.1.2:
detect-node-es "^1.1.0" detect-node-es "^1.1.0"
tslib "^2.0.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" version "1.2.0"
resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz" resolved "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
@@ -7091,12 +7045,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" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
uuid@^8.0.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==
uuid@^8.3.0:
version "8.3.2" version "8.3.2"
resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz" resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
@@ -7331,11 +7280,6 @@ yargs-parser@^20.2.2:
resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz"
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== 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: yargs@^15.3.1:
version "15.4.1" version "15.4.1"
resolved "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz" resolved "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz"
@@ -7366,19 +7310,6 @@ yargs@^16.2.0:
y18n "^5.0.5" y18n "^5.0.5"
yargs-parser "^20.2.2" 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: yocto-queue@^0.1.0:
version "0.1.0" version "0.1.0"
resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"