diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index 2c230ca..94db69b 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -1046,12 +1046,3 @@ const rootRouteChildren: RootRouteChildren = { export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) ._addFileTypes() - -import type { getRouter } from './router.tsx' -import type { createStart } from '@tanstack/react-start' -declare module '@tanstack/react-start' { - interface Register { - ssr: true - router: Awaited> - } -} diff --git a/src/routes/invoices.index.tsx b/src/routes/invoices.index.tsx index 79c5630..95d3718 100644 --- a/src/routes/invoices.index.tsx +++ b/src/routes/invoices.index.tsx @@ -67,6 +67,9 @@ function InvoicesIndex() { const [selectedExp, setSelectedExp] = useState>(new Set()); const [selectedInvoices, setSelectedInvoices] = useState>(new Set()); const [working, setWorking] = useState(false); + const [timePage, setTimePage] = useState(0); + const [expPage, setExpPage] = useState(0); + const TAB_PAGE_SIZE = 200; // Map of placeholder invoice id -> true (placeholder = MARKED-INVOICED-* or IMPORT-PREBILLED-*) const [placeholderIds, setPlaceholderIds] = useState>(new Set()); @@ -207,6 +210,21 @@ function InvoicesIndex() { [visibleExp], ); + // Reset to page 0 when filter or underlying data changes + useEffect(() => { setTimePage(0); }, [timeFilter, time.length]); + useEffect(() => { setExpPage(0); }, [expFilter, expenses.length]); + + const timeTotalPages = Math.max(1, Math.ceil(visibleTime.length / TAB_PAGE_SIZE)); + const expTotalPages = Math.max(1, Math.ceil(visibleExp.length / TAB_PAGE_SIZE)); + const pagedTime = useMemo( + () => visibleTime.slice(timePage * TAB_PAGE_SIZE, (timePage + 1) * TAB_PAGE_SIZE), + [visibleTime, timePage], + ); + const pagedExp = useMemo( + () => visibleExp.slice(expPage * TAB_PAGE_SIZE, (expPage + 1) * TAB_PAGE_SIZE), + [visibleExp, expPage], + ); + const toggleSel = (set: Set, id: string) => { const next = new Set(set); if (next.has(id)) next.delete(id); else next.add(id); @@ -455,8 +473,13 @@ function InvoicesIndex() { 0 && selectedTime.size === visibleTime.length} - onCheckedChange={(c) => setSelectedTime(c ? new Set(visibleTime.map((t) => t.id)) : new Set())} + checked={pagedTime.length > 0 && pagedTime.every((t) => selectedTime.has(t.id))} + onCheckedChange={(c) => setSelectedTime((prev) => { + const next = new Set(prev); + if (c) pagedTime.forEach((t) => next.add(t.id)); + else pagedTime.forEach((t) => next.delete(t.id)); + return next; + })} /> Date @@ -477,7 +500,7 @@ function InvoicesIndex() { No matching time entries. )} - {visibleTime.map((t) => { + {pagedTime.map((t) => { const amount = Number(t.hours) * Number(t.hourly_rate); const marked = isPlaceholder(t.invoice_id); return ( @@ -522,6 +545,18 @@ function InvoicesIndex() { + {visibleTime.length > TAB_PAGE_SIZE && ( +
+ + Showing {timePage * TAB_PAGE_SIZE + 1}–{Math.min((timePage + 1) * TAB_PAGE_SIZE, visibleTime.length)} of {visibleTime.length} + +
+ + Page {timePage + 1} of {timeTotalPages} + +
+
+ )} @@ -544,8 +579,13 @@ function InvoicesIndex() { 0 && selectedExp.size === visibleExp.length} - onCheckedChange={(c) => setSelectedExp(c ? new Set(visibleExp.map((e) => e.id)) : new Set())} + checked={pagedExp.length > 0 && pagedExp.every((e) => selectedExp.has(e.id))} + onCheckedChange={(c) => setSelectedExp((prev) => { + const next = new Set(prev); + if (c) pagedExp.forEach((e) => next.add(e.id)); + else pagedExp.forEach((e) => next.delete(e.id)); + return next; + })} /> Date @@ -564,7 +604,7 @@ function InvoicesIndex() { No matching expenses. )} - {visibleExp.map((e) => { + {pagedExp.map((e) => { const marked = isPlaceholder(e.invoice_id); return ( @@ -606,6 +646,18 @@ function InvoicesIndex() { + {visibleExp.length > TAB_PAGE_SIZE && ( +
+ + Showing {expPage * TAB_PAGE_SIZE + 1}–{Math.min((expPage + 1) * TAB_PAGE_SIZE, visibleExp.length)} of {visibleExp.length} + +
+ + Page {expPage + 1} of {expTotalPages} + +
+
+ )}