"use client"; import * as React from "react"; import { IconChevronDown, IconChevronLeft, IconChevronRight, IconChevronsLeft, IconChevronsRight, IconDotsVertical, IconLayoutColumns, IconSearch, IconShield, IconUser, IconUserCheck, } from "@tabler/icons-react"; import { User as UserIcon, Mail, Calendar, Shield } from "lucide-react"; import { toast } from "sonner"; import { updateUserEmailVerification, deleteUsers, deleteUser, } from "@/lib/actions/admin-actions"; import { ColumnDef, ColumnFiltersState, flexRender, getCoreRowModel, getFacetedRowModel, getFacetedUniqueValues, getFilteredRowModel, getPaginationRowModel, getSortedRowModel, SortingState, useReactTable, VisibilityState, } from "@tanstack/react-table"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter, } from "@/components/ui/dialog"; import Image from "next/image"; type User = { id: string; name: string; email: string; emailVerified: boolean; image: string | null; createdAt: Date; updatedAt: Date; role?: string; }; const getRoleBadge = (role?: string) => { const roleConfig: Record< string, { variant: "default" | "secondary" | "destructive" | "outline"; icon: React.ReactNode; } > = { admin: { variant: "destructive", icon: }, dentist: { variant: "default", icon: }, patient: { variant: "secondary", icon: }, }; const config = roleConfig[role?.toLowerCase() || "patient"] || roleConfig.patient; return ( {config.icon} {role || "Patient"} ); }; const columns: ColumnDef[] = [ { id: "select", header: ({ table }) => ( table.toggleAllPageRowsSelected(!!value)} aria-label="Select all" /> ), cell: ({ row }) => ( row.toggleSelected(!!value)} aria-label="Select row" /> ), enableSorting: false, enableHiding: false, }, { accessorKey: "name", header: "Name", cell: ({ row }) => ( {row.original.image ? ( ) : ( )} {row.original.name} {row.original.email} ), enableHiding: false, }, { accessorKey: "role", header: "Role", cell: ({ row }) => getRoleBadge(row.original.role), }, { accessorKey: "emailVerified", header: "Email Status", cell: ({ row }) => ( {row.original.emailVerified ? "Verified" : "Unverified"} ), }, { accessorKey: "createdAt", header: "Joined", cell: ({ row }) => new Date(row.original.createdAt).toLocaleDateString(), }, { accessorKey: "updatedAt", header: "Last Updated", cell: ({ row }) => new Date(row.original.updatedAt).toLocaleDateString(), }, ]; type AdminUsersTableProps = { users: User[]; }; export function AdminUsersTable({ users }: AdminUsersTableProps) { const [rowSelection, setRowSelection] = React.useState({}); const [columnVisibility, setColumnVisibility] = React.useState({}); const [columnFilters, setColumnFilters] = React.useState( [] ); const [sorting, setSorting] = React.useState([]); const [pagination, setPagination] = React.useState({ pageIndex: 0, pageSize: 10, }); const [isLoading, setIsLoading] = React.useState(false); const [selectedUser, setSelectedUser] = React.useState(null); const [changeRoleUser, setChangeRoleUser] = React.useState(null); const [newRole, setNewRole] = React.useState(""); const [userToDelete, setUserToDelete] = React.useState(null); const [showBulkDeleteDialog, setShowBulkDeleteDialog] = React.useState(false); const handleChangeRole = async () => { if (!changeRoleUser || !newRole) return; setIsLoading(true); try { const response = await fetch(`/api/users/${changeRoleUser.id}/role`, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ role: newRole }), }); const result = await response.json(); if (response.ok && result.success) { toast.success(`Role changed to ${newRole} successfully`); setChangeRoleUser(null); setNewRole(""); window.location.reload(); } else { toast.error(result.error || "Failed to change role"); } } catch (error) { toast.error("Failed to change role"); console.error(error); } finally { setIsLoading(false); } }; const handleBulkEmailVerification = async () => { const selectedRows = table.getFilteredSelectedRowModel().rows; const ids = selectedRows.map((row) => row.original.id); if (ids.length === 0) { toast.error("No users selected"); return; } setIsLoading(true); try { const result = await updateUserEmailVerification(ids, true); if (result.success) { toast.success(result.message); setRowSelection({}); window.location.reload(); } else { toast.error(result.message); } } catch (error) { toast.error("Failed to verify emails"); console.error(error); } finally { setIsLoading(false); } }; const handleBulkDelete = async () => { const selectedRows = table.getFilteredSelectedRowModel().rows; const ids = selectedRows.map((row) => row.original.id); if (ids.length === 0) { toast.error("No users selected"); return; } // Check if any admin is selected const hasAdmin = selectedRows.some((row) => row.original.role === "admin"); if (hasAdmin) { toast.error("Cannot delete admin users"); return; } setIsLoading(true); try { const result = await deleteUsers(ids); if (result.success) { toast.success(result.message); setRowSelection({}); setShowBulkDeleteDialog(false); window.location.reload(); } else { toast.error(result.message); } } catch (error) { toast.error("Failed to delete users"); console.error(error); } finally { setIsLoading(false); } }; const confirmDeleteUser = async () => { if (!userToDelete) return; setIsLoading(true); try { const result = await deleteUser(userToDelete.id); if (result.success) { toast.success(result.message); setUserToDelete(null); window.location.reload(); } else { toast.error(result.message); } } catch (error) { toast.error("Failed to delete user"); console.error(error); } finally { setIsLoading(false); } }; const handleSingleAction = async ( action: () => Promise<{ success: boolean; message: string }>, actionName: string ) => { setIsLoading(true); try { const result = await action(); if (result.success) { toast.success(result.message); window.location.reload(); } else { toast.error(result.message); } } catch (error) { toast.error(`Failed to ${actionName}`); console.error(error); } finally { setIsLoading(false); } }; const actionsColumn: ColumnDef = { id: "actions", cell: ({ row }) => { const isAdmin = row.original.role === "admin"; return ( Open menu setSelectedUser(row.original)}> View Profile toast.info("Edit feature coming soon")} > Edit User { setChangeRoleUser(row.original); setNewRole(row.original.role || "patient"); }} > Change Role {!row.original.emailVerified && ( handleSingleAction( () => updateUserEmailVerification([row.original.id], true), "verify email" ) } > Verify Email )} toast.info("Reset password feature coming soon")} > Reset Password {!isAdmin && ( <> setUserToDelete(row.original)} > Delete User > )} ); }, }; const columnsWithActions = [...columns, actionsColumn]; const table = useReactTable({ data: users, columns: columnsWithActions, state: { sorting, columnVisibility, rowSelection, columnFilters, pagination, }, enableRowSelection: true, onRowSelectionChange: setRowSelection, onSortingChange: setSorting, onColumnFiltersChange: setColumnFilters, onColumnVisibilityChange: setColumnVisibility, onPaginationChange: setPagination, getCoreRowModel: getCoreRowModel(), getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: getPaginationRowModel(), getSortedRowModel: getSortedRowModel(), getFacetedRowModel: getFacetedRowModel(), getFacetedUniqueValues: getFacetedUniqueValues(), }); return ( table.getColumn("name")?.setFilterValue(event.target.value) } className="pl-8" /> Columns {table .getAllColumns() .filter( (column) => typeof column.accessorFn !== "undefined" && column.getCanHide() ) .map((column) => { return ( column.toggleVisibility(!!value) } > {column.id} ); })} {/* Bulk Actions Toolbar */} {table.getFilteredSelectedRowModel().rows.length > 0 && ( {table.getFilteredSelectedRowModel().rows.length} selected Verify Emails toast.info("Change role feature coming soon")} > Change Role More Actions toast.info("Send notification feature coming soon") } > Send Notification toast.info("Reset passwords feature coming soon") } > Reset Passwords toast.info("Export feature coming soon")} > Export Selected setShowBulkDeleteDialog(true)} > Delete Selected )} {table.getHeaderGroups().map((headerGroup) => ( {headerGroup.headers.map((header) => { return ( {header.isPlaceholder ? null : flexRender( header.column.columnDef.header, header.getContext() )} ); })} ))} {table.getRowModel().rows?.length ? ( table.getRowModel().rows.map((row) => ( {row.getVisibleCells().map((cell) => ( {flexRender( cell.column.columnDef.cell, cell.getContext() )} ))} )) ) : ( No users found. )} {table.getFilteredSelectedRowModel().rows.length} of{" "} {table.getFilteredRowModel().rows.length} row(s) selected. Rows per page { table.setPageSize(Number(value)); }} > {[10, 20, 30, 40, 50].map((pageSize) => ( {pageSize} ))} Page {table.getState().pagination.pageIndex + 1} of{" "} {table.getPageCount()} table.setPageIndex(0)} disabled={!table.getCanPreviousPage()} > Go to first page table.previousPage()} disabled={!table.getCanPreviousPage()} > Go to previous page table.nextPage()} disabled={!table.getCanNextPage()} > Go to next page table.setPageIndex(table.getPageCount() - 1)} disabled={!table.getCanNextPage()} > Go to last page {/* User Details Dialog */} setSelectedUser(null)}> User Profile User ID: {selectedUser?.id} {selectedUser && getRoleBadge(selectedUser.role)} {selectedUser && ( {/* Profile Picture & Basic Info */} {selectedUser.image ? ( ) : ( )} {selectedUser.name} {selectedUser.email} {selectedUser.emailVerified ? "✓ Email Verified" : "Email Unverified"} {/* Account Information */} Account Information Full Name {selectedUser.name} Email Address {selectedUser.email} Role {selectedUser.role || "Patient"} Member Since {new Date(selectedUser.createdAt).toLocaleDateString( "en-US", { year: "numeric", month: "long", day: "numeric", } )} {/* Account Status */} Account Status Email Verification {selectedUser.emailVerified ? "Verified ✓" : "Not Verified"} Last Updated {new Date(selectedUser.updatedAt).toLocaleDateString( "en-US", { month: "short", day: "numeric", year: "numeric", } )} {/* Action Buttons */} { setSelectedUser(null); toast.info("Edit feature coming soon"); }} > Edit Profile {!selectedUser.emailVerified && ( { const id = selectedUser.id; setSelectedUser(null); handleSingleAction( () => updateUserEmailVerification([id], true), "verify email" ); }} disabled={isLoading} > Verify Email )} {selectedUser.role !== "admin" && ( { setUserToDelete(selectedUser); setSelectedUser(null); }} disabled={isLoading} > Delete User )} )} {/* Change Role Dialog */} setChangeRoleUser(null)} > Change User Role Change the role for {changeRoleUser?.name} Select New Role Patient Dentist Admin {changeRoleUser?.role && ( Current role:{" "} {changeRoleUser.role} )} setChangeRoleUser(null)} disabled={isLoading} > Cancel {isLoading ? "Changing..." : "Change Role"} {/* Delete User Confirmation Dialog */} setUserToDelete(null)}> Delete User Are you sure you want to delete this user? This action cannot be undone. {userToDelete && ( {userToDelete.image ? ( ) : ( )} {userToDelete.name} {userToDelete.email} {getRoleBadge(userToDelete.role)} ⚠️ This will permanently delete the user and all associated data. )} setUserToDelete(null)} disabled={isLoading} > Cancel {isLoading ? "Deleting..." : "Delete User"} {/* Bulk Delete Confirmation Dialog */} Delete Multiple Users Are you sure you want to delete the selected users? This action cannot be undone. {table.getFilteredSelectedRowModel().rows.length} user(s) will be deleted: {table.getFilteredSelectedRowModel().rows.map((row) => ( {row.original.image ? ( ) : ( )} {row.original.name} {row.original.email} {getRoleBadge(row.original.role)} ))} ⚠️ This will permanently delete all selected users and their associated data. setShowBulkDeleteDialog(false)} disabled={isLoading} > Cancel {isLoading ? "Deleting..." : `Delete ${table.getFilteredSelectedRowModel().rows.length} User(s)`} ); }
{row.original.name}
{row.original.email}
{selectedUser.email}
Full Name
{selectedUser.name}
Email Address
Role
{selectedUser.role || "Patient"}
Member Since
{new Date(selectedUser.createdAt).toLocaleDateString( "en-US", { year: "numeric", month: "long", day: "numeric", } )}
Email Verification
{selectedUser.emailVerified ? "Verified ✓" : "Not Verified"}
Last Updated
{new Date(selectedUser.updatedAt).toLocaleDateString( "en-US", { month: "short", day: "numeric", year: "numeric", } )}
Current role:{" "} {changeRoleUser.role}
{userToDelete.name}
{userToDelete.email}
{table.getFilteredSelectedRowModel().rows.length} user(s) will be deleted: