>();
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
)}
{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 (
);
}
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."}
);
}