diff --git a/src/Web/autopilot-monitor-web/app/dashboard/components/SessionTable.tsx b/src/Web/autopilot-monitor-web/app/dashboard/components/SessionTable.tsx index 0b01a0fb..dee36ff1 100644 --- a/src/Web/autopilot-monitor-web/app/dashboard/components/SessionTable.tsx +++ b/src/Web/autopilot-monitor-web/app/dashboard/components/SessionTable.tsx @@ -71,7 +71,6 @@ interface SessionTableProps { onSessionsPerPageChange: (value: number) => void; hasMore: boolean; loadingMore: boolean; - onLoadMore: () => void; onLoadAll: () => void; adminMode: boolean; globalAdminMode: boolean; @@ -109,7 +108,6 @@ export function SessionTable({ onSessionsPerPageChange, hasMore, loadingMore, - onLoadMore, onLoadAll, adminMode, globalAdminMode, @@ -722,7 +720,17 @@ export function SessionTable({ {paginatedSessions.length === 0 ? ( - No sessions found matching your search. + {loadingMore ? ( + + + + + + Loading sessions... + + ) : ( + "No sessions found matching your search." + )} ) : ( @@ -752,75 +760,39 @@ export function SessionTable({ - {/* Pagination Controls */} - {totalPages > 1 && ( + {/* Pagination Controls — single Prev/Next surface that transparently fetches + the next server batch when the user pages past locally-loaded sessions. + Renders whenever there's somewhere to navigate (more local pages OR more + on the server). */} + {(totalPages > 1 || hasMore) && (
- Page {currentPage} of {totalPages} ({sortedSessions.length}{hasMore ? '+' : ''} total sessions) + Page {currentPage} of {totalPages}{hasMore ? '+' : ''} ({sortedSessions.length}{hasMore ? '+' : ''} total sessions)
)} - - {/* Load More Button */} - {hasMore && ( -
- {searchQuery.trim() ? ( - // Search active: offer to load all remaining sessions - - ) : ( - // No search: standard load-more (100 at a time) - - )} -
- )} ); } diff --git a/src/Web/autopilot-monitor-web/app/dashboard/hooks/useDashboardFilters.ts b/src/Web/autopilot-monitor-web/app/dashboard/hooks/useDashboardFilters.ts index 92fd53f8..4dbb2de2 100644 --- a/src/Web/autopilot-monitor-web/app/dashboard/hooks/useDashboardFilters.ts +++ b/src/Web/autopilot-monitor-web/app/dashboard/hooks/useDashboardFilters.ts @@ -13,6 +13,8 @@ interface UseDashboardFiltersParams { tenantId: string | null | undefined; globalAdminMode: boolean; tenantIdFilter: string; + hasMore: boolean; + loadingMore: boolean; } interface DashboardStats { @@ -57,6 +59,8 @@ export function useDashboardFilters({ tenantId, globalAdminMode, tenantIdFilter, + hasMore, + loadingMore, }: UseDashboardFiltersParams): UseDashboardFiltersReturn { const [searchQuery, setSearchQuery] = useState(""); const [statusFilter, setStatusFilter] = useState(null); @@ -213,7 +217,24 @@ export function useDashboardFilters({ }, [sortedSessions, currentPage, sessionsPerPage]); const handlePreviousPage = () => setCurrentPage((prev) => Math.max(1, prev - 1)); - const handleNextPage = () => setCurrentPage((prev) => Math.min(totalPages, prev + 1)); + // Allow advancing past totalPages when the server has more pages; the auto-load + // effect in page.tsx will fetch the next batch and totalPages catches up. Without + // this overshoot the user could never trigger a load while staying inside a single + // Prev/Next control — they would have to click a separate "Load more" button. + const handleNextPage = () => setCurrentPage((prev) => { + if (prev < totalPages) return prev + 1; + if (hasMore && !loadingMore) return prev + 1; + return prev; + }); + + // Snap currentPage back to the last valid page once a load finishes that did NOT + // grow totalPages (e.g. an active filter narrowed the new batch to zero matches). + // Without this the user would be stuck on an empty paginated slice with no way + // to recover except clicking Prev. + useEffect(() => { + if (loadingMore) return; + if (totalPages > 0 && currentPage > totalPages) setCurrentPage(totalPages); + }, [currentPage, totalPages, loadingMore]); return { searchQuery, setSearchQuery, diff --git a/src/Web/autopilot-monitor-web/app/dashboard/page.tsx b/src/Web/autopilot-monitor-web/app/dashboard/page.tsx index 3be9f249..5c3c04c7 100644 --- a/src/Web/autopilot-monitor-web/app/dashboard/page.tsx +++ b/src/Web/autopilot-monitor-web/app/dashboard/page.tsx @@ -66,6 +66,8 @@ export default function Home() { tenantId, globalAdminMode, tenantIdFilter, + hasMore, + loadingMore, }); // Redirect regular users (non-admin, non-operator) to progress portal – they must never see the session list @@ -275,7 +277,6 @@ export default function Home() { onSessionsPerPageChange={handleSessionsPerPageChange} hasMore={hasMore} loadingMore={loadingMore} - onLoadMore={loadMore} onLoadAll={loadAll} adminMode={adminMode} globalAdminMode={globalAdminMode} diff --git a/src/Web/autopilot-monitor-web/app/fleet-health/page.tsx b/src/Web/autopilot-monitor-web/app/fleet-health/page.tsx index ce3be228..7e716bdd 100644 --- a/src/Web/autopilot-monitor-web/app/fleet-health/page.tsx +++ b/src/Web/autopilot-monitor-web/app/fleet-health/page.tsx @@ -14,7 +14,9 @@ import FleetStatCard from "./components/FleetStatCard"; import { Session } from "@/types"; import { useAdminMode } from "@/hooks/useAdminMode"; -const FLEET_PAGE_SIZE = 200; +// Backend caps pageSize at 1000 — use the maximum so a 90-day window on a busy +// install (10k+ sessions) drains in a handful of round-trips instead of 50+. +const FLEET_PAGE_SIZE = 1000; interface AppMetric { appName: string;