From 36b1fe887b4b1f14105bf599fa2b4668d20a4abe Mon Sep 17 00:00:00 2001 From: ribardej Date: Wed, 5 Nov 2025 18:00:24 +0100 Subject: [PATCH] feat(frontend): Added options for modifying and deleting transactions in the UI --- 7project/frontend/src/api.ts | 11 ++ 7project/frontend/src/pages/Dashboard.tsx | 134 ++++++++++++++++++---- 2 files changed, 124 insertions(+), 21 deletions(-) diff --git a/7project/frontend/src/api.ts b/7project/frontend/src/api.ts index 018d900..fb04336 100644 --- a/7project/frontend/src/api.ts +++ b/7project/frontend/src/api.ts @@ -19,6 +19,17 @@ export type Transaction = { date?: string | null; // ISO date (YYYY-MM-DD) }; +export async function deleteTransaction(id: number): Promise { + const res = await fetch(`${getBaseUrl()}/transactions/${id}/delete`, { + method: 'DELETE', + headers: getHeaders('none'), + }); + if (!res.ok) { + const text = await res.text(); + throw new Error(text || 'Failed to delete transaction'); + } +} + function getBaseUrl() { const base = BACKEND_URL?.replace(/\/$/, '') || ''; return base || ''; diff --git a/7project/frontend/src/pages/Dashboard.tsx b/7project/frontend/src/pages/Dashboard.tsx index 2cb9850..d90c33c 100644 --- a/7project/frontend/src/pages/Dashboard.tsx +++ b/7project/frontend/src/pages/Dashboard.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useState } from 'react'; -import { type Category, type Transaction, type BalancePoint, getCategories, getTransactions, createTransaction, updateTransaction, getBalanceSeries } from '../api'; +import { type Category, type Transaction, type BalancePoint, deleteTransaction, getCategories, getTransactions, createTransaction, updateTransaction, getBalanceSeries } from '../api'; import AccountPage from './AccountPage'; import AppearancePage from './AppearancePage'; import BalanceChart from './BalanceChart'; @@ -161,9 +161,14 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) { // Manual forms moved to ManualManagement page - // Inline edit state for transaction categories + // Inline edit state for transaction editing const [editingTxId, setEditingTxId] = useState(null); const [editingCategoryIds, setEditingCategoryIds] = useState([]); + const [editingAmount, setEditingAmount] = useState(''); + const [editingDescription, setEditingDescription] = useState(''); + const [editingDate, setEditingDate] = useState(''); // YYYY-MM-DD + + async function loadAll() { setLoading(true); @@ -259,22 +264,50 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) { function categoryNameById(id: number) { return categories.find(c => c.id === id)?.name || `#${id}`; } - function beginEditCategories(t: Transaction) { + function beginEditTransaction(t: Transaction) { setEditingTxId(t.id); setEditingCategoryIds([...(t.category_ids || [])]); + setEditingAmount(String(t.amount)); + setEditingDescription(t.description || ''); + setEditingDate(t.date || ''); } - function cancelEditCategories() { + function cancelEditTransaction() { setEditingTxId(null); setEditingCategoryIds([]); + setEditingAmount(''); + setEditingDescription(''); + setEditingDate(''); } - async function saveEditCategories() { + async function saveEditTransaction() { if (editingTxId == null) return; + const amountNum = Number(editingAmount); + if (Number.isNaN(amountNum)) { + alert('Amount must be a number.'); + return; + } try { - const updated = await updateTransaction(editingTxId, { category_ids: editingCategoryIds }); + const updated = await updateTransaction(editingTxId, { + amount: amountNum, + description: editingDescription, + date: editingDate || undefined, + category_ids: editingCategoryIds, + }); setTransactions(prev => prev.map(p => (p.id === updated.id ? updated : p))); - cancelEditCategories(); + // Optionally refresh balance series to reflect changes immediately + try { setBalanceSeries(await getBalanceSeries(startDate || undefined, endDate || undefined)); } catch {} + cancelEditTransaction(); } catch (err: any) { - alert(err?.message || 'Failed to update transaction categories'); + alert(err?.message || 'Failed to update transaction'); + } + } + async function handleDeleteTransaction(id: number) { + if (!confirm('Delete this transaction? This cannot be undone.')) return; + try { + await deleteTransaction(id); + setTransactions(prev => prev.filter(t => t.id !== id)); + try { setBalanceSeries(await getBalanceSeries(startDate || undefined, endDate || undefined)); } catch {} + } catch (err: any) { + alert(err?.message || 'Failed to delete transaction'); } } @@ -383,32 +416,91 @@ export default function Dashboard({ onLogout }: { onLogout: () => void }) { Amount Description Categories + Actions {visible.map(t => ( - {t.date || ''} - {formatAmount(t.amount)} - {t.description || ''} + {/* Date cell */} {editingTxId === t.id ? ( -
- setEditingDate(e.target.value)} + /> + ) : ( + t.date || '' + )} + + + {/* Amount cell */} + + {editingTxId === t.id ? ( + setEditingAmount(e.target.value)} + style={{ textAlign: 'right' }} + /> + ) : ( + formatAmount(t.amount) + )} + + + {/* Description cell */} + + {editingTxId === t.id ? ( + setEditingDescription(e.target.value)} + /> + ) : ( + t.description || '' + )} + + + {/* Categories cell */} + + {editingTxId === t.id ? ( +
+ - -
) : ( -
- {t.category_ids.map(id => categoryNameById(id)).join(', ') || '—'} - + {t.category_ids.map(id => categoryNameById(id)).join(', ') || '—'} + )} + + + {/* Actions cell */} + + {editingTxId === t.id ? ( +
+ + + +
+ ) : ( +
+ +
)}