Files
FRASA-CAS/apps/dashboards/src/components/SeccionEquipo.tsx
Consult AS Technology 1ba7aa49da feat(mejoras): 22 mejoras UX en todos los módulos
Mi día: cancelación con motivo, conteo escrituración real, cita sin retroceder, calendario completo
Cobranza: MoraDashboard respeta alcance, hoy dinámico, cancelar con motivo
Landing: contactos paginados, error vs vacío diferenciado
Dashboards: KPI renombrado, drill-down planes activos
Chat: historial recarga, preguntas rotativas, error solo toast
Transversal: shadow-sm corrupto corregido en 9 archivos

553 API tests + 7 apps construidas
2026-09-11 12:48:47 +00:00

74 lines
3.0 KiB
TypeScript

import { useCallback, useEffect, useState } from 'react'
import { EstadoVacio, TituloSeccion } from './comunes'
import type { ResumenMiDia } from '../lib/mi-tablero'
import { fetchResumenMiDia } from '../services/mi-dia'
const CLASE_CELDA = 'px-4 py-3 text-sm text-[#111827] dark:text-[#E2E8F0]'
const CLASE_ENCABEZADO = 'bg-[#F8FAFC] px-4 py-2 text-left text-xs font-semibold uppercase text-[#64748B] dark:text-[#94A3B8]'
/**
* Sección "Equipo" (solo coordinador/corporativo/admin): tabla vendedor →
* leads asignados y nuevos esta semana, más los leads nuevos sin asignar.
* Fuente: `equipo` de GET /api/mi-dia/resumen (el API solo la incluye para
* esos roles).
*/
export default function SeccionEquipo() {
const [equipo, setEquipo] = useState<ResumenMiDia['equipo'] | null>(null)
const [error, setError] = useState<string | null>(null)
const cargar = useCallback(async () => {
try {
setEquipo((await fetchResumenMiDia()).equipo ?? null)
setError(null)
} catch (err) {
setError(err instanceof Error ? err.message : 'No se pudo cargar el equipo')
}
}, [])
useEffect(() => {
void cargar()
}, [cargar])
return (
<section aria-label="Equipo" className="mt-10">
<TituloSeccion>Equipo: leads por vendedor</TituloSeccion>
{error && (
<p role="alert" className="rounded-lg bg-amber-50 px-4 py-3 text-xs font-medium text-amber-700">
Sin datos del API — {error}
</p>
)}
{!error && !equipo && <EstadoVacio mensaje="Sin datos de equipo" />}
{!error && equipo && (
<>
<p className="mb-4 text-xs text-[#4B5563] dark:text-[#CBD5E1]">
Leads nuevos sin asignar:{' '}
<span className="font-semibold text-[#111827] dark:text-[#E2E8F0]">{equipo.leadsNuevosSinAsignar}</span>
</p>
{equipo.porVendedor.length === 0 ? (
<EstadoVacio mensaje="Sin leads asignados al equipo" />
) : (
<table className="w-full overflow-hidden rounded-xl border border-[#E5E7EB] bg-white shadow-sm dark:border-[#1E293B] dark:bg-[#1E293B]">
<thead className="border-b border-[#E5E7EB]">
<tr>
<th className={CLASE_ENCABEZADO}>Vendedor</th>
<th className={CLASE_ENCABEZADO}>Leads asignados</th>
<th className={CLASE_ENCABEZADO}>Nuevos esta semana</th>
</tr>
</thead>
<tbody className="divide-y divide-[#E5E7EB]">
{equipo.porVendedor.map((v) => (
<tr key={v.usuarioId} className="transition-colors duration-100 hover:bg-[#F8FAFC] dark:bg-[#0F172A]">
<td className={CLASE_CELDA}>{v.nombre ?? 'Sin nombre'}</td>
<td className={CLASE_CELDA}>{v.leadsAsignados}</td>
<td className={CLASE_CELDA}>{v.leadsNuevosSemana}</td>
</tr>
))}
</tbody>
</table>
)}
</>
)}
</section>
)
}