From 171f3282787b2c12a0765e4bb67c42fa593831b4 Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Thu, 19 Oct 2023 09:40:52 +0100 Subject: [PATCH] Added the ability to sort by every column --- src/pages/(admin)/Lists/UserList.tsx | 232 ++++++++++++++++++++++----- src/pages/(admin)/Lists/index.tsx | 6 +- 2 files changed, 191 insertions(+), 47 deletions(-) diff --git a/src/pages/(admin)/Lists/UserList.tsx b/src/pages/(admin)/Lists/UserList.tsx index 23d9c31b..0e9f118e 100644 --- a/src/pages/(admin)/Lists/UserList.tsx +++ b/src/pages/(admin)/Lists/UserList.tsx @@ -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(); export default function UserList({user}: {user: User}) { const [showDemographicInformation, setShowDemographicInformation] = useState(false); + const [sorter, setSorter] = useState(); + const [displayUsers, setDisplayUsers] = useState([]); 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 ; + if (sorter === reverseString(name)) return ; + + return ; + }; + const actionColumn = ({row}: {row: {original: User}}) => { return (
@@ -161,12 +180,21 @@ export default function UserList({user}: {user: User}) { const demographicColumns = [ columnHelper.accessor("name", { - header: "Name", + header: ( + + ) as any, cell: (info) => info.getValue(), - enableSorting: true, }), columnHelper.accessor("demographicInformation.country", { - header: "Country", + header: ( + + ) 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: ( + + ) as any, cell: (info) => info.getValue() || "Not available", enableSorting: true, }), columnHelper.accessor("demographicInformation.employment", { - header: "Employment", + header: ( + + ) as any, cell: (info) => capitalize(info.getValue()) || "Not available", enableSorting: true, }), columnHelper.accessor("demographicInformation.gender", { - header: "Gender", + header: ( + + ) 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: ( + + ) as any, cell: (info) => info.getValue(), - enableSorting: true, }), columnHelper.accessor("email", { - header: "E-mail", + header: ( + + ) as any, cell: (info) => info.getValue(), }), columnHelper.accessor("type", { - header: "Type", + header: ( + + ) as any, cell: (info) => capitalize(info.getValue()), }), columnHelper.accessor("subscriptionExpirationDate", { - header: "Expiry Date", + header: ( + + ) as any, cell: (info) => (!info.getValue() ? "No expiry date" : moment(info.getValue()).format("DD/MM/YYYY")), }), columnHelper.accessor("isVerified", { - header: "Verification", + header: ( + + ) as any, cell: (info) => (
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 ( - - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => ( - - ))} - - ))} - - - {table.getRowModel().rows.map((row) => ( - - {row.getVisibleCells().map((cell) => ( - - ))} - - ))} - -
- {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} -
- {flexRender(cell.column.columnDef.cell, cell.getContext())} -
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + + ))} + + ))} + + + {table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + ))} + + ))} + +
+ {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} +
+ {flexRender(cell.column.columnDef.cell, cell.getContext())} +
+
); } diff --git a/src/pages/(admin)/Lists/index.tsx b/src/pages/(admin)/Lists/index.tsx index 98e76ebb..ad1bdc41 100644 --- a/src/pages/(admin)/Lists/index.tsx +++ b/src/pages/(admin)/Lists/index.tsx @@ -46,15 +46,15 @@ export default function Lists({user}: {user: User}) { - + {user?.type === "developer" && ( - + )} - +