Mutation feedback: toasts on transaction save/delete, confirm override clear

Transaction create/update/delete now confirm success or failure with a
toast (UX-01); save failures always show a user-visible message instead
of console-only (FE-06, with a typed error guard replacing the any
cast). Clearing a balance override — which recalculates every following
month — now requires confirmation like the other destructive actions
(UX-15).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Carlos Escalante
2026-06-10 17:28:21 -06:00
parent 7a1733bf7e
commit 2a68a05ffd
3 changed files with 42 additions and 3 deletions

View File

@@ -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);
}

View File

@@ -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 {

View File

@@ -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<number | null>(null);
const [confirmClearMonth, setConfirmClearMonth] = useState<number | null>(null);
const [editValue, setEditValue] = useState('');
const inputRef = useRef<HTMLInputElement>(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({
})}
</TableBody>
</Table>
{confirmClearMonth !== null && (
<ConfirmDialog
title="Eliminar ajuste de balance"
message={`El balance de ${MONTH_NAMES[confirmClearMonth] ?? confirmClearMonth} volverá al valor calculado y los meses siguientes se recalcularán.`}
confirmLabel="Eliminar ajuste"
onConfirm={handleConfirmClear}
onCancel={() => setConfirmClearMonth(null)}
/>
)}
</div>
);
}