- {!isFixedPrice && (
+ {!isFixedPrice && docType === "invoice" && (
Billing Period
@@ -469,8 +581,8 @@ function CreateInvoiceDialog({ onClose, onCreated }: { onClose: () => void; onCr
)}
- {/* Line items editor (time-based manual only) */}
- {!isFixedPrice && mode === "manual" && (
+ {/* Line items editor (time-based manual only, not for deposit/final) */}
+ {!isFixedPrice && mode === "manual" && docType === "invoice" && (
Line Items
@@ -558,7 +670,9 @@ function CreateInvoiceDialog({ onClose, onCreated }: { onClose: () => void; onCr
@@ -566,18 +680,95 @@ function CreateInvoiceDialog({ onClose, onCreated }: { onClose: () => void; onCr
);
}
-function InvoiceRow({ invoice, isSelected, isHighlighted, reminderCount, onSelect }: {
- invoice: Entity; isSelected: boolean; isHighlighted?: boolean; reminderCount?: number; onSelect: () => void;
+function DocumentTypeBadge({ type }: { type: "deposit" | "final" }) {
+ if (type === "deposit") {
+ return (
+
+ Deposit
+
+ );
+ }
+ return (
+
+ Final
+
+ );
+}
+
+function chainAccentClass(chain: InvoiceChain): string {
+ const schedule = milestoneScheduleStatus(chain.root, chain.deposits);
+ if (isFinalInvoice(chain.root) || schedule?.completeWithoutFinal) return "border-l-2 border-l-blue-400";
+ if (isDeposit(chain.root) || chain.deposits.length > 0) return "border-l-2 border-l-blue-400";
+ return "";
+}
+
+function MilestoneScheduleBadge({ schedule }: { schedule: MilestoneScheduleStatus }) {
+ const { total, invoicedCount, paidCount, allInvoiced, allDepositsPaid, hasFinal, completeWithoutFinal, settled } = schedule;
+
+ if (settled || completeWithoutFinal) {
+ return (
+
+ {completeWithoutFinal ? `${total}/${total} milestones complete` : "All settled"}
+
+ );
+ }
+ if (hasFinal) {
+ return (
+
+ Final invoice · {paidCount}/{total} deposits paid
+
+ );
+ }
+ if (allInvoiced) {
+ return (
+
+ {invoicedCount}/{total} invoiced · {paidCount}/{total} paid
+
+ );
+ }
+ return (
+
+ {invoicedCount}/{total} milestones invoiced
+
+ );
+}
+
+function InvoiceRow({ invoice, isSelected, isHighlighted, reminderCount, depositCount, schedule, onSelect }: {
+ invoice: Entity; isSelected: boolean; isHighlighted?: boolean;
+ reminderCount?: number; depositCount?: number; schedule?: MilestoneScheduleStatus | null; onSelect: () => void;
}) {
const status = invoiceStatus(invoice);
+ const depositLabel = depositMilestoneLabel(invoice);
+ const settlement = isSettlementDeposit(invoice);
+ const showAsFinal = displaysAsFinal(invoice);
return (