UNPKG

@utahdts/utah-design-system

Version:
126 lines (113 loc) 4.72 kB
import { castArray, identity, isEqual } from 'lodash-es'; import { useEffect, useRef } from 'react'; import { useImmer } from 'use-immer'; import { useAriaMessaging } from '../../contexts/UtahDesignSystemContext/hooks/useAriaMessaging'; import { chainSorters } from '../../util/chainSorters'; import { notNullMap } from '../../util/notNullMap'; import { valueAtPath } from '../../util/state/valueAtPath'; import { trailingS } from '../../util/trailingS'; import { TableBodyDataRowContext } from './TableBodyDataRowContext'; import { useTableContext } from './hooks/useTableContext'; import { convertRecordsToFilterValue } from './util/convertRecordsToFilterValue'; import { createTableFilterFunctions } from './util/createTableFilterFunctions'; import { filterTableRecords } from './util/filterTableRecords'; /** * @template RecordT * @param {object} props * @param {import('react').ReactNode} props.children * @param {string} props.recordIdField * @param {(RecordT & object)[]} props.records * @returns {import('react').JSX.Element[] | null} */ export function TableBodyData({ children, recordIdField, records, }) { const timer = useRef(NaN); const { addPoliteMessage } = useAriaMessaging(); const [recordsForContexts, setRecordsForContexts] = useImmer(/** @type {(RecordT & object)[] | null} */(null)); const { state: { currentSortingOrderIsDefault, filterValues, pagination, sortingRules, tableSortingFieldPath, tableSortingFieldPaths, }, setBodyData, } = useTableContext(); const previousFilterValues = useRef(filterValues.value); const [paginatedRecords, setPaginatedRecords] = useImmer(/** @type {{record: any, recordIndex: number, records: any}[]} */([])); useEffect( () => { let newRecordsForContext = records?.map((record, recordIndex) => ({ record, recordIndex, records })); // apply sorting if a column is selected if (tableSortingFieldPath || tableSortingFieldPaths) { const sorters = castArray(tableSortingFieldPaths || tableSortingFieldPath) .map((sortingValue) => sortingRules[sortingValue ?? '']?.sorter) .filter(identity) .map(notNullMap); newRecordsForContext.sort(chainSorters(sorters, newRecordsForContext)); } const filterRules = createTableFilterFunctions(filterValues.value); // convert record values to test to a "safeString" so that it can be compared with the filter rule const recordsToFilter = convertRecordsToFilterValue(newRecordsForContext, filterValues.value); // try the filter rules to see if each record should remain visible // @ts-expect-error newRecordsForContext = filterTableRecords(recordsToFilter, filterRules); let paginationBeginIndex = ( pagination ? pagination.currentPageIndex * pagination.itemsPerPage : 0 ); if (paginationBeginIndex >= newRecordsForContext.length) { paginationBeginIndex = 0; } const paginationEndIndex = ( pagination ? paginationBeginIndex + pagination.itemsPerPage : newRecordsForContext.length ); setPaginatedRecords(newRecordsForContext.slice(paginationBeginIndex, paginationEndIndex)); // create forContexts once for the context provider to avoid recreating objects // @ts-expect-error setRecordsForContexts(newRecordsForContext); // register the current data with the TableContext for filtering and other table global data users setBodyData(records, newRecordsForContext); // only trigger the timer when the filters have changed if (!isEqual(filterValues.value, previousFilterValues.current)) { if (timer.current) { clearTimeout(timer.current); timer.current = NaN; } timer.current = window.setTimeout(() => { addPoliteMessage(`${newRecordsForContext.length} record${trailingS(newRecordsForContext.length)} shown after filtering`); }, 1500); previousFilterValues.current = filterValues.value; } }, [ currentSortingOrderIsDefault, filterValues, pagination, records, sortingRules, tableSortingFieldPath, tableSortingFieldPaths, ] ); return ( recordsForContexts?.length ? ( // repeat the Row/Cell templates for each record paginatedRecords?.map((recordForContext) => ( <TableBodyDataRowContext.Provider value={recordForContext} key={`table-body-data-${valueAtPath({ object: recordForContext.record, path: recordIdField })}`}> {children} </TableBodyDataRowContext.Provider> )) ) : null ); }