Budget: unify transactions section into one card

Toolbar (source tabs, search, CSV, add) and a summary strip (billing
cycle range, count, per-currency total) now live inside the table card
instead of five loose control rows. Cycle range 18-to-18 is finally
visible. Stray English strings translated; back-button spacing fixed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Carlos Escalante
2026-07-04 09:38:36 -06:00
parent 72a5840752
commit 3d71234a61
3 changed files with 96 additions and 61 deletions

View File

@@ -5,7 +5,10 @@ import { useQuery, useQueryClient } from '@tanstack/react-query';
import { toast } from 'sonner';
import api, { type Transaction } from '@/lib/api';
import { MONTH_NAMES_ES as MONTH_NAMES } from '@/lib/dates';
import {
MONTH_NAMES_ES as MONTH_NAMES,
MONTH_NAMES_ES_SHORT as MONTH_NAMES_SHORT,
} from '@/lib/dates';
import { useBudget } from '@/hooks/useBudget';
import { Button } from '@/components/ui/button';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
@@ -86,6 +89,14 @@ export default function Budget() {
});
const transactions = txQuery.data ?? [];
// Make the period explicit: budget month M on the card tab = the billing
// cycle 18/(M-1) → 18/M; cash/transfers use the calendar month.
const cyclePrevMonth = selectedMonth === 1 ? 12 : selectedMonth - 1;
const cycleLabel =
txSource === 'CREDIT_CARD'
? `Ciclo 18 ${MONTH_NAMES_SHORT[cyclePrevMonth].toLowerCase()} 18 ${MONTH_NAMES_SHORT[selectedMonth].toLowerCase()}`
: `Mes de ${MONTH_NAMES[selectedMonth].toLowerCase()}`;
const invalidateTransactionsAndBudget = useCallback(() => {
queryClient.invalidateQueries({ queryKey: ['transactions'] });
queryClient.invalidateQueries({ queryKey: ['installment-plans'] });
@@ -123,6 +134,7 @@ export default function Budget() {
<Button
variant="ghost"
size="sm"
className="mr-4 text-muted-foreground"
onClick={() => navigate('/proyecciones')}
aria-label="Volver a Proyecciones"
>
@@ -181,27 +193,7 @@ export default function Budget() {
)}
</TabsContent>
<TabsContent value="transactions" className="space-y-3 mt-4">
<div className="flex items-center justify-between gap-2">
<Tabs
value={txSource}
onValueChange={(v) => setTxSource(v as typeof txSource)}
>
<TabsList>
<TabsTrigger value="CREDIT_CARD">Tarjeta</TabsTrigger>
<TabsTrigger value="CASH_AND_TRANSFER">Efectivo y Transferencias</TabsTrigger>
</TabsList>
</Tabs>
<Button
variant="outline"
size="sm"
onClick={() => window.open('/api/v1/transactions/export', '_blank')}
title="Descargar todas las transacciones como CSV"
>
<Download className="w-4 h-4 mr-2" aria-hidden="true" />
CSV
</Button>
</div>
<TabsContent value="transactions" className="mt-4">
{txQuery.isError ? (
<ErrorState
message="No se pudieron cargar las transacciones"
@@ -220,6 +212,29 @@ export default function Budget() {
emptyMessage={`Sin transacciones de ${txSource === 'CREDIT_CARD' ? 'tarjeta' : 'efectivo o transferencia'} en ${MONTH_NAMES[selectedMonth]}`}
onToggleDeferred={txSource === 'CREDIT_CARD' ? handleToggleDeferred : undefined}
onConvertTasaCero={txSource === 'CREDIT_CARD' ? setConvertTx : undefined}
summaryLabel={cycleLabel}
toolbarLeft={
<Tabs
value={txSource}
onValueChange={(v) => setTxSource(v as typeof txSource)}
>
<TabsList>
<TabsTrigger value="CREDIT_CARD">Tarjeta</TabsTrigger>
<TabsTrigger value="CASH_AND_TRANSFER">Efectivo y Transferencias</TabsTrigger>
</TabsList>
</Tabs>
}
toolbarRight={
<Button
variant="outline"
size="sm"
onClick={() => window.open('/api/v1/transactions/export', '_blank')}
title="Descargar todas las transacciones como CSV"
>
<Download className="w-4 h-4 mr-2" aria-hidden="true" />
CSV
</Button>
}
/>
)}
</TabsContent>