admin crud ops implemented partialy
This commit is contained in:
dimitar committed 2025-11-09 18:11:40 +01:00
1 parent c357de515f
commit 9d2bfda0ca
21 files changed
+6108 -3172

No files matched your search

+218 -137
View File
@@ -1,167 +1,248 @@
'use client'
"use client";
import React, { useState, useMemo } from 'react'
import { AgGridReact } from 'ag-grid-react'
import { ColDef } 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'
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
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
}
id: string;
membershipType: string;
membershipStatus: string;
joinDate: Date;
lastVisit?: Date;
};
}
interface UserGridProps {
users: User[]
onUserSelect?: (user: User) => void
loading?: boolean
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, loading = false }: UserGridProps) {
const [selectedUser, setSelectedUser] = useState<User | null>(null)
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: 'agSetColumnFilter',
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>`
const columnDefs: ColDef<User>[] = useMemo(
() => [
{
headerName: "Name",
valueGetter: (params) =>
`${params.data?.firstName} ${params.data?.lastName}`,
filter: "agTextColumnFilter",
sortable: true,
minWidth: 150,
},
minWidth: 120,
},
{
headerName: 'Phone',
field: 'phone',
filter: 'agTextColumnFilter',
sortable: true,
minWidth: 130,
},
{
headerName: 'Membership',
valueGetter: (params) => params.data?.client?.membershipType || 'N/A',
filter: 'agSetColumnFilter',
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>`
{
headerName: "Email",
field: "email",
filter: "agTextColumnFilter",
sortable: true,
minWidth: 200,
},
minWidth: 120,
},
{
headerName: 'Status',
valueGetter: (params) => params.data?.client?.membershipStatus || 'N/A',
filter: 'agSetColumnFilter',
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>`
{
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,
},
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,
},
], [])
{
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 defaultColDef: ColDef = useMemo(() => ({
flex: 1,
resizable: true,
floatingFilter: true,
suppressMenu: true,
}), [])
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 onSelectionChanged = () => {
const selectedNodes = gridRef.current?.api.getSelectedNodes()
if (selectedNodes?.length > 0) {
const user = selectedNodes[0].data
setSelectedUser(user)
onUserSelect?.(user)
}
}
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 gridRef = React.useRef<AgGridReact<User>>(null)
const defaultColDef: ColDef = useMemo(
() => ({
flex: 1,
resizable: true,
floatingFilter: true,
suppressMenu: true,
}),
[],
);
const gridRef = React.useRef<AgGridReact<User>>(null);
const gridOptions = {
theme: "legacy",
columnDefs,
defaultColDef,
rowData: users,
rowSelection: 'single',
onSelectionChanged,
enableRangeSelection: true,
enableCellTextSelection: true,
rowSelection: "multiple",
onSelectionChanged: () => {
const selectedNodes = gridRef.current?.api.getSelectedNodes();
const selectedData = selectedNodes?.map((node) => node.data) || [];
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 className="ag-theme-alpine" style={{ height: '600px', width: '100%' }}>
<AgGridReact<User> {...gridOptions} ref={gridRef} />
<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>
)
}
);
}