feat(tenders): enhance pagination functionality and styling
- Added absoluteCurrentPage to the useTenderListPresenter for better pagination state management. - Updated the TendersTable component to utilize absoluteCurrentPage in the pagination key. - Enhanced the Pagination component to support a new "tenders" variant for improved styling. - Introduced custom styles for the active page in the tenders pagination, ensuring a distinct visual representation. - Refactored the useHybridPagination hook to maintain accurate page tracking with cursor navigation.
This commit is contained in:
@@ -19,6 +19,10 @@ type UseHybridPaginationOptions = {
|
||||
error?: unknown;
|
||||
};
|
||||
|
||||
/** Each push records the absolute page we were on when this cursor was added,
|
||||
* so a later pop can fall back to that page via offset if needed. */
|
||||
type CursorStackEntry = { cursor: string; fromPage: number };
|
||||
|
||||
export const useHybridPagination = ({
|
||||
meta,
|
||||
params,
|
||||
@@ -26,9 +30,23 @@ export const useHybridPagination = ({
|
||||
isError,
|
||||
error,
|
||||
}: UseHybridPaginationOptions) => {
|
||||
const [cursorStack, setCursorStack] = useState<string[]>([]);
|
||||
const [cursorStack, setCursorStack] = useState<CursorStackEntry[]>([]);
|
||||
const [lastValidPages, setLastValidPages] = useState<number>(1);
|
||||
const [lastValidLimit, setLastValidLimit] = useState<number>(10);
|
||||
|
||||
const limit = meta?.limit ?? Number(params.limit) ?? 10;
|
||||
const limit = meta?.limit ?? Number(params.limit) ?? lastValidLimit;
|
||||
|
||||
useEffect(() => {
|
||||
if (meta?.limit != null) {
|
||||
setLastValidLimit(meta.limit);
|
||||
}
|
||||
}, [meta?.limit]);
|
||||
|
||||
useEffect(() => {
|
||||
if (meta?.pages != null) {
|
||||
setLastValidPages(meta.pages);
|
||||
}
|
||||
}, [meta?.pages]);
|
||||
|
||||
const clearCursorStack = useCallback(() => {
|
||||
setCursorStack([]);
|
||||
@@ -49,19 +67,40 @@ export const useHybridPagination = ({
|
||||
}, [isError, error, resetToFirstPage]);
|
||||
|
||||
const hasNext = Boolean(meta?.has_more && meta?.next_cursor);
|
||||
const hasPrevious = cursorStack.length > 0 || (meta?.page ?? 1) > 1;
|
||||
|
||||
/** 0-based page index; do not trust meta.page alone while cursor pages are active. */
|
||||
const currentPageIndex = useMemo(() => {
|
||||
const pageSize = limit > 0 ? limit : 1;
|
||||
|
||||
if (cursorStack.length > 0) {
|
||||
const last = cursorStack[cursorStack.length - 1];
|
||||
return Math.max(0, last.fromPage - 1 + cursorStack.length);
|
||||
}
|
||||
|
||||
const paramsCursor =
|
||||
typeof params.cursor === "string" ? params.cursor.trim() : "";
|
||||
const paramsOffset = Number(params.offset);
|
||||
if (!paramsCursor && Number.isFinite(paramsOffset) && paramsOffset >= 0) {
|
||||
return Math.floor(paramsOffset / pageSize);
|
||||
}
|
||||
|
||||
const metaOffset = Number(meta?.offset);
|
||||
if (Number.isFinite(metaOffset) && metaOffset >= 0) {
|
||||
return Math.floor(metaOffset / pageSize);
|
||||
}
|
||||
|
||||
if (meta?.page != null) return meta.page - 1;
|
||||
return cursorStack.length;
|
||||
}, [cursorStack.length, meta?.page]);
|
||||
return 0;
|
||||
}, [cursorStack, params.cursor, params.offset, meta?.offset, meta?.page, limit]);
|
||||
|
||||
const hasPrevious = cursorStack.length > 0 || currentPageIndex > 0;
|
||||
|
||||
const pagination = useMemo(
|
||||
() => ({
|
||||
currentPage: currentPageIndex,
|
||||
totalPages: meta?.pages ?? 1,
|
||||
totalPages: meta?.pages ?? lastValidPages,
|
||||
}),
|
||||
[currentPageIndex, meta?.pages],
|
||||
[currentPageIndex, meta?.pages, lastValidPages],
|
||||
);
|
||||
|
||||
const goToOffsetPage = useCallback(
|
||||
@@ -80,29 +119,39 @@ export const useHybridPagination = ({
|
||||
[clearCursorStack, limit, setParams],
|
||||
);
|
||||
|
||||
const goToNextCursorPage = useCallback(() => {
|
||||
const nextCursor = meta?.next_cursor;
|
||||
if (!meta?.has_more || !nextCursor) return;
|
||||
const goToNextCursorPage = useCallback(
|
||||
(fromPage1Based: number) => {
|
||||
const nextCursor = meta?.next_cursor;
|
||||
if (!meta?.has_more || !nextCursor) return;
|
||||
|
||||
setCursorStack((stack) => [...stack, nextCursor]);
|
||||
setParams((current) => {
|
||||
const next: Record<string, any> = { ...current, cursor: nextCursor, limit };
|
||||
delete next.offset;
|
||||
return next;
|
||||
});
|
||||
}, [limit, meta?.has_more, meta?.next_cursor, setParams]);
|
||||
setCursorStack((stack) => [
|
||||
...stack,
|
||||
{ cursor: nextCursor, fromPage: fromPage1Based },
|
||||
]);
|
||||
setParams((current) => {
|
||||
const next: Record<string, any> = { ...current, cursor: nextCursor, limit };
|
||||
delete next.offset;
|
||||
return next;
|
||||
});
|
||||
},
|
||||
[limit, meta?.has_more, meta?.next_cursor, setParams],
|
||||
);
|
||||
|
||||
const goToPreviousPage = useCallback(() => {
|
||||
if (cursorStack.length > 0) {
|
||||
const poppedEntry = cursorStack[cursorStack.length - 1];
|
||||
const nextStack = cursorStack.slice(0, -1);
|
||||
const previousCursor = nextStack[nextStack.length - 1];
|
||||
const previousEntry = nextStack[nextStack.length - 1];
|
||||
|
||||
setCursorStack(nextStack);
|
||||
setParams((current) => {
|
||||
const next: Record<string, any> = { ...current, limit };
|
||||
if (previousCursor) {
|
||||
next.cursor = previousCursor;
|
||||
if (previousEntry) {
|
||||
next.cursor = previousEntry.cursor;
|
||||
delete next.offset;
|
||||
} else if (poppedEntry.fromPage > 1) {
|
||||
next.offset = (poppedEntry.fromPage - 1) * limit;
|
||||
delete next.cursor;
|
||||
} else {
|
||||
next.offset = 0;
|
||||
delete next.cursor;
|
||||
@@ -112,14 +161,20 @@ export const useHybridPagination = ({
|
||||
return;
|
||||
}
|
||||
|
||||
const page = meta?.page ?? 1;
|
||||
if (page <= 1) {
|
||||
if (currentPageIndex <= 0) {
|
||||
resetToFirstPage();
|
||||
return;
|
||||
}
|
||||
|
||||
goToOffsetPage(page - 2);
|
||||
}, [cursorStack, goToOffsetPage, limit, meta?.page, resetToFirstPage, setParams]);
|
||||
goToOffsetPage(currentPageIndex - 1);
|
||||
}, [
|
||||
cursorStack,
|
||||
currentPageIndex,
|
||||
goToOffsetPage,
|
||||
limit,
|
||||
resetToFirstPage,
|
||||
setParams,
|
||||
]);
|
||||
|
||||
const handlePaginationChange = useCallback(
|
||||
(e: { selected: number }) => {
|
||||
@@ -127,24 +182,40 @@ export const useHybridPagination = ({
|
||||
const delta = targetIndex - currentPageIndex;
|
||||
|
||||
if (delta === 1 && hasNext) {
|
||||
goToNextCursorPage();
|
||||
goToNextCursorPage(currentPageIndex + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (delta === -1 && hasPrevious) {
|
||||
goToPreviousPage();
|
||||
return;
|
||||
if (delta === -1) {
|
||||
const cursorReturnIndex =
|
||||
cursorStack.length > 0
|
||||
? cursorStack[cursorStack.length - 1].fromPage - 1
|
||||
: null;
|
||||
|
||||
if (
|
||||
cursorReturnIndex != null &&
|
||||
targetIndex === cursorReturnIndex &&
|
||||
cursorStack.length > 0
|
||||
) {
|
||||
goToPreviousPage();
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetIndex >= 0) {
|
||||
goToOffsetPage(targetIndex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
goToOffsetPage(targetIndex);
|
||||
},
|
||||
[
|
||||
currentPageIndex,
|
||||
cursorStack,
|
||||
goToNextCursorPage,
|
||||
goToOffsetPage,
|
||||
goToPreviousPage,
|
||||
hasNext,
|
||||
hasPrevious,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user