Added initial support for "mastercorporate"

This commit is contained in:
Joao Ramos
2024-07-13 17:19:42 +01:00
parent 5cfd6d56a6
commit 5d46d7e453
20 changed files with 719 additions and 182 deletions

View File

@@ -104,7 +104,7 @@ export default function MobileMenu({isOpen, onClose, path, user, disableNavigati
)}>
Record
</Link>
{["admin", "developer", "agent", "corporate"].includes(user.type) && (
{["admin", "developer", "agent", "corporate", "mastercorporate"].includes(user.type) && (
<Link
href={disableNavigation ? "" : "/payment-record"}
className={clsx(
@@ -115,7 +115,7 @@ export default function MobileMenu({isOpen, onClose, path, user, disableNavigati
Payment Record
</Link>
)}
{["admin", "developer", "corporate", "teacher"].includes(user.type) && (
{["admin", "developer", "corporate", "teacher", "mastercorporate"].includes(user.type) && (
<Link
href={disableNavigation ? "" : "/settings"}
className={clsx(

View File

@@ -122,7 +122,7 @@ export default function Sidebar({path, navDisabled = false, focusMode = false, u
<Nav disabled={disableNavigation} Icon={BsClockHistory} label="Record" path={path} keyPath="/record" isMinimized={isMinimized} />
</>
)}
{["admin", "developer", "agent", "corporate"].includes(userType || "") && (
{["admin", "developer", "agent", "corporate", "mastercorporate"].includes(userType || "") && (
<Nav
disabled={disableNavigation}
Icon={BsCurrencyDollar}
@@ -132,7 +132,7 @@ export default function Sidebar({path, navDisabled = false, focusMode = false, u
isMinimized={isMinimized}
/>
)}
{["admin", "developer", "corporate", "teacher"].includes(userType || "") && (
{["admin", "developer", "corporate", "teacher", "mastercorporate"].includes(userType || "") && (
<Nav
disabled={disableNavigation}
Icon={BsShieldFill}

View File

@@ -421,7 +421,7 @@ const UserCard = ({user, loggedInUser, onClose, onViewStudents, onViewTeachers,
)}
<div className="flex flex-col md:flex-row gap-8 w-full">
{user.type !== "corporate" && (
{user.type !== "corporate" && user.type !== 'mastercorporate' && (
<div className="relative flex flex-col gap-3 w-full">
<label className="font-normal text-base text-mti-gray-dim">Employment Status</label>
<RadioGroup

View File

@@ -1,18 +1,22 @@
import {Type} from "@/interfaces/user";
import { Type } from "@/interfaces/user";
export const PERMISSIONS = {
generateCode: {
student: ["corporate", "developer", "admin"],
teacher: ["corporate", "developer", "admin"],
student: ["corporate", "developer", "admin", "mastercorporate"],
teacher: ["corporate", "developer", "admin", "mastercorporate"],
corporate: ["admin", "developer"],
mastercorporate: ["admin", "developer"],
admin: ["developer", "admin"],
agent: ["developer", "admin"],
developer: ["developer"],
},
deleteUser: {
student: ["corporate", "developer", "admin"],
teacher: ["corporate", "developer", "admin"],
student: ["corporate", "developer", "admin", "mastercorporate"],
teacher: ["corporate", "developer", "admin", "mastercorporate"],
corporate: ["admin", "developer"],
mastercorporate: ["admin", "developer"],
admin: ["developer", "admin"],
agent: ["developer", "admin"],
developer: ["developer"],
@@ -20,7 +24,10 @@ export const PERMISSIONS = {
updateUser: {
student: ["developer", "admin"],
teacher: ["developer", "admin"],
corporate: ["admin", "developer"],
mastercorporate: ["admin", "developer"],
admin: ["developer", "admin"],
agent: ["developer", "admin"],
developer: ["developer"],
@@ -29,6 +36,8 @@ export const PERMISSIONS = {
student: ["developer", "admin"],
teacher: ["developer", "admin"],
corporate: ["admin", "developer"],
mastercorporate: ["admin", "developer"],
admin: ["developer", "admin"],
agent: ["developer", "admin"],
developer: ["developer"],

View File

@@ -0,0 +1,427 @@
/* eslint-disable @next/next/no-img-element */
import Modal from "@/components/Modal";
import useStats from "@/hooks/useStats";
import useUsers from "@/hooks/useUsers";
import { Group, MasterCorporateUser, Stat, User } from "@/interfaces/user";
import UserList from "@/pages/(admin)/Lists/UserList";
import { dateSorter } from "@/utils";
import moment from "moment";
import { useEffect, useState } from "react";
import {
BsArrowLeft,
BsClipboard2Data,
BsClock,
BsPaperclip,
BsPersonFill,
BsPencilSquare,
BsPersonCheck,
BsPeople,
BsBank,
} from "react-icons/bs";
import UserCard from "@/components/UserCard";
import useGroups from "@/hooks/useGroups";
import { calculateAverageLevel, calculateBandScore } from "@/utils/score";
import { MODULE_ARRAY } from "@/utils/moduleUtils";
import { Module } from "@/interfaces";
import { groupByExam } from "@/utils/stats";
import IconCard from "./IconCard";
import GroupList from "@/pages/(admin)/Lists/GroupList";
import useFilterStore from "@/stores/listFilterStore";
import { useRouter } from "next/router";
import useCodes from "@/hooks/useCodes";
interface Props {
user: MasterCorporateUser;
}
export default function MasterCorporateDashboard({ user }: Props) {
const [page, setPage] = useState("");
const [selectedUser, setSelectedUser] = useState<User>();
const [showModal, setShowModal] = useState(false);
const { stats } = useStats();
const { users, reload } = useUsers();
const { codes } = useCodes(user.id);
const { groups } = useGroups(user.id, user.type);
const masterCorporateUserGroups = [
...new Set(
groups.filter((u) => u.admin === user.id).flatMap((g) => g.participants)
),
];
const corporateUserGroups = [
...new Set(groups.flatMap((g) => g.participants)),
];
const appendUserFilters = useFilterStore((state) => state.appendUserFilter);
const router = useRouter();
useEffect(() => {
setShowModal(!!selectedUser && page === "");
}, [selectedUser, page]);
const studentFilter = (user: User) =>
user.type === "student" && corporateUserGroups.includes(user.id);
const teacherFilter = (user: User) =>
user.type === "teacher" && corporateUserGroups.includes(user.id);
const getStatsByStudent = (user: User) =>
stats.filter((s) => s.user === user.id);
const UserDisplay = (displayUser: User) => (
<div
onClick={() => setSelectedUser(displayUser)}
className="flex w-full p-4 gap-4 items-center hover:bg-mti-purple-ultralight cursor-pointer transition ease-in-out duration-300"
>
<img
src={displayUser.profilePicture}
alt={displayUser.name}
className="rounded-full w-10 h-10"
/>
<div className="flex flex-col gap-1 items-start">
<span>{displayUser.name}</span>
<span className="text-sm opacity-75">{displayUser.email}</span>
</div>
</div>
);
const StudentsList = () => {
const filter = (x: User) =>
x.type === "student" &&
(!!selectedUser
? corporateUserGroups.includes(x.id) || false
: corporateUserGroups.includes(x.id));
return (
<UserList
user={user}
filters={[filter]}
renderHeader={(total) => (
<div className="flex flex-col gap-4">
<div
onClick={() => setPage("")}
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300"
>
<BsArrowLeft className="text-xl" />
<span>Back</span>
</div>
<h2 className="text-2xl font-semibold">Students ({total})</h2>
</div>
)}
/>
);
};
const TeachersList = () => {
const filter = (x: User) =>
x.type === "teacher" &&
(!!selectedUser
? corporateUserGroups.includes(x.id) || false
: corporateUserGroups.includes(x.id));
return (
<UserList
user={user}
filters={[filter]}
renderHeader={(total) => (
<div className="flex flex-col gap-4">
<div
onClick={() => setPage("")}
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300"
>
<BsArrowLeft className="text-xl" />
<span>Back</span>
</div>
<h2 className="text-2xl font-semibold">Teachers ({total})</h2>
</div>
)}
/>
);
};
const corporateUserFilter = (x: User) =>
x.type === "corporate" &&
(!!selectedUser
? masterCorporateUserGroups.includes(x.id) || false
: masterCorporateUserGroups.includes(x.id));
const CorporateList = () => {
return (
<UserList
user={user}
filters={[corporateUserFilter]}
renderHeader={(total) => (
<div className="flex flex-col gap-4">
<div
onClick={() => setPage("")}
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300"
>
<BsArrowLeft className="text-xl" />
<span>Back</span>
</div>
<h2 className="text-2xl font-semibold">Corporates ({total})</h2>
</div>
)}
/>
);
};
const GroupsList = () => {
const filter = (x: Group) =>
x.admin === user.id || x.participants.includes(user.id);
return (
<>
<div className="flex flex-col gap-4">
<div
onClick={() => setPage("")}
className="flex gap-2 items-center text-mti-purple-light cursor-pointer hover:text-mti-purple-dark transition ease-in-out duration-300"
>
<BsArrowLeft className="text-xl" />
<span>Back</span>
</div>
<h2 className="text-2xl font-semibold">
Groups ({groups.filter(filter).length})
</h2>
</div>
<GroupList user={user} />
</>
);
};
const averageLevelCalculator = (studentStats: Stat[]) => {
const formattedStats = studentStats
.map((s) => ({
focus: users.find((u) => u.id === s.user)?.focus,
score: s.score,
module: s.module,
}))
.filter((f) => !!f.focus);
const bandScores = formattedStats.map((s) => ({
module: s.module,
level: calculateBandScore(
s.score.correct,
s.score.total,
s.module,
s.focus!
),
}));
const levels: { [key in Module]: number } = {
reading: 0,
listening: 0,
writing: 0,
speaking: 0,
level: 0,
};
bandScores.forEach((b) => (levels[b.module] += b.level));
return calculateAverageLevel(levels);
};
const DefaultDashboard = () => (
<>
<section className="flex flex-wrap gap-2 items-center -lg:justify-center lg:justify-between text-center">
<IconCard
onClick={() => setPage("students")}
Icon={BsPersonFill}
label="Students"
value={users.filter(studentFilter).length}
color="purple"
/>
<IconCard
onClick={() => setPage("teachers")}
Icon={BsPencilSquare}
label="Teachers"
value={users.filter(teacherFilter).length}
color="purple"
/>
<IconCard
Icon={BsClipboard2Data}
label="Exams Performed"
value={
stats.filter((s) =>
groups.flatMap((g) => g.participants).includes(s.user)
).length
}
color="purple"
/>
<IconCard
Icon={BsPaperclip}
label="Average Level"
value={averageLevelCalculator(
stats.filter((s) =>
groups.flatMap((g) => g.participants).includes(s.user)
)
).toFixed(1)}
color="purple"
/>
<IconCard
onClick={() => setPage("groups")}
Icon={BsPeople}
label="Groups"
value={groups.length}
color="purple"
/>
<IconCard
Icon={BsPersonCheck}
label="User Balance"
value={`${codes.length}/${
user.corporateInformation?.companyInformation?.userAmount || 0
}`}
color="purple"
/>
<IconCard
Icon={BsClock}
label="Expiration Date"
value={
user.subscriptionExpirationDate
? moment(user.subscriptionExpirationDate).format("DD/MM/yyyy")
: "Unlimited"
}
color="rose"
/>
<IconCard
Icon={BsBank}
label="Corporate"
value={masterCorporateUserGroups.length}
color="purple"
onClick={() => setPage("corporate")}
/>
</section>
<section className="grid grid-cols-1 md:grid-cols-2 gap-4 w-full justify-between">
<div className="bg-white shadow flex flex-col rounded-xl w-full">
<span className="p-4">Latest students</span>
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
{users
.filter(studentFilter)
.sort((a, b) => dateSorter(a, b, "desc", "registrationDate"))
.map((x) => (
<UserDisplay key={x.id} {...x} />
))}
</div>
</div>
<div className="bg-white shadow flex flex-col rounded-xl w-full">
<span className="p-4">Latest teachers</span>
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
{users
.filter(teacherFilter)
.sort((a, b) => dateSorter(a, b, "desc", "registrationDate"))
.map((x) => (
<UserDisplay key={x.id} {...x} />
))}
</div>
</div>
<div className="bg-white shadow flex flex-col rounded-xl w-full">
<span className="p-4">Highest level students</span>
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
{users
.filter(studentFilter)
.sort(
(a, b) =>
calculateAverageLevel(b.levels) -
calculateAverageLevel(a.levels)
)
.map((x) => (
<UserDisplay key={x.id} {...x} />
))}
</div>
</div>
<div className="bg-white shadow flex flex-col rounded-xl w-full">
<span className="p-4">Highest exam count students</span>
<div className="flex flex-col items-start h-96 overflow-scroll scrollbar-hide">
{users
.filter(studentFilter)
.sort(
(a, b) =>
Object.keys(groupByExam(getStatsByStudent(b))).length -
Object.keys(groupByExam(getStatsByStudent(a))).length
)
.map((x) => (
<UserDisplay key={x.id} {...x} />
))}
</div>
</div>
</section>
</>
);
return (
<>
<Modal isOpen={showModal} onClose={() => setSelectedUser(undefined)}>
<>
{selectedUser && (
<div className="w-full flex flex-col gap-8">
<UserCard
loggedInUser={user}
onClose={(shouldReload) => {
setSelectedUser(undefined);
if (shouldReload) reload();
}}
onViewStudents={
selectedUser.type === "corporate" ||
selectedUser.type === "teacher"
? () => {
appendUserFilters({
id: "view-students",
filter: (x: User) => x.type === "student",
});
appendUserFilters({
id: "belongs-to-admin",
filter: (x: User) =>
groups
.filter(
(g) =>
g.admin === selectedUser.id ||
g.participants.includes(selectedUser.id)
)
.flatMap((g) => g.participants)
.includes(x.id),
});
router.push("/list/users");
}
: undefined
}
onViewTeachers={
selectedUser.type === "corporate" ||
selectedUser.type === "student"
? () => {
appendUserFilters({
id: "view-teachers",
filter: (x: User) => x.type === "teacher",
});
appendUserFilters({
id: "belongs-to-admin",
filter: (x: User) =>
groups
.filter(
(g) =>
g.admin === selectedUser.id ||
g.participants.includes(selectedUser.id)
)
.flatMap((g) => g.participants)
.includes(x.id),
});
router.push("/list/users");
}
: undefined
}
user={selectedUser}
/>
</div>
)}
</>
</Modal>
{page === "students" && <StudentsList />}
{page === "teachers" && <TeachersList />}
{page === "groups" && <GroupsList />}
{page === "corporate" && <CorporateList />}
{page === "" && <DefaultDashboard />}
</>
);
}

View File

@@ -2,16 +2,23 @@ import {Group, User} from "@/interfaces/user";
import axios from "axios";
import {useEffect, useState} from "react";
export default function useGroups(admin?: string) {
export default function useGroups(admin?: string, userType?: string) {
const [groups, setGroups] = useState<Group[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const isMasterType = userType?.startsWith('master');
const getData = () => {
setIsLoading(true);
const url = admin ? `/api/groups?admin=${admin}` : "/api/groups";
axios
.get<Group[]>("/api/groups")
.get<Group[]>(url)
.then((response) => {
if(isMasterType) {
return setGroups(response.data);
}
const filter = (g: Group) => g.admin === admin || g.participants.includes(admin || "");
const filteredGroups = admin ? response.data.filter(filter) : response.data;
@@ -20,7 +27,7 @@ export default function useGroups(admin?: string) {
.finally(() => setIsLoading(false));
};
useEffect(getData, [admin]);
useEffect(getData, [admin, isMasterType]);
return {groups, isLoading, isError, reload: getData};
}

View File

@@ -1,7 +1,14 @@
import {Module} from ".";
import {InstructorGender} from "./exam";
import { Module } from ".";
import { InstructorGender } from "./exam";
export type User = StudentUser | TeacherUser | CorporateUser | AgentUser | AdminUser | DeveloperUser;
export type User =
| StudentUser
| TeacherUser
| CorporateUser
| AgentUser
| AdminUser
| DeveloperUser
| MasterCorporateUser;
export type UserStatus = "active" | "disabled" | "paymentDue";
export interface BasicUser {
@@ -11,8 +18,8 @@ export interface BasicUser {
id: string;
isFirstLogin: boolean;
focus: "academic" | "general";
levels: {[key in Module]: number};
desiredLevels: {[key in Module]: number};
levels: { [key in Module]: number };
desiredLevels: { [key in Module]: number };
type: Type;
bio: string;
isVerified: boolean;
@@ -39,6 +46,12 @@ export interface CorporateUser extends BasicUser {
demographicInformation?: DemographicCorporateInformation;
}
export interface MasterCorporateUser extends BasicUser {
type: "mastercorporate";
corporateInformation: CorporateInformation;
demographicInformation?: DemographicCorporateInformation;
}
export interface AgentUser extends BasicUser {
type: "agent";
agentInformation: AgentInformation;
@@ -97,15 +110,22 @@ export interface DemographicCorporateInformation {
}
export type Gender = "male" | "female" | "other";
export type EmploymentStatus = "employed" | "student" | "self-employed" | "unemployed" | "retired" | "other";
export const EMPLOYMENT_STATUS: {status: EmploymentStatus; label: string}[] = [
{status: "student", label: "Student"},
{status: "employed", label: "Employed"},
{status: "unemployed", label: "Unemployed"},
{status: "self-employed", label: "Self-employed"},
{status: "retired", label: "Retired"},
{status: "other", label: "Other"},
];
export type EmploymentStatus =
| "employed"
| "student"
| "self-employed"
| "unemployed"
| "retired"
| "other";
export const EMPLOYMENT_STATUS: { status: EmploymentStatus; label: string }[] =
[
{ status: "student", label: "Student" },
{ status: "employed", label: "Employed" },
{ status: "unemployed", label: "Unemployed" },
{ status: "self-employed", label: "Self-employed" },
{ status: "retired", label: "Retired" },
{ status: "other", label: "Other" },
];
export interface Stat {
id: string;
@@ -148,5 +168,20 @@ export interface Code {
passport_id?: string;
}
export type Type = "student" | "teacher" | "corporate" | "admin" | "developer" | "agent";
export const userTypes: Type[] = ["student", "teacher", "corporate", "admin", "developer", "agent"];
export type Type =
| "student"
| "teacher"
| "corporate"
| "admin"
| "developer"
| "agent"
| "mastercorporate";
export const userTypes: Type[] = [
"student",
"teacher",
"corporate",
"admin",
"developer",
"agent",
"mastercorporate",
];

View File

@@ -24,8 +24,9 @@ const USER_TYPE_PERMISSIONS: {[key in Type]: Type[]} = {
teacher: [],
agent: [],
corporate: ["student", "teacher"],
admin: ["student", "teacher", "agent", "corporate", "admin"],
developer: ["student", "teacher", "agent", "corporate", "admin", "developer"],
mastercorporate: ["student", "teacher", "corporate"],
admin: ["student", "teacher", "agent", "corporate", "admin", "mastercorporate"],
developer: ["student", "teacher", "agent", "corporate", "admin", "developer", "mastercorporate"],
};
export default function BatchCodeGenerator({user}: {user: User}) {
@@ -200,7 +201,7 @@ export default function BatchCodeGenerator({user}: {user: User}) {
<Button onClick={openFilePicker} isLoading={isLoading} disabled={isLoading}>
{filesContent.length > 0 ? filesContent[0].name : "Choose a file"}
</Button>
{user && (user.type === "developer" || user.type === "admin" || user.type === "corporate") && (
{user && (["developer","admin","corporate", "mastercorporate"].includes(user.type)) && (
<>
<div className="-md:flex-row -md:items-center flex justify-between gap-2 md:flex-col 2xl:flex-row 2xl:items-center">
<label className="text-mti-gray-dim text-base font-normal">Expiry Date</label>

View File

@@ -17,8 +17,9 @@ const USER_TYPE_PERMISSIONS: {[key in Type]: Type[]} = {
teacher: [],
agent: [],
corporate: ["student", "teacher"],
admin: ["student", "teacher", "agent", "corporate", "admin"],
developer: ["student", "teacher", "agent", "corporate", "admin", "developer"],
mastercorporate: ["student", "teacher", "corporate"],
admin: ["student", "teacher", "agent", "corporate", "admin", "mastercorporate"],
developer: ["student", "teacher", "agent", "corporate", "admin", "developer","mastercorporate"],
};
export default function CodeGenerator({user}: {user: User}) {

View File

@@ -86,7 +86,7 @@ const CreatePanel = ({user, users, group, onClose}: CreateDialogProps) => {
const emailUsers = [...new Set(emails)].map((x) => users.find((y) => y.email.toLowerCase() === x)).filter((x) => x !== undefined);
const filteredUsers = emailUsers.filter(
(x) =>
((user.type === "developer" || user.type === "admin" || user.type === "corporate") &&
((user.type === "developer" || user.type === "admin" || user.type === "corporate" || user.type === "mastercorporate") &&
(x?.type === "student" || x?.type === "teacher")) ||
(user.type === "teacher" && x?.type === "student"),
);
@@ -197,10 +197,10 @@ export default function GroupList({user}: {user: User}) {
const [filterByUser, setFilterByUser] = useState(false);
const {users} = useUsers();
const {groups, reload} = useGroups(user && filterTypes.includes(user?.type) ? user.id : undefined);
const {groups, reload} = useGroups(user && filterTypes.includes(user?.type) ? user.id : undefined, user?.type);
useEffect(() => {
if (user && (user.type === "corporate" || user.type === "teacher")) {
if (user && (['corporate', 'teacher', 'mastercorporate'].includes(user.type))) {
setFilterByUser(true);
}
}, [user]);

View File

@@ -92,7 +92,7 @@ export default function UserList({
const { users, reload } = useUsers();
const { groups } = useGroups(
user && (user?.type === "corporate" || user?.type === "teacher")
user && (['corporate', 'teacher', 'mastercorporate'].includes(user?.type))
? user.id
: undefined
);
@@ -403,7 +403,7 @@ export default function UserList({
}),
columnHelper.accessor(
(x) =>
x.type === "corporate"
x.type === "corporate" || x.type === "mastercorporate"
? x.demographicInformation?.position
: x.demographicInformation?.employment,
{
@@ -706,11 +706,11 @@ export default function UserList({
if (sorter === "employment" || sorter === reverseString("employment")) {
const aSortingItem =
a.type === "corporate"
a.type === "corporate" || a.type === "mastercorporate"
? a.demographicInformation?.position
: a.demographicInformation?.employment;
const bSortingItem =
b.type === "corporate"
b.type === "corporate" || b.type === "mastercorporate"
? b.demographicInformation?.position
: b.demographicInformation?.employment;

View File

@@ -30,12 +30,8 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === "POST") await post(req, res);
}
async function get(req: NextApiRequest, res: NextApiResponse) {
const { admin, participant } = req.query as {
admin: string;
participant: string;
};
const getGroupsForUser = async (admin: string, participant: string) => {
try {
const queryConstraints = [
...(admin ? [where("admin", "==", admin)] : []),
...(participant
@@ -45,14 +41,63 @@ async function get(req: NextApiRequest, res: NextApiResponse) {
const snapshot = await getDocs(
queryConstraints.length > 0
? query(collection(db, "groups"), ...queryConstraints)
: collection(db, "groups"),
: collection(db, "groups")
);
const groups = snapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
})) as Group[];
return groups;
} catch (e) {
console.error(e);
return [];
}
};
async function get(req: NextApiRequest, res: NextApiResponse) {
const { admin, participant } = req.query as {
admin: string;
participant: string;
};
if (req.session?.user?.type === "mastercorporate") {
try {
const masterCorporateGroups = await getGroupsForUser(admin, participant);
const corporatesFromMaster = masterCorporateGroups
.filter((g) => g.name === "Corporate")
.flatMap((g) => g.participants);
if (corporatesFromMaster.length === 0) {
res.status(200).json([]);
return;
}
Promise.all(
corporatesFromMaster.map((c) => getGroupsForUser(c, participant))
)
.then((groups) => {
res.status(200).json([...masterCorporateGroups, ...groups.flat()]);
return;
})
.catch((e) => {
console.error(e);
res.status(500).json({ ok: false });
return;
});
} catch (e) {
console.error(e);
res.status(500).json({ ok: false });
return;
}
return;
}
try {
const groups = await getGroupsForUser(admin, participant);
res.status(200).json(groups);
} catch (e) {
console.error(e);
res.status(500).json({ ok: false });
}
}
async function post(req: NextApiRequest, res: NextApiResponse) {
@@ -60,8 +105,8 @@ async function post(req: NextApiRequest, res: NextApiResponse) {
await Promise.all(
body.participants.map(
async (p) => await updateExpiryDateOnGroup(p, body.admin),
),
async (p) => await updateExpiryDateOnGroup(p, body.admin)
)
);
await setDoc(doc(db, "groups", v4()), {

View File

@@ -143,9 +143,18 @@ async function registerCorporate(req: NextApiRequest, res: NextApiResponse) {
disableEditing: true,
};
const defaultCorporateGroup: Group = {
admin: userId,
id: v4(),
name: "Corporate",
participants: [],
disableEditing: true,
};
await setDoc(doc(db, "users", userId), user);
await setDoc(doc(db, "groups", defaultTeachersGroup.id), defaultTeachersGroup);
await setDoc(doc(db, "groups", defaultStudentsGroup.id), defaultStudentsGroup);
await setDoc(doc(db, "groups", defaultCorporateGroup.id), defaultCorporateGroup);
req.session.user = {...user, id: userId};
await req.session.save();

View File

@@ -27,6 +27,7 @@ import AdminDashboard from "@/dashboards/Admin";
import CorporateDashboard from "@/dashboards/Corporate";
import TeacherDashboard from "@/dashboards/Teacher";
import AgentDashboard from "@/dashboards/Agent";
import MasterCorporateDashboard from "@/dashboards/MasterCorporate";
import PaymentDue from "./(status)/PaymentDue";
import {useRouter} from "next/router";
import {PayPalScriptProvider} from "@paypal/react-paypal-js";
@@ -172,6 +173,7 @@ export default function Home(props: Props) {
{user.type === "student" && <StudentDashboard user={user} />}
{user.type === "teacher" && <TeacherDashboard user={user} />}
{user.type === "corporate" && <CorporateDashboard user={user} />}
{user.type === "mastercorporate" && <MasterCorporateDashboard user={user} />}
{user.type === "agent" && <AgentDashboard user={user} />}
{user.type === "admin" && <AdminDashboard user={user} />}
{user.type === "developer" && (

View File

@@ -42,7 +42,7 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
};
}
if (shouldRedirectHome(user) || !["admin", "developer", "agent", "corporate"].includes(user.type)) {
if (shouldRedirectHome(user) || !["admin", "developer", "agent", "corporate", "mastercorporate"].includes(user.type)) {
return {
redirect: {
destination: "/",
@@ -940,7 +940,7 @@ export default function PaymentRecord() {
<div className="w-full flex flex-end justify-between p-2">
<h1 className="text-2xl font-semibold">Payment Record</h1>
<div className="flex justify-end gap-2">
{(user.type === "developer" || user.type === "admin" || user.type === "agent" || user.type === "corporate") && (
{(["developer", "admin", "agent", "corporate", "mastercorporate"].includes(user.type)) && (
<Button className="max-w-[200px]" variant="outline">
<CSVLink data={csvRows} headers={csvColumns} filename="payment-records.csv">
Download CSV

View File

@@ -84,7 +84,7 @@ function UserProfile({user, mutateUser}: Props) {
const [phone, setPhone] = useState<string>(user.demographicInformation?.phone || "");
const [gender, setGender] = useState<Gender | undefined>(user.demographicInformation?.gender || undefined);
const [employment, setEmployment] = useState<EmploymentStatus | undefined>(
user.type === "corporate" ? undefined : user.demographicInformation?.employment,
user.type === "corporate" || user.type === "mastercorporate" ? undefined : user.demographicInformation?.employment,
);
const [passport_id, setPassportID] = useState<string | undefined>(user.type === "student" ? user.demographicInformation?.passport_id : undefined);

View File

@@ -26,7 +26,7 @@ export const getServerSideProps = withIronSessionSsr(({req, res}) => {
};
}
if (shouldRedirectHome(user) || !["developer", "admin", "corporate", "agent"].includes(user.type)) {
if (shouldRedirectHome(user) || !["developer", "admin", "corporate", "agent", "mastercorporate"].includes(user.type)) {
return {
redirect: {
destination: "/",

View File

@@ -202,7 +202,7 @@ export default function Stats() {
}}
/>
)}
{(user.type === "corporate" || user.type === "teacher") && groups.length > 0 && (
{(["corporate", "teacher", "mastercorporate"].includes(user.type) ) && groups.length > 0 && (
<Select
className="w-full"
options={users

View File

@@ -7,6 +7,7 @@ export const USER_TYPE_LABELS: {[key in Type]: string} = {
agent: "Country Manager",
admin: "Admin",
developer: "Developer",
mastercorporate: "Master Corporate"
};
export function isCorporateUser(user: User): user is CorporateUser {

View File

@@ -25,7 +25,7 @@ export const exportListToExcel = (rowUsers: User[], users: User[], groups: Group
expiryDate: user.subscriptionExpirationDate ? moment(user.subscriptionExpirationDate).format("DD/MM/YYYY") : "Unlimited",
country: user.demographicInformation?.country || "N/A",
phone: user.demographicInformation?.phone || "N/A",
employmentPosition: (user.type === "corporate" ? user.demographicInformation?.position : user.demographicInformation?.employment) || "N/A",
employmentPosition: (user.type === "corporate" || user.type === "mastercorporate" ? user.demographicInformation?.position : user.demographicInformation?.employment) || "N/A",
gender: user.demographicInformation?.gender ? capitalize(user.demographicInformation.gender) : "N/A",
verified: user.isVerified?.toString() || "FALSE",
}));