style: reskin NovaSpark en las 7 apps
Del look editorial (verde #1B4332, esquinas a 0, botones en mayusculas) al sistema NovaSpark: primario azul #2563EB, detalle naranja #F97316, fondo #F8FAFC con tarjetas blancas rounded-xl + shadow-sm, chips rounded-full semanticos, focus ring azul, transiciones 100-200ms. Incluye headers de tabla en los PDFs (cotizador y estado de cuenta).
This commit is contained in:
@@ -32,7 +32,7 @@ import KpiCard from '../components/KpiCard'
|
||||
import SemaforoDot, { semaforoPlan } from '../components/Semaforo'
|
||||
|
||||
const MESES_CORTOS = ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic']
|
||||
const COLORES_PIE = ['#1B4332', '#d4d4d4']
|
||||
const COLORES_PIE = ['#2563EB', '#CBD5E1']
|
||||
|
||||
type ColumnaOrden = 'saldo' | 'vencimiento' | 'atraso' | 'avance'
|
||||
const COLUMNAS_ORDEN: ColumnaOrden[] = ['saldo', 'vencimiento', 'atraso', 'avance']
|
||||
@@ -160,9 +160,9 @@ export default function DashboardPage() {
|
||||
|
||||
if (!resultado) {
|
||||
return errorCarga ? (
|
||||
<div className="border border-red-300 bg-white px-4 py-3 text-sm text-red-700">{errorCarga}</div>
|
||||
<div className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{errorCarga}</div>
|
||||
) : (
|
||||
<p className="text-sm text-neutral-400">Cargando...</p>
|
||||
<p className="text-sm text-[#64748B]">Cargando...</p>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -200,12 +200,12 @@ export default function DashboardPage() {
|
||||
})()
|
||||
|
||||
const encabezadoOrdenable = (columna: ColumnaOrden, titulo: string, alineadoDerecha = false) => (
|
||||
<th scope="col" className={`py-3 pr-4 ${alineadoDerecha ? 'text-right' : ''}`}>
|
||||
<th scope="col" className={`px-4 py-3 ${alineadoDerecha ? 'text-right' : ''}`}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => alternarOrden(columna)}
|
||||
aria-label={`Ordenar por ${titulo}`}
|
||||
className="inline-flex items-center gap-1 font-bold uppercase tracking-widest transition-colors duration-150 hover:text-neutral-900"
|
||||
className="inline-flex items-center gap-1 font-semibold uppercase tracking-wider transition-colors duration-100 ease-out hover:text-[#111827]"
|
||||
>
|
||||
{titulo}
|
||||
{orden === columna && <span aria-hidden="true">{dir === 'asc' ? '↑' : '↓'}</span>}
|
||||
@@ -214,12 +214,12 @@ export default function DashboardPage() {
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="space-y-16">
|
||||
<div className="space-y-8">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-3xl font-light text-neutral-900">Módulo Cobranza — Planes de enganche</h1>
|
||||
<h1 className="text-3xl font-light text-[#111827]">Módulo Cobranza — Planes de enganche</h1>
|
||||
<Link
|
||||
to="/nuevo"
|
||||
className="bg-accent px-5 py-2.5 text-xs font-bold uppercase tracking-widest text-white transition-colors duration-150 hover:opacity-90"
|
||||
className="rounded-lg bg-accent px-5 py-2.5 text-sm font-medium text-white shadow-sm transition-all duration-100 ease-out hover:bg-accent-hover"
|
||||
>
|
||||
Nuevo plan
|
||||
</Link>
|
||||
@@ -248,8 +248,8 @@ export default function DashboardPage() {
|
||||
|
||||
{/* Gráficas */}
|
||||
<div className="grid gap-6 lg:grid-cols-2">
|
||||
<div className="border border-neutral-200 bg-white p-6">
|
||||
<h2 className="mb-6 text-xs font-bold uppercase tracking-widest text-neutral-500">
|
||||
<div className="rounded-xl border border-[#E5E7EB] bg-white p-6 shadow-sm">
|
||||
<h2 className="mb-6 text-xs font-semibold uppercase tracking-wider text-[#64748B]">
|
||||
Al corriente vs vencidos
|
||||
</h2>
|
||||
<ResponsiveContainer width="100%" height={240}>
|
||||
@@ -272,21 +272,21 @@ export default function DashboardPage() {
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
<div className="border border-neutral-200 bg-white p-6">
|
||||
<h2 className="mb-6 text-xs font-bold uppercase tracking-widest text-neutral-500">
|
||||
<div className="rounded-xl border border-[#E5E7EB] bg-white p-6 shadow-sm">
|
||||
<h2 className="mb-6 text-xs font-semibold uppercase tracking-wider text-[#64748B]">
|
||||
Cobrado por mes (últimos 6 meses)
|
||||
</h2>
|
||||
<ResponsiveContainer width="100%" height={240}>
|
||||
<BarChart data={datosBarras}>
|
||||
<CartesianGrid stroke="#e5e5e5" strokeDasharray="3 3" vertical={false} />
|
||||
<XAxis dataKey="mes" tickLine={false} axisLine={{ stroke: '#e5e5e5' }} />
|
||||
<CartesianGrid stroke="#E5E7EB" strokeDasharray="3 3" vertical={false} />
|
||||
<XAxis dataKey="mes" tickLine={false} axisLine={{ stroke: '#E5E7EB' }} />
|
||||
<YAxis
|
||||
tickLine={false}
|
||||
axisLine={false}
|
||||
tickFormatter={(v: number) => `$${(v / 1000).toLocaleString('es-MX')}k`}
|
||||
/>
|
||||
<Tooltip formatter={(v) => formatoMoneda(Number(v))} cursor={{ fill: '#fafafa' }} />
|
||||
<Bar dataKey="cobrado" fill="#1B4332" name="Cobrado" />
|
||||
<Tooltip formatter={(v) => formatoMoneda(Number(v))} cursor={{ fill: '#F8FAFC' }} />
|
||||
<Bar dataKey="cobrado" fill="#2563EB" name="Cobrado" radius={[6, 6, 0, 0]} />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
@@ -295,13 +295,13 @@ export default function DashboardPage() {
|
||||
{/* Filtros */}
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
{/* Toggle Mis planes / Todos (?mios=1 filtra en el API por el vendedor del token) */}
|
||||
<div className="flex border border-neutral-300" role="group" aria-label="Alcance de planes">
|
||||
<div className="flex rounded-lg border border-[#D1D5DB] bg-white" role="group" aria-label="Alcance de planes">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => actualizarParams({ vista: mios ? null : 'mios' })}
|
||||
aria-pressed={mios}
|
||||
className={`px-4 py-2.5 text-xs font-bold uppercase tracking-widest transition-colors duration-150 ${
|
||||
mios ? 'bg-accent text-white' : 'bg-white text-neutral-600 hover:text-neutral-900'
|
||||
className={`rounded-l-lg px-4 py-2.5 text-sm font-medium transition-all duration-100 ease-out ${
|
||||
mios ? 'bg-accent text-white shadow-sm' : 'bg-white text-[#64748B] hover:text-[#111827]'
|
||||
}`}
|
||||
>
|
||||
Mis planes
|
||||
@@ -310,8 +310,8 @@ export default function DashboardPage() {
|
||||
type="button"
|
||||
onClick={() => actualizarParams({ vista: mios ? 'todos' : null })}
|
||||
aria-pressed={!mios}
|
||||
className={`px-4 py-2.5 text-xs font-bold uppercase tracking-widest transition-colors duration-150 ${
|
||||
mios ? 'bg-white text-neutral-600 hover:text-neutral-900' : 'bg-accent text-white'
|
||||
className={`rounded-r-lg px-4 py-2.5 text-sm font-medium transition-all duration-100 ease-out ${
|
||||
mios ? 'bg-white text-[#64748B] hover:text-[#111827]' : 'bg-accent text-white shadow-sm'
|
||||
}`}
|
||||
>
|
||||
Todos
|
||||
@@ -326,7 +326,7 @@ export default function DashboardPage() {
|
||||
value={busqueda}
|
||||
onChange={(e) => actualizarParams({ q: e.target.value })}
|
||||
placeholder="Buscar por cliente o unidad…"
|
||||
className="w-64 border border-neutral-300 bg-white px-3 py-2.5 text-sm transition-colors duration-150 focus:border-neutral-900 focus:outline-none"
|
||||
className="w-64 rounded-lg border border-[#D1D5DB] bg-white px-3 py-2.5 text-sm transition-colors focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/20"
|
||||
/>
|
||||
<label htmlFor="filtro-status" className="sr-only">
|
||||
Filtrar por status
|
||||
@@ -335,7 +335,7 @@ export default function DashboardPage() {
|
||||
id="filtro-status"
|
||||
value={filtroStatus}
|
||||
onChange={(e) => actualizarParams({ status: e.target.value === 'TODOS' ? null : e.target.value })}
|
||||
className="border border-neutral-300 bg-white px-3 py-2.5 text-sm transition-colors duration-150 focus:border-neutral-900 focus:outline-none"
|
||||
className="rounded-lg border border-[#D1D5DB] bg-white px-3 py-2.5 text-sm transition-colors focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/20"
|
||||
>
|
||||
<option value="TODOS">Todos los status</option>
|
||||
<option value="ACTIVO">ACTIVO</option>
|
||||
@@ -349,7 +349,7 @@ export default function DashboardPage() {
|
||||
id="filtro-proyecto"
|
||||
value={filtroProyecto}
|
||||
onChange={(e) => actualizarParams({ proyecto: e.target.value === 'TODOS' ? null : e.target.value })}
|
||||
className="border border-neutral-300 bg-white px-3 py-2.5 text-sm transition-colors duration-150 focus:border-neutral-900 focus:outline-none"
|
||||
className="rounded-lg border border-[#D1D5DB] bg-white px-3 py-2.5 text-sm transition-colors focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/20"
|
||||
>
|
||||
<option value="TODOS">Todos los proyectos</option>
|
||||
{proyectos.map((p) => (
|
||||
@@ -361,49 +361,49 @@ export default function DashboardPage() {
|
||||
</div>
|
||||
|
||||
{/* Tabla de planes */}
|
||||
<div className="overflow-x-auto">
|
||||
<div className="overflow-x-auto rounded-xl border border-[#E5E7EB] bg-white shadow-sm">
|
||||
<table className="w-full text-left text-sm">
|
||||
<thead>
|
||||
<tr className="border-b border-neutral-900 text-xs font-bold uppercase tracking-widest text-neutral-500">
|
||||
<th scope="col" className="py-3 pr-4">Cliente</th>
|
||||
<th scope="col" className="py-3 pr-4">Unidad</th>
|
||||
<th scope="col" className="py-3 pr-4">Proyecto</th>
|
||||
<tr className="border-b border-[#E5E7EB] bg-[#F8FAFC] text-xs font-semibold uppercase tracking-wider text-[#64748B]">
|
||||
<th scope="col" className="px-4 py-3">Cliente</th>
|
||||
<th scope="col" className="px-4 py-3">Unidad</th>
|
||||
<th scope="col" className="px-4 py-3">Proyecto</th>
|
||||
{encabezadoOrdenable('avance', '% pagado')}
|
||||
{encabezadoOrdenable('vencimiento', 'Próx. vencimiento')}
|
||||
{encabezadoOrdenable('saldo', 'Saldo', true)}
|
||||
{encabezadoOrdenable('atraso', 'Días atraso', true)}
|
||||
<th scope="col" className="py-3">Estado</th>
|
||||
<th scope="col" className="px-4 py-3">Estado</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tbody className="divide-y divide-[#E5E7EB]">
|
||||
{ordenados.map((d) => (
|
||||
<tr key={d.plan.id} className="border-b border-neutral-200 transition-colors duration-150 hover:bg-neutral-50">
|
||||
<td className="py-3 pr-4">
|
||||
<tr key={d.plan.id} className="transition-colors duration-100 ease-out hover:bg-[#F8FAFC]">
|
||||
<td className="px-4 py-3">
|
||||
<Link to={`/planes/${d.plan.id}`} className="font-medium text-accent hover:underline">
|
||||
{d.plan.clienteNombre}
|
||||
</Link>
|
||||
</td>
|
||||
<td className="py-3 pr-4">{d.plan.unidadCodigo}</td>
|
||||
<td className="py-3 pr-4">{d.plan.proyecto}</td>
|
||||
<td className="py-3 pr-4">
|
||||
<td className="px-4 py-3">{d.plan.unidadCodigo}</td>
|
||||
<td className="px-4 py-3">{d.plan.proyecto}</td>
|
||||
<td className="px-4 py-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-1 w-24 bg-neutral-200">
|
||||
<div className="h-1.5 w-24 rounded-full bg-[#E5E7EB]">
|
||||
<div
|
||||
className="h-full bg-accent"
|
||||
className="h-full rounded-full bg-accent"
|
||||
style={{ width: `${Math.min(100, d.avance * 100)}%` }}
|
||||
/>
|
||||
</div>
|
||||
<span className="text-xs text-neutral-500">{formatoPorcentaje(d.avance)}</span>
|
||||
<span className="text-xs text-[#64748B]">{formatoPorcentaje(d.avance)}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td className="py-3 pr-4">
|
||||
<td className="px-4 py-3">
|
||||
{d.proximo ? formatoFechaCorta(d.proximo.fechaVencimiento) : '—'}
|
||||
</td>
|
||||
<td className="py-3 pr-4 text-right font-medium">{formatoMoneda(d.saldo)}</td>
|
||||
<td className="py-3 pr-4 text-right">
|
||||
<td className="px-4 py-3 text-right font-medium">{formatoMoneda(d.saldo)}</td>
|
||||
<td className="px-4 py-3 text-right">
|
||||
{d.atraso > 0 ? <span className="text-red-700">{d.atraso}</span> : '0'}
|
||||
</td>
|
||||
<td className="py-3">
|
||||
<td className="px-4 py-3">
|
||||
<SemaforoDot valor={d.semaforo} />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
Reference in New Issue
Block a user