"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import Link from "next/link"; import { AppShell } from "@/components/AppShell"; import { ContextReports } from "@/components/ContextReports"; import { getStats, listCustomers } from "@/lib/api"; import { useCan } from "@/lib/abilities"; import { formatNumber, SIN_NOMBRE } from "@/lib/labels"; import type { BusinessLine, CustomerListItem, CustomerListResponse, CustomerStats, } from "@/lib/types"; type Filter = "all" | BusinessLine; const FILTERS: { key: Filter; label: string }[] = [ { key: "all", label: "Todos" }, { key: "utility", label: "Servicios" }, { key: "insurance", label: "Seguros" }, { key: "both", label: "Ambos" }, ]; export default function ClientesPage() { return ( ); } function ClientesBrowser() { const [stats, setStats] = useState(null); const [query, setQuery] = useState(""); const [filter, setFilter] = useState("all"); const [page, setPage] = useState(1); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const canCreate = useCan("customer:create"); const debounceRef = useRef>(); useEffect(() => { getStats().then(setStats).catch(() => setStats(null)); }, []); const runSearch = useCallback( (q: string, f: Filter, p: number) => { setLoading(true); setError(null); listCustomers({ query: q || undefined, line: f === "all" ? undefined : f, page: p, pageSize: 25, }) .then((res) => { setData(res); setLoading(false); }) .catch((e) => { setError( e?.message ?? "No se pudieron cargar los clientes.", ); setLoading(false); }); }, [], ); // Debounced search on query/filter change; resets to page 1. useEffect(() => { if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(() => { setPage(1); runSearch(query, filter, 1); }, 280); return () => { if (debounceRef.current) clearTimeout(debounceRef.current); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [query, filter]); function goToPage(p: number) { setPage(p); runSearch(query, filter, p); if (typeof window !== "undefined") window.scrollTo({ top: 0, behavior: "smooth" }); } return ( <>

Directorio unificado

Clientes

{canCreate && ( + Nuevo cliente )}
setQuery(e.target.value)} placeholder="Buscar por nombre, ciudad, teléfono…" aria-label="Buscar clientes" />
{FILTERS.map((f) => ( ))}
{data && !loading && !error && (
{data.total === 0 ? "Sin resultados" : `${formatNumber(data.total)} ${ data.total === 1 ? "cliente" : "clientes" }`} {query ? ` para “${query}”` : ""}
)} {error ? (
{error}
) : loading ? ( ) : data && data.items.length === 0 ? ( ) : ( <>
{data?.items.map((c) => ( ))}
{data && data.pageCount > 1 && ( )} )} ); } function StatStrip({ stats }: { stats: CustomerStats | null }) { const cells: { value: string; label: string; accent?: boolean; }[] = stats ? [ { value: formatNumber(stats.customers), label: "Clientes", accent: true }, { value: formatNumber(stats.withUtilities), label: "Con servicios" }, { value: formatNumber(stats.withInsurance), label: "Con seguros" }, { value: formatNumber(stats.bothLines), label: "Ambas líneas", accent: true }, { value: formatNumber(stats.policies), label: "Pólizas" }, { value: formatNumber(stats.properties), label: "Propiedades" }, ] : []; if (!stats) { return (
{Array.from({ length: 6 }).map((_, i) => (
))}
); } return (
{cells.map((c) => (
{c.value}
{c.label}
))}
); } function CustomerRow({ c }: { c: CustomerListItem }) { const location = [c.city?.replace(/,\s*$/, ""), c.state] .filter(Boolean) .join(", "); const contact = c.phone || c.mobile || c.email; return (
{!c.status && ( )} {c.name} {c.nameSource && ( nombre recuperado )}
{location && {location}} {location && contact && ·} {contact && {contact}}
{c.hasUtilities && ( Servicios {c.propertyCount > 0 && ( · {c.propertyCount} )} )} {c.hasInsurance && ( Seguros {c.policyCount > 0 && ( · {c.policyCount} )} )}
); } function Pager({ page, pageCount, onChange, }: { page: number; pageCount: number; onChange: (p: number) => void; }) { return ( ); } function ListSkeleton() { return (
{Array.from({ length: 8 }).map((_, i) => (
))}
); } function EmptyState({ query }: { query: string }) { return (

Sin resultados

{query ? `No encontramos clientes para “${query}”.` : "No hay clientes que coincidan con el filtro."}

); }