diff --git a/package.json b/package.json index 7d935ac..bfbe8fc 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "@syncfusion/ej2-react-navigations": "^25.2.3", "@syncfusion/ej2-react-schedule": "^25.2.3", "@syncfusion/ej2-react-splitbuttons": "^25.2.3", + "@tanstack/react-table": "^8.20.5", "@tweenjs/tween.js": "23.1.1", "class-variance-authority": "^0.7.0", "classnames": "^2.5.1", diff --git a/src/app/company/dashboard/layout.tsx b/src/app/company/dashboard/layout.tsx index 167d2a9..648b11c 100644 --- a/src/app/company/dashboard/layout.tsx +++ b/src/app/company/dashboard/layout.tsx @@ -82,7 +82,7 @@ function DashboardContent({ children }: { children: React.ReactNode }) { )} variant={active === "users" ? "default" : "ghost"}> - Users + Clients diff --git a/src/app/company/dashboard/users/page.tsx b/src/app/company/dashboard/users/page.tsx index a3363c5..fc32624 100644 --- a/src/app/company/dashboard/users/page.tsx +++ b/src/app/company/dashboard/users/page.tsx @@ -1,5 +1,4 @@ "use client"; -import { Input } from "@/components/ui/input"; import { DropdownMenu, DropdownMenuContent, @@ -16,6 +15,7 @@ import { useRouter } from "next/navigation"; import { deleteToken } from "@/lib/authActions"; import Loader from "@/components/layout/Loader"; import { useCompany } from "@/components/dashboard/CompanyContext"; +import { UsersTable } from "@/components/dashboard/UsersTable"; export default function Page() { const { user, loading, companyLoading, company } = useCompany(); @@ -40,7 +40,6 @@ export default function Page() { {company?.getCompany.name} (ID: {company?.getCompany.id})
-
-
-
-
-
- {extractNameInitials(company?.getCompany.name)} -
-
-

{company?.getCompany.name}

-
-
-
-

- {company?.getCompany.description !== "" ? ( - company?.getCompany.description - ) : ( - This company has not provided a description - )} -

+
+
); diff --git a/src/components/dashboard/UsersTable.tsx b/src/components/dashboard/UsersTable.tsx new file mode 100644 index 0000000..a3df39a --- /dev/null +++ b/src/components/dashboard/UsersTable.tsx @@ -0,0 +1,249 @@ +"use client"; + +import * as React from "react"; +import { + type ColumnDef, + type ColumnFiltersState, + type SortingState, + type VisibilityState, + flexRender, + getCoreRowModel, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable +} from "@tanstack/react-table"; +import { ArrowUpDown, ChevronDown, MoreHorizontal } from "lucide-react"; + +import { Button } from "@/components/ui/button"; +import { Checkbox } from "@/components/ui/checkbox"; +import { + DropdownMenu, + DropdownMenuCheckboxItem, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuTrigger +} from "@/components/ui/dropdown-menu"; +import { Input } from "@/components/ui/input"; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import type { User } from "@/types"; +import { getAccessToken, getAllUsers } from "@/lib/authActions"; + +const columns: Array> = [ + { + id: "select", + header: ({ table }) => ( + { + table.toggleAllPageRowsSelected(value as boolean); + }} + aria-label="Select all" + /> + ), + cell: ({ row }) => ( + { + row.toggleSelected(value as boolean); + }} + aria-label="Select row" + /> + ), + enableSorting: false, + enableHiding: false + }, + { + accessorKey: "name", + header: ({ column }) => { + return ( + + ); + } + }, + { + accessorKey: "email", + header: ({ column }) => { + return ( + + ); + } + }, + { + id: "actions", + enableHiding: false, + cell: ({ row }) => { + const user = row.original; + + return ( + + + + + + Actions + { + void navigator.clipboard.writeText(user.id.toString()); + }}> + Copy user ID + + + + ); + } + } +]; + +export function UsersTable(): React.ReactElement { + const [users, setUsers] = React.useState([]); + const [sorting, setSorting] = React.useState([]); + const [columnFilters, setColumnFilters] = React.useState([]); + const [columnVisibility, setColumnVisibility] = React.useState({}); + const [rowSelection, setRowSelection] = React.useState({}); + + React.useEffect(() => { + const fetchUsers = async (): Promise => { + const fetchedUsers = await getAllUsers(await getAccessToken()); + setUsers(fetchedUsers); + }; + void fetchUsers(); + }, []); + + const table = useReactTable({ + data: users, + columns, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFilteredRowModel: getFilteredRowModel(), + onColumnVisibilityChange: setColumnVisibility, + onRowSelectionChange: setRowSelection, + state: { + sorting, + columnFilters, + columnVisibility, + rowSelection + } + }); + + return ( +
+
+ { + table.getColumn("name")?.setFilterValue(event.target.value); + }} + className="max-w-sm" + /> + + + + + + {table + .getAllColumns() + .filter((column) => column.getCanHide()) + .map((column) => { + return ( + { + column.toggleVisibility(Boolean(value)); + }}> + {column.id} + + ); + })} + + +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length !== 0 ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + {flexRender(cell.column.columnDef.cell, cell.getContext())} + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+
+
+ {table.getFilteredSelectedRowModel().rows.length} of {table.getFilteredRowModel().rows.length} row(s) + selected. +
+
+ + +
+
+
+ ); +} diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx index 03b9b11..f51a87b 100644 --- a/src/components/ui/dropdown-menu.tsx +++ b/src/components/ui/dropdown-menu.tsx @@ -45,7 +45,7 @@ const DropdownMenuSubContent = React.forwardRef< >( + ({ className, ...props }, ref) => ( +
+ + + ) +); +Table.displayName = "Table"; + +const TableHeader = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ) +); +TableHeader.displayName = "TableHeader"; + +const TableBody = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ) +); +TableBody.displayName = "TableBody"; + +const TableFooter = React.forwardRef>( + ({ className, ...props }, ref) => ( + tr]:last:border-b-0", className)} {...props} /> + ) +); +TableFooter.displayName = "TableFooter"; + +const TableRow = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ) +); +TableRow.displayName = "TableRow"; + +const TableHead = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +TableHead.displayName = "TableHead"; + +const TableCell = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ) +); +TableCell.displayName = "TableCell"; + +const TableCaption = React.forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +); +TableCaption.displayName = "TableCaption"; + +export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }; diff --git a/src/lib/authActions.ts b/src/lib/authActions.ts index a00b4e0..7a96d87 100644 --- a/src/lib/authActions.ts +++ b/src/lib/authActions.ts @@ -93,6 +93,24 @@ export async function getUser(accessToken?: string): Promise { } } +export async function getAllUsers(accessToken?: string): Promise { + const response = await fetch(process.env.FRONTEND_DOMAIN + "/api/user/getAll", { + method: "GET", + headers: { + "Content-Type": "application/json;charset=UTF-8", + Authorization: "Bearer " + accessToken ?? cookies().get("accessToken")?.value + }, + body: undefined + }); + if (response.ok) { + return (await response.json()) as User[]; + } else if (response.status === 403 || response.status === 500 || response.status === 401) { + return []; + } else { + throw new Error("There was a problem fetching the user: " + response.statusText + " " + response.status); + } +} + export async function getAccessToken() { return cookies().get("accessToken")?.value; }