mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-07-17 12:28:48 +02:00
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:
@@ -11,6 +11,8 @@ import {
|
|||||||
Banknote,
|
Banknote,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
import api, { type Transaction } from '@/lib/api';
|
import api, { type Transaction } from '@/lib/api';
|
||||||
import TransactionModal from './TransactionModal';
|
import TransactionModal from './TransactionModal';
|
||||||
import ConfirmDialog from './ConfirmDialog';
|
import ConfirmDialog from './ConfirmDialog';
|
||||||
@@ -66,8 +68,11 @@ export default function TransactionList({
|
|||||||
setDeleting(true);
|
setDeleting(true);
|
||||||
try {
|
try {
|
||||||
await api.delete(`/transactions/${deleteId}`);
|
await api.delete(`/transactions/${deleteId}`);
|
||||||
|
toast.success('Transacción eliminada');
|
||||||
setDeleteId(null);
|
setDeleteId(null);
|
||||||
onRefresh();
|
onRefresh();
|
||||||
|
} catch {
|
||||||
|
toast.error('No se pudo eliminar la transacción');
|
||||||
} finally {
|
} finally {
|
||||||
setDeleting(false);
|
setDeleting(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
import api, { type Category, type Transaction } from '@/lib/api';
|
import api, { type Category, type Transaction } from '@/lib/api';
|
||||||
import { formatLocalDatetime } from '@/lib/format';
|
import { formatLocalDatetime } from '@/lib/format';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@@ -92,12 +94,18 @@ export default function TransactionModal({ transaction, source, onClose, onSaved
|
|||||||
} else {
|
} else {
|
||||||
await api.post('/transactions/', payload);
|
await api.post('/transactions/', payload);
|
||||||
}
|
}
|
||||||
|
toast.success(transaction ? 'Transacción actualizada' : 'Transacción creada');
|
||||||
onSaved();
|
onSaved();
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err: any) {
|
} catch (err) {
|
||||||
if (err.response?.status === 409) {
|
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.');
|
setError('Duplicate transaction: a transaction with this reference already exists.');
|
||||||
} else {
|
} else {
|
||||||
|
setError('No se pudo guardar la transacción.');
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import ConfirmDialog from '@/components/ConfirmDialog';
|
||||||
import { useState, useRef, useEffect } from 'react';
|
import { useState, useRef, useEffect } from 'react';
|
||||||
import { Pencil } from 'lucide-react';
|
import { Pencil } from 'lucide-react';
|
||||||
|
|
||||||
@@ -42,6 +43,7 @@ export default function YearlyOverview({
|
|||||||
const currentMonth = new Date().getMonth() + 1;
|
const currentMonth = new Date().getMonth() + 1;
|
||||||
const currentYear = new Date().getFullYear();
|
const currentYear = new Date().getFullYear();
|
||||||
const [editingMonth, setEditingMonth] = useState<number | null>(null);
|
const [editingMonth, setEditingMonth] = useState<number | null>(null);
|
||||||
|
const [confirmClearMonth, setConfirmClearMonth] = useState<number | null>(null);
|
||||||
const [editValue, setEditValue] = useState('');
|
const [editValue, setEditValue] = useState('');
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
@@ -61,7 +63,16 @@ export default function YearlyOverview({
|
|||||||
if (editingMonth === null) return;
|
if (editingMonth === null) return;
|
||||||
const trimmed = editValue.trim();
|
const trimmed = editValue.trim();
|
||||||
if (trimmed === '') {
|
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 {
|
} else {
|
||||||
const num = parseFloat(trimmed);
|
const num = parseFloat(trimmed);
|
||||||
if (!isNaN(num)) {
|
if (!isNaN(num)) {
|
||||||
@@ -71,6 +82,12 @@ export default function YearlyOverview({
|
|||||||
setEditingMonth(null);
|
setEditingMonth(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleConfirmClear = async () => {
|
||||||
|
if (confirmClearMonth === null) return;
|
||||||
|
await onClearOverride(confirmClearMonth);
|
||||||
|
setConfirmClearMonth(null);
|
||||||
|
};
|
||||||
|
|
||||||
const handleKeyDown = (e: React.KeyboardEvent) => {
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
handleSave();
|
handleSave();
|
||||||
@@ -202,6 +219,15 @@ export default function YearlyOverview({
|
|||||||
})}
|
})}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user