'use client'; import Link from 'next/link'; import { useSyncExternalStore } from 'react'; import { CALIDAD_LABEL, ESTADO_BADGE, ESTADO_LABEL, PIPELINE_LABEL, PIPELINE_NEXT, TIPO_LABEL, formatEuros, formatFecha, formatRelativo, } from '@/lib/funnel'; export type PanelLead = { id: string; nombre: string; telefono: string; provincia: string | null; tipoReforma: keyof typeof TIPO_LABEL | null; estado: keyof typeof ESTADO_BADGE; pipelineStage: keyof typeof PIPELINE_NEXT; presupuestoEstimado: number | null; renderUrl: string | null; createdAtMs: number; m2Suelo: number | null; calidadGlobal: keyof typeof CALIDAD_LABEL | null; }; type Vista = 'cards' | 'tabla'; const STORAGE_KEY = 'reformix.panel.leadsVista'; // La preferencia de vista vive en localStorage; useSyncExternalStore la lee sin // provocar desajuste de hidratación (el servidor siempre renderiza 'cards'). const listeners = new Set<() => void>(); function subscribeVista(cb: () => void) { listeners.add(cb); window.addEventListener('storage', cb); return () => { listeners.delete(cb); window.removeEventListener('storage', cb); }; } function getVistaSnapshot(): Vista { return window.localStorage.getItem(STORAGE_KEY) === 'tabla' ? 'tabla' : 'cards'; } function getVistaServerSnapshot(): Vista { return 'cards'; } function setVistaPersistida(v: Vista) { window.localStorage.setItem(STORAGE_KEY, v); for (const cb of listeners) cb(); } function iniciales(nombre: string): string { const partes = nombre.trim().split(/\s+/).slice(0, 2); return partes.map((p) => p[0]?.toUpperCase() ?? '').join('') || '?'; } function subtitulo(l: PanelLead): string { const partes = [ l.tipoReforma ? TIPO_LABEL[l.tipoReforma] : null, l.provincia, ].filter(Boolean); return partes.join(' · '); } function detalle(l: PanelLead): string { const partes = [ formatRelativo(new Date(l.createdAtMs)), l.m2Suelo ? `${l.m2Suelo} m²` : null, l.calidadGlobal ? CALIDAD_LABEL[l.calidadGlobal] : null, ].filter(Boolean); return partes.join(' · '); } function ToggleVista({ vista, onChange }: { vista: Vista; onChange: (v: Vista) => void }) { const opciones: { value: Vista; label: string; icon: React.ReactNode }[] = [ { value: 'cards', label: 'Tarjetas', icon: ( <> ), }, { value: 'tabla', label: 'Tabla', icon: ( <> ), }, ]; return (
{opciones.map((o) => { const activo = vista === o.value; return ( ); })}
); } function Tarjeta({ l }: { l: PanelLead }) { return (
{iniciales(l.nombre)}
{l.nombre} {ESTADO_LABEL[l.estado]}

{subtitulo(l) || l.telefono}

{detalle(l)}

{l.renderUrl && (
{/* eslint-disable-next-line @next/next/no-img-element */}
)}

{PIPELINE_LABEL[l.pipelineStage]}

{PIPELINE_NEXT[l.pipelineStage]}

{formatEuros(l.presupuestoEstimado)}

{l.presupuestoEstimado != null && (

orientativo

)}
); } export default function LeadsView({ leads }: { leads: PanelLead[] }) { const vista = useSyncExternalStore(subscribeVista, getVistaSnapshot, getVistaServerSnapshot); return (
{leads.length === 0 ? (
No hay leads con este estado.
) : vista === 'cards' ? (
{leads.map((l) => ( ))}
) : (
{leads.map((l) => ( ))}
Render Cliente Fecha Estado Presupuesto Siguiente paso
{l.renderUrl ? ( // eslint-disable-next-line @next/next/no-img-element ) : ( sin render )} {l.nombre}
{subtitulo(l) || l.telefono}
{formatFecha(new Date(l.createdAtMs))} {ESTADO_LABEL[l.estado]} {formatEuros(l.presupuestoEstimado)}
{PIPELINE_LABEL[l.pipelineStage]}
{PIPELINE_NEXT[l.pipelineStage]}
)}
); }