Files
tm_panel/src/hooks/useHybridPagination.ts
T
AmirReza Jamali f4a425a6e5 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.
2026-05-17 17:01:28 +03:30

172 lines
4.4 KiB
TypeScript

"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,
};
};