diff --git a/src/components/Tables/tenders/index.tsx b/src/components/Tables/tenders/index.tsx index 13528e2..b337af5 100644 --- a/src/components/Tables/tenders/index.tsx +++ b/src/components/Tables/tenders/index.tsx @@ -5,7 +5,6 @@ import EmptyListWrapper from "@/components/HOC/EmptyListWrapper"; import IsVisible from "@/components/ui/IsVisible"; import ListHeader from "@/components/ui/ListHeader"; import ListWrapper from "@/components/ui/ListWrapper"; -import Pagination from "@/components/ui/pagination"; import Status from "@/components/ui/Status"; import { Table, @@ -49,7 +48,8 @@ const TendersTable = () => { navigateToTenderDetails, navigateToTenderFeedback, handlePaginationChange, - buildWindowedPageLabel, + hasNext, + hasPrevious, getRowNumber, formatDateTimeCell, pagination, @@ -204,16 +204,37 @@ const TendersTable = () => { /> - + ); diff --git a/src/components/Tables/tenders/useTenderListPresenter.ts b/src/components/Tables/tenders/useTenderListPresenter.ts index c283a20..a2cab02 100644 --- a/src/components/Tables/tenders/useTenderListPresenter.ts +++ b/src/components/Tables/tenders/useTenderListPresenter.ts @@ -8,14 +8,20 @@ import { TCustomer } from "@/lib/api"; import { buildQueryString, deleteEmptyKeys, - getPaginatedRowNumber, isDateRangeActive, unixToDate, } from "@/utils/shared"; import gsap from "gsap"; import { useIsMutating } from "@tanstack/react-query"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; -import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; +import { + useCallback, + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, +} from "react"; import { useForm } from "react-hook-form"; const COMMA_SEPARATED_FILTER_KEYS = new Set([ @@ -356,11 +362,10 @@ const useTenderListPresenter = () => { useEffect(() => { const newParams: Record = { ...apiDefaultParams, - offset: 0, - limit: 10, + limit: 20, }; for (const key of searchParams.keys()) { - if (key === "cpv_codes") { + if (key === "cpv_codes" || key === "include_total" || key === "cursor") { continue; } newParams[key] = searchParams.get(key); @@ -376,53 +381,21 @@ const useTenderListPresenter = () => { const { data, error, isPending, isError } = useTendersQuery(params); - const { pagination, handlePaginationChange, buildFirstPageParams } = - useHybridPagination({ - meta: data?.meta, - params, - setParams, - isError, - error, - }); + const { + pagination, + handlePaginationChange, + buildFirstPageParams, + hasNext, + hasPrevious, + } = useHybridPagination({ + meta: data?.meta, + params, + setParams, + isError, + error, + }); - const MAX_PAGE_JUMP = 10; - const paginationWindow = useMemo(() => { - const windowStart = Math.max(0, pagination.currentPage - MAX_PAGE_JUMP); - const windowEnd = Math.min( - Math.max(pagination.totalPages - 1, 0), - pagination.currentPage + MAX_PAGE_JUMP, - ); - return { - windowStart, - virtualCurrent: pagination.currentPage - windowStart, - virtualTotal: Math.max(windowEnd - windowStart + 1, 1), - }; - }, [pagination.currentPage, pagination.totalPages]); - - const handleWindowedPaginationChange = useCallback( - (e: { selected: number }) => { - handlePaginationChange({ - selected: e.selected + paginationWindow.windowStart, - }); - }, - [handlePaginationChange, paginationWindow.windowStart], - ); - - const buildWindowedPageLabel = useCallback( - (virtualPage: number) => String(virtualPage + paginationWindow.windowStart), - [paginationWindow.windowStart], - ); - - const windowedPagination = useMemo( - () => ({ - currentPage: paginationWindow.virtualCurrent, - totalPages: paginationWindow.virtualTotal, - }), - [paginationWindow.virtualCurrent, paginationWindow.virtualTotal], - ); - - const shouldShowPagination = - !isPending && (data?.meta?.pages ?? 1) * (data?.meta?.limit ?? 10) > 10; + const shouldShowPagination = !isPending && (hasPrevious || hasNext); const tendersList = data?.data?.tenders ?? []; @@ -449,12 +422,11 @@ const useTenderListPresenter = () => { const getRowNumber = useCallback( (index: number) => - getPaginatedRowNumber({ - index, - page: data?.meta?.page, - limit: data?.meta?.limit, - }), - [data?.meta?.limit, data?.meta?.page], + pagination.currentPage * + (data?.meta?.limit ?? Number(params.limit) ?? 20) + + index + + 1, + [data?.meta?.limit, pagination.currentPage, params.limit], ); const formatDateTimeCell = useCallback((unix: number) => { @@ -541,7 +513,7 @@ const useTenderListPresenter = () => { const clearAllFilters = useCallback(() => { const cleared = buildFirstPageParams({ ...apiDefaultParams, - limit: 10, + limit: 20, }); filterFormReset(mergeTenderFilterFormState(cleared)); router.push(pathName); @@ -629,11 +601,12 @@ const useTenderListPresenter = () => { tendersList, navigateToTenderDetails, navigateToTenderFeedback, - handlePaginationChange: handleWindowedPaginationChange, - buildWindowedPageLabel, + handlePaginationChange, + hasNext, + hasPrevious, getRowNumber, formatDateTimeCell, - pagination: windowedPagination, + pagination, absoluteCurrentPage: pagination.currentPage, }; }; diff --git a/src/hooks/queries/useTenders.ts b/src/hooks/queries/useTenders.ts index d390427..5f6c3bb 100644 --- a/src/hooks/queries/useTenders.ts +++ b/src/hooks/queries/useTenders.ts @@ -1,9 +1,10 @@ import { API_ENDPOINTS, tendersService } from "@/lib/api"; +import { TTenderListParams } from "@/lib/api/types/Tenders"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMemo } from "react"; import { toast } from "react-toastify"; -export const useTendersQuery = (params?: Record) => { +export const useTendersQuery = (params?: TTenderListParams) => { const queryKey = useMemo( () => [API_ENDPOINTS.TENDERS.READ_ALL, "read-tenders", params], [params], @@ -15,7 +16,10 @@ export const useTendersQuery = (params?: Record) => { }); }; -export const useTenderDetailQuery = (id: string, params?: Record) => { +export const useTenderDetailQuery = ( + id: string, + params?: Record, +) => { const queryKey = useMemo( () => [API_ENDPOINTS.TENDERS.DETAILS(id), params], [id, params], @@ -52,13 +56,10 @@ export const useGetTenderDocumentsQuery = ( id: string, options?: { enabled?: boolean }, ) => { - const queryKey = useMemo( - () => [API_ENDPOINTS.TENDERS.DOCUMENTS(id)], - [id], - ); + const queryKey = useMemo(() => [API_ENDPOINTS.TENDERS.DOCUMENTS(id)], [id]); return useQuery({ queryKey, queryFn: () => tendersService.getDocuments(id), enabled: options?.enabled ?? true, }); -}; \ No newline at end of file +}; diff --git a/src/hooks/useHybridPagination.ts b/src/hooks/useHybridPagination.ts index 749abc2..bd6e7bc 100644 --- a/src/hooks/useHybridPagination.ts +++ b/src/hooks/useHybridPagination.ts @@ -1,6 +1,5 @@ "use client"; -import { IMetaData } from "@/lib/api/types/Factory"; import { AxiosError } from "axios"; import { useCallback, useEffect, useMemo, useState } from "react"; @@ -12,7 +11,14 @@ const isInvalidCursorError = (error: unknown) => { }; type UseHybridPaginationOptions = { - meta?: IMetaData; + meta?: { + limit: number; + offset: number; + page?: number; + pages?: number; + has_more?: boolean; + next_cursor?: string; + }; params: Record; setParams: React.Dispatch>>; isError?: boolean; @@ -91,7 +97,14 @@ export const useHybridPagination = ({ if (meta?.page != null) return meta.page - 1; return 0; - }, [cursorStack, params.cursor, params.offset, meta?.offset, meta?.page, limit]); + }, [ + cursorStack, + params.cursor, + params.offset, + meta?.offset, + meta?.page, + limit, + ]); const hasPrevious = cursorStack.length > 0 || currentPageIndex > 0; @@ -129,7 +142,11 @@ export const useHybridPagination = ({ { cursor: nextCursor, fromPage: fromPage1Based }, ]); setParams((current) => { - const next: Record = { ...current, cursor: nextCursor, limit }; + const next: Record = { + ...current, + cursor: nextCursor, + limit, + }; delete next.offset; return next; }); diff --git a/src/lib/api/services/tenders-service.ts b/src/lib/api/services/tenders-service.ts index c7dc395..2fa8841 100644 --- a/src/lib/api/services/tenders-service.ts +++ b/src/lib/api/services/tenders-service.ts @@ -5,14 +5,16 @@ import { ApiResponse } from "../types"; import { TTenderDetails, TTenderDocumentsResponse, + TTenderListParams, + TTenderListMeta, TTenderResponse, TTenderTranslationResponse, } from "../types/Tenders"; export const tendersService = { tendersList: async ( - params?: Record, - ): Promise> => { + params?: TTenderListParams, + ): Promise> => { try { return ( await api.get(API_ENDPOINTS.TENDERS.READ_ALL, { @@ -30,7 +32,8 @@ export const tendersService = { params?: Record, ): Promise> => { try { - return (await api.get(API_ENDPOINTS.TENDERS.DETAILS(id), { params })).data; + return (await api.get(API_ENDPOINTS.TENDERS.DETAILS(id), { params })) + .data; } catch (error) { console.error( "ERROR caught in Tender Service => tender details: ", @@ -53,12 +56,17 @@ export const tendersService = { }) ).data; } catch (error) { - console.error("ERROR caught in Tender Service => translate tender: ", error); + console.error( + "ERROR caught in Tender Service => translate tender: ", + error, + ); throw error; } }, - getDocuments: async (id: string): Promise> => { + getDocuments: async ( + id: string, + ): Promise> => { try { return (await api.get(API_ENDPOINTS.TENDERS.DOCUMENTS(id))).data; } catch (error) { diff --git a/src/lib/api/types/Tenders.ts b/src/lib/api/types/Tenders.ts index 5203058..a89d0d1 100644 --- a/src/lib/api/types/Tenders.ts +++ b/src/lib/api/types/Tenders.ts @@ -123,9 +123,33 @@ export const TendersResponse = z.object({ tenders: z.array(TenderSchema).or(z.null()), }); -export type TTenderDocumentsResponse = z.infer; +export type TTenderDocumentsResponse = z.infer< + typeof TenderDocumentsResponseSchema +>; export type TTenderResponse = z.infer; export type TTenderDetails = z.infer; +export interface TTenderListParams { + limit?: number; + offset?: number; + cursor?: string; + include_total?: boolean; + sort_by?: string; + sort_order?: "asc" | "desc"; + q?: string; + status?: string[]; + country?: string; + documents_scraped?: boolean; + [key: string]: unknown; +} +export interface TTenderListMeta { + limit: number; + offset: number; + has_more: boolean; + next_cursor?: string; + total?: number; + page?: number; + pages?: number; +} export type TTenderTranslationResponse = z.infer< typeof TenderTranslationResponseSchema >; diff --git a/src/lib/api/types/shared.ts b/src/lib/api/types/shared.ts index aee151f..a577578 100644 --- a/src/lib/api/types/shared.ts +++ b/src/lib/api/types/shared.ts @@ -1,9 +1,9 @@ import { IMetaData } from "./Factory"; -export interface ApiResponse { +export interface ApiResponse { data: T; message: string; code?: number; - meta?: IMetaData; + meta?: TMeta; success: boolean; }