mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-05-19 11:08:47 +02:00
Add Proyecciones page with yearly financial projections view
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import Budget from './pages/Budget';
|
||||
import Analytics from './pages/Analytics';
|
||||
import Salarios from './pages/Salarios';
|
||||
import Pensions from './pages/Pensions';
|
||||
import Proyecciones from './pages/Proyecciones';
|
||||
import ServiciosMunicipales from './pages/ServiciosMunicipales';
|
||||
|
||||
function ProtectedRoute({ children }: { children: React.ReactNode }) {
|
||||
@@ -35,6 +36,7 @@ function AppRoutes() {
|
||||
<Route path="/" element={<Dashboard />} />
|
||||
<Route path="/budget" element={<Budget />} />
|
||||
<Route path="/analytics" element={<Analytics />} />
|
||||
<Route path="/proyecciones" element={<Proyecciones />} />
|
||||
<Route path="/salarios" element={<Salarios />} />
|
||||
<Route path="/pensions" element={<Pensions />} />
|
||||
<Route path="/servicios-municipales" element={<ServiciosMunicipales />} />
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
PiggyBank,
|
||||
Droplets,
|
||||
LogOut,
|
||||
TrendingUp,
|
||||
Wallet,
|
||||
Menu,
|
||||
Sun,
|
||||
@@ -51,6 +52,7 @@ const navSections: NavSection[] = [
|
||||
{ to: '/budget', icon: Calculator, label: 'Presupuesto' },
|
||||
{ to: '/salarios', icon: Landmark, label: 'Salarios' },
|
||||
{ to: '/pensions', icon: PiggyBank, label: 'Pensiones' },
|
||||
{ to: '/proyecciones', icon: TrendingUp, label: 'Proyecciones' },
|
||||
{ to: '/analytics', icon: BarChart3, label: 'Analytics' },
|
||||
],
|
||||
},
|
||||
|
||||
120
frontend/src/pages/Proyecciones.tsx
Normal file
120
frontend/src/pages/Proyecciones.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import { ChevronLeft, ChevronRight, Loader2, TrendingUp } from 'lucide-react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { useBudget } from '@/hooks/useBudget';
|
||||
import { formatAmount } from '@/lib/format';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import YearlyOverview from '@/components/budget/YearlyOverview';
|
||||
|
||||
const MIN_YEAR = 2026;
|
||||
const MAX_YEAR = 2030;
|
||||
|
||||
export default function Proyecciones() {
|
||||
const currentYear = Math.max(MIN_YEAR, new Date().getFullYear());
|
||||
const {
|
||||
year,
|
||||
setYear,
|
||||
setSelectedMonth,
|
||||
projection,
|
||||
loading,
|
||||
saveBalanceOverride,
|
||||
clearBalanceOverride,
|
||||
} = useBudget(currentYear);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<TrendingUp className="w-6 h-6 text-primary" />
|
||||
<h1 className="text-2xl font-bold tracking-tight">Proyecciones</h1>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button variant="outline" size="icon" disabled={year <= MIN_YEAR} onClick={() => setYear(year - 1)}>
|
||||
<ChevronLeft className="w-4 h-4" />
|
||||
</Button>
|
||||
<span className="w-16 text-center font-semibold tabular-nums">{year}</span>
|
||||
<Button variant="outline" size="icon" disabled={year >= MAX_YEAR} onClick={() => setYear(year + 1)}>
|
||||
<ChevronRight className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Annual summary cards */}
|
||||
{projection && (
|
||||
<div className="grid gap-3 grid-cols-2 md:grid-cols-4">
|
||||
<Card>
|
||||
<CardContent className="pt-4 pb-3 px-4">
|
||||
<p className="text-xs text-muted-foreground">Ingresos Anuales</p>
|
||||
<p data-sensitive className="text-lg font-bold font-mono text-primary">
|
||||
{formatAmount(projection.annual_income, 'CRC')}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-4 pb-3 px-4">
|
||||
<p className="text-xs text-muted-foreground">Egresos Anuales</p>
|
||||
<p data-sensitive className="text-lg font-bold font-mono">
|
||||
{formatAmount(projection.annual_expenses, 'CRC')}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-4 pb-3 px-4">
|
||||
<p className="text-xs text-muted-foreground">Ahorro Anual</p>
|
||||
<p data-sensitive className="text-lg font-bold font-mono">
|
||||
{formatAmount(projection.annual_savings, 'CRC')}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-4 pb-3 px-4">
|
||||
<p className="text-xs text-muted-foreground">Balance Neto Anual</p>
|
||||
<p
|
||||
data-sensitive
|
||||
className={cn(
|
||||
'text-lg font-bold font-mono',
|
||||
projection.annual_net >= 0 ? 'text-primary' : 'text-destructive',
|
||||
)}
|
||||
>
|
||||
{projection.annual_net >= 0 ? '+' : ''}
|
||||
{formatAmount(projection.annual_net, 'CRC')}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Yearly table */}
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-12">
|
||||
<Loader2 className="w-6 h-6 animate-spin text-muted-foreground" />
|
||||
</div>
|
||||
) : projection ? (
|
||||
<Card>
|
||||
<CardContent className="p-0">
|
||||
<YearlyOverview
|
||||
months={projection.months}
|
||||
selectedMonth={0}
|
||||
year={year}
|
||||
onSelectMonth={(m) => {
|
||||
setSelectedMonth(m);
|
||||
navigate('/budget');
|
||||
}}
|
||||
onSaveOverride={async (month, value) => {
|
||||
await saveBalanceOverride(year, month, value);
|
||||
}}
|
||||
onClearOverride={async (month) => {
|
||||
await clearBalanceOverride(year, month);
|
||||
}}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user