From 570e7dcc566462193d9187556c86dfa772f87900 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 18:58:34 +0000 Subject: [PATCH 1/8] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/invoices.index.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/routes/invoices.index.tsx b/src/routes/invoices.index.tsx index 37503ea..2667cf5 100644 --- a/src/routes/invoices.index.tsx +++ b/src/routes/invoices.index.tsx @@ -26,6 +26,8 @@ export const Route = createFileRoute("/invoices/")({ type UnbilledStatus = "unbilled" | "marked" | "all"; +const PAGE_SIZE = 100; + function InvoicesIndex() { const navigate = useNavigate(); const { user } = useAuth(); @@ -33,6 +35,8 @@ function InvoicesIndex() { const [loading, setLoading] = useState(true); const [q, setQ] = useState(""); const [status, setStatus] = useState("all"); + const [page, setPage] = useState(0); + const [totalCount, setTotalCount] = useState(0); const [time, setTime] = useState([]); const [expenses, setExpenses] = useState([]); From e3cf7e2cd9de9452ec4790c8c6314ff02a86fb75 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 18:58:50 +0000 Subject: [PATCH 2/8] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/invoices.index.tsx | 57 ++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/src/routes/invoices.index.tsx b/src/routes/invoices.index.tsx index 2667cf5..7855ffc 100644 --- a/src/routes/invoices.index.tsx +++ b/src/routes/invoices.index.tsx @@ -53,25 +53,46 @@ function InvoicesIndex() { useEffect(() => { (async () => { - const { data } = await supabase - .from("invoices") - .select("*, client:clients(id, name), case:cases(id, case_number, title)") - .order("created_at", { ascending: false }); - setInvoices(data ?? []); - setLoading(false); + const fetchInvoices = async () => { + setLoading(true); + let query = supabase + .from("invoices") + .select("*, client:clients(id, name), case:cases(id, case_number, title)", { count: "exact" }) + .order("created_at", { ascending: false }); - // Track placeholder invoices so we can identify "marked-invoiced" items - const placeholders = new Set( - (data ?? []) - .filter((i: any) => - typeof i.invoice_number === "string" && - (i.invoice_number.startsWith("MARKED-INVOICED-") || i.invoice_number.startsWith("IMPORT-PREBILLED-")) - ) - .map((i: any) => i.id), - ); - setPlaceholderIds(placeholders); - })(); - }, []); + if (status !== "all") query = query.eq("status", status); + if (q.trim()) { + const s = q.trim().replace(/[%,]/g, " "); + query = query.or(`invoice_number.ilike.%${s}%`); + } + + const from = page * PAGE_SIZE; + const to = from + PAGE_SIZE - 1; + const { data, count } = await query.range(from, to); + + setInvoices(data ?? []); + setTotalCount(count ?? 0); + setLoading(false); + + const placeholders = new Set( + (data ?? []) + .filter((i: any) => + typeof i.invoice_number === "string" && + (i.invoice_number.startsWith("MARKED-INVOICED-") || i.invoice_number.startsWith("IMPORT-PREBILLED-")) + ) + .map((i: any) => i.id), + ); + setPlaceholderIds(placeholders); + }; + + useEffect(() => { fetchInvoices(); /* eslint-disable-next-line react-hooks/exhaustive-deps */ }, [page, status]); + + // Debounce search + useEffect(() => { + const t = setTimeout(() => { setPage(0); fetchInvoices(); }, 300); + return () => clearTimeout(t); + /* eslint-disable-next-line react-hooks/exhaustive-deps */ + }, [q]); const loadUnbilled = async () => { const [{ data: t }, { data: e }] = await Promise.all([ From 763dbb4fa8ba4e9a7a668390599f4c09160e7c7f 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 18:59:00 +0000 Subject: [PATCH 3/8] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/invoices.index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/routes/invoices.index.tsx b/src/routes/invoices.index.tsx index 7855ffc..400e71d 100644 --- a/src/routes/invoices.index.tsx +++ b/src/routes/invoices.index.tsx @@ -51,8 +51,6 @@ function InvoicesIndex() { // Map of placeholder invoice id -> true (placeholder = MARKED-INVOICED-* or IMPORT-PREBILLED-*) const [placeholderIds, setPlaceholderIds] = useState>(new Set()); - useEffect(() => { - (async () => { const fetchInvoices = async () => { setLoading(true); let query = supabase From 26a9f8cbb5a62597b9308176ce114ecf2c3b34c3 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 18:59:06 +0000 Subject: [PATCH 4/8] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/invoices.index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/invoices.index.tsx b/src/routes/invoices.index.tsx index 400e71d..2157e94 100644 --- a/src/routes/invoices.index.tsx +++ b/src/routes/invoices.index.tsx @@ -58,7 +58,7 @@ function InvoicesIndex() { .select("*, client:clients(id, name), case:cases(id, case_number, title)", { count: "exact" }) .order("created_at", { ascending: false }); - if (status !== "all") query = query.eq("status", status); + if (status !== "all") query = query.eq("status", status as any); if (q.trim()) { const s = q.trim().replace(/[%,]/g, " "); query = query.or(`invoice_number.ilike.%${s}%`); From 23ca4e1a4e8d474464cb44c0043f0f0e201ab69a 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 18:59:22 +0000 Subject: [PATCH 5/8] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/invoices.index.tsx | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/routes/invoices.index.tsx b/src/routes/invoices.index.tsx index 2157e94..30e9a12 100644 --- a/src/routes/invoices.index.tsx +++ b/src/routes/invoices.index.tsx @@ -114,24 +114,15 @@ function InvoicesIndex() { useEffect(() => { loadUnbilled(); }, []); - const filtered = useMemo(() => { - return invoices.filter((i) => { - if (status !== "all" && i.status !== status) return false; - if (!q) return true; - const s = q.toLowerCase(); - return ( - i.invoice_number?.toLowerCase().includes(s) || - i.client?.name?.toLowerCase().includes(s) || - i.case?.case_number?.toLowerCase().includes(s) - ); - }); - }, [invoices, q, status]); + const filtered = invoices; const totals = useMemo(() => { const outstanding = filtered.reduce((s, i) => s + (Number(i.total) - Number(i.amount_paid)), 0); const paid = filtered.reduce((s, i) => s + Number(i.amount_paid), 0); - return { outstanding, paid, count: filtered.length }; - }, [filtered]); + return { outstanding, paid, count: totalCount }; + }, [filtered, totalCount]); + + const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE)); // Classify each row: unbilled (no invoice_id), marked (linked to placeholder), real (linked to real invoice — not shown) const isPlaceholder = (invId: string | null) => !!invId && placeholderIds.has(invId); From 75b847a483fa8a28a86c64388d02adad1ccbc754 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 18:59:35 +0000 Subject: [PATCH 6/8] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/invoices.index.tsx | 28 ++-------------------------- 1 file changed, 2 insertions(+), 26 deletions(-) diff --git a/src/routes/invoices.index.tsx b/src/routes/invoices.index.tsx index 30e9a12..238f03c 100644 --- a/src/routes/invoices.index.tsx +++ b/src/routes/invoices.index.tsx @@ -198,19 +198,7 @@ function InvoicesIndex() { toast.success(`Marked ${idArr.length} item(s) as unbilled`); } // Refetch invoices to refresh placeholder set + unbilled lists - const { data: inv } = await supabase - .from("invoices") - .select("*, client:clients(id, name), case:cases(id, case_number, title)") - .order("created_at", { ascending: false }); - setInvoices(inv ?? []); - setPlaceholderIds(new Set( - (inv ?? []) - .filter((i: any) => - typeof i.invoice_number === "string" && - (i.invoice_number.startsWith("MARKED-INVOICED-") || i.invoice_number.startsWith("IMPORT-PREBILLED-")) - ) - .map((i: any) => i.id), - )); + await fetchInvoices(); await loadUnbilled(); if (table === "time_entries") setSelectedTime(new Set()); else setSelectedExp(new Set()); } catch (err: any) { @@ -221,19 +209,7 @@ function InvoicesIndex() { }; const refreshInvoices = async () => { - const { data } = await supabase - .from("invoices") - .select("*, client:clients(id, name), case:cases(id, case_number, title)") - .order("created_at", { ascending: false }); - setInvoices(data ?? []); - setPlaceholderIds(new Set( - (data ?? []) - .filter((i: any) => - typeof i.invoice_number === "string" && - (i.invoice_number.startsWith("MARKED-INVOICED-") || i.invoice_number.startsWith("IMPORT-PREBILLED-")) - ) - .map((i: any) => i.id), - )); + await fetchInvoices(); }; const bulkInvoiceAction = async (action: "delete" | "sent" | "void") => { From 674e6329b3a0ddcf24a809bb135f66987eccd507 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 18:59:45 +0000 Subject: [PATCH 7/8] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/invoices.index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/invoices.index.tsx b/src/routes/invoices.index.tsx index 238f03c..b8a23e2 100644 --- a/src/routes/invoices.index.tsx +++ b/src/routes/invoices.index.tsx @@ -274,7 +274,7 @@ function InvoicesIndex() { setQ(e.target.value)} /> - { setPage(0); setStatus(v); }}> All statuses From 354ef188bc890fbfeb278610f025a2bdc75edde0 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 18:59:56 +0000 Subject: [PATCH 8/8] Changes Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com> --- src/routes/invoices.index.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/routes/invoices.index.tsx b/src/routes/invoices.index.tsx index b8a23e2..c165f72 100644 --- a/src/routes/invoices.index.tsx +++ b/src/routes/invoices.index.tsx @@ -366,6 +366,22 @@ function InvoicesIndex() { + {totalCount > PAGE_SIZE && ( +
+ + Showing {totalCount === 0 ? 0 : page * PAGE_SIZE + 1}–{Math.min((page + 1) * PAGE_SIZE, totalCount)} of {totalCount} + +
+ + Page {page + 1} of {totalPages} + +
+
+ )}