diff --git a/frontend/src/components/TransactionList.tsx b/frontend/src/components/TransactionList.tsx index 3adc8ff..9c54aab 100644 --- a/frontend/src/components/TransactionList.tsx +++ b/frontend/src/components/TransactionList.tsx @@ -11,6 +11,8 @@ import { Banknote, } from 'lucide-react'; +import { toast } from 'sonner'; + import api, { type Transaction } from '@/lib/api'; import TransactionModal from './TransactionModal'; import ConfirmDialog from './ConfirmDialog'; @@ -66,8 +68,11 @@ export default function TransactionList({ setDeleting(true); try { await api.delete(`/transactions/${deleteId}`); + toast.success('Transacción eliminada'); setDeleteId(null); onRefresh(); + } catch { + toast.error('No se pudo eliminar la transacción'); } finally { setDeleting(false); } diff --git a/frontend/src/components/TransactionModal.tsx b/frontend/src/components/TransactionModal.tsx index b943aec..11bb44c 100644 --- a/frontend/src/components/TransactionModal.tsx +++ b/frontend/src/components/TransactionModal.tsx @@ -1,4 +1,6 @@ import { useEffect, useState } from 'react'; +import { toast } from 'sonner'; + import api, { type Category, type Transaction } from '@/lib/api'; import { formatLocalDatetime } from '@/lib/format'; import { Button } from '@/components/ui/button'; @@ -92,12 +94,18 @@ export default function TransactionModal({ transaction, source, onClose, onSaved } else { await api.post('/transactions/', payload); } + toast.success(transaction ? 'Transacción actualizada' : 'Transacción creada'); onSaved(); onClose(); - } catch (err: any) { - if (err.response?.status === 409) { + } catch (err) { + const status = + err && typeof err === 'object' && 'response' in err + ? (err as { response: { status: number } }).response.status + : null; + if (status === 409) { setError('Duplicate transaction: a transaction with this reference already exists.'); } else { + setError('No se pudo guardar la transacción.'); console.error(err); } } finally { diff --git a/frontend/src/components/budget/YearlyOverview.tsx b/frontend/src/components/budget/YearlyOverview.tsx index 265dc3a..5957dc2 100644 --- a/frontend/src/components/budget/YearlyOverview.tsx +++ b/frontend/src/components/budget/YearlyOverview.tsx @@ -1,3 +1,4 @@ +import ConfirmDialog from '@/components/ConfirmDialog'; import { useState, useRef, useEffect } from 'react'; import { Pencil } from 'lucide-react'; @@ -42,6 +43,7 @@ export default function YearlyOverview({ const currentMonth = new Date().getMonth() + 1; const currentYear = new Date().getFullYear(); const [editingMonth, setEditingMonth] = useState(null); + const [confirmClearMonth, setConfirmClearMonth] = useState(null); const [editValue, setEditValue] = useState(''); const inputRef = useRef(null); @@ -61,7 +63,16 @@ export default function YearlyOverview({ if (editingMonth === null) return; const trimmed = editValue.trim(); if (trimmed === '') { - await onClearOverride(editingMonth); + // Clearing an override is destructive — confirm first (it changes the + // cumulative chain for every following month). + const hadOverride = months.find( + (m) => m.month === editingMonth, + )?.balance_overridden; + if (hadOverride) { + setConfirmClearMonth(editingMonth); + setEditingMonth(null); + return; + } } else { const num = parseFloat(trimmed); if (!isNaN(num)) { @@ -71,6 +82,12 @@ export default function YearlyOverview({ setEditingMonth(null); }; + const handleConfirmClear = async () => { + if (confirmClearMonth === null) return; + await onClearOverride(confirmClearMonth); + setConfirmClearMonth(null); + }; + const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { handleSave(); @@ -202,6 +219,15 @@ export default function YearlyOverview({ })} + {confirmClearMonth !== null && ( + setConfirmClearMonth(null)} + /> + )} ); }