import React, { useEffect, useRef, useCallback, useMemo } from 'react'; import { useTable, useExpanded, useRowSelect, usePagination, useResizeColumns, useSortBy, useFlexLayout, useAsyncDebounce, } from 'react-table'; import { Checkbox, Spinner, ContextMenu } from '@blueprintjs/core'; import classnames from 'classnames'; import { FixedSizeList } from 'react-window'; import { useSticky } from 'react-table-sticky'; import { ScrollSync, ScrollSyncPane } from 'react-scroll-sync'; import { useUpdateEffect } from 'hooks'; import { If, Pagination, Choose } from 'components'; import { ConditionalWrapper, saveInvoke } from 'utils'; const IndeterminateCheckbox = React.forwardRef( ({ indeterminate, ...rest }, ref) => { return ; }, ); export default function DataTable({ columns, data, loading, onFetchData, onSelectedRowsChange, manualSortBy = false, manualPagination = true, selectionColumn = false, expandSubRows = true, className, noResults = 'This report does not contain any data.', expanded = {}, rowClassNames, sticky = false, virtualizedRows = false, fixedSizeHeight = 100, fixedItemSize = 30, payload, expandable = false, expandToggleColumn = 2, noInitialFetch = false, spinnerProps = { size: 30 }, pagination = false, pagesCount: controlledPageCount, // Pagination props. initialPageIndex = 0, initialPageSize = 10, rowContextMenu, expandColumnSpace = 1.5, updateDebounceTime = 200, selectionColumnWidth = 42, // Read this document to know why! https://bit.ly/2Uw9SEc autoResetPage = true, autoResetExpanded = true, autoResetGroupBy = true, autoResetSelectedRows = true, autoResetSortBy = true, autoResetFilters = true, autoResetRowState = true, }) { const { getTableProps, getTableBodyProps, headerGroups, prepareRow, page, rows, selectedFlatRows, getToggleAllRowsExpandedProps, isAllRowsExpanded, totalColumnsWidth, // page, pageCount, canPreviousPage, canNextPage, gotoPage, previousPage, nextPage, setPageSize, // Get the state from the instance state: { pageIndex, pageSize, sortBy, selectedRowIds }, } = useTable( { columns, data, initialState: { pageIndex: initialPageIndex, pageSize: initialPageSize, expanded }, manualPagination, pageCount: controlledPageCount, getSubRows: (row) => row.children, manualSortBy, expandSubRows, payload, autoResetPage, autoResetExpanded, autoResetGroupBy, autoResetSelectedRows, autoResetSortBy, autoResetFilters, autoResetRowState, }, useSortBy, useExpanded, useRowSelect, useResizeColumns, useFlexLayout, useSticky, usePagination, (hooks) => { hooks.visibleColumns.push((columns) => [ // Let's make a column for selection ...(selectionColumn ? [ { id: 'selection', disableResizing: true, minWidth: selectionColumnWidth, width: selectionColumnWidth, maxWidth: selectionColumnWidth, // The header can use the table's getToggleAllRowsSelectedProps method // to render a checkbox Header: ({ getToggleAllRowsSelectedProps }) => (
), // The cell can use the individual row's getToggleRowSelectedProps method // to the render a checkbox Cell: ({ row }) => (
), className: 'selection', ...(typeof selectionColumn === 'object' ? selectionColumn : {}), }, ] : []), ...columns, ]); }, ); const isInitialMount = useRef(noInitialFetch); const onFetchDataDebounced = useAsyncDebounce( (...args) => { saveInvoke(onFetchData, ...args); }, updateDebounceTime, ); // When these table states change, fetch new data! useEffect(() => { if (isInitialMount.current) { isInitialMount.current = false; } else { onFetchDataDebounced({ pageIndex, pageSize, sortBy }); } }, [pageIndex, pageSize, sortBy, onFetchDataDebounced]); useUpdateEffect(() => { saveInvoke(onSelectedRowsChange, selectedFlatRows); }, [selectedRowIds, onSelectedRowsChange]); // Renders table cell. const RenderCell = useCallback( ({ row, cell, index }) => ( (
{children}
)} > { // Use the row.canExpand and row.getToggleRowExpandedProps prop getter // to build the toggle for expanding a row } {cell.render('Cell')}
), [expandable, expandToggleColumn, expandColumnSpace], ); // Handle rendering row context menu. const handleRowContextMenu = useMemo( () => (cell, row) => (e) => { if (typeof rowContextMenu === 'function') { e.preventDefault(); const tr = e.currentTarget.closest('.tr'); tr.classList.add('is-context-menu-active'); const DropdownEl = rowContextMenu(cell, row); ContextMenu.show( DropdownEl, { left: e.clientX, top: e.clientY }, () => { tr.classList.remove('is-context-menu-active'); }, ); } }, [rowContextMenu], ); // Renders table row. const RenderRow = useCallback( ({ style = {}, row }) => { prepareRow(row); const rowClasses = rowClassNames && rowClassNames(row); return (
{row.cells.map((cell, i) => { const index = i + 1; return (
{RenderCell({ cell, row, index })}
); })}
); }, [prepareRow, rowClassNames, RenderCell, handleRowContextMenu], ); // Renders virtualize circle table rows. const RenderVirtualizedRows = useCallback( ({ index, style }) => { const row = rows[index]; return RenderRow({ row, style }); }, [RenderRow, rows], ); // Renders page with multi-rows. const RenderPage = useCallback( ({ style, index } = {}) => { return page.map((row, index) => RenderRow({ row })); }, [RenderRow, page], ); // Renders fixed size tbody. const RenderTBody = useCallback(() => { return virtualizedRows ? ( {RenderVirtualizedRows} ) : ( RenderPage() ); }, [ fixedSizeHeight, rows, fixedItemSize, virtualizedRows, RenderVirtualizedRows, RenderPage, ]); const handlePageChange = useCallback( (currentPage) => { gotoPage(currentPage - 1); }, [gotoPage], ); const handlePageSizeChange = useCallback( (pageSize, currentPage) => { gotoPage(0); setPageSize(pageSize); }, [gotoPage, setPageSize], ); return (
{headerGroups.map((headerGroup) => (
{headerGroup.headers.map((column, index) => (
{column.render('Header')}
{column.canResize && (
)}
))}
))}
{RenderTBody()}
{noResults}
); }