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 && (
+
+ )}
);