"use client"; import { useState, useEffect, useCallback } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Card, CardContent } from "@/components/ui/card"; import { ClientTable } from "@/components/clients/client-table"; import { ClientForm } from "@/components/clients/client-form"; import { ClientDetailDialog } from "@/components/clients/client-detail-dialog"; import { AssignMembershipDialog } from "@/components/memberships/assign-membership-dialog"; import { StatCard, StatCardSkeleton } from "@/components/dashboard/stat-card"; interface Client { id: string; firstName: string; lastName: string; email: string | null; phone: string | null; avatar?: string | null; level: string | null; notes: string | null; isActive: boolean; createdAt: string; memberships?: Array<{ id: string; status: string; startDate: string; endDate: string; remainingHours: number | null; plan: { id: string; name: string; price: number | string; durationMonths: number; courtHours: number | null; discountPercent: number | string | null; }; }>; _count?: { bookings: number; }; stats?: { totalBookings: number; totalSpent: number; balance: number; }; } interface ClientsResponse { data: Client[]; pagination: { total: number; limit: number; offset: number; hasMore: boolean; }; } interface MembershipPlan { id: string; name: string; price: number | string; durationMonths: number; courtHours: number | null; discountPercent: number | string | null; } const membershipFilters = [ { value: "", label: "Todos" }, { value: "with", label: "Con membresia" }, { value: "without", label: "Sin membresia" }, ]; const ITEMS_PER_PAGE = 10; export default function ClientsPage() { // Clients state const [clients, setClients] = useState([]); const [loadingClients, setLoadingClients] = useState(true); const [searchQuery, setSearchQuery] = useState(""); const [membershipFilter, setMembershipFilter] = useState(""); const [currentPage, setCurrentPage] = useState(1); const [totalClients, setTotalClients] = useState(0); // Modal state const [showCreateForm, setShowCreateForm] = useState(false); const [editingClient, setEditingClient] = useState(null); const [selectedClient, setSelectedClient] = useState(null); const [showAssignMembership, setShowAssignMembership] = useState(false); const [formLoading, setFormLoading] = useState(false); // Stats state const [stats, setStats] = useState({ totalClients: 0, withMembership: 0, newThisMonth: 0, }); const [loadingStats, setLoadingStats] = useState(true); // Membership plans for assignment dialog const [membershipPlans, setMembershipPlans] = useState([]); const [error, setError] = useState(null); // Fetch clients const fetchClients = useCallback(async () => { setLoadingClients(true); try { const params = new URLSearchParams(); if (searchQuery) params.append("search", searchQuery); params.append("limit", ITEMS_PER_PAGE.toString()); params.append("offset", ((currentPage - 1) * ITEMS_PER_PAGE).toString()); const response = await fetch(`/api/clients?${params.toString()}`); if (!response.ok) throw new Error("Error al cargar clientes"); const data: ClientsResponse = await response.json(); // Filter by membership status client-side for simplicity let filteredData = data.data; if (membershipFilter === "with") { filteredData = data.data.filter( (c) => c.memberships && c.memberships.length > 0 && c.memberships[0].status === "ACTIVE" ); } else if (membershipFilter === "without") { filteredData = data.data.filter( (c) => !c.memberships || c.memberships.length === 0 || c.memberships[0].status !== "ACTIVE" ); } setClients(filteredData); setTotalClients(data.pagination.total); } catch (err) { console.error("Error fetching clients:", err); setError(err instanceof Error ? err.message : "Error desconocido"); } finally { setLoadingClients(false); } }, [searchQuery, currentPage, membershipFilter]); // Fetch stats const fetchStats = useCallback(async () => { setLoadingStats(true); try { // Fetch all clients to calculate stats const response = await fetch("/api/clients?limit=1000"); if (!response.ok) throw new Error("Error al cargar estadisticas"); const data: ClientsResponse = await response.json(); const allClients = data.data; // Calculate stats const now = new Date(); const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); const withMembership = allClients.filter( (c) => c.memberships && c.memberships.length > 0 && c.memberships[0].status === "ACTIVE" ).length; const newThisMonth = allClients.filter( (c) => new Date(c.createdAt) >= startOfMonth ).length; setStats({ totalClients: data.pagination.total, withMembership, newThisMonth, }); } catch (err) { console.error("Error fetching stats:", err); } finally { setLoadingStats(false); } }, []); // Fetch membership plans const fetchMembershipPlans = useCallback(async () => { try { const response = await fetch("/api/membership-plans"); if (!response.ok) throw new Error("Error al cargar planes"); const data = await response.json(); setMembershipPlans(data.filter((p: MembershipPlan & { isActive?: boolean }) => p.isActive !== false)); } catch (err) { console.error("Error fetching membership plans:", err); } }, []); // Fetch client details const fetchClientDetails = async (clientId: string) => { try { const response = await fetch(`/api/clients/${clientId}`); if (!response.ok) throw new Error("Error al cargar detalles del cliente"); const data = await response.json(); setSelectedClient(data); } catch (err) { console.error("Error fetching client details:", err); setError(err instanceof Error ? err.message : "Error desconocido"); } }; useEffect(() => { fetchClients(); fetchStats(); fetchMembershipPlans(); }, [fetchClients, fetchStats, fetchMembershipPlans]); // Debounce search const [debouncedSearch, setDebouncedSearch] = useState(searchQuery); useEffect(() => { const timer = setTimeout(() => { setDebouncedSearch(searchQuery); }, 300); return () => clearTimeout(timer); }, [searchQuery]); useEffect(() => { setCurrentPage(1); }, [debouncedSearch, membershipFilter]); // Handle create client const handleCreateClient = async (data: { firstName: string; lastName: string; email: string; phone: string; avatar?: string; }) => { setFormLoading(true); try { const response = await fetch("/api/clients", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || "Error al crear cliente"); } setShowCreateForm(false); await Promise.all([fetchClients(), fetchStats()]); } catch (err) { throw err; } finally { setFormLoading(false); } }; // Handle update client const handleUpdateClient = async (data: { firstName: string; lastName: string; email: string; phone: string; avatar?: string; }) => { if (!editingClient) return; setFormLoading(true); try { const response = await fetch(`/api/clients/${editingClient.id}`, { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || "Error al actualizar cliente"); } setEditingClient(null); await fetchClients(); // Update selected client if viewing details if (selectedClient?.id === editingClient.id) { await fetchClientDetails(editingClient.id); } } catch (err) { throw err; } finally { setFormLoading(false); } }; // Handle delete client const handleDeleteClient = async (client: Client) => { if ( !confirm( `¿Estas seguro de desactivar a ${client.firstName} ${client.lastName}?` ) ) { return; } try { const response = await fetch(`/api/clients/${client.id}`, { method: "DELETE", }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || "Error al desactivar cliente"); } await Promise.all([fetchClients(), fetchStats()]); } catch (err) { console.error("Error deleting client:", err); setError(err instanceof Error ? err.message : "Error desconocido"); } }; // Handle assign membership const handleAssignMembership = async (data: { clientId: string; planId: string; startDate: string; endDate: string; }) => { setFormLoading(true); try { const response = await fetch("/api/memberships", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data), }); if (!response.ok) { const errorData = await response.json(); throw new Error(errorData.error || "Error al asignar membresia"); } setShowAssignMembership(false); await Promise.all([fetchClients(), fetchStats()]); // Update selected client if viewing details if (selectedClient) { await fetchClientDetails(selectedClient.id); } } catch (err) { throw err; } finally { setFormLoading(false); } }; // Handle row click to view details const handleRowClick = (client: Client) => { fetchClientDetails(client.id); }; // Calculate pagination const totalPages = Math.ceil(totalClients / ITEMS_PER_PAGE); return (
{/* Header */}

Clientes

Gestiona los clientes de tu centro

{/* Error Message */} {error && (

{error}

)} {/* Stats Cards */}
{loadingStats ? ( <> ) : ( <> } /> } /> } /> )}
{/* Filters */}
{/* Search */}
setSearchQuery(e.target.value)} className="w-full" />
{/* Membership Filter */}
{membershipFilters.map((filter) => ( ))}
{/* Clients Table */} setEditingClient(client)} onDelete={handleDeleteClient} isLoading={loadingClients} currentPage={currentPage} totalPages={totalPages} onPageChange={setCurrentPage} /> {/* Create Client Form Modal */} {showCreateForm && ( setShowCreateForm(false)} isLoading={formLoading} mode="create" /> )} {/* Edit Client Form Modal */} {editingClient && ( setEditingClient(null)} isLoading={formLoading} mode="edit" /> )} {/* Client Detail Dialog */} {selectedClient && !editingClient && ( setSelectedClient(null)} onEdit={() => { setEditingClient(selectedClient); }} onAssignMembership={() => { setShowAssignMembership(true); }} /> )} {/* Assign Membership Dialog */} {showAssignMembership && selectedClient && ( setShowAssignMembership(false)} onAssign={handleAssignMembership} isLoading={formLoading} /> )}
); }