/** Cursor pagination state (previous-page stack) shared by all paged tables. */ import { useCallback, useEffect, useMemo, useState } from "react"; export interface CursorPager { /** Cursor to pass to the backend for the current page (null = first page). */ cursor: string | null; /** 1-based page number. */ page: number; canPrev: boolean; goNext: (nextCursor: string | null) => void; goPrev: () => void; reset: () => void; } export interface PagerState { cursor: string | null; /** Stack of cursors that produced the previous pages (page 1 = null). */ stack: (string | null)[]; } export const INITIAL_PAGER_STATE: PagerState = { cursor: null, stack: [] }; /** Advance to the page identified by `nextCursor`; unchanged when null. */ export function advancePager(state: PagerState, nextCursor: string | null): PagerState { if (!nextCursor) return state; return { cursor: nextCursor, stack: [...state.stack, state.cursor] }; } /** Go back one page; unchanged on the first page. */ export function retreatPager(state: PagerState): PagerState { if (state.stack.length === 0) return state; const stack = [...state.stack]; const cursor = stack.pop() ?? null; return { cursor, stack }; } export function pageNumber(state: PagerState): number { return state.stack.length + 1; } /** * Cursor pager that resets whenever `resetKey` changes (run id, filters…). */ export function useCursorPager(resetKey: string): CursorPager { const [state, setState] = useState(INITIAL_PAGER_STATE); useEffect(() => { setState(INITIAL_PAGER_STATE); }, [resetKey]); const goNext = useCallback((nextCursor: string | null) => { setState((s) => advancePager(s, nextCursor)); }, []); const goPrev = useCallback(() => { setState((s) => retreatPager(s)); }, []); const reset = useCallback(() => setState(INITIAL_PAGER_STATE), []); return useMemo( () => ({ cursor: state.cursor, page: pageNumber(state), canPrev: state.stack.length > 0, goNext, goPrev, reset, }), [state, goNext, goPrev, reset], ); }