'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: (
<>
{subtitulo(l) || l.telefono}
{detalle(l)}
{PIPELINE_LABEL[l.pipelineStage]}
{PIPELINE_NEXT[l.pipelineStage]}
{formatEuros(l.presupuestoEstimado)}
{l.presupuestoEstimado != null && (orientativo
)}| Render | Cliente | Fecha | Estado | Presupuesto | Siguiente paso |
|---|---|---|---|---|---|
|
{l.renderUrl ? (
// eslint-disable-next-line @next/next/no-img-element
|
{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]}
|