|
|
|
|
@@ -1,37 +1,38 @@
|
|
|
|
|
import Button from "@/components/Low/Button";
|
|
|
|
|
import {PERMISSIONS} from "@/constants/userPermissions";
|
|
|
|
|
import { PERMISSIONS } from "@/constants/userPermissions";
|
|
|
|
|
import useGroups from "@/hooks/useGroups";
|
|
|
|
|
import useUsers from "@/hooks/useUsers";
|
|
|
|
|
import {Type, User, userTypes, CorporateUser, Group} from "@/interfaces/user";
|
|
|
|
|
import {Popover, Transition} from "@headlessui/react";
|
|
|
|
|
import {createColumnHelper, flexRender, getCoreRowModel, useReactTable} from "@tanstack/react-table";
|
|
|
|
|
import { Type, User, userTypes, CorporateUser, Group } from "@/interfaces/user";
|
|
|
|
|
import { Popover, Transition } from "@headlessui/react";
|
|
|
|
|
import { createColumnHelper, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
|
|
|
|
import axios from "axios";
|
|
|
|
|
import clsx from "clsx";
|
|
|
|
|
import {capitalize, reverse} from "lodash";
|
|
|
|
|
import { capitalize, reverse } from "lodash";
|
|
|
|
|
import moment from "moment";
|
|
|
|
|
import {Fragment, useEffect, useState, useMemo} from "react";
|
|
|
|
|
import {BsArrowDown, BsArrowDownUp, BsArrowUp, BsCheck, BsCheckCircle, BsEye, BsFillExclamationOctagonFill, BsPerson, BsTrash} from "react-icons/bs";
|
|
|
|
|
import {toast} from "react-toastify";
|
|
|
|
|
import {countries, TCountries} from "countries-list";
|
|
|
|
|
import { Fragment, useEffect, useState, useMemo } from "react";
|
|
|
|
|
import { BsArrowDown, BsArrowDownUp, BsArrowUp, BsCheck, BsCheckCircle, BsEye, BsFillExclamationOctagonFill, BsPerson, BsTrash } from "react-icons/bs";
|
|
|
|
|
import { toast } from "react-toastify";
|
|
|
|
|
import { countries, TCountries } from "countries-list";
|
|
|
|
|
import countryCodes from "country-codes-list";
|
|
|
|
|
import Modal from "@/components/Modal";
|
|
|
|
|
import UserCard from "@/components/UserCard";
|
|
|
|
|
import {getUserCompanyName, isAgentUser, USER_TYPE_LABELS} from "@/resources/user";
|
|
|
|
|
import { getUserCompanyName, isAgentUser, USER_TYPE_LABELS } from "@/resources/user";
|
|
|
|
|
import useFilterStore from "@/stores/listFilterStore";
|
|
|
|
|
import {useRouter} from "next/router";
|
|
|
|
|
import {isCorporateUser} from "@/resources/user";
|
|
|
|
|
import {useListSearch} from "@/hooks/useListSearch";
|
|
|
|
|
import {getUserCorporate} from "@/utils/groups";
|
|
|
|
|
import {asyncSorter} from "@/utils";
|
|
|
|
|
import {exportListToExcel, UserListRow} from "@/utils/users";
|
|
|
|
|
import {checkAccess} from "@/utils/permissions";
|
|
|
|
|
import {PermissionType} from "@/interfaces/permissions";
|
|
|
|
|
import { useRouter } from "next/router";
|
|
|
|
|
import { isCorporateUser } from "@/resources/user";
|
|
|
|
|
import { useListSearch } from "@/hooks/useListSearch";
|
|
|
|
|
import { getUserCorporate } from "@/utils/groups";
|
|
|
|
|
import { asyncSorter } from "@/utils";
|
|
|
|
|
import { exportListToExcel, UserListRow } from "@/utils/users";
|
|
|
|
|
import { checkAccess } from "@/utils/permissions";
|
|
|
|
|
import { PermissionType } from "@/interfaces/permissions";
|
|
|
|
|
import usePermissions from "@/hooks/usePermissions";
|
|
|
|
|
import useUserBalance from "@/hooks/useUserBalance";
|
|
|
|
|
import Input from "@/components/Low/Input";
|
|
|
|
|
const columnHelper = createColumnHelper<User>();
|
|
|
|
|
const searchFields = [["name"], ["email"], ["corporateInformation", "companyInformation", "name"]];
|
|
|
|
|
|
|
|
|
|
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 [isLoading, setIsLoading] = useState(false);
|
|
|
|
|
|
|
|
|
|
@@ -59,20 +60,13 @@ export default function UserList({
|
|
|
|
|
const [displayUsers, setDisplayUsers] = useState<User[]>([]);
|
|
|
|
|
const [selectedUser, setSelectedUser] = useState<User>();
|
|
|
|
|
const [page, setPage] = useState(0);
|
|
|
|
|
const [searchTerm, setSearchTerm] = useState<string | undefined>(undefined);
|
|
|
|
|
|
|
|
|
|
const userHash = useMemo(
|
|
|
|
|
() => ({
|
|
|
|
|
type,
|
|
|
|
|
size: 16,
|
|
|
|
|
page,
|
|
|
|
|
}),
|
|
|
|
|
[type, page],
|
|
|
|
|
);
|
|
|
|
|
const { users, total, isLoading, reload } = useUsers({type: type, size: 16, page: page, searchTerm: searchTerm});
|
|
|
|
|
|
|
|
|
|
const {users, total, isLoading, reload} = useUsers(userHash);
|
|
|
|
|
const {permissions} = usePermissions(user?.id || "");
|
|
|
|
|
const {balance} = useUserBalance();
|
|
|
|
|
const {groups} = useGroups({
|
|
|
|
|
const { permissions } = usePermissions(user?.id || "");
|
|
|
|
|
const { balance } = useUserBalance();
|
|
|
|
|
const { groups } = useGroups({
|
|
|
|
|
admin: user && ["corporate", "teacher", "mastercorporate"].includes(user?.type) ? user.id : undefined,
|
|
|
|
|
userType: user?.type,
|
|
|
|
|
});
|
|
|
|
|
@@ -106,20 +100,20 @@ export default function UserList({
|
|
|
|
|
if (!confirm(`Are you sure you want to delete ${user.name}'s account?`)) return;
|
|
|
|
|
|
|
|
|
|
axios
|
|
|
|
|
.delete<{ok: boolean}>(`/api/user?id=${user.id}`)
|
|
|
|
|
.delete<{ ok: boolean }>(`/api/user?id=${user.id}`)
|
|
|
|
|
.then(() => {
|
|
|
|
|
toast.success("User deleted successfully!");
|
|
|
|
|
reload();
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {
|
|
|
|
|
toast.error("Something went wrong!", {toastId: "delete-error"});
|
|
|
|
|
toast.error("Something went wrong!", { toastId: "delete-error" });
|
|
|
|
|
})
|
|
|
|
|
.finally(reload);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const verifyAccount = (user: User) => {
|
|
|
|
|
axios
|
|
|
|
|
.post<{user?: User; ok?: boolean}>(`/api/users/update?id=${user.id}`, {
|
|
|
|
|
.post<{ user?: User; ok?: boolean }>(`/api/users/update?id=${user.id}`, {
|
|
|
|
|
...user,
|
|
|
|
|
isVerified: true,
|
|
|
|
|
})
|
|
|
|
|
@@ -128,22 +122,21 @@ export default function UserList({
|
|
|
|
|
reload();
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {
|
|
|
|
|
toast.error("Something went wrong!", {toastId: "update-error"});
|
|
|
|
|
toast.error("Something went wrong!", { toastId: "update-error" });
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const toggleDisableAccount = (user: User) => {
|
|
|
|
|
if (
|
|
|
|
|
!confirm(
|
|
|
|
|
`Are you sure you want to ${user.status === "disabled" ? "enable" : "disable"} ${
|
|
|
|
|
user.name
|
|
|
|
|
`Are you sure you want to ${user.status === "disabled" ? "enable" : "disable"} ${user.name
|
|
|
|
|
}'s account? This change is usually related to their payment state.`,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
axios
|
|
|
|
|
.post<{user?: User; ok?: boolean}>(`/api/users/update?id=${user.id}`, {
|
|
|
|
|
.post<{ user?: User; ok?: boolean }>(`/api/users/update?id=${user.id}`, {
|
|
|
|
|
...user,
|
|
|
|
|
status: user.status === "disabled" ? "active" : "disabled",
|
|
|
|
|
})
|
|
|
|
|
@@ -152,18 +145,18 @@ export default function UserList({
|
|
|
|
|
reload();
|
|
|
|
|
})
|
|
|
|
|
.catch(() => {
|
|
|
|
|
toast.error("Something went wrong!", {toastId: "update-error"});
|
|
|
|
|
toast.error("Something went wrong!", { toastId: "update-error" });
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const SorterArrow = ({name}: {name: string}) => {
|
|
|
|
|
const SorterArrow = ({ name }: { name: string }) => {
|
|
|
|
|
if (sorter === name) return <BsArrowUp />;
|
|
|
|
|
if (sorter === reverseString(name)) return <BsArrowDown />;
|
|
|
|
|
|
|
|
|
|
return <BsArrowDownUp />;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const actionColumn = ({row}: {row: {original: User}}) => {
|
|
|
|
|
const actionColumn = ({ row }: { row: { original: User } }) => {
|
|
|
|
|
const updateUserPermission = PERMISSIONS.updateUser[row.original.type] as {
|
|
|
|
|
list: Type[];
|
|
|
|
|
perm: PermissionType;
|
|
|
|
|
@@ -208,11 +201,11 @@ export default function UserList({
|
|
|
|
|
<SorterArrow name="name" />
|
|
|
|
|
</button>
|
|
|
|
|
) as any,
|
|
|
|
|
cell: ({row, getValue}) => (
|
|
|
|
|
cell: ({ row, getValue }) => (
|
|
|
|
|
<div
|
|
|
|
|
className={clsx(
|
|
|
|
|
checkAccess(user, ["admin", "corporate", "developer", "mastercorporate"]) &&
|
|
|
|
|
"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={() =>
|
|
|
|
|
checkAccess(user, ["admin", "corporate", "developer", "mastercorporate"]) ? setSelectedUser(row.original) : null
|
|
|
|
|
@@ -230,9 +223,8 @@ export default function UserList({
|
|
|
|
|
) as any,
|
|
|
|
|
cell: (info) =>
|
|
|
|
|
info.getValue()
|
|
|
|
|
? `${countryCodes.findOne("countryCode" as any, info.getValue())?.flag} ${
|
|
|
|
|
countries[info.getValue() as unknown as keyof TCountries]?.name
|
|
|
|
|
} (+${countryCodes.findOne("countryCode" as any, info.getValue())?.countryCallingCode})`
|
|
|
|
|
? `${countryCodes.findOne("countryCode" as any, info.getValue())?.flag} ${countries[info.getValue() as unknown as keyof TCountries]?.name
|
|
|
|
|
} (+${countryCodes.findOne("countryCode" as any, info.getValue())?.countryCallingCode})`
|
|
|
|
|
: "N/A",
|
|
|
|
|
}),
|
|
|
|
|
columnHelper.accessor("demographicInformation.phone", {
|
|
|
|
|
@@ -298,11 +290,11 @@ export default function UserList({
|
|
|
|
|
<SorterArrow name="name" />
|
|
|
|
|
</button>
|
|
|
|
|
) as any,
|
|
|
|
|
cell: ({row, getValue}) => (
|
|
|
|
|
cell: ({ row, getValue }) => (
|
|
|
|
|
<div
|
|
|
|
|
className={clsx(
|
|
|
|
|
checkAccess(user, ["admin", "corporate", "developer", "mastercorporate"]) &&
|
|
|
|
|
"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={() =>
|
|
|
|
|
checkAccess(user, ["admin", "corporate", "developer", "mastercorporate"]) ? setSelectedUser(row.original) : null
|
|
|
|
|
@@ -318,11 +310,11 @@ export default function UserList({
|
|
|
|
|
<SorterArrow name="email" />
|
|
|
|
|
</button>
|
|
|
|
|
) as any,
|
|
|
|
|
cell: ({row, getValue}) => (
|
|
|
|
|
cell: ({ row, getValue }) => (
|
|
|
|
|
<div
|
|
|
|
|
className={clsx(
|
|
|
|
|
PERMISSIONS.updateExpiryDate[row.original.type]?.includes(user.type) &&
|
|
|
|
|
"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={() => (PERMISSIONS.updateExpiryDate[row.original.type]?.includes(user.type) ? setSelectedUser(row.original) : null)}>
|
|
|
|
|
{getValue()}
|
|
|
|
|
@@ -505,19 +497,17 @@ export default function UserList({
|
|
|
|
|
return a.id.localeCompare(b.id);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const {rows: filteredRows, renderSearch} = useListSearch<User>(searchFields, displayUsers);
|
|
|
|
|
|
|
|
|
|
const table = useReactTable({
|
|
|
|
|
data: filteredRows,
|
|
|
|
|
data: displayUsers,
|
|
|
|
|
columns: (!showDemographicInformation ? defaultColumns : demographicColumns) as any,
|
|
|
|
|
getCoreRowModel: getCoreRowModel(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const downloadExcel = () => {
|
|
|
|
|
const csv = exportListToExcel(filteredRows, users, groups);
|
|
|
|
|
const csv = exportListToExcel(displayUsers, users, groups);
|
|
|
|
|
|
|
|
|
|
const element = document.createElement("a");
|
|
|
|
|
const file = new Blob([csv], {type: "text/csv"});
|
|
|
|
|
const file = new Blob([csv], { type: "text/csv" });
|
|
|
|
|
element.href = URL.createObjectURL(file);
|
|
|
|
|
element.download = "users.csv";
|
|
|
|
|
document.body.appendChild(element);
|
|
|
|
|
@@ -551,53 +541,53 @@ export default function UserList({
|
|
|
|
|
onViewStudents={
|
|
|
|
|
(selectedUser.type === "corporate" || selectedUser.type === "teacher") && studentsFromAdmin.length > 0
|
|
|
|
|
? () => {
|
|
|
|
|
appendUserFilters({
|
|
|
|
|
id: "view-students",
|
|
|
|
|
filter: viewStudentFilter,
|
|
|
|
|
});
|
|
|
|
|
appendUserFilters({
|
|
|
|
|
id: "belongs-to-admin",
|
|
|
|
|
filter: belongsToAdminFilter,
|
|
|
|
|
});
|
|
|
|
|
appendUserFilters({
|
|
|
|
|
id: "view-students",
|
|
|
|
|
filter: viewStudentFilter,
|
|
|
|
|
});
|
|
|
|
|
appendUserFilters({
|
|
|
|
|
id: "belongs-to-admin",
|
|
|
|
|
filter: belongsToAdminFilter,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
router.push("/list/users");
|
|
|
|
|
}
|
|
|
|
|
router.push("/list/users");
|
|
|
|
|
}
|
|
|
|
|
: undefined
|
|
|
|
|
}
|
|
|
|
|
onViewTeachers={
|
|
|
|
|
(selectedUser.type === "corporate" || selectedUser.type === "student") && teachersFromAdmin.length > 0
|
|
|
|
|
? () => {
|
|
|
|
|
appendUserFilters({
|
|
|
|
|
id: "view-teachers",
|
|
|
|
|
filter: viewTeacherFilter,
|
|
|
|
|
});
|
|
|
|
|
appendUserFilters({
|
|
|
|
|
id: "belongs-to-admin",
|
|
|
|
|
filter: belongsToAdminFilter,
|
|
|
|
|
});
|
|
|
|
|
appendUserFilters({
|
|
|
|
|
id: "view-teachers",
|
|
|
|
|
filter: viewTeacherFilter,
|
|
|
|
|
});
|
|
|
|
|
appendUserFilters({
|
|
|
|
|
id: "belongs-to-admin",
|
|
|
|
|
filter: belongsToAdminFilter,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
router.push("/list/users");
|
|
|
|
|
}
|
|
|
|
|
router.push("/list/users");
|
|
|
|
|
}
|
|
|
|
|
: undefined
|
|
|
|
|
}
|
|
|
|
|
onViewCorporate={
|
|
|
|
|
selectedUser.type === "teacher" || selectedUser.type === "student"
|
|
|
|
|
? () => {
|
|
|
|
|
appendUserFilters({
|
|
|
|
|
id: "view-corporate",
|
|
|
|
|
filter: (x: User) => x.type === "corporate",
|
|
|
|
|
});
|
|
|
|
|
appendUserFilters({
|
|
|
|
|
id: "belongs-to-admin",
|
|
|
|
|
filter: (x: User) =>
|
|
|
|
|
groups
|
|
|
|
|
.filter((g) => g.participants.includes(selectedUser.id))
|
|
|
|
|
.flatMap((g) => [g.admin, ...g.participants])
|
|
|
|
|
.includes(x.id),
|
|
|
|
|
});
|
|
|
|
|
appendUserFilters({
|
|
|
|
|
id: "view-corporate",
|
|
|
|
|
filter: (x: User) => x.type === "corporate",
|
|
|
|
|
});
|
|
|
|
|
appendUserFilters({
|
|
|
|
|
id: "belongs-to-admin",
|
|
|
|
|
filter: (x: User) =>
|
|
|
|
|
groups
|
|
|
|
|
.filter((g) => g.participants.includes(selectedUser.id))
|
|
|
|
|
.flatMap((g) => [g.admin, ...g.participants])
|
|
|
|
|
.includes(x.id),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
router.push("/list/users");
|
|
|
|
|
}
|
|
|
|
|
router.push("/list/users");
|
|
|
|
|
}
|
|
|
|
|
: undefined
|
|
|
|
|
}
|
|
|
|
|
onClose={(shouldReload) => {
|
|
|
|
|
@@ -619,7 +609,7 @@ export default function UserList({
|
|
|
|
|
</Modal>
|
|
|
|
|
<div className="w-full flex flex-col gap-2">
|
|
|
|
|
<div className="w-full flex gap-2 items-end">
|
|
|
|
|
{renderSearch()}
|
|
|
|
|
<Input label="Search" type="text" name="search" onChange={setSearchTerm} placeholder="Enter search text" value={searchTerm} />
|
|
|
|
|
<Button className="w-full max-w-[200px] mb-1" variant="outline" onClick={downloadExcel}>
|
|
|
|
|
Download List
|
|
|
|
|
</Button>
|
|
|
|
|
|