mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-05-19 08:48:48 +02:00
Split budget Resumen into Detalle, Transacciones, and Proyecciones sub-tabs
All checks were successful
Deploy to VPS / deploy (push) Successful in 14s
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:
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user