From 41d4306e869990b094f19c869868f2c369fb4381 Mon Sep 17 00:00:00 2001 From: Carlos Escalante Date: Sat, 4 Jul 2026 12:53:30 -0600 Subject: [PATCH] Standardize page headers and chart tokens across all pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PageHeader on Budget, Proyecciones, Pensiones, Planificador, Municipalidad, Sincronización, Financiamientos, Asistente. Pensiones fund identity and Municipalidad meter/charge series now use the validated chart tokens (color-mix for translucent fills). Planificador stat cards on StatTile; deterministic chart rendering everywhere. Municipalidad page title now matches its nav/breadcrumb label. Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend/src/pages/Asistente.tsx | 13 +++-- frontend/src/pages/Budget.tsx | 52 +++++++++--------- frontend/src/pages/Financiamientos.tsx | 14 +++-- frontend/src/pages/Pensions.tsx | 26 ++++----- frontend/src/pages/Planificador.tsx | 59 ++++++--------------- frontend/src/pages/Proyecciones.tsx | 29 +++++----- frontend/src/pages/ServiciosMunicipales.tsx | 32 ++++++----- frontend/src/pages/SyncStatus.tsx | 43 +++++++-------- 8 files changed, 117 insertions(+), 151 deletions(-) diff --git a/frontend/src/pages/Asistente.tsx b/frontend/src/pages/Asistente.tsx index e259fc3..690e573 100644 --- a/frontend/src/pages/Asistente.tsx +++ b/frontend/src/pages/Asistente.tsx @@ -8,6 +8,7 @@ import { } from "@copilotkit/react-core/v2"; import { useCopilotAction } from "@copilotkit/react-core"; import { Sparkles } from "lucide-react"; +import { PageHeader } from '@/components/page-header'; import { SpendingSummaryCard, type SpendingSummaryArgs } from "@/components/chat/ChatCards"; const STATIC_SUGGESTIONS = { @@ -87,13 +88,11 @@ export default function Asistente() { // max-w wrapper are all min-h-0 flex columns) — no vh math.
-

- - Asistente -

-

- Pregúntale a WealthySmart sobre tus finanzas. -

+
diff --git a/frontend/src/pages/Budget.tsx b/frontend/src/pages/Budget.tsx index eb0f09f..1ab808f 100644 --- a/frontend/src/pages/Budget.tsx +++ b/frontend/src/pages/Budget.tsx @@ -12,6 +12,7 @@ import { import { useBudget } from '@/hooks/useBudget'; import { Button } from '@/components/ui/button'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; +import { PageHeader } from '@/components/page-header'; import ErrorState from '@/components/ErrorState'; import PeriodNavigator from '@/components/PeriodNavigator'; import MonthlyDetail from '@/components/budget/MonthlyDetail'; @@ -128,31 +129,32 @@ export default function Budget() { return (
{/* Header */} -
-
- {cameFromProyecciones && ( - - )} - -

Presupuesto

-
- setYear(year - 1)} - onNext={() => setYear(year + 1)} - prevDisabled={year <= MIN_YEAR} - nextDisabled={year >= MAX_YEAR} - /> -
+ {cameFromProyecciones && ( + + )} + setYear(year - 1)} + onNext={() => setYear(year + 1)} + prevDisabled={year <= MIN_YEAR} + nextDisabled={year >= MAX_YEAR} + /> + } + /> diff --git a/frontend/src/pages/Financiamientos.tsx b/frontend/src/pages/Financiamientos.tsx index 8dd7b38..e8e951e 100644 --- a/frontend/src/pages/Financiamientos.tsx +++ b/frontend/src/pages/Financiamientos.tsx @@ -19,6 +19,7 @@ import { TableRow, } from '@/components/ui/table'; import ErrorState from '@/components/ErrorState'; +import { PageHeader } from '@/components/page-header'; import ConvertToInstallmentsDialog from '@/components/transactions/ConvertToInstallmentsDialog'; function formatDate(iso: string) { @@ -104,14 +105,11 @@ export default function Financiamientos() { return (
-
- -

Financiamientos

-
-

- Compras Tasa Cero pagadas en cuotas mensuales sin intereses. Las cuotas - futuras ya cuentan en el presupuesto de sus meses. -

+ {/* Summary */}
diff --git a/frontend/src/pages/Pensions.tsx b/frontend/src/pages/Pensions.tsx index 0627d75..45e86e8 100644 --- a/frontend/src/pages/Pensions.tsx +++ b/frontend/src/pages/Pensions.tsx @@ -1,6 +1,7 @@ import { useState, useMemo, useCallback, useRef } from 'react'; import { useQuery } from '@tanstack/react-query'; import ErrorState from '@/components/ErrorState'; +import { PageHeader } from '@/components/page-header'; import PensionAssumptionsDialog, { type PensionAssumptions } from '@/components/PensionAssumptionsDialog'; import { LineChart, @@ -103,7 +104,7 @@ const FUNDS_DEFAULT: Record = { key: 'FCL', name: 'FCL', fullName: 'Fondo de Capitalización Laboral', - color: '#3b82f6', + color: 'var(--chart-5)', startBalance: 650_468, monthlyContribution: 150_000, annualRate: 7.5, @@ -115,7 +116,7 @@ const FUNDS_DEFAULT: Record = { key: 'ROP', name: 'ROP', fullName: 'Régimen Obligatorio de Pensiones', - color: '#10b981', + color: 'var(--chart-1)', startBalance: 18_684_765, monthlyContribution: 120_000, annualRate: 6.0, @@ -127,7 +128,7 @@ const FUNDS_DEFAULT: Record = { key: 'VOL', name: 'VOL', fullName: 'Fondo Voluntario', - color: '#f43f5e', + color: 'var(--chart-4)', startBalance: 2_500_381, monthlyContribution: 400_000, annualRate: 8.0, @@ -480,17 +481,11 @@ export default function Pensions() { /> )} {/* ── Page Header ─────────────────────────────────────────────────── */} -
-
- -
-
-

Pensiones

-

- Seguimiento de aportes, rendimientos y proyecciones -

-
-
+ {/* ── Section 1: Fund Overview Cards ──────────────────────────────── */}
@@ -606,7 +601,7 @@ export default function Pensions() { className="flex items-center gap-1.5 px-3 py-1.5 rounded-full text-xs font-medium transition-all border cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" style={{ borderColor: fund.color, - background: active ? fund.color + '22' : 'transparent', + background: active ? `color-mix(in oklab, ${fund.color} 13%, transparent)` : 'transparent', color: active ? fund.color : 'var(--muted-foreground)', }} > @@ -652,6 +647,7 @@ export default function Pensions() { stroke={FUNDS[key].color} strokeWidth={2} dot={false} + isAnimationActive={false} activeDot={{ r: 4 }} /> ) : null, diff --git a/frontend/src/pages/Planificador.tsx b/frontend/src/pages/Planificador.tsx index 3ba1b3e..f524dcc 100644 --- a/frontend/src/pages/Planificador.tsx +++ b/frontend/src/pages/Planificador.tsx @@ -4,6 +4,8 @@ import { Line, LineChart, XAxis, YAxis } from 'recharts'; import { Telescope } from 'lucide-react'; import { getYearlyProjection } from '@/lib/api'; +import { PageHeader } from '@/components/page-header'; +import { StatTile } from '@/components/stat-tile'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; @@ -78,15 +80,11 @@ export default function Planificador() { return (
-
-
+ @@ -138,41 +136,18 @@ export default function Planificador() {
- - -

- Ritmo actual -

-

- {formatCRC(baselineEnd)} -

-
-
- - -

- Escenario -

-

- {formatCRC(scenarioEnd)} -

-
-
- - -

- Diferencia -

-

- +{formatCRC(scenarioEnd - baselineEnd)} -

-
-
+ + +
- + Crecimiento comparado @@ -191,8 +166,8 @@ export default function Planificador() { formatCRC(Number(value))} /> } /> - - + +

diff --git a/frontend/src/pages/Proyecciones.tsx b/frontend/src/pages/Proyecciones.tsx index 0f2b692..c749393 100644 --- a/frontend/src/pages/Proyecciones.tsx +++ b/frontend/src/pages/Proyecciones.tsx @@ -5,6 +5,7 @@ import { useBudget } from '@/hooks/useBudget'; import { formatAmount } from '@/lib/format'; import { cn } from '@/lib/utils'; import { Card, CardContent } from '@/components/ui/card'; +import { PageHeader } from '@/components/page-header'; import ErrorState from '@/components/ErrorState'; import PeriodNavigator from '@/components/PeriodNavigator'; import YearlyOverview from '@/components/budget/YearlyOverview'; @@ -30,20 +31,20 @@ export default function Proyecciones() { return (

- {/* Header */} -
-
- -

Proyecciones

-
- setYear(year - 1)} - onNext={() => setYear(year + 1)} - prevDisabled={year <= MIN_YEAR} - nextDisabled={year >= MAX_YEAR} - /> -
+ setYear(year - 1)} + onNext={() => setYear(year + 1)} + prevDisabled={year <= MIN_YEAR} + nextDisabled={year >= MAX_YEAR} + /> + } + /> {/* Annual summary cards */} {projection && ( diff --git a/frontend/src/pages/ServiciosMunicipales.tsx b/frontend/src/pages/ServiciosMunicipales.tsx index 2a21720..8db80ce 100644 --- a/frontend/src/pages/ServiciosMunicipales.tsx +++ b/frontend/src/pages/ServiciosMunicipales.tsx @@ -2,6 +2,7 @@ import { useState, useCallback, useRef, useMemo } from 'react'; import { useQuery } from '@tanstack/react-query'; import { MONTH_NAMES_ES_SHORT as MONTH_NAMES_ES } from '@/lib/dates'; import MeterLabelsDialog from '@/components/MeterLabelsDialog'; +import { PageHeader } from '@/components/page-header'; import ErrorState from '@/components/ErrorState'; import { BarChart, @@ -51,13 +52,13 @@ import { // ─── Constants ─────────────────────────────────────────────────────────────── const METER_COLORS: Record = { - '7335': '#3b82f6', - '7345': '#10b981', - '9345': '#f59e0b', + '7335': 'var(--chart-5)', + '7345': 'var(--chart-1)', + '9345': 'var(--chart-2)', }; -const DEFAULT_METER_COLOR = '#8b5cf6'; +const DEFAULT_METER_COLOR = 'var(--chart-3)'; // ─── Utilities ─────────────────────────────────────────────────────────────── @@ -105,8 +106,11 @@ function getMeterIds(readings: WaterMeterReading[]): string[] { // ─── Charge Trend Data ─────────────────────────────────────────────────────── +// Fixed categorical slots (validated palette); series beyond the 5th read +// muted — never cycle hues. const CHARGE_COLORS = [ - '#ef4444', '#f59e0b', '#10b981', '#3b82f6', '#8b5cf6', '#ec4899', '#06b6d4', '#84cc16', + 'var(--chart-1)', 'var(--chart-2)', 'var(--chart-3)', 'var(--chart-4)', + 'var(--chart-5)', 'var(--muted-foreground)', ]; interface ChargeTrendPoint { @@ -306,17 +310,11 @@ export default function ServiciosMunicipales() { /> )} {/* ── Page Header ─────────────────────────────────────────────────── */} -
-
- -
-
-

Servicios Municipales

-

- Municipalidad de Belén — recibos y consumo de agua -

-
-
+ {/* ── Summary Cards ───────────────────────────────────────────────── */}
@@ -546,7 +544,7 @@ export default function ServiciosMunicipales() { type="monotone" dataKey={name} name={name} - stroke={CHARGE_COLORS[i % CHARGE_COLORS.length]} + stroke={CHARGE_COLORS[Math.min(i, CHARGE_COLORS.length - 1)]} strokeWidth={2} dot={{ r: 3 }} activeDot={{ r: 5 }} diff --git a/frontend/src/pages/SyncStatus.tsx b/frontend/src/pages/SyncStatus.tsx index 6f5e031..287a02b 100644 --- a/frontend/src/pages/SyncStatus.tsx +++ b/frontend/src/pages/SyncStatus.tsx @@ -11,6 +11,7 @@ import { formatRelativeAge } from '@/lib/dates'; import { Button } from '@/components/ui/button'; import { Card, CardContent } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; +import { PageHeader } from '@/components/page-header'; import ErrorState from '@/components/ErrorState'; import { Skeleton } from '@/components/ui/skeleton'; @@ -38,29 +39,25 @@ export default function SyncStatus() { return (
-
-
-
- -
+ query.refetch()} + disabled={query.isFetching} + > +