import { useState, useCallback, useMemo } from 'react'; import { Cursor, NestedKey, SortType, UseTableConfig } from './types'; export const getNestedObjectValue = >(obj: T, path: string) => { const pathArr = path.split('.'); try { return pathArr.reduce((acc, curr) => acc[curr], obj); } catch { return undefined; } }; export const useTable = (inputData: T[] = [], config: Partial>) => { const [search, setSearch] = useState(''); const [sort, setSort] = useState( config.defaultSort || { field: 'createdAt' as NestedKey, order: 'ASC' }, ); const [page, setPage] = useState(0); const [perPage, setPerPage] = useState(config.limit || 10); const initialConfig: UseTableConfig = useMemo( () => ({ defaultSort: { field: 'createdAt' as NestedKey, order: 'ASC' }, limit: config.limit || 10, searchFields: [], filterFn: () => true, sortFn: (fa: T, fb: T) => { const { field, order } = sort || {}; if (!field || !order) return 0; const [a, b] = [getNestedObjectValue(fa, field), getNestedObjectValue(fb, field)]; if(typeof a ==='string' && typeof b ==='string') return sort.order === 'ASC' ? a.localeCompare(b, undefined, { sensitivity: 'base' }) : b.localeCompare(a, undefined, { sensitivity: 'base' }) if (sort.order === 'DESC') return (a || 0) > (b || 0) ? -1 : 1; return (a || 0) < (b || 0) ? -1 : 1; }, }), [sort], ); const cfg = useMemo(() => ({ ...initialConfig, ...config }), [initialConfig, config]); const onPageChange = useCallback((npage: number) => setPage(npage), []); const applySearch = useCallback((query: string) => { if (cfg.searchFields) { setPage(0); setSearch(query); } }, []); const onRowsPerPageChange = useCallback((rows: number) => setPerPage(rows), []); const data = useMemo(() => { const filtered = inputData .filter(i => cfg.searchFields && cfg.searchFields.length ? cfg.searchFields.some(field => getNestedObjectValue(i, field)?.toString().toLowerCase().includes(search.toLowerCase()), ) : true, ) .filter(cfg.filterFn); const sorted = filtered.sort(cfg.sortFn); return sorted.filter(cfg.filterFn); }, [inputData, search, cfg]); const cursor = useMemo( () => ({ page, perPage, hasNextPage: page < Math.ceil(data.length / perPage) - 1, hasPreviousPage: page > 0, totalPages: Math.ceil(data.length / perPage), total: data.length, }), [page, data, perPage], ); const returnData = useMemo( () => data.slice(cursor.page * cursor.perPage, cursor.page * cursor.perPage + cursor.perPage), [cursor, data], ); return { cursor, onRowsPerPageChange, paginatedData: returnData, allFoundData: data, onPageChange, applySearch, sort, setSort, }; };