/** * Pagination Hook * * Manages pagination state and actions for the moderation queue. */ import { useState, useCallback, useEffect, useMemo } from 'react'; import { MODERATION_CONSTANTS } from '@/lib/moderation/constants'; import * as storage from '@/lib/localStorage'; import { logger } from '@/lib/logger'; export interface PaginationConfig { /** Initial page number (1-indexed) */ initialPage?: number; /** Initial page size */ initialPageSize?: number; /** Whether to persist pagination state */ persist?: boolean; /** localStorage key for persistence */ storageKey?: string; /** Callback when page changes */ onPageChange?: (page: number) => void; /** Callback when page size changes */ onPageSizeChange?: (pageSize: number) => void; } export interface PaginationState { /** Current page (1-indexed) */ currentPage: number; /** Items per page */ pageSize: number; /** Total number of items */ totalCount: number; /** Total number of pages */ totalPages: number; /** Start index for current page (0-indexed) */ startIndex: number; /** End index for current page (0-indexed) */ endIndex: number; /** Whether there is a previous page */ hasPrevPage: boolean; /** Whether there is a next page */ hasNextPage: boolean; /** Set current page */ setCurrentPage: (page: number) => void; /** Set page size */ setPageSize: (size: number) => void; /** Set total count */ setTotalCount: (count: number) => void; /** Go to next page */ nextPage: () => void; /** Go to previous page */ prevPage: () => void; /** Go to first page */ firstPage: () => void; /** Go to last page */ lastPage: () => void; /** Reset pagination */ reset: () => void; /** Get page range for display */ getPageRange: (maxPages?: number) => number[]; } /** * Hook for managing pagination state * * @param config - Configuration options * @returns Pagination state and actions * * @example * ```tsx * const pagination = usePagination({ * initialPageSize: 25, * persist: true, * onPageChange: (page) => fetchData(page) * }); * * // Set total count from API * pagination.setTotalCount(response.count); * * // Use in query * const { startIndex, endIndex } = pagination; * query.range(startIndex, endIndex); * ``` */ export function usePagination(config: PaginationConfig = {}): PaginationState { const { initialPage = 1, initialPageSize = MODERATION_CONSTANTS.DEFAULT_PAGE_SIZE, persist = false, storageKey = 'pagination_state', onPageChange, onPageSizeChange, } = config; // Load persisted state const loadPersistedState = useCallback(() => { if (!persist) return null; try { const saved = localStorage.getItem(storageKey); if (saved) { return JSON.parse(saved); } } catch (error: unknown) { // Silent - localStorage failures are non-critical } return null; }, [persist, storageKey]); const persisted = loadPersistedState(); // State const [currentPage, setCurrentPageState] = useState( persisted?.currentPage || initialPage ); const [pageSize, setPageSizeState] = useState( persisted?.pageSize || initialPageSize ); const [totalCount, setTotalCount] = useState(0); // Computed values const totalPages = useMemo(() => Math.ceil(totalCount / pageSize), [totalCount, pageSize]); const startIndex = useMemo(() => (currentPage - 1) * pageSize, [currentPage, pageSize]); const endIndex = useMemo(() => startIndex + pageSize - 1, [startIndex, pageSize]); const hasPrevPage = currentPage > 1; const hasNextPage = currentPage < totalPages; // Persist state useEffect(() => { if (persist) { storage.setJSON(storageKey, { currentPage, pageSize, }); } }, [currentPage, pageSize, persist, storageKey]); // Set current page with bounds checking const setCurrentPage = useCallback( (page: number) => { const boundedPage = Math.max(1, Math.min(page, totalPages || 1)); setCurrentPageState(boundedPage); onPageChange?.(boundedPage); }, [totalPages, onPageChange] ); // Set page size and reset to first page const setPageSize = useCallback( (size: number) => { setPageSizeState(size); setCurrentPageState(1); onPageSizeChange?.(size); }, [onPageSizeChange] ); // Navigation actions const nextPage = useCallback(() => { if (hasNextPage) { setCurrentPage(currentPage + 1); } }, [currentPage, hasNextPage, setCurrentPage]); const prevPage = useCallback(() => { if (hasPrevPage) { setCurrentPage(currentPage - 1); } }, [currentPage, hasPrevPage, setCurrentPage]); const firstPage = useCallback(() => { setCurrentPage(1); }, [setCurrentPage]); const lastPage = useCallback(() => { setCurrentPage(totalPages); }, [totalPages, setCurrentPage]); // Reset pagination const reset = useCallback(() => { setCurrentPageState(initialPage); setPageSizeState(initialPageSize); setTotalCount(0); }, [initialPage, initialPageSize]); // Get page range for pagination controls const getPageRange = useCallback( (maxPages: number = 5): number[] => { if (totalPages <= maxPages) { return Array.from({ length: totalPages }, (_, i) => i + 1); } const half = Math.floor(maxPages / 2); let start = Math.max(1, currentPage - half); let end = Math.min(totalPages, start + maxPages - 1); // Adjust start if we're near the end if (end - start < maxPages - 1) { start = Math.max(1, end - maxPages + 1); } return Array.from({ length: end - start + 1 }, (_, i) => start + i); }, [currentPage, totalPages] ); // Return without useMemo wrapper (OPTIMIZED) return { currentPage, pageSize, totalCount, totalPages, startIndex, endIndex, hasPrevPage, hasNextPage, setCurrentPage, setPageSize, setTotalCount, nextPage, prevPage, firstPage, lastPage, reset, getPageRange, }; }