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:
@@ -8,6 +8,7 @@ import {
|
|||||||
useGetAdminListQuery,
|
useGetAdminListQuery,
|
||||||
} from "@/hooks/queries";
|
} from "@/hooks/queries";
|
||||||
import { IUser, TChangeAdminStatusCredentials } from "@/lib/api";
|
import { IUser, TChangeAdminStatusCredentials } from "@/lib/api";
|
||||||
|
import { useHybridPagination } from "@/hooks/useHybridPagination";
|
||||||
import { deleteEmptyKeys, getPaginatedRowNumber } from "@/utils/shared";
|
import { deleteEmptyKeys, getPaginatedRowNumber } from "@/utils/shared";
|
||||||
import gsap from "gsap";
|
import gsap from "gsap";
|
||||||
import { useIsMutating } from "@tanstack/react-query";
|
import { useIsMutating } from "@tanstack/react-query";
|
||||||
@@ -70,25 +71,26 @@ export const useAdminsPresenter = () => {
|
|||||||
filterFormReset(newParams);
|
filterFormReset(newParams);
|
||||||
}, [searchParams, filterFormReset]);
|
}, [searchParams, filterFormReset]);
|
||||||
|
|
||||||
const { data, isPending } = useGetAdminListQuery({
|
const { data, isPending, isError, error } = useGetAdminListQuery({
|
||||||
...apiDefaultParams,
|
...apiDefaultParams,
|
||||||
...params,
|
...params,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { pagination, handlePaginationChange, buildFirstPageParams } =
|
||||||
|
useHybridPagination({
|
||||||
|
meta: data?.meta,
|
||||||
|
params: { ...apiDefaultParams, ...params },
|
||||||
|
setParams,
|
||||||
|
isError,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
const shouldShowPagination =
|
const shouldShowPagination =
|
||||||
!isPending && (data?.meta?.pages ?? 1) * (data?.meta?.limit ?? 10) > 10;
|
!isPending && (data?.meta?.pages ?? 1) * (data?.meta?.limit ?? 10) > 10;
|
||||||
|
|
||||||
const usersList = data?.data?.users ?? [];
|
const usersList = data?.data?.users ?? [];
|
||||||
const metadata = data?.meta;
|
const metadata = data?.meta;
|
||||||
|
|
||||||
const pagination = useMemo(
|
|
||||||
() => ({
|
|
||||||
currentPage: data?.meta?.page ? data.meta.page - 1 : 0,
|
|
||||||
totalPages: data?.meta?.pages ?? 1,
|
|
||||||
}),
|
|
||||||
[data?.meta?.page, data?.meta?.pages],
|
|
||||||
);
|
|
||||||
|
|
||||||
const getRowNumber = useCallback(
|
const getRowNumber = useCallback(
|
||||||
(index: number) =>
|
(index: number) =>
|
||||||
getPaginatedRowNumber({
|
getPaginatedRowNumber({
|
||||||
@@ -99,18 +101,6 @@ export const useAdminsPresenter = () => {
|
|||||||
[data?.meta?.limit, data?.meta?.page],
|
[data?.meta?.limit, data?.meta?.page],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handlePaginationChange = useCallback(
|
|
||||||
(e: { selected: number }) => {
|
|
||||||
const limit = data?.meta?.limit ?? 10;
|
|
||||||
setParams((currentParams) => ({
|
|
||||||
...currentParams,
|
|
||||||
offset: e.selected * limit,
|
|
||||||
limit,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
[data?.meta?.limit, setParams],
|
|
||||||
);
|
|
||||||
|
|
||||||
const navigateToEditAdmin = useCallback(
|
const navigateToEditAdmin = useCallback(
|
||||||
(userId: string) => {
|
(userId: string) => {
|
||||||
router.push(`/admins/edit/${userId}`);
|
router.push(`/admins/edit/${userId}`);
|
||||||
@@ -193,7 +183,7 @@ export const useAdminsPresenter = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const search = (formData: any) => {
|
const search = (formData: any) => {
|
||||||
const newParams = { ...params, ...formData, offset: 0 };
|
const newParams = buildFirstPageParams({ ...params, ...formData });
|
||||||
const cleanedParams = deleteEmptyKeys(newParams);
|
const cleanedParams = deleteEmptyKeys(newParams);
|
||||||
const queryString = new URLSearchParams(cleanedParams).toString();
|
const queryString = new URLSearchParams(cleanedParams).toString();
|
||||||
router.push(`${pathName}?${queryString}`);
|
router.push(`${pathName}?${queryString}`);
|
||||||
|
|||||||
+12
-22
@@ -5,6 +5,7 @@ import {
|
|||||||
useDeleteCompanyCategoryQuery,
|
useDeleteCompanyCategoryQuery,
|
||||||
useToggleCompanyCategoryPublished,
|
useToggleCompanyCategoryPublished,
|
||||||
} from "@/hooks/queries";
|
} from "@/hooks/queries";
|
||||||
|
import { useHybridPagination } from "@/hooks/useHybridPagination";
|
||||||
import { useTableSectionRowAnimations } from "@/hooks/useTableSectionRowAnimations";
|
import { useTableSectionRowAnimations } from "@/hooks/useTableSectionRowAnimations";
|
||||||
import { TCompanyCategory } from "@/lib/api";
|
import { TCompanyCategory } from "@/lib/api";
|
||||||
import { deleteEmptyKeys } from "@/utils/shared";
|
import { deleteEmptyKeys } from "@/utils/shared";
|
||||||
@@ -49,7 +50,16 @@ const useCompanyCategoriesPresenter = () => {
|
|||||||
filterFormReset(newParams);
|
filterFormReset(newParams);
|
||||||
}, [searchParams, filterFormReset]);
|
}, [searchParams, filterFormReset]);
|
||||||
|
|
||||||
const { data, isLoading, isError } = useCompanyCategoriesQuery(params);
|
const { data, isLoading, isError, error } = useCompanyCategoriesQuery(params);
|
||||||
|
|
||||||
|
const { pagination, handlePaginationChange, buildFirstPageParams } =
|
||||||
|
useHybridPagination({
|
||||||
|
meta: data?.meta,
|
||||||
|
params,
|
||||||
|
setParams,
|
||||||
|
isError,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
useTableSectionRowAnimations(isLoading, data, {
|
useTableSectionRowAnimations(isLoading, data, {
|
||||||
tableSectionRef,
|
tableSectionRef,
|
||||||
@@ -63,26 +73,6 @@ const useCompanyCategoriesPresenter = () => {
|
|||||||
!isLoading &&
|
!isLoading &&
|
||||||
(data?.meta?.pages ?? 1) * (data?.meta?.limit ?? 10) > 10;
|
(data?.meta?.pages ?? 1) * (data?.meta?.limit ?? 10) > 10;
|
||||||
|
|
||||||
const pagination = useMemo(
|
|
||||||
() => ({
|
|
||||||
currentPage: data?.meta?.page ? data.meta.page - 1 : 0,
|
|
||||||
totalPages: data?.meta?.pages ?? 1,
|
|
||||||
}),
|
|
||||||
[data?.meta?.page, data?.meta?.pages],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handlePaginationChange = useCallback(
|
|
||||||
(e: { selected: number }) => {
|
|
||||||
const limit = data?.meta?.limit ?? 10;
|
|
||||||
setParams((currentParams) => ({
|
|
||||||
...currentParams,
|
|
||||||
offset: e.selected * limit,
|
|
||||||
limit,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
[data?.meta?.limit],
|
|
||||||
);
|
|
||||||
|
|
||||||
const [optimisticCategories, addOptimisticPublished] = useOptimistic<
|
const [optimisticCategories, addOptimisticPublished] = useOptimistic<
|
||||||
TCompanyCategory[],
|
TCompanyCategory[],
|
||||||
TogglePublishedOptimistic
|
TogglePublishedOptimistic
|
||||||
@@ -96,7 +86,7 @@ const useCompanyCategoriesPresenter = () => {
|
|||||||
useToggleCompanyCategoryPublished();
|
useToggleCompanyCategoryPublished();
|
||||||
|
|
||||||
const search = (formData: any) => {
|
const search = (formData: any) => {
|
||||||
const newParams = { ...params, ...formData, offset: 0 };
|
const newParams = buildFirstPageParams({ ...params, ...formData });
|
||||||
const cleanedParams = deleteEmptyKeys(newParams);
|
const cleanedParams = deleteEmptyKeys(newParams);
|
||||||
const queryString = new URLSearchParams(cleanedParams).toString();
|
const queryString = new URLSearchParams(cleanedParams).toString();
|
||||||
router.push(`${pathName}?${queryString}`);
|
router.push(`${pathName}?${queryString}`);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
useCompaniesQuery,
|
useCompaniesQuery,
|
||||||
useDeleteCompanyQuery,
|
useDeleteCompanyQuery,
|
||||||
} from "@/hooks/queries";
|
} from "@/hooks/queries";
|
||||||
|
import { useHybridPagination } from "@/hooks/useHybridPagination";
|
||||||
import { useTableSectionRowAnimations } from "@/hooks/useTableSectionRowAnimations";
|
import { useTableSectionRowAnimations } from "@/hooks/useTableSectionRowAnimations";
|
||||||
import { TCompany } from "@/lib/api";
|
import { TCompany } from "@/lib/api";
|
||||||
import { deleteEmptyKeys } from "@/utils/shared";
|
import { deleteEmptyKeys } from "@/utils/shared";
|
||||||
@@ -65,6 +66,15 @@ export const useCompanyListPresenter = () => {
|
|||||||
|
|
||||||
const { data, error, isPending, isError } = useCompaniesQuery(params);
|
const { data, error, isPending, isError } = useCompaniesQuery(params);
|
||||||
|
|
||||||
|
const { pagination, handlePaginationChange, buildFirstPageParams } =
|
||||||
|
useHybridPagination({
|
||||||
|
meta: data?.meta,
|
||||||
|
params,
|
||||||
|
setParams,
|
||||||
|
isError,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
useTableSectionRowAnimations(isPending, data, {
|
useTableSectionRowAnimations(isPending, data, {
|
||||||
tableSectionRef,
|
tableSectionRef,
|
||||||
skeletonTbodyRef,
|
skeletonTbodyRef,
|
||||||
@@ -79,7 +89,7 @@ export const useCompanyListPresenter = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const search = (formData: any) => {
|
const search = (formData: any) => {
|
||||||
const newParams = { ...params, ...formData, offset: 0 };
|
const newParams = buildFirstPageParams({ ...params, ...formData });
|
||||||
const cleanedParams = deleteEmptyKeys(newParams);
|
const cleanedParams = deleteEmptyKeys(newParams);
|
||||||
const queryString = new URLSearchParams(cleanedParams).toString();
|
const queryString = new URLSearchParams(cleanedParams).toString();
|
||||||
router.push(`${pathName}?${queryString}`);
|
router.push(`${pathName}?${queryString}`);
|
||||||
@@ -89,26 +99,6 @@ export const useCompanyListPresenter = () => {
|
|||||||
router.push(pathName);
|
router.push(pathName);
|
||||||
}, [pathName, router]);
|
}, [pathName, router]);
|
||||||
|
|
||||||
const pagination = useMemo(
|
|
||||||
() => ({
|
|
||||||
currentPage: data?.meta?.page ? data.meta.page - 1 : 0,
|
|
||||||
totalPages: data?.meta?.pages ?? 1,
|
|
||||||
}),
|
|
||||||
[data?.meta?.page, data?.meta?.pages],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handlePaginationChange = useCallback(
|
|
||||||
(e: { selected: number }) => {
|
|
||||||
const limit = data?.meta?.limit ?? 10;
|
|
||||||
setParams((currentParams) => ({
|
|
||||||
...currentParams,
|
|
||||||
offset: e.selected * limit,
|
|
||||||
limit,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
[data?.meta?.limit],
|
|
||||||
);
|
|
||||||
|
|
||||||
const onConfirmDelete = () => {
|
const onConfirmDelete = () => {
|
||||||
if (currentCompany) {
|
if (currentCompany) {
|
||||||
deleteCompany(currentCompany.id);
|
deleteCompany(currentCompany.id);
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
useDeleteContactUs,
|
useDeleteContactUs,
|
||||||
useReadAllContactUs,
|
useReadAllContactUs,
|
||||||
} from "@/hooks/queries/useContactUsQuery";
|
} from "@/hooks/queries/useContactUsQuery";
|
||||||
|
import { useHybridPagination } from "@/hooks/useHybridPagination";
|
||||||
import { useTableSectionRowAnimations } from "@/hooks/useTableSectionRowAnimations";
|
import { useTableSectionRowAnimations } from "@/hooks/useTableSectionRowAnimations";
|
||||||
import { TContact } from "@/lib/api/types/TContacts";
|
import { TContact } from "@/lib/api/types/TContacts";
|
||||||
import { deleteEmptyKeys } from "@/utils/shared";
|
import { deleteEmptyKeys } from "@/utils/shared";
|
||||||
@@ -55,7 +56,16 @@ export const useContactUsListPresenter = () => {
|
|||||||
filterFormReset(newParams as any);
|
filterFormReset(newParams as any);
|
||||||
}, [searchParams, filterFormReset]);
|
}, [searchParams, filterFormReset]);
|
||||||
|
|
||||||
const { data, isPending } = useReadAllContactUs(params);
|
const { data, isPending, isError, error } = useReadAllContactUs(params);
|
||||||
|
|
||||||
|
const { pagination, handlePaginationChange, buildFirstPageParams } =
|
||||||
|
useHybridPagination({
|
||||||
|
meta: data?.meta,
|
||||||
|
params,
|
||||||
|
setParams,
|
||||||
|
isError,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
useTableSectionRowAnimations(isPending, data, {
|
useTableSectionRowAnimations(isPending, data, {
|
||||||
tableSectionRef,
|
tableSectionRef,
|
||||||
@@ -70,28 +80,8 @@ export const useContactUsListPresenter = () => {
|
|||||||
const shouldShowPagination =
|
const shouldShowPagination =
|
||||||
!isPending && (data?.meta?.pages ?? 1) * (data?.meta?.limit ?? 10) > 10;
|
!isPending && (data?.meta?.pages ?? 1) * (data?.meta?.limit ?? 10) > 10;
|
||||||
|
|
||||||
const pagination = useMemo(
|
|
||||||
() => ({
|
|
||||||
currentPage: data?.meta?.page ? data.meta.page - 1 : 0,
|
|
||||||
totalPages: data?.meta?.pages ?? 1,
|
|
||||||
}),
|
|
||||||
[data?.meta?.page, data?.meta?.pages],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handlePaginationChange = useCallback(
|
|
||||||
(e: { selected: number }) => {
|
|
||||||
const limit = data?.meta?.limit ?? 10;
|
|
||||||
setParams((currentParams) => ({
|
|
||||||
...currentParams,
|
|
||||||
offset: e.selected * limit,
|
|
||||||
limit,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
[data?.meta?.limit],
|
|
||||||
);
|
|
||||||
|
|
||||||
const search = (formData: any) => {
|
const search = (formData: any) => {
|
||||||
const newParams = { ...params, ...formData, offset: 0 };
|
const newParams = buildFirstPageParams({ ...params, ...formData });
|
||||||
const cleanedParams = deleteEmptyKeys(newParams);
|
const cleanedParams = deleteEmptyKeys(newParams);
|
||||||
const queryString = new URLSearchParams(
|
const queryString = new URLSearchParams(
|
||||||
cleanedParams as Record<string, string>,
|
cleanedParams as Record<string, string>,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
useCustomersQuery,
|
useCustomersQuery,
|
||||||
useDeleteCustomerQuery,
|
useDeleteCustomerQuery,
|
||||||
} from "@/hooks/queries";
|
} from "@/hooks/queries";
|
||||||
|
import { useHybridPagination } from "@/hooks/useHybridPagination";
|
||||||
import { useTableSectionRowAnimations } from "@/hooks/useTableSectionRowAnimations";
|
import { useTableSectionRowAnimations } from "@/hooks/useTableSectionRowAnimations";
|
||||||
import { TAssignCustomerToCompanyCredentials, TCustomer } from "@/lib/api";
|
import { TAssignCustomerToCompanyCredentials, TCustomer } from "@/lib/api";
|
||||||
import { apiDefaultParams } from "@/constants/Api_Params";
|
import { apiDefaultParams } from "@/constants/Api_Params";
|
||||||
@@ -59,7 +60,16 @@ const useCustomerListPresenter = () => {
|
|||||||
filterFormReset(newParams);
|
filterFormReset(newParams);
|
||||||
}, [searchParams, filterFormReset]);
|
}, [searchParams, filterFormReset]);
|
||||||
|
|
||||||
const { data, isPending } = useCustomersQuery(params);
|
const { data, isPending, isError, error } = useCustomersQuery(params);
|
||||||
|
|
||||||
|
const { pagination, handlePaginationChange, buildFirstPageParams } =
|
||||||
|
useHybridPagination({
|
||||||
|
meta: data?.meta,
|
||||||
|
params,
|
||||||
|
setParams,
|
||||||
|
isError,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
useTableSectionRowAnimations(isPending, data, {
|
useTableSectionRowAnimations(isPending, data, {
|
||||||
tableSectionRef,
|
tableSectionRef,
|
||||||
@@ -81,7 +91,7 @@ const useCustomerListPresenter = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const search = (formData: any) => {
|
const search = (formData: any) => {
|
||||||
const newParams = { ...params, ...formData, offset: 0 };
|
const newParams = buildFirstPageParams({ ...params, ...formData });
|
||||||
const cleanedParams = deleteEmptyKeys(newParams);
|
const cleanedParams = deleteEmptyKeys(newParams);
|
||||||
const queryString = new URLSearchParams(cleanedParams).toString();
|
const queryString = new URLSearchParams(cleanedParams).toString();
|
||||||
router.push(`${pathName}?${queryString}`);
|
router.push(`${pathName}?${queryString}`);
|
||||||
@@ -112,26 +122,6 @@ const useCustomerListPresenter = () => {
|
|||||||
const shouldShowPagination =
|
const shouldShowPagination =
|
||||||
!isPending && (data?.meta?.pages ?? 1) * (data?.meta?.limit ?? 10) > 10;
|
!isPending && (data?.meta?.pages ?? 1) * (data?.meta?.limit ?? 10) > 10;
|
||||||
|
|
||||||
const pagination = useMemo(
|
|
||||||
() => ({
|
|
||||||
currentPage: data?.meta?.page ? data.meta.page - 1 : 0,
|
|
||||||
totalPages: data?.meta?.pages ?? 1,
|
|
||||||
}),
|
|
||||||
[data?.meta?.page, data?.meta?.pages],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handlePaginationChange = useCallback(
|
|
||||||
(e: { selected: number }) => {
|
|
||||||
const limit = data?.meta?.limit ?? 10;
|
|
||||||
setParams((currentParams) => ({
|
|
||||||
...currentParams,
|
|
||||||
offset: e.selected * limit,
|
|
||||||
limit,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
[data?.meta?.limit],
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
allCustomers: customers,
|
allCustomers: customers,
|
||||||
metadata: data?.meta,
|
metadata: data?.meta,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { apiDefaultParams } from "@/constants/Api_Params";
|
import { apiDefaultParams } from "@/constants/Api_Params";
|
||||||
import { useGetFeedback } from "@/hooks/queries";
|
import { useGetFeedback } from "@/hooks/queries";
|
||||||
|
import { useHybridPagination } from "@/hooks/useHybridPagination";
|
||||||
import { useTableSectionRowAnimations } from "@/hooks/useTableSectionRowAnimations";
|
import { useTableSectionRowAnimations } from "@/hooks/useTableSectionRowAnimations";
|
||||||
import { deleteEmptyKeys } from "@/utils/shared";
|
import { deleteEmptyKeys } from "@/utils/shared";
|
||||||
import { useIsMutating } from "@tanstack/react-query";
|
import { useIsMutating } from "@tanstack/react-query";
|
||||||
@@ -50,7 +51,16 @@ const useFeedbackTablePresenter = ({ id, paramKey }: IProps) => {
|
|||||||
[params, paramKey, id],
|
[params, paramKey, id],
|
||||||
);
|
);
|
||||||
|
|
||||||
const { data, isLoading } = useGetFeedback(queryParams);
|
const { data, isLoading, isError, error } = useGetFeedback(queryParams);
|
||||||
|
|
||||||
|
const { pagination, handlePaginationChange, buildFirstPageParams } =
|
||||||
|
useHybridPagination({
|
||||||
|
meta: data?.meta,
|
||||||
|
params: queryParams,
|
||||||
|
setParams,
|
||||||
|
isError,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
useTableSectionRowAnimations(isLoading, data, {
|
useTableSectionRowAnimations(isLoading, data, {
|
||||||
tableSectionRef,
|
tableSectionRef,
|
||||||
@@ -70,28 +80,8 @@ const useFeedbackTablePresenter = ({ id, paramKey }: IProps) => {
|
|||||||
!isLoading &&
|
!isLoading &&
|
||||||
(data?.meta?.pages ?? 1) * (data?.meta?.limit ?? 10) > 10;
|
(data?.meta?.pages ?? 1) * (data?.meta?.limit ?? 10) > 10;
|
||||||
|
|
||||||
const pagination = useMemo(
|
|
||||||
() => ({
|
|
||||||
currentPage: data?.meta?.page ? data.meta.page - 1 : 0,
|
|
||||||
totalPages: data?.meta?.pages ?? 1,
|
|
||||||
}),
|
|
||||||
[data?.meta?.page, data?.meta?.pages],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handlePaginationChange = useCallback(
|
|
||||||
(e: { selected: number }) => {
|
|
||||||
const limit = data?.meta?.limit ?? 10;
|
|
||||||
setParams((currentParams) => ({
|
|
||||||
...currentParams,
|
|
||||||
offset: e.selected * limit,
|
|
||||||
limit,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
[data?.meta?.limit],
|
|
||||||
);
|
|
||||||
|
|
||||||
const search = (formData: any) => {
|
const search = (formData: any) => {
|
||||||
const next = { ...params, ...formData, offset: 0 };
|
const next = buildFirstPageParams({ ...params, ...formData });
|
||||||
const cleaned = deleteEmptyKeys(next);
|
const cleaned = deleteEmptyKeys(next);
|
||||||
const queryString = new URLSearchParams(
|
const queryString = new URLSearchParams(
|
||||||
cleaned as Record<string, string>,
|
cleaned as Record<string, string>,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {
|
|||||||
useDeleteInquiry,
|
useDeleteInquiry,
|
||||||
useGetInquiries,
|
useGetInquiries,
|
||||||
} from "@/hooks/queries/useInquiryQueries";
|
} from "@/hooks/queries/useInquiryQueries";
|
||||||
|
import { useHybridPagination } from "@/hooks/useHybridPagination";
|
||||||
import { useTableSectionRowAnimations } from "@/hooks/useTableSectionRowAnimations";
|
import { useTableSectionRowAnimations } from "@/hooks/useTableSectionRowAnimations";
|
||||||
import { TInquiries } from "@/lib/api";
|
import { TInquiries } from "@/lib/api";
|
||||||
import { deleteEmptyKeys } from "@/utils/shared";
|
import { deleteEmptyKeys } from "@/utils/shared";
|
||||||
@@ -55,7 +56,16 @@ const useInquiriesListPresenter = () => {
|
|||||||
filterFormReset(newParams);
|
filterFormReset(newParams);
|
||||||
}, [searchParams, filterFormReset]);
|
}, [searchParams, filterFormReset]);
|
||||||
|
|
||||||
const { data, isPending } = useGetInquiries(params);
|
const { data, isPending, isError, error } = useGetInquiries(params);
|
||||||
|
|
||||||
|
const { pagination, handlePaginationChange, buildFirstPageParams } =
|
||||||
|
useHybridPagination({
|
||||||
|
meta: data?.meta,
|
||||||
|
params,
|
||||||
|
setParams,
|
||||||
|
isError,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
useTableSectionRowAnimations(isPending, data, {
|
useTableSectionRowAnimations(isPending, data, {
|
||||||
tableSectionRef,
|
tableSectionRef,
|
||||||
@@ -66,32 +76,12 @@ const useInquiriesListPresenter = () => {
|
|||||||
const shouldShowPagination =
|
const shouldShowPagination =
|
||||||
!isPending && (data?.meta?.pages ?? 1) * (data?.meta?.limit ?? 10) > 10;
|
!isPending && (data?.meta?.pages ?? 1) * (data?.meta?.limit ?? 10) > 10;
|
||||||
|
|
||||||
const pagination = useMemo(
|
|
||||||
() => ({
|
|
||||||
currentPage: data?.meta?.page ? data.meta.page - 1 : 0,
|
|
||||||
totalPages: data?.meta?.pages ?? 1,
|
|
||||||
}),
|
|
||||||
[data?.meta?.page, data?.meta?.pages],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handlePaginationChange = useCallback(
|
|
||||||
(e: { selected: number }) => {
|
|
||||||
const limit = data?.meta?.limit ?? 10;
|
|
||||||
setParams((currentParams) => ({
|
|
||||||
...currentParams,
|
|
||||||
offset: e.selected * limit,
|
|
||||||
limit,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
[data?.meta?.limit],
|
|
||||||
);
|
|
||||||
|
|
||||||
const { mutate } = useDeleteInquiry(() => setIsDeleteModalOpen(false));
|
const { mutate } = useDeleteInquiry(() => setIsDeleteModalOpen(false));
|
||||||
const { mutate: changeStatus } =
|
const { mutate: changeStatus } =
|
||||||
useChangeInquiryStatus(() => setIsChangeStatusModalOpen(false));
|
useChangeInquiryStatus(() => setIsChangeStatusModalOpen(false));
|
||||||
|
|
||||||
const search = (formData: any) => {
|
const search = (formData: any) => {
|
||||||
const newParams = { ...params, ...formData, offset: 0 };
|
const newParams = buildFirstPageParams({ ...params, ...formData });
|
||||||
const cleanedParams = deleteEmptyKeys(newParams);
|
const cleanedParams = deleteEmptyKeys(newParams);
|
||||||
const queryString = new URLSearchParams(cleanedParams).toString();
|
const queryString = new URLSearchParams(cleanedParams).toString();
|
||||||
router.push(`${pathname}?${queryString}`);
|
router.push(`${pathname}?${queryString}`);
|
||||||
|
|||||||
+13
-22
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { apiDefaultParams } from "@/constants/Api_Params";
|
import { apiDefaultParams } from "@/constants/Api_Params";
|
||||||
import { useGetNotificationHistoryQuery } from "@/hooks/queries/useNotificationQueries";
|
import { useGetNotificationHistoryQuery } from "@/hooks/queries/useNotificationQueries";
|
||||||
|
import { useHybridPagination } from "@/hooks/useHybridPagination";
|
||||||
import { useTableSectionRowAnimations } from "@/hooks/useTableSectionRowAnimations";
|
import { useTableSectionRowAnimations } from "@/hooks/useTableSectionRowAnimations";
|
||||||
import { deleteEmptyKeys } from "@/utils/shared";
|
import { deleteEmptyKeys } from "@/utils/shared";
|
||||||
import { useIsMutating } from "@tanstack/react-query";
|
import { useIsMutating } from "@tanstack/react-query";
|
||||||
@@ -24,7 +25,17 @@ const useNotificationHistoryTablePresenter = () => {
|
|||||||
const skeletonTbodyRef = useRef<HTMLTableSectionElement>(null);
|
const skeletonTbodyRef = useRef<HTMLTableSectionElement>(null);
|
||||||
const dataTbodyRef = useRef<HTMLTableSectionElement>(null);
|
const dataTbodyRef = useRef<HTMLTableSectionElement>(null);
|
||||||
|
|
||||||
const { data, isLoading } = useGetNotificationHistoryQuery(params);
|
const { data, isLoading, isError, error } =
|
||||||
|
useGetNotificationHistoryQuery(params);
|
||||||
|
|
||||||
|
const { pagination, handlePaginationChange, buildFirstPageParams } =
|
||||||
|
useHybridPagination({
|
||||||
|
meta: data?.meta,
|
||||||
|
params,
|
||||||
|
setParams,
|
||||||
|
isError,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
useTableSectionRowAnimations(isLoading, data, {
|
useTableSectionRowAnimations(isLoading, data, {
|
||||||
tableSectionRef,
|
tableSectionRef,
|
||||||
@@ -64,28 +75,8 @@ const useNotificationHistoryTablePresenter = () => {
|
|||||||
filterFormReset(newParams);
|
filterFormReset(newParams);
|
||||||
}, [searchParams, filterFormReset]);
|
}, [searchParams, filterFormReset]);
|
||||||
|
|
||||||
const pagination = useMemo(
|
|
||||||
() => ({
|
|
||||||
currentPage: data?.meta?.page ? data.meta.page - 1 : 0,
|
|
||||||
totalPages: data?.meta?.pages ?? 1,
|
|
||||||
}),
|
|
||||||
[data?.meta?.page, data?.meta?.pages],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handlePaginationChange = useCallback(
|
|
||||||
(e: { selected: number }) => {
|
|
||||||
const limit = data?.meta?.limit ?? 10;
|
|
||||||
setParams((currentParams: Record<string, any>) => ({
|
|
||||||
...currentParams,
|
|
||||||
offset: e.selected * limit,
|
|
||||||
limit,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
[data?.meta?.limit],
|
|
||||||
);
|
|
||||||
|
|
||||||
const search = (formData: any) => {
|
const search = (formData: any) => {
|
||||||
const newParams = { ...params, ...formData, offset: 0 };
|
const newParams = buildFirstPageParams({ ...params, ...formData });
|
||||||
const cleanedParams = deleteEmptyKeys(newParams);
|
const cleanedParams = deleteEmptyKeys(newParams);
|
||||||
const queryString = new URLSearchParams(cleanedParams).toString();
|
const queryString = new URLSearchParams(cleanedParams).toString();
|
||||||
router.push(`${pathName}?${queryString}`);
|
router.push(`${pathName}?${queryString}`);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { ConfirmationModalProps } from "@/components/ui/ConfirmationModal";
|
import { ConfirmationModalProps } from "@/components/ui/ConfirmationModal";
|
||||||
import { apiDefaultParams } from "@/constants/Api_Params";
|
import { apiDefaultParams } from "@/constants/Api_Params";
|
||||||
import { useTendersQuery } from "@/hooks/queries";
|
import { useTendersQuery } from "@/hooks/queries";
|
||||||
|
import { useHybridPagination } from "@/hooks/useHybridPagination";
|
||||||
import { TCustomer } from "@/lib/api";
|
import { TCustomer } from "@/lib/api";
|
||||||
import { deleteEmptyKeys, getPaginatedRowNumber, unixToDate } from "@/utils/shared";
|
import { deleteEmptyKeys, getPaginatedRowNumber, unixToDate } from "@/utils/shared";
|
||||||
import gsap from "gsap";
|
import gsap from "gsap";
|
||||||
@@ -284,7 +285,17 @@ const useTenderListPresenter = () => {
|
|||||||
filterFormReset(mergeTenderFilterFormState(newParams));
|
filterFormReset(mergeTenderFilterFormState(newParams));
|
||||||
}, [searchParams, filterFormReset]);
|
}, [searchParams, filterFormReset]);
|
||||||
|
|
||||||
const { data, error, isPending } = useTendersQuery(params);
|
const { data, error, isPending, isError } = useTendersQuery(params);
|
||||||
|
|
||||||
|
const { pagination, handlePaginationChange, buildFirstPageParams } =
|
||||||
|
useHybridPagination({
|
||||||
|
meta: data?.meta,
|
||||||
|
params,
|
||||||
|
setParams,
|
||||||
|
isError,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
|
||||||
const shouldShowPagination =
|
const shouldShowPagination =
|
||||||
!isPending && (data?.meta?.pages ?? 1) * (data?.meta?.limit ?? 10) > 10;
|
!isPending && (data?.meta?.pages ?? 1) * (data?.meta?.limit ?? 10) > 10;
|
||||||
|
|
||||||
@@ -311,18 +322,6 @@ const useTenderListPresenter = () => {
|
|||||||
[pathName, router, langQuerySuffix],
|
[pathName, router, langQuerySuffix],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handlePaginationChange = useCallback(
|
|
||||||
(e: { selected: number }) => {
|
|
||||||
const limit = data?.meta?.limit ?? 10;
|
|
||||||
setParams((currentParams) => ({
|
|
||||||
...currentParams,
|
|
||||||
offset: e.selected * limit,
|
|
||||||
limit,
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
[data?.meta?.limit, setParams],
|
|
||||||
);
|
|
||||||
|
|
||||||
const getRowNumber = useCallback(
|
const getRowNumber = useCallback(
|
||||||
(index: number) =>
|
(index: number) =>
|
||||||
getPaginatedRowNumber({
|
getPaginatedRowNumber({
|
||||||
@@ -337,14 +336,6 @@ const useTenderListPresenter = () => {
|
|||||||
return unixToDate({ unix, hasTime: true });
|
return unixToDate({ unix, hasTime: true });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const pagination = useMemo(
|
|
||||||
() => ({
|
|
||||||
currentPage: data?.meta?.page ? data.meta.page - 1 : 0,
|
|
||||||
totalPages: data?.meta?.pages ?? 1,
|
|
||||||
}),
|
|
||||||
[data?.meta?.page, data?.meta?.pages],
|
|
||||||
);
|
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const reduced =
|
const reduced =
|
||||||
typeof window !== "undefined" &&
|
typeof window !== "undefined" &&
|
||||||
@@ -401,21 +392,20 @@ const useTenderListPresenter = () => {
|
|||||||
delete merged[key];
|
delete merged[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const newParams = { ...merged, ...normalizedData, offset: 0 };
|
const newParams = buildFirstPageParams({ ...merged, ...normalizedData });
|
||||||
const cleanedParams = deleteEmptyKeys(newParams);
|
const cleanedParams = deleteEmptyKeys(newParams);
|
||||||
const queryString = new URLSearchParams(cleanedParams).toString();
|
const queryString = new URLSearchParams(cleanedParams).toString();
|
||||||
router.push(`${pathName}?${queryString}`);
|
router.push(`${pathName}?${queryString}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearAllFilters = useCallback(() => {
|
const clearAllFilters = useCallback(() => {
|
||||||
const cleared: Record<string, any> = {
|
const cleared = buildFirstPageParams({
|
||||||
...apiDefaultParams,
|
...apiDefaultParams,
|
||||||
offset: 0,
|
|
||||||
limit: 10,
|
limit: 10,
|
||||||
};
|
});
|
||||||
filterFormReset(mergeTenderFilterFormState(cleared));
|
filterFormReset(mergeTenderFilterFormState(cleared));
|
||||||
router.push(pathName);
|
router.push(pathName);
|
||||||
}, [pathName, router, filterFormReset]);
|
}, [buildFirstPageParams, pathName, router, filterFormReset]);
|
||||||
const columns = [
|
const columns = [
|
||||||
"row",
|
"row",
|
||||||
"title",
|
"title",
|
||||||
|
|||||||
@@ -14,8 +14,9 @@ const Pagination: FC<IProps> = ({ totalPages, currentPage, onPageChange }) => {
|
|||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<ReactPaginate
|
<ReactPaginate
|
||||||
|
key={currentPage}
|
||||||
pageCount={totalPages}
|
pageCount={totalPages}
|
||||||
initialPage={currentPage}
|
forcePage={currentPage}
|
||||||
nextLabel="›"
|
nextLabel="›"
|
||||||
previousLabel="‹"
|
previousLabel="‹"
|
||||||
pageRangeDisplayed={5}
|
pageRangeDisplayed={5}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,26 +1,24 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const MetaDataSchema = z.object({
|
const MetaDataSchema = z.object({
|
||||||
limit: z.number(),
|
limit: z.number(),
|
||||||
offset: z.number(),
|
offset: z.number(),
|
||||||
page: z.number(),
|
page: z.number(),
|
||||||
pages: z.number(),
|
pages: z.number(),
|
||||||
total: z.number(),
|
total: z.number(),
|
||||||
|
has_more: z.boolean().optional(),
|
||||||
|
next_cursor: z.string().optional(),
|
||||||
|
prev_cursor: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export function createApiResponseSchema<T extends z.ZodTypeAny>(dataSchema: T) {
|
export function createApiResponseSchema<T extends z.ZodTypeAny>(dataSchema: T) {
|
||||||
return z.object({
|
return z.object({
|
||||||
data: dataSchema,
|
data: dataSchema,
|
||||||
meta: z
|
meta: MetaDataSchema.optional(),
|
||||||
.object({
|
|
||||||
limit: z.number(),
|
|
||||||
offset: z.number(),
|
|
||||||
page: z.number(),
|
|
||||||
pages: z.number(),
|
|
||||||
total: z.number(),
|
|
||||||
})
|
|
||||||
.optional(),
|
|
||||||
message: z.string(),
|
message: z.string(),
|
||||||
code: z.number().optional(),
|
code: z.number().optional(),
|
||||||
success: z.boolean(),
|
success: z.boolean(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IMetaData = z.infer<typeof MetaDataSchema>;
|
export type IMetaData = z.infer<typeof MetaDataSchema>;
|
||||||
|
|||||||
Reference in New Issue
Block a user