From 969f8acb9ab78aaaedfd740909419273efdaff47 Mon Sep 17 00:00:00 2001 From: rckrdmrd Date: Tue, 20 Jan 2026 02:15:12 -0600 Subject: [PATCH] [MCH-FE] feat: Connect Customers to real API - Replace mock data with React Query hooks - Add CRUD operations (create/update customers) - Add loading, error, and empty states - Add modal for customer form - Maintain existing UI structure Co-Authored-By: Claude Opus 4.5 --- src/pages/Customers.tsx | 437 +++++++++++++++++++++++++++++++--------- 1 file changed, 346 insertions(+), 91 deletions(-) diff --git a/src/pages/Customers.tsx b/src/pages/Customers.tsx index ba759aa..0f04a2e 100644 --- a/src/pages/Customers.tsx +++ b/src/pages/Customers.tsx @@ -1,61 +1,162 @@ import { useState } from 'react'; -import { Search, Plus, Phone, Mail, MapPin, CreditCard } from 'lucide-react'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { Search, Plus, Phone, Mail, MapPin, CreditCard, X, Loader2, AlertCircle, Edit2 } from 'lucide-react'; +import { customersApi } from '../lib/api'; -const mockCustomers = [ - { - id: '1', - name: 'Maria Lopez', - phone: '5551234567', - email: 'maria@email.com', - address: 'Calle 1 #123', - totalPurchases: 45, - totalSpent: 3450.00, - fiadoBalance: 150.00, - fiadoLimit: 500.00, - lastVisit: '2024-01-15', - }, - { - id: '2', - name: 'Juan Perez', - phone: '5559876543', - email: null, - address: null, - totalPurchases: 23, - totalSpent: 1890.00, - fiadoBalance: 0, - fiadoLimit: 300.00, - lastVisit: '2024-01-14', - }, - { - id: '3', - name: 'Ana Garcia', - phone: '5555555555', - email: 'ana@email.com', - address: 'Av. Principal #456', - totalPurchases: 67, - totalSpent: 5670.00, - fiadoBalance: 320.00, - fiadoLimit: 1000.00, - lastVisit: '2024-01-15', - }, -]; +interface Customer { + id: string; + name: string; + phone: string; + email?: string | null; + address?: string | null; + totalPurchases?: number; + totalSpent?: number; + fiadoBalance?: number; + fiadoLimit?: number; + lastVisit?: string; + createdAt?: string; +} + +interface CustomerFormData { + name: string; + phone: string; + email?: string; + address?: string; + fiadoLimit?: number; +} export function Customers() { const [search, setSearch] = useState(''); + const [isModalOpen, setIsModalOpen] = useState(false); + const [editingCustomer, setEditingCustomer] = useState(null); + const [formData, setFormData] = useState({ + name: '', + phone: '', + email: '', + address: '', + fiadoLimit: 0, + }); + const [formError, setFormError] = useState(null); - const filteredCustomers = mockCustomers.filter((customer) => - customer.name.toLowerCase().includes(search.toLowerCase()) || - customer.phone.includes(search) - ); + const queryClient = useQueryClient(); + + // Fetch customers with search + const { data: customersResponse, isLoading, error } = useQuery({ + queryKey: ['customers', { search }], + queryFn: () => customersApi.getAll({ search: search || undefined }), + }); + + const customers: Customer[] = customersResponse?.data || []; + + // Create customer mutation + const createMutation = useMutation({ + mutationFn: (data: CustomerFormData) => customersApi.create(data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['customers'] }); + closeModal(); + }, + onError: (err: unknown) => { + const error = err as { response?: { data?: { message?: string } } }; + setFormError(error.response?.data?.message || 'Error al crear cliente'); + }, + }); + + // Update customer mutation + const updateMutation = useMutation({ + mutationFn: ({ id, data }: { id: string; data: CustomerFormData }) => + customersApi.update(id, data), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['customers'] }); + closeModal(); + }, + onError: (err: unknown) => { + const error = err as { response?: { data?: { message?: string } } }; + setFormError(error.response?.data?.message || 'Error al actualizar cliente'); + }, + }); + + const openCreateModal = () => { + setEditingCustomer(null); + setFormData({ + name: '', + phone: '', + email: '', + address: '', + fiadoLimit: 0, + }); + setFormError(null); + setIsModalOpen(true); + }; + + const openEditModal = (customer: Customer) => { + setEditingCustomer(customer); + setFormData({ + name: customer.name, + phone: customer.phone, + email: customer.email || '', + address: customer.address || '', + fiadoLimit: customer.fiadoLimit || 0, + }); + setFormError(null); + setIsModalOpen(true); + }; + + const closeModal = () => { + setIsModalOpen(false); + setEditingCustomer(null); + setFormError(null); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + setFormError(null); + + if (!formData.name.trim()) { + setFormError('El nombre es requerido'); + return; + } + if (!formData.phone.trim()) { + setFormError('El telefono es requerido'); + return; + } + + const dataToSend = { + name: formData.name.trim(), + phone: formData.phone.trim(), + email: formData.email?.trim() || undefined, + address: formData.address?.trim() || undefined, + fiadoLimit: formData.fiadoLimit || 0, + }; + + if (editingCustomer) { + updateMutation.mutate({ id: editingCustomer.id, data: dataToSend }); + } else { + createMutation.mutate(dataToSend); + } + }; + + const isSubmitting = createMutation.isPending || updateMutation.isPending; + + // Format date for display + const formatDate = (dateString?: string) => { + if (!dateString) return '-'; + try { + return new Date(dateString).toLocaleDateString('es-MX'); + } catch { + return dateString; + } + }; return (

Clientes

-

{mockCustomers.length} clientes registrados

+

+ {isLoading ? 'Cargando...' : `${customers.length} clientes registrados`} +

- @@ -73,62 +174,216 @@ export function Customers() { />
+ {/* Loading State */} + {isLoading && ( +
+ + Cargando clientes... +
+ )} + + {/* Error State */} + {error && ( +
+ +

Error al cargar clientes. Intenta de nuevo.

+
+ )} + + {/* Empty State */} + {!isLoading && !error && customers.length === 0 && ( +
+

+ {search ? 'No se encontraron clientes con esa busqueda' : 'No hay clientes registrados'} +

+ {!search && ( + + )} +
+ )} + {/* Customers List */} -
- {filteredCustomers.map((customer) => ( -
-
-
-

{customer.name}

-
- - {customer.phone} + {!isLoading && !error && customers.length > 0 && ( +
+ {customers.map((customer) => ( +
+
+
+

{customer.name}

+
+ + {customer.phone} +
+ {customer.email && ( +
+ + {customer.email} +
+ )} + {customer.address && ( +
+ + {customer.address} +
+ )}
- {customer.email && ( -
- - {customer.email} +
+ +
+

Ultima visita

+

{formatDate(customer.lastVisit || customer.createdAt)}

- )} - {customer.address && ( -
- - {customer.address} +
+
+ +
+
+

Compras

+

{customer.totalPurchases || 0}

+
+
+

Total gastado

+

${(customer.totalSpent || 0).toFixed(0)}

+
+
+

Fiado

+
+ 0 ? 'text-orange-500' : 'text-green-500' + }`} /> +

0 ? 'text-orange-600' : 'text-green-600' + }`}> + ${(customer.fiadoBalance || 0).toFixed(0)} +

- )} -
-
-

Ultima visita

-

{customer.lastVisit}

+
+ ))} +
+ )} + + {/* Create/Edit Modal */} + {isModalOpen && ( +
+
+
+

+ {editingCustomer ? 'Editar Cliente' : 'Nuevo Cliente'} +

+ +
-
-
-

Compras

-

{customer.totalPurchases}

-
-
-

Total gastado

-

${customer.totalSpent.toFixed(0)}

-
-
-

Fiado

-
- 0 ? 'text-orange-500' : 'text-green-500' - }`} /> -

0 ? 'text-orange-600' : 'text-green-600' - }`}> - ${customer.fiadoBalance.toFixed(0)} -

+
+ {formError && ( +
+ +

{formError}

+ )} + +
+ + setFormData({ ...formData, name: e.target.value })} + className="input" + placeholder="Nombre del cliente" + required + />
-
+ +
+ + setFormData({ ...formData, phone: e.target.value })} + className="input" + placeholder="5551234567" + required + /> +
+ +
+ + setFormData({ ...formData, email: e.target.value })} + className="input" + placeholder="cliente@email.com" + /> +
+ +
+ + setFormData({ ...formData, address: e.target.value })} + className="input" + placeholder="Calle y numero" + /> +
+ +
+ + setFormData({ ...formData, fiadoLimit: Number(e.target.value) })} + className="input" + placeholder="0" + min="0" + step="50" + /> +
+ +
+ + +
+
- ))} -
+
+ )}
); }