From 8a2ea608244d2223ae74b328b2fc72dc9ddd6e85 Mon Sep 17 00:00:00 2001 From: renee-png Date: Mon, 8 Jun 2026 19:33:39 -0400 Subject: [PATCH] Accounting: editable journal entries + Reports refresh button - Journal Entries: add Edit (row + detail drawer) that loads an entry into the form and saves changes (updates the entry and replaces its lines). Warns when editing an auto-generated entry (may be overwritten by its source sync). - Reports: add a Refresh button that re-fetches the underlying data so the report reflects the latest GL. Co-Authored-By: Claude Opus 4.8 --- .../AccountingJournalEntriesPage.tsx | 73 ++++++++++++++++--- .../accounting/AccountingReportsPage.tsx | 14 +++- 2 files changed, 73 insertions(+), 14 deletions(-) diff --git a/src/pages/accounting/AccountingJournalEntriesPage.tsx b/src/pages/accounting/AccountingJournalEntriesPage.tsx index bf106c8..518952d 100644 --- a/src/pages/accounting/AccountingJournalEntriesPage.tsx +++ b/src/pages/accounting/AccountingJournalEntriesPage.tsx @@ -10,7 +10,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from " import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@/components/ui/table"; import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet"; -import { Plus, Trash2, ChevronRight, AlertCircle, Loader2 } from "lucide-react"; +import { Plus, Trash2, ChevronRight, AlertCircle, Loader2, Pencil } from "lucide-react"; import { toast } from "sonner"; import { money, fmtDate } from "./lib/format"; @@ -37,6 +37,7 @@ export default function AccountingJournalEntriesPage() { const [reference, setReference] = useState(""); const [lines, setLines] = useState([newLine(), newLine()]); const [saving, setSaving] = useState(false); + const [editingId, setEditingId] = useState(null); const { data: entries = [] } = useQuery({ queryKey: ["journal-entries", cid], @@ -87,12 +88,35 @@ export default function AccountingJournalEntriesPage() { }; const resetForm = () => { + setEditingId(null); setDate(new Date().toLocaleDateString("en-CA", { timeZone: "America/New_York" })); setDescription(""); setReference(""); setLines([newLine(), newLine()]); }; + const openEdit = (e: any) => { + setEditingId(e.id); + setDate(e.date); + setDescription(e.description ?? ""); + setReference(e.reference ?? ""); + const ls: JELine[] = (e.journal_entry_lines ?? []).map((l: any) => ({ + id: crypto.randomUUID(), + account_id: l.account_id, + amount: Number(l.debit) > 0 ? String(Number(l.debit)) : String(-Number(l.credit)), + description: l.description ?? "", + })); + while (ls.length < 2) ls.push(newLine()); + setLines(ls); + setDetailId(null); + setOpen(true); + }; + + const editingEntry = useMemo( + () => (editingId ? (entries as any[]).find((e) => e.id === editingId) : null), + [entries, editingId] + ); + const saveEntry = async () => { if (!description.trim()) return toast.error("Description required"); const activeLines = lines.filter((l) => l.amount.trim() !== "" && (Number(l.amount) || 0) !== 0); @@ -102,17 +126,29 @@ export default function AccountingJournalEntriesPage() { if (totalDebits === 0) return toast.error("Entry amount can't be zero"); setSaving(true); - const { data: je, error: jeErr } = await accounting - .from("journal_entries") - .insert({ company_id: cid, date, description, reference: reference || null }) - .select("id") - .single(); - if (jeErr || !je) { setSaving(false); return toast.error(jeErr?.message ?? "Failed to create entry"); } + let jeId = editingId; + if (editingId) { + const { error: upErr } = await accounting + .from("journal_entries") + .update({ date, description, reference: reference || null }) + .eq("id", editingId); + if (upErr) { setSaving(false); return toast.error(upErr.message); } + // Replace the lines wholesale + await accounting.from("journal_entry_lines").delete().eq("journal_entry_id", editingId); + } else { + const { data: je, error: jeErr } = await accounting + .from("journal_entries") + .insert({ company_id: cid, date, description, reference: reference || null }) + .select("id") + .single(); + if (jeErr || !je) { setSaving(false); return toast.error(jeErr?.message ?? "Failed to create entry"); } + jeId = je.id; + } const lineRows = activeLines.map((l) => { const a = Number(l.amount) || 0; return { - journal_entry_id: je.id, + journal_entry_id: jeId, account_id: l.account_id, debit: a > 0 ? a : 0, credit: a < 0 ? -a : 0, @@ -124,7 +160,7 @@ export default function AccountingJournalEntriesPage() { setSaving(false); if (linesErr) return toast.error(linesErr.message); - toast.success("Journal entry posted"); + toast.success(editingId ? "Journal entry updated" : "Journal entry posted"); setOpen(false); resetForm(); qc.invalidateQueries({ queryKey: ["journal-entries", cid] }); @@ -190,6 +226,9 @@ export default function AccountingJournalEntriesPage() { {money(credits, cur)}
+ @@ -216,7 +255,14 @@ export default function AccountingJournalEntriesPage() { {/* New journal entry dialog */} { setOpen(o); if (!o) resetForm(); }}> - New journal entry + {editingId ? "Edit journal entry" : "New journal entry"} + + {editingEntry?.external_source && ( +
+ + This entry was auto‑generated from {editingEntry.external_source.replace(/^acmacc_/, "")}. Edits here may be overwritten the next time its source syncs — edit the source record instead where possible. +
+ )}
@@ -308,7 +354,7 @@ export default function AccountingJournalEntriesPage() { @@ -360,7 +406,10 @@ export default function AccountingJournalEntriesPage() { -
+
+ diff --git a/src/pages/accounting/AccountingReportsPage.tsx b/src/pages/accounting/AccountingReportsPage.tsx index 1be505e..d256459 100644 --- a/src/pages/accounting/AccountingReportsPage.tsx +++ b/src/pages/accounting/AccountingReportsPage.tsx @@ -1,5 +1,5 @@ import { Link } from "react-router-dom"; -import { useQuery } from "@tanstack/react-query"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; import { Fragment, useEffect, useMemo, useState } from "react"; import { accounting } from "@/lib/accountingClient"; import { supabase } from "@/integrations/supabase/client"; @@ -13,7 +13,7 @@ import { Switch } from "@/components/ui/switch"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip as RTooltip, Legend, ResponsiveContainer } from "recharts"; -import { FileText, Download, FileDown, Eye } from "lucide-react"; +import { FileText, Download, FileDown, Eye, RefreshCw } from "lucide-react"; import { toast } from "sonner"; import { money, fmtDate } from "./lib/format"; import jsPDF from "jspdf"; @@ -199,6 +199,13 @@ function compareDates(mode: CompareMode, from: string, to: string, customFrom: s export default function AccountingReportsPage({ association }: { association?: { id: string; name?: string } | null } = {}) { const { companyId, associationName, associationId } = useCompanyId(association); + const qc = useQueryClient(); + const [refreshing, setRefreshing] = useState(false); + const refreshReport = async () => { + setRefreshing(true); + await qc.invalidateQueries(); + setTimeout(() => setRefreshing(false), 600); + }; const cid = companyId ?? ""; const cur = "USD"; const [active, setActive] = useState("pnl"); @@ -474,6 +481,9 @@ export default function AccountingReportsPage({ association }: { association?: {

{rangeLabel}

+ {hasOwnExport ? ( Export available inside the report ↓ ) : (