diff --git a/src/components/invoices/generate-invoice-dialog.tsx b/src/components/invoices/generate-invoice-dialog.tsx index 17144b7..4f883d5 100644 --- a/src/components/invoices/generate-invoice-dialog.tsx +++ b/src/components/invoices/generate-invoice-dialog.tsx @@ -42,6 +42,7 @@ export function GenerateInvoiceDialog({ open, onOpenChange, clientId, clientName const [dueDays, setDueDays] = useState("30"); const [notes, setNotes] = useState(""); const [isRetainer, setIsRetainer] = useState(false); + const [splitPerCase, setSplitPerCase] = useState(false); const [loading, setLoading] = useState(false); const [saving, setSaving] = useState(false); @@ -152,6 +153,24 @@ export function GenerateInvoiceDialog({ open, onOpenChange, clientId, clientName navigate({ to: "/invoices/$invoiceId", params: { invoiceId: inv.id } }); return; } + if (splitPerCase && selectedIds.length > 1) { + const results: { id: string; number: string }[] = []; + for (const cid of selectedIds) { + const r = await generateInvoiceForClient({ + clientId, + caseIds: [cid], + createdBy: user.id, + taxRate: (Number(taxPct) || 0) / 100, + dueDays: Number(dueDays) || 30, + notes: notes || undefined, + }); + results.push({ id: r.invoiceId, number: r.invoiceNumber }); + } + toast.success(`${results.length} invoices created`); + onOpenChange(false); + navigate({ to: "/invoices" }); + return; + } const { invoiceId, invoiceNumber } = await generateInvoiceForClient({ clientId, caseIds: selectedIds, @@ -216,8 +235,27 @@ export function GenerateInvoiceDialog({ open, onOpenChange, clientId, clientName ) : ( <>
- -
+
+ +
+ + ยท + +
+
+
{cases.map((c) => (
+ {selectedIds.length > 1 && ( + + )} +
@@ -250,7 +304,9 @@ export function GenerateInvoiceDialog({ open, onOpenChange, clientId, clientName
-
Estimated total
+
+ {splitPerCase && selectedIds.length > 1 ? "Combined total" : "Estimated total"} +
{formatCurrency(total)}
{tax > 0 &&
{formatCurrency(subtotal)} + {formatCurrency(tax)} tax
}
@@ -272,7 +328,11 @@ export function GenerateInvoiceDialog({ open, onOpenChange, clientId, clientName disabled={saving || (!isRetainer && (cases.length === 0 || selectedIds.length === 0))} > {saving ? : } - {isRetainer ? "Create retainer invoice" : "Generate draft invoice"} + {isRetainer + ? "Create retainer invoice" + : splitPerCase && selectedIds.length > 1 + ? `Generate ${selectedIds.length} draft invoices` + : "Generate draft invoice"} diff --git a/src/routes/collections.$collectionId.tsx b/src/routes/collections.$collectionId.tsx index 40c6ca8..3b63c37 100644 --- a/src/routes/collections.$collectionId.tsx +++ b/src/routes/collections.$collectionId.tsx @@ -35,6 +35,7 @@ import { CaseTimeTab } from "@/components/cases/time-tab"; import { CaseExpensesTab } from "@/components/cases/expenses-tab"; import { CaseStatusTab } from "@/components/cases/status-tab"; import { CaseInvoicesTab } from "@/components/cases/invoices-tab"; +import { GenerateInvoiceDialog } from "@/components/invoices/generate-invoice-dialog"; import { CaseLitigationTab } from "@/components/cases/litigation-tab"; import { CaseCustomFieldsTab } from "@/components/cases/custom-fields-tab"; import { formatDate } from "@/lib/format"; @@ -55,6 +56,7 @@ import { Clock, DollarSign, Receipt, + FilePlus, Phone, Tag, } from "lucide-react"; @@ -101,6 +103,7 @@ function CollectionDetailRoute() { const [escalating, setEscalating] = useState(false); const [addTaskOpen, setAddTaskOpen] = useState(false); const [reload, setReload] = useState(0); + const [invoiceOpen, setInvoiceOpen] = useState(false); useEffect(() => { let active = true; @@ -289,14 +292,21 @@ function CollectionDetailRoute() { return ( - +
+ + {collection.case?.client?.id && ( + + )} +
{/* Tabs: Workflow (collections-specific) + same case tabs */} @@ -506,6 +516,16 @@ function CollectionDetailRoute() { userId={user?.id} onSaved={refreshTasks} /> + + {collection.case?.client?.id && ( + + )}
);