feat(pagination): implement hybrid pagination across multiple components
- Introduced a new `useHybridPagination` hook to manage pagination logic, enhancing data fetching and navigation. - Updated various components including `useAdminsPresenter`, `useCompanyListPresenter`, `useCustomerListPresenter`, and others to utilize the new pagination hook, improving code consistency and reducing duplication. - Refactored pagination handling in each component to streamline state management and improve user experience during data navigation. - Enhanced search functionality to reset pagination state when new search parameters are applied, ensuring accurate data display.
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
"use client";
|
||||
|
||||
import { IMetaData } from "@/lib/api/types/Factory";
|
||||
import { AxiosError } from "axios";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
const isInvalidCursorError = (error: unknown) => {
|
||||
const axiosError = error as AxiosError<{ error?: { message?: string } }>;
|
||||
if (axiosError.response?.status !== 400) return false;
|
||||
const message = axiosError.response?.data?.error?.message ?? "";
|
||||
return /cursor|pagination/i.test(message);
|
||||
};
|
||||
|
||||
type UseHybridPaginationOptions = {
|
||||
meta?: IMetaData;
|
||||
params: Record<string, unknown>;
|
||||
setParams: React.Dispatch<React.SetStateAction<Record<string, any>>>;
|
||||
isError?: boolean;
|
||||
error?: unknown;
|
||||
};
|
||||
|
||||
export const useHybridPagination = ({
|
||||
meta,
|
||||
params,
|
||||
setParams,
|
||||
isError,
|
||||
error,
|
||||
}: UseHybridPaginationOptions) => {
|
||||
const [cursorStack, setCursorStack] = useState<string[]>([]);
|
||||
|
||||
const limit = meta?.limit ?? Number(params.limit) ?? 10;
|
||||
|
||||
const clearCursorStack = useCallback(() => {
|
||||
setCursorStack([]);
|
||||
}, []);
|
||||
|
||||
const resetToFirstPage = useCallback(() => {
|
||||
clearCursorStack();
|
||||
setParams((current) => {
|
||||
const next: Record<string, any> = { ...current, offset: 0, limit };
|
||||
delete next.cursor;
|
||||
return next;
|
||||
});
|
||||
}, [clearCursorStack, limit, setParams]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isError || !error || !isInvalidCursorError(error)) return;
|
||||
resetToFirstPage();
|
||||
}, [isError, error, resetToFirstPage]);
|
||||
|
||||
const hasNext = Boolean(meta?.has_more && meta?.next_cursor);
|
||||
const hasPrevious = cursorStack.length > 0 || (meta?.page ?? 1) > 1;
|
||||
|
||||
const currentPageIndex = useMemo(() => {
|
||||
if (meta?.page != null) return meta.page - 1;
|
||||
return cursorStack.length;
|
||||
}, [cursorStack.length, meta?.page]);
|
||||
|
||||
const pagination = useMemo(
|
||||
() => ({
|
||||
currentPage: currentPageIndex,
|
||||
totalPages: meta?.pages ?? 1,
|
||||
}),
|
||||
[currentPageIndex, meta?.pages],
|
||||
);
|
||||
|
||||
const goToOffsetPage = useCallback(
|
||||
(pageIndex: number) => {
|
||||
clearCursorStack();
|
||||
setParams((current) => {
|
||||
const next: Record<string, any> = {
|
||||
...current,
|
||||
offset: pageIndex * limit,
|
||||
limit,
|
||||
};
|
||||
delete next.cursor;
|
||||
return next;
|
||||
});
|
||||
},
|
||||
[clearCursorStack, limit, setParams],
|
||||
);
|
||||
|
||||
const goToNextCursorPage = useCallback(() => {
|
||||
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]);
|
||||
|
||||
const goToPreviousPage = useCallback(() => {
|
||||
if (cursorStack.length > 0) {
|
||||
const nextStack = cursorStack.slice(0, -1);
|
||||
const previousCursor = nextStack[nextStack.length - 1];
|
||||
|
||||
setCursorStack(nextStack);
|
||||
setParams((current) => {
|
||||
const next: Record<string, any> = { ...current, limit };
|
||||
if (previousCursor) {
|
||||
next.cursor = previousCursor;
|
||||
delete next.offset;
|
||||
} else {
|
||||
next.offset = 0;
|
||||
delete next.cursor;
|
||||
}
|
||||
return next;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const page = meta?.page ?? 1;
|
||||
if (page <= 1) {
|
||||
resetToFirstPage();
|
||||
return;
|
||||
}
|
||||
|
||||
goToOffsetPage(page - 2);
|
||||
}, [cursorStack, goToOffsetPage, limit, meta?.page, resetToFirstPage, setParams]);
|
||||
|
||||
const handlePaginationChange = useCallback(
|
||||
(e: { selected: number }) => {
|
||||
const targetIndex = e.selected;
|
||||
const delta = targetIndex - currentPageIndex;
|
||||
|
||||
if (delta === 1 && hasNext) {
|
||||
goToNextCursorPage();
|
||||
return;
|
||||
}
|
||||
|
||||
if (delta === -1 && hasPrevious) {
|
||||
goToPreviousPage();
|
||||
return;
|
||||
}
|
||||
|
||||
goToOffsetPage(targetIndex);
|
||||
},
|
||||
[
|
||||
currentPageIndex,
|
||||
goToNextCursorPage,
|
||||
goToOffsetPage,
|
||||
goToPreviousPage,
|
||||
hasNext,
|
||||
hasPrevious,
|
||||
],
|
||||
);
|
||||
|
||||
const buildFirstPageParams = useCallback(
|
||||
(base: Record<string, any>) => {
|
||||
const next: Record<string, any> = { ...base, offset: 0, limit };
|
||||
delete next.cursor;
|
||||
clearCursorStack();
|
||||
return next;
|
||||
},
|
||||
[clearCursorStack, limit],
|
||||
);
|
||||
|
||||
return {
|
||||
pagination,
|
||||
handlePaginationChange,
|
||||
buildFirstPageParams,
|
||||
resetToFirstPage,
|
||||
clearCursorStack,
|
||||
currentPageIndex,
|
||||
hasNext,
|
||||
hasPrevious,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user