"use client"; import React, { useState, useMemo } from "react"; import { AgGridReact } from "ag-grid-react"; import { ColDef, ModuleRegistry, AllCommunityModule } from "ag-grid-community"; import "ag-grid-community/styles/ag-grid.css"; import "ag-grid-community/styles/ag-theme-alpine.css"; import { formatDate } from "@/lib/utils"; ModuleRegistry.registerModules([AllCommunityModule]); function getTimeAgo(date: Date): string { const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMins / 60); const diffDays = Math.floor(diffHours / 24); if (diffMins < 1) return "just now"; if (diffMins < 60) return `${diffMins}m ago`; if (diffHours < 24) return `${diffHours}h ago`; return `${diffDays}d ago`; } interface User { id: string; email: string; firstName: string; lastName: string; role: string; phone?: string; gymId?: string; createdAt: Date; isCheckedIn?: boolean; checkInTime?: Date; client?: { id: string; membershipType: string; membershipStatus: string; joinDate: Date; lastVisit?: Date; }; } interface UserGridProps { users: User[]; onUserSelect?: (user: User) => void; onEditUser?: (user: User) => void; onDeleteUser?: (user: User) => void; onBulkDelete?: (users: User[]) => void; loading?: boolean; } export function UserGrid({ users, onUserSelect, onEditUser, onDeleteUser, onBulkDelete, loading = false, }: UserGridProps) { const [selectedUsers, setSelectedUsers] = useState([]); const [searchQuery, setSearchQuery] = useState(""); const [gymNames, setGymNames] = useState>({}); React.useEffect(() => { let isMounted = true; (async () => { try { const res = await fetch("/api/gyms"); const data = await res.json(); if (isMounted && Array.isArray(data)) { const map: Record = {}; for (const g of data) { if (g && g.id) { map[g.id] = g.name || g.id; } } setGymNames(map); } } catch (e) { // silently fail; we'll show gymId if name not available } })(); return () => { isMounted = false; }; }, []); const columnDefs: ColDef[] = useMemo( () => [ { headerName: "Name", valueGetter: (params) => `${params.data?.firstName} ${params.data?.lastName}`, filter: "agTextColumnFilter", sortable: true, minWidth: 150, }, { headerName: "Email", field: "email", filter: "agTextColumnFilter", sortable: true, minWidth: 200, }, { headerName: "Role", field: "role", filter: "agTextColumnFilter", sortable: true, cellRenderer: (params: any) => { const roleColors = { superAdmin: "bg-red-100 text-red-800", admin: "bg-purple-100 text-purple-800", trainer: "bg-blue-100 text-blue-800", client: "bg-green-100 text-green-800", }; const colorClass = roleColors[params.value as keyof typeof roleColors] || "bg-gray-100 text-gray-800"; const label = params.value === "superAdmin" ? "Super Admin" : params.value.charAt(0).toUpperCase() + params.value.slice(1); return ( {label} ); }, minWidth: 120, }, { headerName: "Gym", field: "gymId", filter: "agTextColumnFilter", sortable: true, minWidth: 160, valueFormatter: (params: any) => { const gymId = params.value; if (!gymId) return "None"; return gymNames[gymId] || gymId; }, }, { headerName: "Phone", field: "phone", filter: "agTextColumnFilter", sortable: true, minWidth: 130, valueFormatter: (params: any) => params.value || "N/A", }, { headerName: "Membership", valueGetter: (params) => params.data?.client?.membershipType || "N/A", filter: "agTextColumnFilter", sortable: true, cellRenderer: (params: any) => { if (!params.value || params.value === "N/A") return N/A; const membershipColors = { vip: "bg-yellow-100 text-yellow-800 border-yellow-200", premium: "bg-blue-100 text-blue-800 border-blue-200", basic: "bg-slate-100 text-slate-800 border-slate-200", }; const colorClass = membershipColors[params.value as keyof typeof membershipColors] || "bg-gray-100 text-gray-800"; const label = params.value.charAt(0).toUpperCase() + params.value.slice(1); return ( {label} ); }, minWidth: 120, }, { headerName: "Status", valueGetter: (params) => params.data?.client?.membershipStatus || "N/A", filter: "agTextColumnFilter", sortable: true, cellRenderer: (params: any) => { if (!params.value || params.value === "N/A") return N/A; const statusColors = { active: "bg-green-100 text-green-800", inactive: "bg-red-100 text-red-800", suspended: "bg-orange-100 text-orange-800", expired: "bg-gray-100 text-gray-800", }; const colorClass = statusColors[params.value as keyof typeof statusColors] || "bg-gray-100 text-gray-800"; const label = params.value.charAt(0).toUpperCase() + params.value.slice(1); return ( {label} ); }, minWidth: 120, }, { headerName: "Currently Checked In", valueGetter: (params) => params.data?.isCheckedIn, filter: "agTextColumnFilter", sortable: true, cellRenderer: (params: any) => { if (!params.data?.isCheckedIn) { return ; } const checkInTime = params.data.checkInTime ? new Date(params.data.checkInTime) : null; const timeAgo = checkInTime ? getTimeAgo(checkInTime) : ""; return ( ✓ Checked In {timeAgo && `(${timeAgo})`} ); }, minWidth: 180, }, { headerName: "Join Date", valueGetter: (params) => params.data?.client?.joinDate || params.data?.createdAt, filter: "agDateColumnFilter", sortable: true, valueFormatter: (params: any) => formatDate(new Date(params.value)), minWidth: 120, }, { headerName: "Last Visit", valueGetter: (params) => params.data?.client?.lastVisit, filter: "agDateColumnFilter", sortable: true, valueFormatter: (params: any) => params.value ? formatDate(new Date(params.value)) : "Never", minWidth: 120, }, ], [], ); const defaultColDef: ColDef = useMemo( () => ({ flex: 1, resizable: true, floatingFilter: true, suppressMenu: true, }), [], ); const gridRef = React.useRef>(null); const gridOptions = { theme: "legacy" as const, columnDefs, defaultColDef, rowData: users, rowSelection: "multiple" as const, onSelectionChanged: () => { const selectedNodes = gridRef.current?.api.getSelectedNodes(); const selectedData = selectedNodes?.map((node) => node.data).filter((u): u is User => !!u) || []; setSelectedUsers(selectedData); if (selectedData.length === 1 && onUserSelect) { onUserSelect(selectedData[0]); } }, suppressRowClickSelection: false, animateRows: true, loading: loading, pagination: true, paginationPageSize: 20, paginationPageSizeSelector: [10, 20, 50, 100], quickFilterText: searchQuery, }; const handleEdit = () => { if (selectedUsers.length === 1 && onEditUser) { onEditUser(selectedUsers[0]); } }; const handleDelete = () => { if (selectedUsers.length === 1 && onDeleteUser) { onDeleteUser(selectedUsers[0]); } }; const handleBulkDelete = () => { if (selectedUsers.length > 0 && onBulkDelete) { onBulkDelete(selectedUsers); } }; return (
setSearchQuery(e.target.value)} />
{...gridOptions} ref={gridRef} />
); }