252 lines
7.2 KiB
TypeScript
252 lines
7.2 KiB
TypeScript
"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]);
|
|
|
|
interface User {
|
|
id: string;
|
|
email: string;
|
|
firstName: string;
|
|
lastName: string;
|
|
role: string;
|
|
phone?: string;
|
|
createdAt: 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<User[]>([]);
|
|
const [searchQuery, setSearchQuery] = useState<string>("");
|
|
|
|
const columnDefs: ColDef<User>[] = 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 = {
|
|
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";
|
|
return `<span class="px-2 py-1 rounded text-xs font-medium ${colorClass}">${params.value}</span>`;
|
|
},
|
|
minWidth: 120,
|
|
},
|
|
{
|
|
headerName: "Phone",
|
|
field: "phone",
|
|
filter: "agTextColumnFilter",
|
|
sortable: true,
|
|
minWidth: 130,
|
|
},
|
|
{
|
|
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",
|
|
premium: "bg-blue-100 text-blue-800",
|
|
basic: "bg-gray-100 text-gray-800",
|
|
};
|
|
const colorClass =
|
|
membershipColors[params.value as keyof typeof membershipColors] ||
|
|
"bg-gray-100 text-gray-800";
|
|
return `<span class="px-2 py-1 rounded text-xs font-medium ${colorClass}">${params.value}</span>`;
|
|
},
|
|
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-yellow-100 text-yellow-800",
|
|
};
|
|
const colorClass =
|
|
statusColors[params.value as keyof typeof statusColors] ||
|
|
"bg-gray-100 text-gray-800";
|
|
return `<span class="px-2 py-1 rounded text-xs font-medium ${colorClass}">${params.value}</span>`;
|
|
},
|
|
minWidth: 120,
|
|
},
|
|
{
|
|
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<AgGridReact<User>>(null);
|
|
|
|
const gridOptions = {
|
|
theme: "legacy" as const,
|
|
columnDefs,
|
|
defaultColDef,
|
|
rowData: users,
|
|
rowSelection: { mode: "multiRow" as const },
|
|
onSelectionChanged: () => {
|
|
const selectedNodes = gridRef.current?.api.getSelectedNodes();
|
|
const selectedData =
|
|
selectedNodes
|
|
?.map((node) => node.data)
|
|
.filter((data): data is User => data !== undefined) || [];
|
|
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 (
|
|
<div>
|
|
<div className="flex justify-between items-center mb-4">
|
|
<input
|
|
type="text"
|
|
placeholder="Search users..."
|
|
className="border border-gray-300 rounded px-4 py-2"
|
|
value={searchQuery}
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
/>
|
|
<div className="flex gap-2">
|
|
<button
|
|
className="bg-green-500 text-white px-4 py-2 rounded disabled:opacity-50"
|
|
onClick={handleEdit}
|
|
disabled={selectedUsers.length !== 1}
|
|
>
|
|
Edit User
|
|
</button>
|
|
<button
|
|
className="bg-red-500 text-white px-4 py-2 rounded disabled:opacity-50"
|
|
onClick={handleDelete}
|
|
disabled={selectedUsers.length !== 1}
|
|
>
|
|
Delete User
|
|
</button>
|
|
<button
|
|
className="bg-yellow-500 text-white px-4 py-2 rounded disabled:opacity-50"
|
|
onClick={handleBulkDelete}
|
|
disabled={selectedUsers.length === 0}
|
|
>
|
|
Bulk Delete
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div
|
|
className="ag-theme-alpine"
|
|
style={{ height: "600px", width: "100%" }}
|
|
>
|
|
<AgGridReact<User> {...gridOptions} ref={gridRef} />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|