Added 200-page pagination
X-Lovable-Edit-ID: edt-6d77a572-8493-4558-8082-8bc375786ef0 Co-authored-by: renee-png <262607627+renee-png@users.noreply.github.com>
This commit is contained in:
@@ -1046,12 +1046,3 @@ const rootRouteChildren: RootRouteChildren = {
|
||||
export const routeTree = rootRouteImport
|
||||
._addFileChildren(rootRouteChildren)
|
||||
._addFileTypes<FileRouteTypes>()
|
||||
|
||||
import type { getRouter } from './router.tsx'
|
||||
import type { createStart } from '@tanstack/react-start'
|
||||
declare module '@tanstack/react-start' {
|
||||
interface Register {
|
||||
ssr: true
|
||||
router: Awaited<ReturnType<typeof getRouter>>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,9 @@ function InvoicesIndex() {
|
||||
const [selectedExp, setSelectedExp] = useState<Set<string>>(new Set());
|
||||
const [selectedInvoices, setSelectedInvoices] = useState<Set<string>>(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<Set<string>>(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<string>, id: string) => {
|
||||
const next = new Set(set);
|
||||
if (next.has(id)) next.delete(id); else next.add(id);
|
||||
@@ -455,8 +473,13 @@ function InvoicesIndex() {
|
||||
<tr>
|
||||
<th className="px-3 py-3 w-9">
|
||||
<Checkbox
|
||||
checked={visibleTime.length > 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;
|
||||
})}
|
||||
/>
|
||||
</th>
|
||||
<th className="text-left px-4 py-3 font-medium">Date</th>
|
||||
@@ -477,7 +500,7 @@ function InvoicesIndex() {
|
||||
No matching time entries.
|
||||
</td></tr>
|
||||
)}
|
||||
{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() {
|
||||
</table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{visibleTime.length > TAB_PAGE_SIZE && (
|
||||
<div className="flex items-center justify-between mt-3 text-sm">
|
||||
<span className="text-muted-foreground">
|
||||
Showing {timePage * TAB_PAGE_SIZE + 1}–{Math.min((timePage + 1) * TAB_PAGE_SIZE, visibleTime.length)} of {visibleTime.length}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" size="sm" disabled={timePage === 0} onClick={() => setTimePage((p) => Math.max(0, p - 1))}>Previous</Button>
|
||||
<span className="text-muted-foreground">Page {timePage + 1} of {timeTotalPages}</span>
|
||||
<Button variant="outline" size="sm" disabled={timePage + 1 >= timeTotalPages} onClick={() => setTimePage((p) => p + 1)}>Next</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="expenses" className="mt-4">
|
||||
@@ -544,8 +579,13 @@ function InvoicesIndex() {
|
||||
<tr>
|
||||
<th className="px-3 py-3 w-9">
|
||||
<Checkbox
|
||||
checked={visibleExp.length > 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;
|
||||
})}
|
||||
/>
|
||||
</th>
|
||||
<th className="text-left px-4 py-3 font-medium">Date</th>
|
||||
@@ -564,7 +604,7 @@ function InvoicesIndex() {
|
||||
No matching expenses.
|
||||
</td></tr>
|
||||
)}
|
||||
{visibleExp.map((e) => {
|
||||
{pagedExp.map((e) => {
|
||||
const marked = isPlaceholder(e.invoice_id);
|
||||
return (
|
||||
<tr key={e.id} className="border-t hover:bg-muted/30">
|
||||
@@ -606,6 +646,18 @@ function InvoicesIndex() {
|
||||
</table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
{visibleExp.length > TAB_PAGE_SIZE && (
|
||||
<div className="flex items-center justify-between mt-3 text-sm">
|
||||
<span className="text-muted-foreground">
|
||||
Showing {expPage * TAB_PAGE_SIZE + 1}–{Math.min((expPage + 1) * TAB_PAGE_SIZE, visibleExp.length)} of {visibleExp.length}
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="outline" size="sm" disabled={expPage === 0} onClick={() => setExpPage((p) => Math.max(0, p - 1))}>Previous</Button>
|
||||
<span className="text-muted-foreground">Page {expPage + 1} of {expTotalPages}</span>
|
||||
<Button variant="outline" size="sm" disabled={expPage + 1 >= expTotalPages} onClick={() => setExpPage((p) => p + 1)}>Next</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</PageContainer>
|
||||
|
||||
Reference in New Issue
Block a user