Split budget Resumen into Detalle, Transacciones, and Proyecciones sub-tabs
All checks were successful
Deploy to VPS / deploy (push) Successful in 14s

Reduces scrolling by organizing the budget overview into three inner tabs.
Clicking a month in the yearly table auto-switches to the Detalle tab.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Carlos Escalante
2026-03-30 11:14:43 -06:00
parent 26a26b8ca2
commit 99d0c4ebd7

View File

@@ -36,6 +36,8 @@ export default function Budget() {
refresh,
} = useBudget(currentYear);
const [subTab, setSubTab] = useState<'detail' | 'transactions' | 'projections'>('detail');
// Transaction list state for the selected month
const [transactions, setTransactions] = useState<Transaction[]>([]);
const [txLoading, setTxLoading] = useState(false);
@@ -95,100 +97,113 @@ export default function Budget() {
<TabsTrigger value="items">Items Recurrentes</TabsTrigger>
</TabsList>
<TabsContent value="overview" className="space-y-6 mt-4">
{/* Annual Summary */}
{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 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 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 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
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>
)}
<TabsContent value="overview" className="mt-4">
<Tabs
value={subTab}
onValueChange={(v) => setSubTab(v as typeof subTab)}
>
<TabsList variant="line">
<TabsTrigger value="detail">
Detalle: {MONTH_NAMES[selectedMonth]} {year}
</TabsTrigger>
<TabsTrigger value="transactions">Transacciones</TabsTrigger>
<TabsTrigger value="projections">Proyecciones</TabsTrigger>
</TabsList>
{/* Yearly Overview 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={selectedMonth}
onSelectMonth={setSelectedMonth}
/>
</CardContent>
</Card>
) : null}
<TabsContent value="detail" className="space-y-6 mt-4">
{monthDetail && <MonthlyDetail detail={monthDetail} loading={monthLoading} />}
</TabsContent>
{/* Monthly Detail */}
{monthDetail && <MonthlyDetail detail={monthDetail} loading={monthLoading} />}
<TabsContent value="transactions" className="space-y-3 mt-4">
<Tabs
value={txSource}
onValueChange={(v) => setTxSource(v as typeof txSource)}
>
<TabsList>
<TabsTrigger value="CREDIT_CARD">Tarjeta</TabsTrigger>
<TabsTrigger value="CASH">Efectivo</TabsTrigger>
<TabsTrigger value="TRANSFER">Transferencias</TabsTrigger>
</TabsList>
</Tabs>
<TransactionList
transactions={transactions}
loading={txLoading}
source={txSource}
search={txSearch}
onSearchChange={setTxSearch}
onRefresh={() => {
fetchTransactions();
refresh();
}}
showCategory={txSource === 'CREDIT_CARD'}
emptyMessage={`Sin transacciones de ${txSource === 'CREDIT_CARD' ? 'tarjeta' : txSource === 'CASH' ? 'efectivo' : 'transferencia'} en ${MONTH_NAMES[selectedMonth]}`}
/>
</TabsContent>
{/* Transactions for selected month */}
<div className="space-y-3">
<h3 className="text-lg font-semibold">
Transacciones {MONTH_NAMES[selectedMonth]} {year}
</h3>
<Tabs
value={txSource}
onValueChange={(v) => setTxSource(v as typeof txSource)}
>
<TabsList>
<TabsTrigger value="CREDIT_CARD">Tarjeta</TabsTrigger>
<TabsTrigger value="CASH">Efectivo</TabsTrigger>
<TabsTrigger value="TRANSFER">Transferencias</TabsTrigger>
</TabsList>
</Tabs>
<TransactionList
transactions={transactions}
loading={txLoading}
source={txSource}
search={txSearch}
onSearchChange={setTxSearch}
onRefresh={() => {
fetchTransactions();
refresh();
}}
showCategory={txSource === 'CREDIT_CARD'}
emptyMessage={`Sin transacciones de ${txSource === 'CREDIT_CARD' ? 'tarjeta' : txSource === 'CASH' ? 'efectivo' : 'transferencia'} en ${MONTH_NAMES[selectedMonth]}`}
/>
</div>
<TabsContent value="projections" className="space-y-6 mt-4">
{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 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 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 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
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>
)}
{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={selectedMonth}
onSelectMonth={(m) => {
setSelectedMonth(m);
setSubTab('detail');
}}
/>
</CardContent>
</Card>
) : null}
</TabsContent>
</Tabs>
</TabsContent>
<TabsContent value="items" className="mt-4">