feat(bitacora): modal de cliente rediseñado con sidebar lateral
- 5 secciones: Info, Cita, Tareas, Notas, Miembros - Sidebar de navegación con íconos (lucide-react) - Notas con búsqueda, botón +Añadir, empty state - Cita programada visible - Tareas pendientes con checkbox - Dark mode completo - Max-w-5xl, layout flex horizontal
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { useCallback, useEffect, useState, type FormEvent } from 'react'
|
||||
import { X, User, Calendar, CheckSquare, FileText, Users, Search } from 'lucide-react'
|
||||
import type { LeadMiDia, OportunidadMiDia, ApartadoMiDia } from '../lib/mi-dia'
|
||||
import { formatoFechaCorta } from '../lib/mi-dia'
|
||||
import TimelineLead, { type EventoLead } from './TimelineLead'
|
||||
import { listarRecordatorios, crearRecordatorio, type Recordatorio } from '../services/recordatorios'
|
||||
|
||||
type LeadItem = LeadMiDia | OportunidadMiDia | ApartadoMiDia
|
||||
@@ -9,30 +9,24 @@ type LeadItem = LeadMiDia | OportunidadMiDia | ApartadoMiDia
|
||||
function esLead(item: LeadItem): item is LeadMiDia {
|
||||
return 'email' in item && 'origen' in item && !('modeloInteres' in item)
|
||||
}
|
||||
|
||||
function esOportunidad(item: LeadItem): item is OportunidadMiDia {
|
||||
return 'modeloInteres' in item
|
||||
}
|
||||
|
||||
export default function DetalleLeadModal({
|
||||
item,
|
||||
onCerrar,
|
||||
}: {
|
||||
item: LeadItem
|
||||
onCerrar: () => void
|
||||
}) {
|
||||
type TabId = 'info' | 'cita' | 'tareas' | 'notas' | 'miembros'
|
||||
|
||||
const TABS: { id: TabId; titulo: string; icono: typeof User }[] = [
|
||||
{ id: 'info', titulo: 'Información del cliente potencial', icono: User },
|
||||
{ id: 'cita', titulo: 'Reserve o actualice una cita', icono: Calendar },
|
||||
{ id: 'tareas', titulo: 'Tareas', icono: CheckSquare },
|
||||
{ id: 'notas', titulo: 'Notas', icono: FileText },
|
||||
{ id: 'miembros', titulo: 'Objetos de los miembros', icono: Users },
|
||||
]
|
||||
|
||||
export default function DetalleLeadModal({ item, onCerrar }: { item: LeadItem; onCerrar: () => void }) {
|
||||
const esOp = esOportunidad(item)
|
||||
const esLd = esLead(item)
|
||||
|
||||
// --- Historial ---
|
||||
const eventos: EventoLead[] = item.ultimoCambio
|
||||
? [{
|
||||
fechaISO: item.ultimoCambio.fechaISO,
|
||||
usuario: item.ultimoCambio.usuario,
|
||||
detalle: item.ultimoCambio.detalle,
|
||||
fuente: esLd ? item.fuente : esOp ? undefined : undefined,
|
||||
}]
|
||||
: []
|
||||
const [tab, setTab] = useState<TabId>('info')
|
||||
|
||||
// --- Notas ---
|
||||
const [notas, setNotas] = useState<Recordatorio[]>([])
|
||||
@@ -40,155 +34,193 @@ export default function DetalleLeadModal({
|
||||
const [notaError, setNotaError] = useState('')
|
||||
const [nuevaNota, setNuevaNota] = useState('')
|
||||
const [enviandoNota, setEnviandoNota] = useState(false)
|
||||
const [busquedaNota, setBusquedaNota] = useState('')
|
||||
|
||||
const cargarNotas = useCallback(async () => {
|
||||
setCargandoNotas(true)
|
||||
setNotaError('')
|
||||
try {
|
||||
const todas = await listarRecordatorios()
|
||||
setNotas(todas.filter((r) => r.leadId === item.id))
|
||||
} catch {
|
||||
setNotaError('No se pudieron cargar las notas.')
|
||||
} finally {
|
||||
setCargandoNotas(false)
|
||||
}
|
||||
} catch { setNotaError('Error al cargar notas.') }
|
||||
finally { setCargandoNotas(false) }
|
||||
}, [item.id])
|
||||
|
||||
useEffect(() => {
|
||||
void cargarNotas()
|
||||
}, [cargarNotas])
|
||||
useEffect(() => { void cargarNotas() }, [cargarNotas])
|
||||
|
||||
async function agregarNota(e: FormEvent) {
|
||||
e.preventDefault()
|
||||
const texto = nuevaNota.trim()
|
||||
if (texto === '') return
|
||||
if (!nuevaNota.trim()) return
|
||||
setEnviandoNota(true)
|
||||
setNotaError('')
|
||||
try {
|
||||
await crearRecordatorio({
|
||||
titulo: texto,
|
||||
leadId: item.id,
|
||||
fechaRecordar: new Date().toISOString(),
|
||||
})
|
||||
await crearRecordatorio({ titulo: nuevaNota.trim(), leadId: item.id, fechaRecordar: new Date().toISOString() })
|
||||
setNuevaNota('')
|
||||
await cargarNotas()
|
||||
} catch {
|
||||
setNotaError('No se pudo guardar la nota.')
|
||||
} finally {
|
||||
setEnviandoNota(false)
|
||||
}
|
||||
} catch { setNotaError('No se pudo guardar.') }
|
||||
finally { setEnviandoNota(false) }
|
||||
}
|
||||
|
||||
// --- Cerrar con Escape ---
|
||||
useEffect(() => {
|
||||
function onKey(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') onCerrar()
|
||||
}
|
||||
window.addEventListener('keydown', onKey)
|
||||
return () => window.removeEventListener('keydown', onKey)
|
||||
const h = (e: KeyboardEvent) => { if (e.key === 'Escape') onCerrar() }
|
||||
window.addEventListener('keydown', h)
|
||||
return () => window.removeEventListener('keydown', h)
|
||||
}, [onCerrar])
|
||||
|
||||
const notasFiltradas = notas.filter((n) =>
|
||||
busquedaNota === '' || n.titulo.toLowerCase().includes(busquedaNota.toLowerCase())
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-20 flex items-center justify-center bg-black/50 px-4" role="dialog" aria-modal="true">
|
||||
<div className="w-full max-w-lg rounded-2xl border border-[#E5E7EB] bg-white dark:border-[#1E293B] dark:bg-[#1E293B] shadow-xl max-h-[85vh] flex flex-col">
|
||||
<div className="w-full max-w-5xl h-[90vh] rounded-2xl border border-[#E5E7EB] bg-white dark:border-[#1E293B] dark:bg-[#0F172A] shadow-xl flex overflow-hidden">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between border-b border-[#E5E7EB] dark:border-[#334155] px-6 py-4">
|
||||
<p className="text-xs font-semibold uppercase tracking-wider text-[#64748B] dark:text-[#94A3B8]">Detalle del lead</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onCerrar}
|
||||
className="rounded-lg px-2 py-1 text-xs font-medium text-[#64748B] dark:text-[#94A3B8] hover:bg-[#F9FAFB] dark:hover:bg-[#334155]"
|
||||
>
|
||||
Cerrar ✕
|
||||
</button>
|
||||
<div className="absolute top-0 left-0 right-0 flex items-center justify-between border-b border-[#E5E7EB] dark:border-[#334155] px-6 py-3 bg-white dark:bg-[#1E293B] z-10">
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-[#111827] dark:text-[#E2E8F0]">Editar "{item.nombre}"</h2>
|
||||
<p className="text-xs text-[#64748B] dark:text-[#94A3B8]">Añadir y editar oportunidad Detalles, Actividades, notas y Cita.</p>
|
||||
</div>
|
||||
<button type="button" onClick={onCerrar} className="rounded-lg p-2 text-[#64748B] hover:bg-[#F1F5F9] dark:text-[#94A3B8] dark:hover:bg-[#334155]"><X className="h-5 w-5" /></button>
|
||||
</div>
|
||||
|
||||
{/* Body scrollable */}
|
||||
<div className="flex-1 overflow-y-auto px-6 py-4 space-y-6">
|
||||
{/* Sección Información */}
|
||||
<section>
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wider text-[#64748B] dark:text-[#94A3B8] mb-3">Información</h3>
|
||||
<div className="grid grid-cols-2 gap-3 text-sm">
|
||||
<div className="flex w-full pt-14">
|
||||
{/* Sidebar izquierda */}
|
||||
<nav className="w-56 shrink-0 border-r border-[#E5E7EB] dark:border-[#334155] bg-[#F9FAFB] dark:bg-[#0F172A] p-3 space-y-1 overflow-y-auto">
|
||||
{TABS.map((t) => {
|
||||
const activo = tab === t.id
|
||||
return (
|
||||
<button key={t.id} type="button" onClick={() => setTab(t.id)}
|
||||
className={`w-full flex items-center gap-2 rounded-lg px-3 py-2 text-left text-sm transition-colors ${activo ? 'bg-accent text-white font-medium' : 'text-[#374151] dark:text-[#CBD5E1] hover:bg-[#E5E7EB] dark:hover:bg-[#1E293B]'}`}>
|
||||
<t.icono className="h-4 w-4 shrink-0" />
|
||||
<span className="truncate">{t.titulo}</span>
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
|
||||
{/* Contenido */}
|
||||
<div className="flex-1 overflow-y-auto px-6 py-5">
|
||||
{tab === 'info' && (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<InfoCampo etiqueta="Nombre" valor={item.nombre} />
|
||||
{esLd && item.telefono && <InfoCampo etiqueta="Teléfono" valor={item.telefono} />}
|
||||
{esOp && item.telefono && <InfoCampo etiqueta="Teléfono" valor={item.telefono} />}
|
||||
{esLd && item.email && <InfoCampo etiqueta="Email" valor={item.email} />}
|
||||
{esOp && item.email && <InfoCampo etiqueta="Email" valor={item.email} />}
|
||||
{esLd && item.desarrollo && <InfoCampo etiqueta="Desarrollo" valor={item.desarrollo} />}
|
||||
{esOp && item.desarrollo && <InfoCampo etiqueta="Desarrollo" valor={item.desarrollo} />}
|
||||
{(esLd || esOp) && item.telefono && <InfoCampo etiqueta="Teléfono" valor={item.telefono} />}
|
||||
{(esLd || esOp) && item.email && <InfoCampo etiqueta="Email" valor={item.email} />}
|
||||
{(esLd || esOp) && item.desarrollo && <InfoCampo etiqueta="Desarrollo" valor={item.desarrollo} />}
|
||||
{esOp && item.modeloInteres && <InfoCampo etiqueta="Modelo" valor={item.modeloInteres} />}
|
||||
{esOp && item.unidadCodigo && <InfoCampo etiqueta="Unidad" valor={item.unidadCodigo} />}
|
||||
{esLd && item.origen && <InfoCampo etiqueta="Origen" valor={item.origen} />}
|
||||
<InfoCampo etiqueta="Estado" valor={esOp ? (item.etapa || '—') : item.estado || '—'} />
|
||||
{esOp && item.fechaCita && (
|
||||
<InfoCampo etiqueta="Cita" valor={item.fechaCita.replace('T', ' ')} />
|
||||
)}
|
||||
{!esLd && !esOp && (item as ApartadoMiDia).unidadCodigo && (
|
||||
<InfoCampo etiqueta="Unidad" valor={(item as ApartadoMiDia).unidadCodigo} />
|
||||
)}
|
||||
{!esLd && !esOp && (item as ApartadoMiDia).fechaLimite && (
|
||||
<InfoCampo etiqueta="Fecha límite" valor={formatoFechaCorta((item as ApartadoMiDia).fechaLimite)} />
|
||||
)}
|
||||
<InfoCampo etiqueta="Estado" valor={esOp ? item.etapa : item.estado || '—'} />
|
||||
{esOp && item.fechaCita && <InfoCampo etiqueta="Cita" valor={item.fechaCita.replace('T', ' ')} />}
|
||||
{!esLd && !esOp && (item as ApartadoMiDia).unidadCodigo && <InfoCampo etiqueta="Unidad" valor={(item as ApartadoMiDia).unidadCodigo} />}
|
||||
{!esLd && !esOp && (item as ApartadoMiDia).fechaLimite && <InfoCampo etiqueta="Fecha límite" valor={formatoFechaCorta((item as ApartadoMiDia).fechaLimite)} />}
|
||||
{item.ultimoCambio && (
|
||||
<div className="col-span-2">
|
||||
<p className="text-[11px] text-[#94A3B8]">
|
||||
Último cambio: {item.ultimoCambio.detalle} · {item.ultimoCambio.usuario ?? '—'} ·{' '}
|
||||
{formatoFechaCorta(item.ultimoCambio.fechaISO.slice(0, 10))}
|
||||
</p>
|
||||
<div className="sm:col-span-2 mt-2 rounded-lg bg-[#F8FAFC] dark:bg-[#1E293B] p-3">
|
||||
<p className="text-[11px] text-[#94A3B8]">Último cambio: {item.ultimoCambio.detalle} · {item.ultimoCambio.usuario ?? '—'} · {formatoFechaCorta(item.ultimoCambio.fechaISO.slice(0, 10))}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Sección Notas */}
|
||||
<section>
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wider text-[#64748B] dark:text-[#94A3B8] mb-3">Notas</h3>
|
||||
<form onSubmit={agregarNota} className="flex gap-2 mb-3">
|
||||
<input
|
||||
type="text"
|
||||
value={nuevaNota}
|
||||
onChange={(e) => setNuevaNota(e.target.value)}
|
||||
placeholder="Escribe una nota…"
|
||||
disabled={enviandoNota}
|
||||
className="flex-1 rounded-lg border border-[#D1D5DB] bg-white px-3 dark:border-[#334155] dark:bg-[#0F172A] dark:text-[#E2E8F0] py-2 text-sm transition-colors focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/20 disabled:opacity-50"
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={enviandoNota || nuevaNota.trim() === ''}
|
||||
className="shrink-0 rounded-lg bg-accent px-3 py-2 text-xs font-medium text-white shadow-sm transition-all hover:bg-accent-hover disabled:opacity-50"
|
||||
>
|
||||
{enviandoNota ? '…' : 'Agregar'}
|
||||
</button>
|
||||
</form>
|
||||
{notaError && (
|
||||
<p className="text-xs text-red-600 mb-2">{notaError}</p>
|
||||
{tab === 'cita' && (
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-sm font-semibold text-[#111827] dark:text-[#E2E8F0]">Reserve o actualice una cita</h3>
|
||||
{esOp && item.fechaCita ? (
|
||||
<div className="rounded-lg border border-[#E5E7EB] dark:border-[#334155] bg-[#F8FAFC] dark:bg-[#1E293B] p-4">
|
||||
<p className="text-sm font-medium text-[#111827] dark:text-[#E2E8F0]">Cita programada</p>
|
||||
<p className="text-xs text-[#64748B] dark:text-[#94A3B8] mt-1">{item.fechaCita.replace('T', ' a las ')}</p>
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-[#94A3B8]">No hay cita programada. Use los botones de fase en la lista para agendar una.</p>
|
||||
)}
|
||||
{cargandoNotas ? (
|
||||
<p className="text-xs text-[#94A3B8]">Cargando notas…</p>
|
||||
) : notas.length === 0 ? (
|
||||
<p className="text-xs text-[#94A3B8]">Sin notas registradas.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tab === 'tareas' && (
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-sm font-semibold text-[#111827] dark:text-[#E2E8F0]">Tareas pendientes</h3>
|
||||
{notasFiltradas.filter((n) => !n.completado).length === 0 ? (
|
||||
<p className="text-sm text-[#94A3B8]">No hay tareas pendientes.</p>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{[...notas].reverse().map((n) => (
|
||||
<li key={n.id} className="rounded-lg border border-[#E5E7EB] dark:border-[#334155] bg-[#F8FAFC] dark:bg-[#0F172A] px-3 py-2">
|
||||
<p className="text-sm text-[#111827] dark:text-[#E2E8F0]">{n.titulo}</p>
|
||||
{n.descripcion && (
|
||||
<p className="mt-0.5 text-xs text-[#64748B] dark:text-[#94A3B8]">{n.descripcion}</p>
|
||||
)}
|
||||
<p className="mt-0.5 text-[10px] text-[#94A3B8]">
|
||||
{formatoFechaCorta(n.createdAt.slice(0, 10))}
|
||||
</p>
|
||||
{notasFiltradas.filter((n) => !n.completado).map((n) => (
|
||||
<li key={n.id} className="flex items-center gap-2 rounded-lg border border-[#E5E7EB] dark:border-[#334155] bg-[#F8FAFC] dark:bg-[#1E293B] px-3 py-2">
|
||||
<input type="checkbox" className="h-4 w-4 rounded" readOnly />
|
||||
<span className="text-sm text-[#111827] dark:text-[#E2E8F0]">{n.titulo}</span>
|
||||
<span className="ml-auto text-[10px] text-[#94A3B8]">{formatoFechaCorta(n.createdAt.slice(0, 10))}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Sección Historial */}
|
||||
<section>
|
||||
<h3 className="text-xs font-semibold uppercase tracking-wider text-[#64748B] dark:text-[#94A3B8] mb-3">Historial</h3>
|
||||
<TimelineLead eventos={eventos} />
|
||||
</section>
|
||||
{tab === 'notas' && (
|
||||
<div className="space-y-4">
|
||||
{/* Barra de herramientas */}
|
||||
<div className="flex items-center gap-3">
|
||||
<h3 className="text-lg font-semibold text-[#111827] dark:text-[#E2E8F0]">Notas</h3>
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<div className="relative">
|
||||
<Search className="absolute left-2.5 top-1/2 h-4 w-4 -translate-y-1/2 text-[#94A3B8]" />
|
||||
<input type="text" value={busquedaNota} onChange={(e) => setBusquedaNota(e.target.value)}
|
||||
placeholder="Buscar notas"
|
||||
className="w-48 rounded-lg border border-[#D1D5DB] bg-white pl-8 pr-3 py-1.5 text-sm dark:border-[#334155] dark:bg-[#0F172A] dark:text-[#E2E8F0]" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Botón añadir */}
|
||||
<button type="button" onClick={() => { setTab('notas') }}
|
||||
className="w-full rounded-lg border-2 border-dashed border-[#D1D5DB] dark:border-[#334155] bg-[#F8FAFC] dark:bg-[#0F172A] py-3 text-sm font-medium text-[#374151] dark:text-[#CBD5E1] hover:border-accent hover:text-accent transition-colors">
|
||||
+ Añadir nota
|
||||
</button>
|
||||
|
||||
{notaError && <p className="text-xs text-red-600">{notaError}</p>}
|
||||
|
||||
{/* Formulario inline */}
|
||||
<form onSubmit={agregarNota} className="flex gap-2">
|
||||
<input type="text" value={nuevaNota} onChange={(e) => setNuevaNota(e.target.value)}
|
||||
placeholder="Escribe tu nota…" disabled={enviandoNota}
|
||||
className="flex-1 rounded-lg border border-[#D1D5DB] bg-white px-3 py-2 text-sm dark:border-[#334155] dark:bg-[#0F172A] dark:text-[#E2E8F0] focus:border-accent focus:ring-2 focus:ring-accent/20 disabled:opacity-50" />
|
||||
<button type="submit" disabled={enviandoNota || !nuevaNota.trim()}
|
||||
className="shrink-0 rounded-lg bg-accent px-4 py-2 text-sm font-medium text-white hover:bg-accent-hover disabled:opacity-50">
|
||||
{enviandoNota ? '…' : 'Guardar'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{/* Lista de notas */}
|
||||
{cargandoNotas ? (
|
||||
<p className="text-sm text-[#94A3B8]">Cargando notas…</p>
|
||||
) : notasFiltradas.length === 0 ? (
|
||||
<div className="py-12 text-center">
|
||||
<FileText className="mx-auto h-10 w-10 text-[#D1D5DB] dark:text-[#334155] mb-3" />
|
||||
<p className="text-sm font-medium text-[#111827] dark:text-[#E2E8F0]">Todavía no hay notas</p>
|
||||
<p className="text-xs text-[#94A3B8] mt-1">Mantenga un registro de los detalles importantes añadiendo su primera nota</p>
|
||||
</div>
|
||||
) : (
|
||||
<ul className="space-y-2">
|
||||
{[...notasFiltradas].reverse().map((n) => (
|
||||
<li key={n.id} className="rounded-lg border border-[#E5E7EB] dark:border-[#334155] bg-[#F8FAFC] dark:bg-[#1E293B] px-4 py-3">
|
||||
<p className="text-sm font-medium text-[#111827] dark:text-[#E2E8F0]">{n.titulo}</p>
|
||||
{n.descripcion && <p className="mt-1 text-xs text-[#64748B] dark:text-[#94A3B8]">{n.descripcion}</p>}
|
||||
<p className="mt-1.5 text-[10px] text-[#94A3B8]">{formatoFechaCorta(n.createdAt.slice(0, 10))}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tab === 'miembros' && (
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-sm font-semibold text-[#111827] dark:text-[#E2E8F0]">Objetos de los miembros</h3>
|
||||
<p className="text-sm text-[#94A3B8]">Información de los miembros del equipo asignados a este lead.</p>
|
||||
<div className="rounded-lg border border-[#E5E7EB] dark:border-[#334155] bg-[#F8FAFC] dark:bg-[#1E293B] p-4">
|
||||
<p className="text-xs text-[#94A3B8]">Vendedor: {(item as any).vendedor ?? '—'}</p>
|
||||
<p className="text-xs text-[#94A3B8] mt-1">Origen: {esLd ? item.origen : '—'}</p>
|
||||
<p className="text-xs text-[#94A3B8] mt-1">Último cambio: {item.ultimoCambio ? `${item.ultimoCambio.detalle} por ${item.ultimoCambio.usuario ?? '—'}` : 'Sin cambios registrados'}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -197,9 +229,9 @@ export default function DetalleLeadModal({
|
||||
|
||||
function InfoCampo({ etiqueta, valor }: { etiqueta: string; valor: string }) {
|
||||
return (
|
||||
<div>
|
||||
<p className="text-[11px] font-medium text-[#94A3B8] uppercase tracking-wider">{etiqueta}</p>
|
||||
<p className="text-sm text-[#111827] dark:text-[#E2E8F0]">{valor || '—'}</p>
|
||||
<div className="rounded-lg border border-[#E5E7EB] dark:border-[#334155] bg-[#F8FAFC] dark:bg-[#1E293B] p-3">
|
||||
<p className="text-[10px] font-semibold uppercase tracking-wider text-[#94A3B8] mb-1">{etiqueta}</p>
|
||||
<p className="text-sm font-medium text-[#111827] dark:text-[#E2E8F0]">{valor || '—'}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user