From 6fa8e0fb638deb71250c970e31f002296e2bb0bf Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 18 Apr 2026 01:08:34 +0000 Subject: [PATCH] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- .../invoices/generate-invoice-dialog.tsx | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/components/invoices/generate-invoice-dialog.tsx b/src/components/invoices/generate-invoice-dialog.tsx index 0fa0f5e..1dff29a 100644 --- a/src/components/invoices/generate-invoice-dialog.tsx +++ b/src/components/invoices/generate-invoice-dialog.tsx @@ -41,6 +41,7 @@ export function GenerateInvoiceDialog({ open, onOpenChange, clientId, clientName const [taxPct, setTaxPct] = useState("0"); const [dueDays, setDueDays] = useState("30"); const [notes, setNotes] = useState(""); + const [isRetainer, setIsRetainer] = useState(false); const [loading, setLoading] = useState(false); const [saving, setSaving] = useState(false); @@ -104,9 +105,53 @@ export function GenerateInvoiceDialog({ open, onOpenChange, clientId, clientName const submit = async () => { if (!user?.id) return; - if (selectedIds.length === 0) return toast.error("Select at least one case"); + if (selectedIds.length === 0 && !isRetainer) return toast.error("Select at least one case"); setSaving(true); try { + if (isRetainer) { + // Retainer invoices have no time/expenses — create a single line item for the deposit. + const yr = new Date().getFullYear(); + const num = `INV-${yr}-${Math.floor(1000 + Math.random() * 9000)}`; + const due = new Date(); + due.setDate(due.getDate() + (Number(dueDays) || 30)); + const amt = Math.max(0, Number((document.getElementById("retainer-amount") as HTMLInputElement)?.value) || 0); + if (amt <= 0) { + setSaving(false); + return toast.error("Enter a retainer amount"); + } + const tax = +(amt * (Number(taxPct) || 0) / 100).toFixed(2); + const { data: inv, error: ie } = await supabase + .from("invoices") + .insert({ + invoice_number: num, + client_id: clientId, + status: "draft", + issue_date: new Date().toISOString().slice(0, 10), + due_date: due.toISOString().slice(0, 10), + subtotal: amt, + tax, + total: +(amt + tax).toFixed(2), + notes: notes || "Retainer deposit", + is_retainer: true, + created_by: user.id, + }) + .select("id, invoice_number") + .single(); + if (ie || !inv) throw ie ?? new Error("Failed to create"); + await supabase.from("invoice_line_items").insert({ + invoice_id: inv.id, + kind: "manual", + description: "Retainer deposit into trust account", + quantity: 1, + rate: amt, + amount: amt, + sort_order: 0, + }); + toast.success(`Retainer invoice ${inv.invoice_number} created`); + onOpenChange(false); + navigate({ to: "/invoices/$invoiceId", params: { invoiceId: inv.id } }); + return; + } const { invoiceId, invoiceNumber } = await generateInvoiceForClient({ clientId, caseIds: selectedIds,