From 8b2459c304a5bc03e5be3b3f377046bb60860026 Mon Sep 17 00:00:00 2001 From: Tiago Ribeiro Date: Wed, 8 May 2024 15:46:24 +0100 Subject: [PATCH] ENCOA-37: Added the ability for users to download a list of the shown users --- src/hooks/useListSearch.tsx | 66 ++++++++++++---------------- src/pages/(admin)/Lists/UserList.tsx | 61 ++++++++++++------------- src/resources/user.ts | 15 ++++++- src/utils/users.ts | 36 +++++++++++++++ 4 files changed, 105 insertions(+), 73 deletions(-) create mode 100644 src/utils/users.ts diff --git a/src/hooks/useListSearch.tsx b/src/hooks/useListSearch.tsx index a559b400..ff3ddb85 100644 --- a/src/hooks/useListSearch.tsx +++ b/src/hooks/useListSearch.tsx @@ -1,4 +1,4 @@ -import {useState, useMemo} from 'react'; +import {useState, useMemo} from "react"; import Input from "@/components/Low/Input"; /*fields example = [ @@ -6,43 +6,33 @@ import Input from "@/components/Low/Input"; ['companyInformation', 'companyInformation', 'name'] ]*/ - const getFieldValue = (fields: string[], data: any): string => { - if(fields.length === 0) return data; - const [key, ...otherFields] = fields; + if (fields.length === 0) return data; + const [key, ...otherFields] = fields; - if(data[key]) return getFieldValue(otherFields, data[key]); - return data; + if (data[key]) return getFieldValue(otherFields, data[key]); + return data; +}; + +export function useListSearch(fields: string[][], rows: T[]) { + const [text, setText] = useState(""); + + const renderSearch = () => ; + + const updatedRows = useMemo(() => { + const searchText = text.toLowerCase(); + return rows.filter((row) => { + return fields.some((fieldsKeys) => { + const value = getFieldValue(fieldsKeys, row); + if (typeof value === "string") { + return value.toLowerCase().includes(searchText); + } + }); + }); + }, [fields, rows, text]); + + return { + rows: updatedRows, + renderSearch, + }; } - -export const useListSearch = (fields: string[][], rows: any[]) => { - const [text, setText] = useState(''); - - const renderSearch = () => ( - - ) - - const updatedRows = useMemo(() => { - const searchText = text.toLowerCase(); - return rows.filter((row) => { - return fields.some((fieldsKeys) => { - const value = getFieldValue(fieldsKeys, row); - if(typeof value === 'string') { - return value.toLowerCase().includes(searchText); - } - }) - }) - }, [fields, rows, text]) - - return { - rows: updatedRows, - renderSearch, - } -} \ No newline at end of file diff --git a/src/pages/(admin)/Lists/UserList.tsx b/src/pages/(admin)/Lists/UserList.tsx index c163187d..9ade7c31 100644 --- a/src/pages/(admin)/Lists/UserList.tsx +++ b/src/pages/(admin)/Lists/UserList.tsx @@ -16,49 +16,25 @@ import {countries, TCountries} from "countries-list"; import countryCodes from "country-codes-list"; import Modal from "@/components/Modal"; import UserCard from "@/components/UserCard"; -import {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"; const columnHelper = createColumnHelper(); const searchFields = [["name"], ["email"], ["corporateInformation", "companyInformation", "name"]]; -const getCompanyName = async (user: User) => { - if (isCorporateUser(user)) { - return user.corporateInformation?.companyInformation?.name; - } - - if (isAgentUser(user)) { - return user.agentInformation.companyName; - } - - if (user.type === "teacher" || user.type === "student") { - const userCorporate = await getUserCorporate(user.id); - return userCorporate?.corporateInformation?.companyInformation.name || ""; - } - - return ""; -}; - -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); - useEffect(() => { - if (isCorporateUser(user)) return setCompanyName(user.corporateInformation?.companyInformation?.name || user.name) - if (isAgentUser(user)) return setCompanyName(user.agentInformation?.companyName || user.name) - - const belongingGroups = groups.filter((x) => x.participants.includes(user.id)) - const belongingGroupsAdmins = belongingGroups.map((x) => users.find((u) => u.id === x.admin)).filter((x) => !!x && isCorporateUser(x)) - - if (belongingGroupsAdmins.length === 0) return setCompanyName("") - - const admin = (belongingGroupsAdmins[0] as CorporateUser) - setCompanyName(admin.corporateInformation?.companyInformation.name || admin.name) + useEffect(() => { + const name = getUserCompanyName(user, users, groups); + setCompanyName(name); }, [user, users, groups]); return isLoading ? Loading... : <>{companyName}; @@ -501,8 +477,8 @@ export default function UserList({user, filters = []}: {user: User; filters?: (( } if (sorter === "companyName" || sorter === reverseString("companyName")) { - const aCorporateName = await getCompanyName(a); - const bCorporateName = await getCompanyName(b); + const aCorporateName = getUserCompanyName(a, users, groups); + const bCorporateName = getUserCompanyName(b, users, groups); if (!aCorporateName && bCorporateName) return sorter === "companyName" ? -1 : 1; if (aCorporateName && !bCorporateName) return sorter === "companyName" ? 1 : -1; if (!aCorporateName && !bCorporateName) return 0; @@ -513,7 +489,7 @@ export default function UserList({user, filters = []}: {user: User; filters?: (( return a.id.localeCompare(b.id); }; - const {rows: filteredRows, renderSearch} = useListSearch(searchFields, displayUsers); + const {rows: filteredRows, renderSearch} = useListSearch(searchFields, displayUsers); const table = useReactTable({ data: filteredRows, @@ -521,6 +497,18 @@ export default function UserList({user, filters = []}: {user: User; filters?: (( getCoreRowModel: getCoreRowModel(), }); + const downloadExcel = () => { + const csv = exportListToExcel(filteredRows, users, groups); + + const element = document.createElement("a"); + const file = new Blob([csv], {type: "text/plain"}); + element.href = URL.createObjectURL(file); + element.download = "users.xlsx"; + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); + }; + return (
setSelectedUser(undefined)}> @@ -600,7 +588,12 @@ export default function UserList({user, filters = []}: {user: User; filters?: ((
- {renderSearch()} +
+ {renderSearch()} + +
{table.getHeaderGroups().map((headerGroup) => ( diff --git a/src/resources/user.ts b/src/resources/user.ts index c6a516c1..dfeac0da 100644 --- a/src/resources/user.ts +++ b/src/resources/user.ts @@ -1,4 +1,4 @@ -import {Type, User, CorporateUser, AgentUser} from "@/interfaces/user"; +import {Type, User, CorporateUser, AgentUser, Group} from "@/interfaces/user"; export const USER_TYPE_LABELS: {[key in Type]: string} = { student: "Student", @@ -16,3 +16,16 @@ export function isCorporateUser(user: User): user is CorporateUser { export function isAgentUser(user: User): user is AgentUser { return (user as AgentUser).agentInformation !== undefined; } + +export function getUserCompanyName(user: User, users: User[], groups: Group[]) { + if (isCorporateUser(user)) return user.corporateInformation?.companyInformation?.name || user.name; + if (isAgentUser(user)) return user.agentInformation?.companyName || user.name; + + const belongingGroups = groups.filter((x) => x.participants.includes(user.id)); + const belongingGroupsAdmins = belongingGroups.map((x) => users.find((u) => u.id === x.admin)).filter((x) => !!x && isCorporateUser(x)); + + if (belongingGroupsAdmins.length === 0) return ""; + + const admin = belongingGroupsAdmins[0] as CorporateUser; + return admin.corporateInformation?.companyInformation.name || admin.name; +} diff --git a/src/utils/users.ts b/src/utils/users.ts new file mode 100644 index 00000000..a5918bf7 --- /dev/null +++ b/src/utils/users.ts @@ -0,0 +1,36 @@ +import {Group, User} from "@/interfaces/user"; +import {getUserCompanyName, USER_TYPE_LABELS} from "@/resources/user"; +import {capitalize} from "lodash"; +import moment from "moment"; + +export interface UserListRow { + name: string; + email: string; + type: string; + companyName: string; + expiryDate: string; + verified: string; + country: string; + phone: string; + employmentPosition: string; + gender: string; +} + +export const exportListToExcel = (rowUsers: User[], users: User[], groups: Group[]) => { + const rows: UserListRow[] = rowUsers.map((user) => ({ + name: user.name, + email: user.email, + type: USER_TYPE_LABELS[user.type], + companyName: getUserCompanyName(user, users, groups), + 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", + gender: user.demographicInformation?.gender ? capitalize(user.demographicInformation.gender) : "N/A", + verified: user.isVerified?.toString() || "FALSE", + })); + const header = "Name,Email,Type,Company Name,Expiry Date,Country,Phone,Employment/Position,Gender,Verification"; + const rowsString = rows.map((x) => Object.values(x).join(",")).join("\n"); + + return `${header}\n${rowsString}`; +};