Added the ability to sort by every column
This commit is contained in:
@@ -2,15 +2,15 @@ import Button from "@/components/Low/Button";
|
||||
import {PERMISSIONS} from "@/constants/userPermissions";
|
||||
import useGroups from "@/hooks/useGroups";
|
||||
import useUsers from "@/hooks/useUsers";
|
||||
import {Type, User} from "@/interfaces/user";
|
||||
import {Type, User, userTypes} 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} from "lodash";
|
||||
import {capitalize, reverse} from "lodash";
|
||||
import moment from "moment";
|
||||
import {Fragment, useState} from "react";
|
||||
import {BsCheck, BsCheckCircle, BsFillExclamationOctagonFill, BsPerson, BsTrash} from "react-icons/bs";
|
||||
import {Fragment, useEffect, useState} from "react";
|
||||
import {BsArrowDown, BsArrowDownUp, BsArrowUp, BsCheck, BsCheckCircle, BsFillExclamationOctagonFill, BsPerson, BsTrash} from "react-icons/bs";
|
||||
import {toast} from "react-toastify";
|
||||
import {countries, TCountries} from "countries-list";
|
||||
import countryCodes from "country-codes-list";
|
||||
@@ -19,10 +19,22 @@ const columnHelper = createColumnHelper<User>();
|
||||
|
||||
export default function UserList({user}: {user: User}) {
|
||||
const [showDemographicInformation, setShowDemographicInformation] = useState(false);
|
||||
const [sorter, setSorter] = useState<string>();
|
||||
const [displayUsers, setDisplayUsers] = useState<User[]>([]);
|
||||
|
||||
const {users, reload} = useUsers();
|
||||
const {groups} = useGroups(user ? user.id : undefined);
|
||||
|
||||
useEffect(() => {
|
||||
if (user && users) {
|
||||
const filterUsers =
|
||||
user.type === "admin" || user.type === "student" ? users.filter((u) => groups.flatMap((g) => g.participants).includes(u.id)) : users;
|
||||
|
||||
setDisplayUsers([...filterUsers.sort(sortFunction)]);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [user, users, sorter, groups]);
|
||||
|
||||
const deleteAccount = (user: User) => {
|
||||
if (!confirm(`Are you sure you want to delete ${user.name}'s account?`)) return;
|
||||
|
||||
@@ -84,6 +96,13 @@ export default function UserList({user}: {user: User}) {
|
||||
});
|
||||
};
|
||||
|
||||
const SorterArrow = ({name}: {name: string}) => {
|
||||
if (sorter === name) return <BsArrowUp />;
|
||||
if (sorter === reverseString(name)) return <BsArrowDown />;
|
||||
|
||||
return <BsArrowDownUp />;
|
||||
};
|
||||
|
||||
const actionColumn = ({row}: {row: {original: User}}) => {
|
||||
return (
|
||||
<div className="flex gap-4">
|
||||
@@ -161,12 +180,21 @@ export default function UserList({user}: {user: User}) {
|
||||
|
||||
const demographicColumns = [
|
||||
columnHelper.accessor("name", {
|
||||
header: "Name",
|
||||
header: (
|
||||
<button className="flex gap-2 items-center" onClick={() => setSorter((prev) => selectSorter(prev, "name"))}>
|
||||
<span>Name</span>
|
||||
<SorterArrow name="name" />
|
||||
</button>
|
||||
) as any,
|
||||
cell: (info) => info.getValue(),
|
||||
enableSorting: true,
|
||||
}),
|
||||
columnHelper.accessor("demographicInformation.country", {
|
||||
header: "Country",
|
||||
header: (
|
||||
<button className="flex gap-2 items-center" onClick={() => setSorter((prev) => selectSorter(prev, "country"))}>
|
||||
<span>Country</span>
|
||||
<SorterArrow name="country" />
|
||||
</button>
|
||||
) as any,
|
||||
cell: (info) =>
|
||||
info.getValue()
|
||||
? `${countryCodes.findOne("countryCode" as any, info.getValue()).flag} ${
|
||||
@@ -175,17 +203,32 @@ export default function UserList({user}: {user: User}) {
|
||||
: "Not available",
|
||||
}),
|
||||
columnHelper.accessor("demographicInformation.phone", {
|
||||
header: "Phone",
|
||||
header: (
|
||||
<button className="flex gap-2 items-center" onClick={() => setSorter((prev) => selectSorter(prev, "phone"))}>
|
||||
<span>Phone</span>
|
||||
<SorterArrow name="phone" />
|
||||
</button>
|
||||
) as any,
|
||||
cell: (info) => info.getValue() || "Not available",
|
||||
enableSorting: true,
|
||||
}),
|
||||
columnHelper.accessor("demographicInformation.employment", {
|
||||
header: "Employment",
|
||||
header: (
|
||||
<button className="flex gap-2 items-center" onClick={() => setSorter((prev) => selectSorter(prev, "employment"))}>
|
||||
<span>Employment</span>
|
||||
<SorterArrow name="employment" />
|
||||
</button>
|
||||
) as any,
|
||||
cell: (info) => capitalize(info.getValue()) || "Not available",
|
||||
enableSorting: true,
|
||||
}),
|
||||
columnHelper.accessor("demographicInformation.gender", {
|
||||
header: "Gender",
|
||||
header: (
|
||||
<button className="flex gap-2 items-center" onClick={() => setSorter((prev) => selectSorter(prev, "gender"))}>
|
||||
<span>Gender</span>
|
||||
<SorterArrow name="gender" />
|
||||
</button>
|
||||
) as any,
|
||||
cell: (info) => capitalize(info.getValue()) || "Not available",
|
||||
enableSorting: true,
|
||||
}),
|
||||
@@ -202,24 +245,48 @@ export default function UserList({user}: {user: User}) {
|
||||
|
||||
const defaultColumns = [
|
||||
columnHelper.accessor("name", {
|
||||
header: "Name",
|
||||
header: (
|
||||
<button className="flex gap-2 items-center" onClick={() => setSorter((prev) => selectSorter(prev, "name"))}>
|
||||
<span>Name</span>
|
||||
<SorterArrow name="name" />
|
||||
</button>
|
||||
) as any,
|
||||
cell: (info) => info.getValue(),
|
||||
enableSorting: true,
|
||||
}),
|
||||
columnHelper.accessor("email", {
|
||||
header: "E-mail",
|
||||
header: (
|
||||
<button className="flex gap-2 items-center" onClick={() => setSorter((prev) => selectSorter(prev, "email"))}>
|
||||
<span>E-mail</span>
|
||||
<SorterArrow name="email" />
|
||||
</button>
|
||||
) as any,
|
||||
cell: (info) => info.getValue(),
|
||||
}),
|
||||
columnHelper.accessor("type", {
|
||||
header: "Type",
|
||||
header: (
|
||||
<button className="flex gap-2 items-center" onClick={() => setSorter((prev) => selectSorter(prev, "type"))}>
|
||||
<span>Type</span>
|
||||
<SorterArrow name="type" />
|
||||
</button>
|
||||
) as any,
|
||||
cell: (info) => capitalize(info.getValue()),
|
||||
}),
|
||||
columnHelper.accessor("subscriptionExpirationDate", {
|
||||
header: "Expiry Date",
|
||||
header: (
|
||||
<button className="flex gap-2 items-center" onClick={() => setSorter((prev) => selectSorter(prev, "expiryDate"))}>
|
||||
<span>Expiry Date</span>
|
||||
<SorterArrow name="expiryDate" />
|
||||
</button>
|
||||
) as any,
|
||||
cell: (info) => (!info.getValue() ? "No expiry date" : moment(info.getValue()).format("DD/MM/YYYY")),
|
||||
}),
|
||||
columnHelper.accessor("isVerified", {
|
||||
header: "Verification",
|
||||
header: (
|
||||
<button className="flex gap-2 items-center" onClick={() => setSorter((prev) => selectSorter(prev, "verification"))}>
|
||||
<span>Verification</span>
|
||||
<SorterArrow name="verification" />
|
||||
</button>
|
||||
) as any,
|
||||
cell: (info) => (
|
||||
<div className="flex gap-3 items-center text-mti-gray-dim text-sm self-center">
|
||||
<div
|
||||
@@ -244,16 +311,92 @@ export default function UserList({user}: {user: User}) {
|
||||
},
|
||||
];
|
||||
|
||||
const reverseString = (str: string) => reverse(str.split("")).join("");
|
||||
|
||||
const selectSorter = (previous: string | undefined, name: string) => {
|
||||
if (!previous) return name;
|
||||
if (previous === name) return reverseString(name);
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const sortFunction = (a: User, b: User) => {
|
||||
if (sorter === "name" || sorter === reverseString("name"))
|
||||
return sorter === "name" ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name);
|
||||
|
||||
if (sorter === "email" || sorter === reverseString("email"))
|
||||
return sorter === "email" ? a.email.localeCompare(b.email) : b.email.localeCompare(a.email);
|
||||
|
||||
if (sorter === "type" || sorter === reverseString("type"))
|
||||
return sorter === "type"
|
||||
? userTypes.findIndex((t) => a.type === t) - userTypes.findIndex((t) => b.type === t)
|
||||
: userTypes.findIndex((t) => b.type === t) - userTypes.findIndex((t) => a.type === t);
|
||||
|
||||
if (sorter === "verification" || sorter === reverseString("verification"))
|
||||
return sorter === "verification"
|
||||
? a.isVerified.toString().localeCompare(b.isVerified.toString())
|
||||
: b.isVerified.toString().localeCompare(a.isVerified.toString());
|
||||
|
||||
if (sorter === "expiryDate" || sorter === reverseString("expiryDate")) {
|
||||
if (!a.subscriptionExpirationDate && b.subscriptionExpirationDate) return sorter === "expiryDate" ? -1 : 1;
|
||||
if (a.subscriptionExpirationDate && !b.subscriptionExpirationDate) return sorter === "expiryDate" ? 1 : -1;
|
||||
if (!a.subscriptionExpirationDate && !b.subscriptionExpirationDate) return 0;
|
||||
if (moment(a.subscriptionExpirationDate).isAfter(b.subscriptionExpirationDate)) return sorter === "expiryDate" ? -1 : 1;
|
||||
if (moment(b.subscriptionExpirationDate).isAfter(a.subscriptionExpirationDate)) return sorter === "expiryDate" ? 1 : -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (sorter === "country" || sorter === reverseString("country")) {
|
||||
if (!a.demographicInformation?.country && b.demographicInformation?.country) return sorter === "country" ? -1 : 1;
|
||||
if (a.demographicInformation?.country && !b.demographicInformation?.country) return sorter === "country" ? 1 : -1;
|
||||
if (!a.demographicInformation?.country && !b.demographicInformation?.country) return 0;
|
||||
|
||||
return sorter === "country"
|
||||
? a.demographicInformation!.country.localeCompare(b.demographicInformation!.country)
|
||||
: b.demographicInformation!.country.localeCompare(a.demographicInformation!.country);
|
||||
}
|
||||
|
||||
if (sorter === "phone" || sorter === reverseString("phone")) {
|
||||
if (!a.demographicInformation?.phone && b.demographicInformation?.phone) return sorter === "phone" ? -1 : 1;
|
||||
if (a.demographicInformation?.phone && !b.demographicInformation?.phone) return sorter === "phone" ? 1 : -1;
|
||||
if (!a.demographicInformation?.phone && !b.demographicInformation?.phone) return 0;
|
||||
|
||||
return sorter === "phone"
|
||||
? a.demographicInformation!.phone.localeCompare(b.demographicInformation!.phone)
|
||||
: b.demographicInformation!.phone.localeCompare(a.demographicInformation!.phone);
|
||||
}
|
||||
|
||||
if (sorter === "employment" || sorter === reverseString("employment")) {
|
||||
if (!a.demographicInformation?.employment && b.demographicInformation?.employment) return sorter === "employment" ? -1 : 1;
|
||||
if (a.demographicInformation?.employment && !b.demographicInformation?.employment) return sorter === "employment" ? 1 : -1;
|
||||
if (!a.demographicInformation?.employment && !b.demographicInformation?.employment) return 0;
|
||||
|
||||
return sorter === "employment"
|
||||
? a.demographicInformation!.employment.localeCompare(b.demographicInformation!.employment)
|
||||
: b.demographicInformation!.employment.localeCompare(a.demographicInformation!.employment);
|
||||
}
|
||||
|
||||
if (sorter === "gender" || sorter === reverseString("gender")) {
|
||||
if (!a.demographicInformation?.gender && b.demographicInformation?.gender) return sorter === "employment" ? -1 : 1;
|
||||
if (a.demographicInformation?.gender && !b.demographicInformation?.gender) return sorter === "employment" ? 1 : -1;
|
||||
if (!a.demographicInformation?.gender && !b.demographicInformation?.gender) return 0;
|
||||
|
||||
return sorter === "gender"
|
||||
? a.demographicInformation!.gender.localeCompare(b.demographicInformation!.gender)
|
||||
: b.demographicInformation!.gender.localeCompare(a.demographicInformation!.gender);
|
||||
}
|
||||
|
||||
return a.id.localeCompare(b.id);
|
||||
};
|
||||
|
||||
const table = useReactTable({
|
||||
data:
|
||||
user && (user.type === "admin" || user.type === "student")
|
||||
? users.filter((u) => groups.flatMap((g) => g.participants).includes(u.id))
|
||||
: users,
|
||||
data: displayUsers,
|
||||
columns: (!showDemographicInformation ? defaultColumns : demographicColumns) as any,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<table className="rounded-xl bg-mti-purple-ultralight/40 w-full">
|
||||
<thead>
|
||||
{table.getHeaderGroups().map((headerGroup) => (
|
||||
@@ -278,5 +421,6 @@ export default function UserList({user}: {user: User}) {
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -46,15 +46,15 @@ export default function Lists({user}: {user: User}) {
|
||||
</Tab>
|
||||
</Tab.List>
|
||||
<Tab.Panels className="mt-2">
|
||||
<Tab.Panel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide shadow">
|
||||
<Tab.Panel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide">
|
||||
<UserList user={user} />
|
||||
</Tab.Panel>
|
||||
{user?.type === "developer" && (
|
||||
<Tab.Panel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide shadow">
|
||||
<Tab.Panel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide">
|
||||
<ExamList user={user} />
|
||||
</Tab.Panel>
|
||||
)}
|
||||
<Tab.Panel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide shadow">
|
||||
<Tab.Panel className="overflow-y-scroll max-h-[600px] rounded-xl scrollbar-hide">
|
||||
<GroupList user={user} />
|
||||
</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
|
||||
Reference in New Issue
Block a user