From 8122d070690a2811d424fce2e9a43c80792dd91d Mon Sep 17 00:00:00 2001 From: null Date: Thu, 28 May 2026 02:53:35 -0500 Subject: [PATCH] inline editing --- client/pages/TrackerPage.jsx | 110 ++++++++++++++++++++++++++++++++--- 1 file changed, 103 insertions(+), 7 deletions(-) diff --git a/client/pages/TrackerPage.jsx b/client/pages/TrackerPage.jsx index 6844a2c..22b5828 100644 --- a/client/pages/TrackerPage.jsx +++ b/client/pages/TrackerPage.jsx @@ -1231,6 +1231,11 @@ function Row({ row, year, month, refresh, index, onEditBill }) { const [nudgeAmount, setNudgeAmount] = useState(null); const [, startTransition] = useTransition(); + const [editingExpected, setEditingExpected] = useState(false); + const [expectedDraft, setExpectedDraft] = useState(''); + const [editingDue, setEditingDue] = useState(false); + const [dueDraft, setDueDraft] = useState(''); + // Effective amount threshold: optimistic override → monthly override → template default. const effectiveActual = optimisticActual !== undefined ? optimisticActual : row.actual_amount; const threshold = effectiveActual != null ? effectiveActual : row.expected_amount; @@ -1357,6 +1362,50 @@ function Row({ row, year, month, refresh, index, onEditBill }) { } } + async function handleSaveExpected() { + setEditingExpected(false); + const val = parseFloat(expectedDraft); + if (!isFinite(val) || val < 0) return; + const current = effectiveActual ?? row.expected_amount; + if (val === current) return; + + if (effectiveActual != null) { + setOptimisticActual(val); + try { + await api.saveBillMonthlyState(row.id, { + year, month, + actual_amount: val, + notes: row.monthly_notes || null, + is_skipped: row.is_skipped, + }); + refresh?.(); + } catch (err) { + setOptimisticActual(undefined); + toast.error(err.message || 'Failed to update amount'); + } + } else { + try { + await api.updateBill(row.id, { name: row.name, due_day: row.due_day, expected_amount: val }); + refresh?.(); + } catch (err) { + toast.error(err.message || 'Failed to update expected amount'); + } + } + } + + async function handleSaveDue() { + setEditingDue(false); + const day = parseInt(dueDraft, 10); + if (!isFinite(day) || day < 1 || day > 31) return; + if (day === row.due_day) return; + try { + await api.updateBill(row.id, { name: row.name, due_day: day, expected_amount: row.expected_amount }); + refresh?.(); + } catch (err) { + toast.error(err.message || 'Failed to update due date'); + } + } + async function handleConfirmSuggestion() { setSuggestionLoading(true); try { @@ -1444,21 +1493,68 @@ function Row({ row, year, month, refresh, index, onEditBill }) { {/* Due */} - {fmtDate(row.due_date)} + {editingDue ? ( + setDueDraft(e.target.value)} + onBlur={handleSaveDue} + onKeyDown={e => { + if (e.key === 'Enter') e.currentTarget.blur(); + if (e.key === 'Escape') { setEditingDue(false); } + }} + className="w-12 rounded border border-border bg-transparent px-1 py-0.5 text-sm font-mono text-foreground outline-none focus:ring-[2px] focus:ring-ring/50" + title="Day of month (1–31)" + /> + ) : ( + + )} {/* Expected / Actual — shows actual_amount in amber when it overrides the template */} - {effectiveActual != null ? ( - setExpectedDraft(e.target.value)} + onBlur={handleSaveExpected} + onKeyDown={e => { + if (e.key === 'Enter') e.currentTarget.blur(); + if (e.key === 'Escape') { setEditingExpected(false); } + }} + className="w-24 rounded border border-border bg-transparent px-1 py-0.5 text-right text-sm font-mono text-foreground outline-none focus:ring-[2px] focus:ring-ring/50" + /> + ) : effectiveActual != null ? ( + ) : (
- {fmt(row.expected_amount)} + {row.amount_suggestion?.suggestion != null && Math.abs(row.amount_suggestion.suggestion - row.expected_amount) / row.expected_amount > 0.05 && (