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, refresh,
} = useBudget(currentYear); } = useBudget(currentYear);
const [subTab, setSubTab] = useState<'detail' | 'transactions' | 'projections'>('detail');
// Transaction list state for the selected month // Transaction list state for the selected month
const [transactions, setTransactions] = useState<Transaction[]>([]); const [transactions, setTransactions] = useState<Transaction[]>([]);
const [txLoading, setTxLoading] = useState(false); const [txLoading, setTxLoading] = useState(false);
@@ -95,100 +97,113 @@ export default function Budget() {
<TabsTrigger value="items">Items Recurrentes</TabsTrigger> <TabsTrigger value="items">Items Recurrentes</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="overview" className="space-y-6 mt-4"> <TabsContent value="overview" className="mt-4">
{/* Annual Summary */} <Tabs
{projection && ( value={subTab}
<div className="grid gap-3 grid-cols-2 md:grid-cols-4"> onValueChange={(v) => setSubTab(v as typeof subTab)}
<Card> >
<CardContent className="pt-4 pb-3 px-4"> <TabsList variant="line">
<p className="text-xs text-muted-foreground">Ingresos Anuales</p> <TabsTrigger value="detail">
<p className="text-lg font-bold font-mono text-primary"> Detalle: {MONTH_NAMES[selectedMonth]} {year}
{formatAmount(projection.annual_income, 'CRC')} </TabsTrigger>
</p> <TabsTrigger value="transactions">Transacciones</TabsTrigger>
</CardContent> <TabsTrigger value="projections">Proyecciones</TabsTrigger>
</Card> </TabsList>
<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>
)}
{/* Yearly Overview Table */} <TabsContent value="detail" className="space-y-6 mt-4">
{loading ? ( {monthDetail && <MonthlyDetail detail={monthDetail} loading={monthLoading} />}
<div className="flex items-center justify-center py-12"> </TabsContent>
<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}
{/* Monthly Detail */} <TabsContent value="transactions" className="space-y-3 mt-4">
{monthDetail && <MonthlyDetail detail={monthDetail} loading={monthLoading} />} <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 */} <TabsContent value="projections" className="space-y-6 mt-4">
<div className="space-y-3"> {projection && (
<h3 className="text-lg font-semibold"> <div className="grid gap-3 grid-cols-2 md:grid-cols-4">
Transacciones {MONTH_NAMES[selectedMonth]} {year} <Card>
</h3> <CardContent className="pt-4 pb-3 px-4">
<Tabs <p className="text-xs text-muted-foreground">Ingresos Anuales</p>
value={txSource} <p className="text-lg font-bold font-mono text-primary">
onValueChange={(v) => setTxSource(v as typeof txSource)} {formatAmount(projection.annual_income, 'CRC')}
> </p>
<TabsList> </CardContent>
<TabsTrigger value="CREDIT_CARD">Tarjeta</TabsTrigger> </Card>
<TabsTrigger value="CASH">Efectivo</TabsTrigger> <Card>
<TabsTrigger value="TRANSFER">Transferencias</TabsTrigger> <CardContent className="pt-4 pb-3 px-4">
</TabsList> <p className="text-xs text-muted-foreground">Egresos Anuales</p>
</Tabs> <p className="text-lg font-bold font-mono">
<TransactionList {formatAmount(projection.annual_expenses, 'CRC')}
transactions={transactions} </p>
loading={txLoading} </CardContent>
source={txSource} </Card>
search={txSearch} <Card>
onSearchChange={setTxSearch} <CardContent className="pt-4 pb-3 px-4">
onRefresh={() => { <p className="text-xs text-muted-foreground">Ahorro Anual</p>
fetchTransactions(); <p className="text-lg font-bold font-mono">
refresh(); {formatAmount(projection.annual_savings, 'CRC')}
}} </p>
showCategory={txSource === 'CREDIT_CARD'} </CardContent>
emptyMessage={`Sin transacciones de ${txSource === 'CREDIT_CARD' ? 'tarjeta' : txSource === 'CASH' ? 'efectivo' : 'transferencia'} en ${MONTH_NAMES[selectedMonth]}`} </Card>
/> <Card>
</div> <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>
<TabsContent value="items" className="mt-4"> <TabsContent value="items" className="mt-4">