From 7601368bd368749c7b3e5a26d4eb2f93b0381ae4 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Mon, 29 Jun 2026 09:45:56 +0330 Subject: [PATCH] pagination --- src/hooks/useUrlPagination.ts | 48 +++++++++++++++++++++++ src/pages/customers/List.tsx | 26 ++++++------ src/pages/orders/List.tsx | 14 +++++-- src/pages/orders/hooks/useOrderFilters.ts | 9 ++--- src/pages/orders/types/Types.ts | 6 ++- src/types/response.types.ts | 7 ++++ 6 files changed, 87 insertions(+), 23 deletions(-) create mode 100644 src/hooks/useUrlPagination.ts diff --git a/src/hooks/useUrlPagination.ts b/src/hooks/useUrlPagination.ts new file mode 100644 index 0000000..db7be56 --- /dev/null +++ b/src/hooks/useUrlPagination.ts @@ -0,0 +1,48 @@ +import { useCallback, useMemo } from "react"; +import { useSearchParams } from "react-router-dom"; + +const PAGE_PARAM = "page"; + +export const useUrlPagination = (defaultPage = 1) => { + const [searchParams, setSearchParams] = useSearchParams(); + + const currentPage = useMemo(() => { + const page = Number.parseInt(searchParams.get(PAGE_PARAM) ?? "", 10); + return Number.isFinite(page) && page > 0 ? page : defaultPage; + }, [searchParams, defaultPage]); + + const handlePageChange = useCallback( + (page: number) => { + setSearchParams( + (prev) => { + const next = new URLSearchParams(prev); + if (page <= defaultPage) { + next.delete(PAGE_PARAM); + } else { + next.set(PAGE_PARAM, page.toString()); + } + return next; + }, + { replace: false }, + ); + }, + [setSearchParams, defaultPage], + ); + + const resetPage = useCallback(() => { + setSearchParams( + (prev) => { + const next = new URLSearchParams(prev); + next.delete(PAGE_PARAM); + return next; + }, + { replace: true }, + ); + }, [setSearchParams]); + + return { + currentPage, + handlePageChange, + resetPage, + }; +}; diff --git a/src/pages/customers/List.tsx b/src/pages/customers/List.tsx index 618fae9..8857334 100644 --- a/src/pages/customers/List.tsx +++ b/src/pages/customers/List.tsx @@ -1,7 +1,8 @@ import Table from '@/components/Table' import Filters, { type FilterValues, type FieldType } from '@/components/Filters' -import { type FC, useState, useMemo, useCallback } from 'react' +import { type FC, useState, useMemo, useCallback, useEffect } from 'react' import { useGetUsers } from './hooks/useUsersData' +import { useUrlPagination } from '@/hooks/useUrlPagination' import type { Customer, CustomerListRow } from '@/pages/customers/types/Types' import { formatOptionalDate, formatFaNumber } from '@/helpers/func' @@ -22,7 +23,7 @@ const mapCustomerToRow = (customer: Customer): CustomerListRow => { const CustomersList: FC = () => { const { data: usersData, isLoading } = useGetUsers() const [filters, setFilters] = useState({ search: '', status: 'all' }) - const [currentPage, setCurrentPage] = useState(1) + const { currentPage, handlePageChange, resetPage } = useUrlPagination() const limit = 10 const filterFields = useMemo(() => [ @@ -67,11 +68,18 @@ const CustomersList: FC = () => { }, [customers, filters]) const totalPages = Math.max(1, Math.ceil(filteredCustomers.length / limit) || 1) + const effectivePage = Math.min(currentPage, totalPages) + + useEffect(() => { + if (currentPage > totalPages) { + handlePageChange(totalPages) + } + }, [currentPage, totalPages, handlePageChange]) const paginatedCustomers = useMemo(() => { - const startIndex = (currentPage - 1) * limit + const startIndex = (effectivePage - 1) * limit return filteredCustomers.slice(startIndex, startIndex + limit) - }, [filteredCustomers, currentPage, limit]) + }, [filteredCustomers, effectivePage, limit]) const tableData = useMemo(() => paginatedCustomers.map(mapCustomerToRow), [paginatedCustomers]) @@ -117,14 +125,10 @@ const CustomersList: FC = () => { }, ], []) - const handlePageChange = (page: number) => { - setCurrentPage(page) - } - const handleFiltersChange = useCallback((newFilters: FilterValues) => { setFilters(newFilters) - setCurrentPage(1) - }, []) + resetPage() + }, [resetPage]) return (
@@ -155,7 +159,7 @@ const CustomersList: FC = () => { isloading={isLoading} selectable pagination={{ - currentPage, + currentPage: effectivePage, totalPages, onPageChange: handlePageChange, }} diff --git a/src/pages/orders/List.tsx b/src/pages/orders/List.tsx index 7e5d172..cf40fb2 100644 --- a/src/pages/orders/List.tsx +++ b/src/pages/orders/List.tsx @@ -1,4 +1,4 @@ -import { type FC } from 'react' +import { type FC, useEffect } from 'react' import { useTranslation } from 'react-i18next' import Table from '@/components/Table' import Filters from '@/components/Filters' @@ -17,7 +17,6 @@ const OrdersList: FC = () => { apiParams, handleFiltersChange, handlePageChange, - limit, } = useOrderFilters() const { data: ordersData, isLoading } = useGetOrders(apiParams) @@ -26,7 +25,14 @@ const OrdersList: FC = () => { const columns = getOrderTableColumns({ t }) const filterFields = useOrderFiltersFields() - const totalPages = Math.ceil(orders.length / limit) || 1 + const totalPages = ordersData?.meta?.totalPages || 1 + const effectivePage = Math.min(currentPage, totalPages) + + useEffect(() => { + if (currentPage > totalPages) { + handlePageChange(totalPages) + } + }, [currentPage, totalPages, handlePageChange]) return (
@@ -48,7 +54,7 @@ const OrdersList: FC = () => { data={orders as (Order & RowDataType)[]} isloading={isLoading} pagination={{ - currentPage, + currentPage: effectivePage, totalPages, onPageChange: handlePageChange, }} diff --git a/src/pages/orders/hooks/useOrderFilters.ts b/src/pages/orders/hooks/useOrderFilters.ts index c8d5f19..5f3cdc9 100644 --- a/src/pages/orders/hooks/useOrderFilters.ts +++ b/src/pages/orders/hooks/useOrderFilters.ts @@ -1,12 +1,13 @@ import { useState, useMemo } from "react"; import type { FilterValues } from "@/components/Filters"; +import { useUrlPagination } from "@/hooks/useUrlPagination"; const DEFAULT_PAGE = 1; const DEFAULT_LIMIT = 10; export const useOrderFilters = () => { const [filters, setFilters] = useState({}); - const [currentPage, setCurrentPage] = useState(DEFAULT_PAGE); + const { currentPage, handlePageChange, resetPage } = useUrlPagination(DEFAULT_PAGE); const [limit] = useState(DEFAULT_LIMIT); const apiParams = useMemo(() => { @@ -32,11 +33,7 @@ export const useOrderFilters = () => { const handleFiltersChange = (newFilters: FilterValues) => { setFilters(newFilters); - setCurrentPage(DEFAULT_PAGE); - }; - - const handlePageChange = (page: number) => { - setCurrentPage(page); + resetPage(); }; return { diff --git a/src/pages/orders/types/Types.ts b/src/pages/orders/types/Types.ts index 2bf6cc0..e25766c 100644 --- a/src/pages/orders/types/Types.ts +++ b/src/pages/orders/types/Types.ts @@ -1,4 +1,4 @@ -import type { IResponse } from "@/types/response.types"; +import type { IResponse, PaginationMeta } from "@/types/response.types"; import type { RowDataType } from "@/components/types/TableTypes"; export interface OrderUser { @@ -220,5 +220,7 @@ export interface Order extends RowDataType { items: OrderItem[]; } -export type GetOrdersResponse = IResponse; +export type GetOrdersResponse = IResponse & { + meta?: PaginationMeta; +}; export type GetOrderByIdResponse = IResponse; diff --git a/src/types/response.types.ts b/src/types/response.types.ts index f672619..3a28fbb 100644 --- a/src/types/response.types.ts +++ b/src/types/response.types.ts @@ -6,3 +6,10 @@ export interface IBaseResponse { export interface IResponse extends IBaseResponse { data: T; } + +export interface PaginationMeta { + total: number; + page: string; + limit: string; + totalPages: number; +}