Files
thrilltrack-explorer/src-old/hooks/moderation/usePagination.ts

251 lines
6.1 KiB
TypeScript

/**
* 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<number>(
persisted?.currentPage || initialPage
);
const [pageSize, setPageSizeState] = useState<number>(
persisted?.pageSize || initialPageSize
);
const [totalCount, setTotalCount] = useState<number>(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,
};
}