137 lines
3.6 KiB
TypeScript
137 lines
3.6 KiB
TypeScript
import { useState, useMemo, useCallback } from 'react';
|
|
import { UI_CONSTANTS } from '../constants';
|
|
|
|
interface PaginationConfig {
|
|
itemsPerPage?: number;
|
|
initialPage?: number;
|
|
autoLoadMore?: boolean;
|
|
}
|
|
|
|
interface PaginationResult<T> {
|
|
// Current state
|
|
currentPage: number;
|
|
items: T[];
|
|
hasMore: boolean;
|
|
isLoading: boolean;
|
|
|
|
// Actions
|
|
loadMore: () => void;
|
|
resetPage: () => void;
|
|
setPage: (page: number) => void;
|
|
setSearchTerm: (term: string) => void;
|
|
|
|
// Computed values
|
|
totalItems: number;
|
|
totalPages: number;
|
|
startIndex: number;
|
|
endIndex: number;
|
|
searchTerm: string;
|
|
}
|
|
|
|
export const usePaginatedData = <T>(
|
|
allItems: T[],
|
|
config: PaginationConfig = {}
|
|
): PaginationResult<T> => {
|
|
const {
|
|
itemsPerPage = UI_CONSTANTS.PAGINATION.ITEMS_PER_PAGE,
|
|
initialPage = 1,
|
|
autoLoadMore = false
|
|
} = config;
|
|
|
|
const [currentPage, setCurrentPage] = useState(initialPage);
|
|
const [searchTerm, setSearchTerm] = useState('');
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
// Filter items by search term if provided
|
|
const filteredItems = useMemo(() => {
|
|
if (!searchTerm.trim()) return allItems;
|
|
|
|
// Simple search implementation - can be overridden by passing filtered items
|
|
return allItems.filter((item: T) => {
|
|
if (typeof item === 'string') {
|
|
return (item || '').toLowerCase().includes((searchTerm || '').toLowerCase());
|
|
}
|
|
if (typeof item === 'object' && item !== null) {
|
|
return Object.values(item as Record<string, unknown>).some(value =>
|
|
String(value || '').toLowerCase().includes((searchTerm || '').toLowerCase())
|
|
);
|
|
}
|
|
return false;
|
|
});
|
|
}, [allItems, searchTerm]);
|
|
|
|
// Calculate pagination values
|
|
const totalItems = filteredItems.length;
|
|
const totalPages = Math.ceil(totalItems / itemsPerPage);
|
|
const startIndex = 0;
|
|
const endIndex = currentPage * itemsPerPage;
|
|
|
|
// Get paginated items
|
|
const items = useMemo(() => {
|
|
return filteredItems.slice(startIndex, endIndex);
|
|
}, [filteredItems, endIndex]);
|
|
|
|
// Check if there are more items to load
|
|
const hasMore = useMemo(() => {
|
|
return totalItems > itemsPerPage && items.length < totalItems;
|
|
}, [totalItems, itemsPerPage, items.length]);
|
|
|
|
// Load more items
|
|
const loadMore = useCallback(() => {
|
|
if (hasMore && !isLoading) {
|
|
setIsLoading(true);
|
|
// Simulate a small delay to show loading state
|
|
setTimeout(() => {
|
|
setCurrentPage(prev => prev + 1);
|
|
setIsLoading(false);
|
|
}, 100);
|
|
}
|
|
}, [hasMore, isLoading]);
|
|
|
|
// Reset to first page
|
|
const resetPage = useCallback(() => {
|
|
setCurrentPage(initialPage);
|
|
}, [initialPage]);
|
|
|
|
// Set specific page
|
|
const setPage = useCallback((page: number) => {
|
|
setCurrentPage(page);
|
|
}, []);
|
|
|
|
// Handle search term changes
|
|
const handleSearchTermChange = useCallback((term: string) => {
|
|
setSearchTerm(term);
|
|
resetPage(); // Reset to first page when searching
|
|
}, [resetPage]);
|
|
|
|
// Auto-load more if enabled and we're near the end
|
|
useMemo(() => {
|
|
if (autoLoadMore && hasMore && !isLoading && items.length > 0) {
|
|
const lastItemIndex = items.length - 1;
|
|
if (lastItemIndex >= endIndex - 5) { // Load more when 5 items away from end
|
|
loadMore();
|
|
}
|
|
}
|
|
}, [autoLoadMore, hasMore, isLoading, items.length, endIndex, loadMore]);
|
|
|
|
return {
|
|
// Current state
|
|
currentPage,
|
|
items,
|
|
hasMore,
|
|
isLoading,
|
|
|
|
// Actions
|
|
loadMore,
|
|
resetPage,
|
|
setPage,
|
|
setSearchTerm: handleSearchTermChange,
|
|
|
|
// Computed values
|
|
totalItems,
|
|
totalPages,
|
|
startIndex,
|
|
endIndex,
|
|
searchTerm,
|
|
};
|
|
};
|