From 76862e7a64a08a5c682d3f806b0c0e255b75c6c9 Mon Sep 17 00:00:00 2001
From: "gpt-engineer-app[bot]"
<159125892+gpt-engineer-app[bot]@users.noreply.github.com>
Date: Sun, 19 Apr 2026 20:08:42 +0000
Subject: [PATCH 1/3] Changes
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
---
src/routes/invoices.$invoiceId.tsx | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/src/routes/invoices.$invoiceId.tsx b/src/routes/invoices.$invoiceId.tsx
index 535eaae..d71eded 100644
--- a/src/routes/invoices.$invoiceId.tsx
+++ b/src/routes/invoices.$invoiceId.tsx
@@ -130,6 +130,19 @@ function InvoiceDetail() {
const isDraft = invoice.status === "draft";
const balance = Number(invoice.total) - Number(invoice.amount_paid);
+ // Billable fees vs billable expenses subtotals (non-billable items have amount 0 and are excluded).
+ const { feesSubtotal, expensesSubtotal } = useMemo(() => {
+ let fees = 0;
+ let exp = 0;
+ for (const it of items) {
+ const amt = Number(it.amount) || 0;
+ if (amt === 0) continue;
+ if (it.kind === "expense") exp += amt;
+ else fees += amt;
+ }
+ return { feesSubtotal: fees, expensesSubtotal: exp };
+ }, [items]);
+
const setStatus = async (status: string) => {
if (status === "void") {
if (!confirm("Void this invoice? Totals will be zeroed out and time/expenses will be unbilled.")) return;
From b3c09ae9d03022c111899da4fa28244eeef14348 Mon Sep 17 00:00:00 2001
From: "gpt-engineer-app[bot]"
<159125892+gpt-engineer-app[bot]@users.noreply.github.com>
Date: Sun, 19 Apr 2026 20:08:54 +0000
Subject: [PATCH 2/3] Changes
Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
---
src/routes/invoices.$invoiceId.tsx | 26 +++++++++++++-------------
1 file changed, 13 insertions(+), 13 deletions(-)
diff --git a/src/routes/invoices.$invoiceId.tsx b/src/routes/invoices.$invoiceId.tsx
index d71eded..a74b8e1 100644
--- a/src/routes/invoices.$invoiceId.tsx
+++ b/src/routes/invoices.$invoiceId.tsx
@@ -114,6 +114,19 @@ function InvoiceDetail() {
return Array.from(map.values());
}, [items]);
+ // Billable fees vs billable expenses subtotals (non-billable items have amount 0 and are excluded).
+ const { feesSubtotal, expensesSubtotal } = useMemo(() => {
+ let fees = 0;
+ let exp = 0;
+ for (const it of items) {
+ const amt = Number(it.amount) || 0;
+ if (amt === 0) continue;
+ if (it.kind === "expense") exp += amt;
+ else fees += amt;
+ }
+ return { feesSubtotal: fees, expensesSubtotal: exp };
+ }, [items]);
+
if (loading) return Loading…