diff --git a/src/app/admin/stops/page.tsx b/src/app/admin/stops/page.tsx index a8e3e50..3c36b1e 100644 --- a/src/app/admin/stops/page.tsx +++ b/src/app/admin/stops/page.tsx @@ -7,6 +7,8 @@ import { redirect } from "next/navigation"; import { pool } from "@/lib/db"; import { type Stop } from "@/components/admin/stops/types"; +const PAGE_SIZE = 50; + const StopIcon = () => ( @@ -14,7 +16,15 @@ const StopIcon = () => ( ); -export default async function AdminStopsPage() { +interface PageProps { + searchParams: Promise<{ page?: string }>; +} + +export default async function AdminStopsPage({ searchParams }: PageProps) { + const params = await searchParams; + const page = Math.max(1, parseInt(params.page ?? "1", 10) || 1); + const offset = (page - 1) * PAGE_SIZE; + const adminUser = await getAdminUser(); if (!adminUser) return ; @@ -24,6 +34,7 @@ export default async function AdminStopsPage() { } let stops: Stop[] = []; + let totalCount = 0; let error: { message: string } | null = null; try { @@ -31,6 +42,13 @@ export default async function AdminStopsPage() { ? `AND brand_id = '${adminUser.brand_id}'` : ""; + // Get total count for pagination + const countResult = await pool.query<{ count: string }>(` + SELECT count(*) as count FROM stops WHERE status = 'active' ${brandFilter} + `); + totalCount = parseInt(countResult.rows[0]?.count ?? "0", 10); + + // Get paginated stops const { rows } = await pool.query(` SELECT s.id, @@ -51,6 +69,7 @@ export default async function AdminStopsPage() { WHERE s.status = 'active' ${brandFilter} ORDER BY s.date ASC + LIMIT ${PAGE_SIZE} OFFSET ${offset} `); stops = rows.map((r) => ({ @@ -73,6 +92,8 @@ export default async function AdminStopsPage() { error = { message: e instanceof Error ? e.message : String(e) }; } + const totalPages = Math.ceil(totalCount / PAGE_SIZE); + if (error) { return (
@@ -81,7 +102,7 @@ export default async function AdminStopsPage() {

Error loading stops @@ -112,7 +133,12 @@ export default async function AdminStopsPage() { {/* Content */}
- +

); diff --git a/src/components/admin/stops/StopsDashboardClient.tsx b/src/components/admin/stops/StopsDashboardClient.tsx index ce29829..9a08c09 100644 --- a/src/components/admin/stops/StopsDashboardClient.tsx +++ b/src/components/admin/stops/StopsDashboardClient.tsx @@ -8,6 +8,9 @@ import StopsList from "./StopsList"; type Props = { stops: Stop[]; + page?: number; + totalPages?: number; + totalCount?: number; }; const TABS: { value: StopView; label: string; hint: string }[] = [ @@ -16,7 +19,7 @@ const TABS: { value: StopView; label: string; hint: string }[] = [ { value: "list", label: "List", hint: "All stops in order" }, ]; -export default function StopsDashboardClient({ stops }: Props) { +export default function StopsDashboardClient({ stops, page = 1, totalPages = 1, totalCount = 0 }: Props) { const [view, setView] = useState("calendar"); const stats = useMemo(() => { @@ -52,7 +55,7 @@ export default function StopsDashboardClient({ stops }: Props) { className="pointer-events-none absolute inset-0 opacity-[0.04]" style={{ backgroundImage: - "url(\"data:image/svg+xml;utf8,\")", + "url(\"data:image/svg+xml;utf8,\")", }} />
@@ -134,6 +137,55 @@ export default function StopsDashboardClient({ stops }: Props) { value={stats.venues} />
+ + {/* Pagination */} + {totalPages > 1 && ( +
+

+ Showing {((page - 1) * 50) + 1}–{Math.min(page * 50, totalCount)} of {totalCount} +

+
+ 1 ? `?page=${page - 1}` : "#"} + aria-disabled={page <= 1} + className={`inline-flex h-8 w-8 items-center justify-center rounded-md text-sm font-medium transition-colors ${ + page <= 1 + ? "text-[var(--admin-text-muted)] cursor-not-allowed pointer-events-none" + : "text-[var(--admin-text-secondary)] hover:bg-[var(--admin-bg-subtle)]" + }`} + > + ‹ + + {Array.from({ length: Math.min(totalPages, 7) }, (_, i) => { + const p = i + 1; + return ( + + {p} + + ); + })} += totalPages} + className={`inline-flex h-8 w-8 items-center justify-center rounded-md text-sm font-medium transition-colors ${ + page >= totalPages + ? "text-[var(--admin-text-muted)] cursor-not-allowed pointer-events-none" + : "text-[var(--admin-text-secondary)] hover:bg-[var(--admin-bg-subtle)]" + }`} + > + › + +
+
+ )} {/* Active view */}