2022-01-13 17:06:32 +00:00
/* eslint-disable react/prop-types */
// disable this rule as it was throwing an error in Header and Cell component
// definitions for the selection row for some reason when we dont really need it.
2021-08-18 23:58:56 +00:00
import React , { useMemo , useEffect , useCallback , useContext } from "react" ;
import { TableContext } from "context/table" ;
2021-09-10 19:06:37 +00:00
import classnames from "classnames" ;
2021-10-18 19:14:24 +00:00
import {
2022-04-07 15:37:43 +00:00
Column ,
HeaderGroup ,
2021-10-18 19:14:24 +00:00
Row ,
2021-11-09 17:31:28 +00:00
useFilters ,
2022-04-07 15:37:43 +00:00
useGlobalFilter ,
usePagination ,
useRowSelect ,
useSortBy ,
useTable ,
2021-10-18 19:14:24 +00:00
} from "react-table" ;
2022-06-14 22:57:43 +00:00
import { kebabCase , noop , omit , pick } from "lodash" ;
2022-04-07 16:08:00 +00:00
import { useDebouncedCallback } from "use-debounce" ;
2021-07-26 17:07:27 +00:00
2022-04-22 16:45:35 +00:00
import useDeepEffect from "hooks/useDeepEffect" ;
2021-09-29 04:04:58 +00:00
import sort from "utilities/sort" ;
2021-11-15 20:11:22 +00:00
import { AppContext } from "context/app" ;
2021-04-04 12:45:24 +00:00
2021-10-18 19:14:24 +00:00
import Button from "components/buttons/Button" ;
// @ts-ignore
import FleetIcon from "components/icons/FleetIcon" ;
2021-11-07 06:41:09 +00:00
import Spinner from "components/Spinner" ;
2023-04-25 12:44:08 +00:00
import ActionButton from "./ActionButton" ;
import { IActionButtonProps } from "./ActionButton/ActionButton" ;
2021-04-04 12:45:24 +00:00
2022-04-07 19:12:38 +00:00
const baseClass = "data-table-block" ;
2021-04-04 12:45:24 +00:00
2021-07-10 17:29:27 +00:00
interface IDataTableProps {
2022-01-13 17:06:32 +00:00
columns : Column [ ] ;
2021-07-10 17:29:27 +00:00
data : any ;
2022-01-31 22:41:54 +00:00
filters? : Record < string , string | number | boolean > ;
2021-07-10 17:29:27 +00:00
isLoading : boolean ;
2021-08-03 19:42:48 +00:00
manualSortBy? : boolean ;
2021-07-10 17:29:27 +00:00
sortHeader : any ;
sortDirection : any ;
onSort : any ; // TODO: an event type
2021-09-10 19:06:37 +00:00
disableMultiRowSelect : boolean ;
2021-07-10 17:29:27 +00:00
showMarkAllPages : boolean ;
isAllPagesSelected : boolean ; // TODO: make dependent on showMarkAllPages
toggleAllPagesSelected? : any ; // TODO: an event type and make it dependent on showMarkAllPages
resultsTitle : string ;
defaultPageSize : number ;
2023-04-24 13:24:28 +00:00
defaultPageIndex? : number ;
2023-05-11 19:25:56 +00:00
primarySelectAction? : IActionButtonProps ;
2021-07-26 17:07:27 +00:00
secondarySelectActions? : IActionButtonProps [ ] ;
2021-11-05 04:16:42 +00:00
isClientSidePagination? : boolean ;
2023-04-27 13:24:02 +00:00
onClientSidePaginationChange ? : ( pageIndex : number ) = > void ; // Used to set URL to correct path and include page query param
2021-11-09 17:31:28 +00:00
isClientSideFilter? : boolean ;
2022-11-02 18:18:08 +00:00
disableHighlightOnHover? : boolean ;
2021-11-09 17:31:28 +00:00
searchQuery? : string ;
searchQueryColumn? : string ;
selectedDropdownFilter? : string ;
2021-12-22 17:07:12 +00:00
onSelectSingleRow ? : ( value : Row ) = > void ;
onResultsCountChange ? : ( value : number ) = > void ;
2022-01-31 22:24:20 +00:00
renderFooter ? : ( ) = > JSX . Element | null ;
renderPagination ? : ( ) = > JSX . Element | null ;
2022-06-13 23:20:57 +00:00
setExportRows ? : ( rows : Row [ ] ) = > void ;
2021-07-10 17:29:27 +00:00
}
2022-08-18 13:51:31 +00:00
interface IHeaderGroup extends HeaderGroup {
title? : string ;
}
2021-10-18 19:14:24 +00:00
const CLIENT_SIDE_DEFAULT_PAGE_SIZE = 20 ;
2021-04-04 12:45:24 +00:00
// This data table uses react-table for implementation. The relevant documentation of the library
// can be found here https://react-table.tanstack.com/docs/api/useTable
2021-07-10 17:29:27 +00:00
const DataTable = ( {
columns : tableColumns ,
data : tableData ,
2022-01-31 22:41:54 +00:00
filters : tableFilters ,
2021-07-10 17:29:27 +00:00
isLoading ,
2021-08-03 19:42:48 +00:00
manualSortBy = false ,
2021-07-10 17:29:27 +00:00
sortHeader ,
sortDirection ,
onSort ,
2021-09-10 19:06:37 +00:00
disableMultiRowSelect ,
2021-07-10 17:29:27 +00:00
showMarkAllPages ,
isAllPagesSelected ,
toggleAllPagesSelected ,
resultsTitle ,
defaultPageSize ,
2023-04-24 13:24:28 +00:00
defaultPageIndex ,
2023-05-11 19:25:56 +00:00
primarySelectAction ,
2021-07-26 17:07:27 +00:00
secondarySelectActions ,
2021-11-05 04:16:42 +00:00
isClientSidePagination ,
2023-04-27 13:24:02 +00:00
onClientSidePaginationChange ,
2021-11-09 17:31:28 +00:00
isClientSideFilter ,
2022-11-02 18:18:08 +00:00
disableHighlightOnHover ,
2021-11-09 17:31:28 +00:00
searchQuery ,
searchQueryColumn ,
selectedDropdownFilter ,
2021-12-22 17:07:12 +00:00
onSelectSingleRow ,
2021-11-09 17:31:28 +00:00
onResultsCountChange ,
2022-01-31 22:24:20 +00:00
renderFooter ,
renderPagination ,
2022-06-13 23:20:57 +00:00
setExportRows ,
2021-07-26 17:07:27 +00:00
} : IDataTableProps ) : JSX . Element = > {
2021-08-18 23:58:56 +00:00
const { resetSelectedRows } = useContext ( TableContext ) ;
2021-11-15 20:11:22 +00:00
const { isOnlyObserver } = useContext ( AppContext ) ;
2021-08-18 23:58:56 +00:00
2021-04-04 12:45:24 +00:00
const columns = useMemo ( ( ) = > {
return tableColumns ;
} , [ tableColumns ] ) ;
// The table data needs to be ordered by the order we received from the API.
const data = useMemo ( ( ) = > {
return tableData ;
} , [ tableData ] ) ;
Live query performance improvements (#11995)
## Addresses #11856
Improve performance of the rendering of live query results by:
- rendering the table on a set interval instead of with each new result
- preventing redundant rerenders of various sorts
Partial run, with memory leak:
<img width="2552" alt="partial run with memory leak, after smaller
optimizations, before debouncing queryResults"
src="https://github.com/fleetdm/fleet/assets/61553566/5288bffb-6940-43da-9083-59adb4a25916">
Full run after debounce, no memory leak (10x improvement of max JS heap
size):
<img width="2559" alt="full run after debounce, no memory leak"
src="https://github.com/fleetdm/fleet/assets/61553566/be056610-e7a5-4289-a433-1070cf016e83">
**NOTE** - there are further optimizations to try on this page, and the
debounce interval can potentially be shortened to improve UX. In
experimenting with that, it's not immediately clear what a good balance
of UX / performance is. Since the customer seems keen to solve this, I
think we should merge as-is and send them a demo build to confirm this
fixes their problem, then iterate once they've confirmed it does.
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [x] Changes file added for user-visible changes in `changes/`
- [x] Manual QA for all new/changed functionality
---------
Co-authored-by: Lucas Rodriguez <lucas@fleetdm.com>
Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
Co-authored-by: Sarah Gillespie <sarah@fleetdm.com>
2023-06-06 20:23:09 +00:00
const initialSortBy = useMemo ( ( ) = > {
return [ { id : sortHeader , desc : sortDirection === "desc" } ] ;
} , [ sortHeader , sortDirection ] ) ;
2021-05-13 14:30:42 +00:00
const {
headerGroups ,
rows ,
prepareRow ,
selectedFlatRows ,
toggleAllRowsSelected ,
2021-07-10 17:29:27 +00:00
isAllRowsSelected ,
2021-05-13 14:30:42 +00:00
state : tableState ,
2021-10-18 19:14:24 +00:00
page , // Instead of using 'rows', we'll use page,
// which has only the rows for the active page
// The rest of these things are super handy, too ;)
canPreviousPage ,
canNextPage ,
2021-11-09 17:31:28 +00:00
// pageOptions,
// pageCount,
2023-05-08 13:17:19 +00:00
gotoPage ,
2021-10-18 19:14:24 +00:00
nextPage ,
previousPage ,
setPageSize ,
2022-04-07 15:37:43 +00:00
setFilter , // sets a specific column-level filter
setAllFilters , // sets all of the column-level filters; rows are included in filtered results only if each column filter return true
setGlobalFilter , // sets the global filter; this serves as a global free text search across all columns (excluding only those where `disableGlobalFilter: true`)
2021-05-13 14:30:42 +00:00
} = useTable (
2021-04-04 12:45:24 +00:00
{
columns ,
data ,
initialState : {
Live query performance improvements (#11995)
## Addresses #11856
Improve performance of the rendering of live query results by:
- rendering the table on a set interval instead of with each new result
- preventing redundant rerenders of various sorts
Partial run, with memory leak:
<img width="2552" alt="partial run with memory leak, after smaller
optimizations, before debouncing queryResults"
src="https://github.com/fleetdm/fleet/assets/61553566/5288bffb-6940-43da-9083-59adb4a25916">
Full run after debounce, no memory leak (10x improvement of max JS heap
size):
<img width="2559" alt="full run after debounce, no memory leak"
src="https://github.com/fleetdm/fleet/assets/61553566/be056610-e7a5-4289-a433-1070cf016e83">
**NOTE** - there are further optimizations to try on this page, and the
debounce interval can potentially be shortened to improve UX. In
experimenting with that, it's not immediately clear what a good balance
of UX / performance is. Since the customer seems keen to solve this, I
think we should merge as-is and send them a demo build to confirm this
fixes their problem, then iterate once they've confirmed it does.
# Checklist for submitter
If some of the following don't apply, delete the relevant line.
- [x] Changes file added for user-visible changes in `changes/`
- [x] Manual QA for all new/changed functionality
---------
Co-authored-by: Lucas Rodriguez <lucas@fleetdm.com>
Co-authored-by: Jacob Shandling <jacob@fleetdm.com>
Co-authored-by: Sarah Gillespie <sarah@fleetdm.com>
2023-06-06 20:23:09 +00:00
sortBy : initialSortBy ,
2023-04-24 13:24:28 +00:00
pageIndex : defaultPageIndex ,
2021-04-04 12:45:24 +00:00
} ,
disableMultiSort : true ,
2021-08-16 21:02:00 +00:00
disableSortRemove : true ,
2021-08-03 19:42:48 +00:00
manualSortBy ,
2021-08-18 23:58:56 +00:00
// Initializes as false, but changes briefly to true on successful notification
autoResetSelectedRows : resetSelectedRows ,
2022-01-31 22:41:54 +00:00
// Expands the enumerated `filterTypes` for react-table
2022-04-04 16:33:02 +00:00
// (see https://github.com/TanStack/react-table/blob/alpha/packages/react-table/src/filterTypes.ts)
2022-01-31 22:41:54 +00:00
// with custom `filterTypes` defined for this `useTable` instance
filterTypes : React.useMemo (
( ) = > ( {
hasLength : (
// eslint-disable-next-line @typescript-eslint/no-shadow
rows : Row [ ] ,
columnIds : string [ ] ,
filterValue : boolean
) = > {
return ! filterValue
? rows
: rows ? . filter ( ( row ) = > {
return columnIds ? . some ( ( id ) = > row ? . values ? . [ id ] ? . length ) ;
} ) ;
} ,
} ) ,
[ ]
) ,
2022-06-13 23:20:57 +00:00
autoResetFilters : false ,
2022-01-31 22:41:54 +00:00
// Expands the enumerated `sortTypes` for react-table
// (see https://github.com/tannerlinsley/react-table/blob/master/src/sortTypes.js)
// with custom `sortTypes` defined for this `useTable` instance
2021-08-18 14:00:02 +00:00
sortTypes : React.useMemo (
( ) = > ( {
2022-06-08 19:01:38 +00:00
boolean : (
a : { values : Record < string , unknown > } ,
b : { values : Record < string , unknown > } ,
id : string
) = > sort . booleanAsc ( a . values [ id ] , b . values [ id ] ) ,
2022-04-13 16:08:37 +00:00
caseInsensitive : (
a : { values : Record < string , unknown > } ,
b : { values : Record < string , unknown > } ,
id : string
) = > sort . caseInsensitiveAsc ( a . values [ id ] , b . values [ id ] ) ,
2021-08-18 14:00:02 +00:00
2022-04-13 16:08:37 +00:00
dateStrings : (
a : { values : Record < string , string > } ,
b : { values : Record < string , string > } ,
id : string
) = > sort . dateStringsAsc ( a . values [ id ] , b . values [ id ] ) ,
2021-08-18 14:00:02 +00:00
2022-04-13 16:08:37 +00:00
hasLength : (
a : { values : Record < string , unknown [ ] > } ,
b : { values : Record < string , unknown [ ] > } ,
id : string
) = > {
return sort . hasLength ( a . values [ id ] , b . values [ id ] ) ;
2021-08-18 14:00:02 +00:00
} ,
} ) ,
[ ]
) ,
2021-04-04 12:45:24 +00:00
} ,
2022-04-07 15:37:43 +00:00
useGlobalFilter , // order of these hooks matters; here we first apply the global filter (if any); this could be reversed depending on where we want to target performance
useFilters , // react-table applies column-level filters after first applying the global filter (if any)
2021-05-13 14:30:42 +00:00
useSortBy ,
2021-10-18 19:14:24 +00:00
usePagination ,
2021-05-13 14:30:42 +00:00
useRowSelect
2021-04-04 12:45:24 +00:00
) ;
2023-04-27 13:24:02 +00:00
const { sortBy , selectedRowIds , pageIndex } = tableState ;
2021-11-09 17:31:28 +00:00
2022-01-31 22:41:54 +00:00
useEffect ( ( ) = > {
if ( tableFilters ) {
2022-04-07 15:37:43 +00:00
const filtersToSet = tableFilters ;
const global = filtersToSet . global ;
setGlobalFilter ( global ) ;
delete filtersToSet . global ;
const allFilters = Object . entries ( filtersToSet ) . map ( ( [ id , value ] ) = > ( {
2022-01-31 22:41:54 +00:00
id ,
value ,
} ) ) ;
! ! allFilters . length && setAllFilters ( allFilters ) ;
2022-06-13 23:20:57 +00:00
setExportRows && setExportRows ( rows ) ;
2022-01-31 22:41:54 +00:00
}
} , [ tableFilters ] ) ;
2022-06-13 23:20:57 +00:00
useEffect ( ( ) = > {
setExportRows && setExportRows ( rows ) ;
2022-07-14 16:21:26 +00:00
} , [ tableState . filters , rows . length ] ) ;
2022-06-13 23:20:57 +00:00
2021-11-09 17:31:28 +00:00
// Listen for changes to filters if clientSideFilter is enabled
const setDebouncedClientFilter = useDebouncedCallback (
( column : string , query : string ) = > {
setFilter ( column , query ) ;
} ,
300
) ;
useEffect ( ( ) = > {
if ( isClientSideFilter && onResultsCountChange ) {
onResultsCountChange ( rows . length ) ;
}
} , [ isClientSideFilter , onResultsCountChange , rows . length ] ) ;
useEffect ( ( ) = > {
if ( isClientSideFilter && searchQueryColumn ) {
setDebouncedClientFilter ( searchQueryColumn , searchQuery || "" ) ;
}
} , [ searchQuery , searchQueryColumn ] ) ;
useEffect ( ( ) = > {
if ( isClientSideFilter && selectedDropdownFilter ) {
selectedDropdownFilter === "all"
? setDebouncedClientFilter ( "platforms" , "" )
: setDebouncedClientFilter ( "platforms" , selectedDropdownFilter ) ;
}
} , [ selectedDropdownFilter ] ) ;
2021-04-04 12:45:24 +00:00
2021-05-13 14:30:42 +00:00
// This is used to listen for changes to sort. If there is a change
// Then the sortHandler change is fired.
2021-04-04 12:45:24 +00:00
useEffect ( ( ) = > {
const column = sortBy [ 0 ] ;
if ( column !== undefined ) {
2021-04-14 16:52:15 +00:00
if (
column . id !== sortHeader ||
column . desc !== ( sortDirection === "desc" )
) {
2021-04-04 12:45:24 +00:00
onSort ( column . id , column . desc ) ;
}
} else {
onSort ( undefined ) ;
}
2023-05-08 13:17:19 +00:00
if ( isClientSidePagination ) {
gotoPage ( 0 ) ; // Return to page 0 after changing sort clientside
}
2021-05-13 14:30:42 +00:00
} , [ sortBy , sortHeader , onSort , sortDirection ] ) ;
2021-07-10 17:29:27 +00:00
useEffect ( ( ) = > {
if ( isAllPagesSelected ) {
toggleAllRowsSelected ( true ) ;
}
2021-11-09 17:31:28 +00:00
} , [ isAllPagesSelected , toggleAllRowsSelected ] ) ;
useEffect ( ( ) = > {
2022-04-05 20:04:00 +00:00
setPageSize ( defaultPageSize || CLIENT_SIDE_DEFAULT_PAGE_SIZE ) ;
2021-11-09 17:31:28 +00:00
} , [ setPageSize ] ) ;
2021-07-10 17:29:27 +00:00
useDeepEffect ( ( ) = > {
if (
Object . keys ( selectedRowIds ) . length < rows . length &&
toggleAllPagesSelected
) {
toggleAllPagesSelected ( false ) ;
}
} , [ tableState . selectedRowIds , toggleAllPagesSelected ] ) ;
const onToggleAllPagesClick = useCallback ( ( ) = > {
toggleAllPagesSelected ( ) ;
} , [ toggleAllPagesSelected ] ) ;
2021-05-13 14:30:42 +00:00
const onClearSelectionClick = useCallback ( ( ) = > {
toggleAllRowsSelected ( false ) ;
2021-07-10 17:29:27 +00:00
toggleAllPagesSelected ( false ) ;
2021-11-09 17:31:28 +00:00
} , [ toggleAllPagesSelected , toggleAllRowsSelected ] ) ;
2021-04-04 12:45:24 +00:00
2021-09-10 19:06:37 +00:00
const onSingleRowClick = useCallback (
( row ) = > {
if ( disableMultiRowSelect ) {
row . toggleRowSelected ( ) ;
onSelectSingleRow && onSelectSingleRow ( row ) ;
toggleAllRowsSelected ( false ) ;
}
} ,
2021-11-09 17:31:28 +00:00
[ disableMultiRowSelect , onSelectSingleRow , toggleAllRowsSelected ]
2021-09-10 19:06:37 +00:00
) ;
2022-08-18 13:51:31 +00:00
const renderColumnHeader = ( column : IHeaderGroup ) = > {
2022-06-14 22:57:43 +00:00
// if there is a column filter, we want the `onClick` event listener attached
// just to the child title span so that clicking into the column filter input
// doesn't also sort the column
const spanProps = column . Filter
? pick ( column . getSortByToggleProps ( ) , "onClick" )
: { } ;
2022-01-13 17:06:32 +00:00
return (
< div className = "column-header" >
2022-06-14 22:57:43 +00:00
< span { ...spanProps } > { column . render ( "Header" ) } < / span >
2022-01-13 17:06:32 +00:00
{ column . Filter && column . render ( "Filter" ) }
< / div >
) ;
} ;
2021-08-03 20:09:01 +00:00
const renderSelectedCount = ( ) : JSX . Element = > {
return (
< p >
2021-10-04 20:20:56 +00:00
< span >
{ selectedFlatRows . length }
{ isAllPagesSelected && "+" }
< / span > { " " }
selected
2021-08-03 20:09:01 +00:00
< / p >
) ;
} ;
const renderAreAllSelected = ( ) : JSX . Element | null = > {
2021-07-10 17:29:27 +00:00
if ( isAllPagesSelected ) {
return < p > All matching { resultsTitle } are selected < / p > ;
}
if ( isAllRowsSelected ) {
return < p > All { resultsTitle } on this page are selected < / p > ;
}
2021-08-03 20:09:01 +00:00
return null ;
2021-07-10 17:29:27 +00:00
} ;
2021-07-26 17:07:27 +00:00
const renderActionButton = (
actionButtonProps : IActionButtonProps
) : JSX . Element = > {
const {
name ,
onActionButtonClick ,
buttonText ,
targetIds ,
variant ,
hideButton ,
2023-05-30 21:16:47 +00:00
iconSvg ,
2021-08-03 20:09:01 +00:00
iconPosition ,
2023-04-27 15:53:30 +00:00
indicatePremiumFeature ,
2021-07-26 17:07:27 +00:00
} = actionButtonProps ;
return (
< div className = { ` ${ baseClass } __ ${ kebabCase ( name ) } ` } >
< ActionButton
key = { kebabCase ( name ) }
name = { name }
buttonText = { buttonText }
onActionButtonClick = { onActionButtonClick || noop }
targetIds = { targetIds }
variant = { variant }
hideButton = { hideButton }
2023-04-27 15:53:30 +00:00
indicatePremiumFeature = { indicatePremiumFeature }
2023-05-30 21:16:47 +00:00
iconSvg = { iconSvg }
2021-08-03 20:09:01 +00:00
iconPosition = { iconPosition }
2021-07-26 17:07:27 +00:00
/ >
< / div >
) ;
} ;
const renderPrimarySelectAction = ( ) : JSX . Element | null = > {
const targetIds = selectedFlatRows . map ( ( row : any ) = > row . original . id ) ;
const buttonText =
2023-05-11 19:25:56 +00:00
typeof primarySelectAction ? . buttonText === "function"
? primarySelectAction ? . buttonText ( targetIds )
: primarySelectAction ? . buttonText ;
2021-07-26 17:07:27 +00:00
const name = buttonText ? kebabCase ( buttonText ) : "primary-select-action" ;
2021-07-29 19:47:04 +00:00
2021-07-26 17:07:27 +00:00
const actionProps = {
name ,
buttonText : buttonText || "" ,
2023-05-11 19:25:56 +00:00
onActionButtonClick : primarySelectAction?.onActionButtonClick || noop ,
2021-07-26 17:07:27 +00:00
targetIds ,
2023-05-11 19:25:56 +00:00
variant : primarySelectAction?.variant ,
2023-05-30 21:16:47 +00:00
iconSvg : primarySelectAction?.iconSvg ,
2021-07-26 17:07:27 +00:00
} ;
return ! buttonText ? null : renderActionButton ( actionProps ) ;
} ;
const renderSecondarySelectActions = ( ) : JSX . Element [ ] | null = > {
if ( secondarySelectActions ) {
const targetIds = selectedFlatRows . map ( ( row : any ) = > row . original . id ) ;
const buttons = secondarySelectActions . map ( ( actionProps ) = > {
actionProps = { . . . actionProps , targetIds } ;
return renderActionButton ( actionProps ) ;
} ) ;
return buttons ;
}
return null ;
2021-07-15 17:01:52 +00:00
} ;
2021-07-10 17:29:27 +00:00
const shouldRenderToggleAllPages =
Object . keys ( selectedRowIds ) . length >= defaultPageSize &&
showMarkAllPages &&
! isAllPagesSelected ;
2021-07-26 17:07:27 +00:00
2021-11-05 04:16:42 +00:00
const pageOrRows = isClientSidePagination ? page : rows ;
2021-10-18 19:14:24 +00:00
const previousButton = (
< >
< FleetIcon name = "chevronleft" / > Previous
< / >
) ;
const nextButton = (
< >
Next < FleetIcon name = "chevronright" / >
< / >
) ;
2021-11-09 17:31:28 +00:00
2021-11-15 20:11:22 +00:00
const tableStyles = classnames ( {
"data-table__table" : true ,
"is-observer" : isOnlyObserver ,
} ) ;
2021-04-04 12:45:24 +00:00
return (
< div className = { baseClass } >
2021-10-18 17:42:54 +00:00
{ isLoading && (
< div className = { "loading-overlay" } >
< Spinner / >
< / div >
) }
2021-04-14 16:52:15 +00:00
< div className = { "data-table data-table__wrapper" } >
2021-11-15 20:11:22 +00:00
< table className = { tableStyles } >
2021-05-13 14:30:42 +00:00
{ Object . keys ( selectedRowIds ) . length !== 0 && (
< thead className = { "active-selection" } >
< tr { ...headerGroups [ 0 ] .getHeaderGroupProps ( ) } >
< th
2022-04-07 19:12:38 +00:00
className = { "active-selection__checkbox" }
2021-05-13 14:30:42 +00:00
{ . . . headerGroups [ 0 ] . headers [ 0 ] . getHeaderProps (
headerGroups [ 0 ] . headers [ 0 ] . getSortByToggleProps ( )
) }
>
{ headerGroups [ 0 ] . headers [ 0 ] . render ( "Header" ) }
< / th >
< th className = { "active-selection__container" } >
< div className = { "active-selection__inner" } >
2021-08-03 20:09:01 +00:00
{ renderSelectedCount ( ) }
2021-07-10 17:29:27 +00:00
< div className = { "active-selection__inner-left" } >
2021-07-26 17:07:27 +00:00
{ secondarySelectActions && renderSecondarySelectActions ( ) }
2021-07-10 17:29:27 +00:00
< / div >
< div className = { "active-selection__inner-right" } >
2023-05-11 19:25:56 +00:00
{ primarySelectAction && renderPrimarySelectAction ( ) }
2021-07-10 17:29:27 +00:00
< / div >
2021-08-03 20:09:01 +00:00
{ toggleAllPagesSelected && renderAreAllSelected ( ) }
{ shouldRenderToggleAllPages && (
< Button
onClick = { onToggleAllPagesClick }
variant = { "text-link" }
className = { "light-text" }
>
< > Select all matching { resultsTitle } < / >
< / Button >
) }
< Button
onClick = { onClearSelectionClick }
variant = { "text-link" }
>
Clear selection
< / Button >
2021-05-13 14:30:42 +00:00
< / div >
< / th >
< / tr >
< / thead >
) }
2021-04-04 12:45:24 +00:00
< thead >
2021-04-14 16:52:15 +00:00
{ headerGroups . map ( ( headerGroup ) = > (
2021-04-04 12:45:24 +00:00
< tr { ...headerGroup.getHeaderGroupProps ( ) } >
2022-06-14 22:57:43 +00:00
{ headerGroup . headers . map ( ( column ) = > {
2022-08-18 13:51:31 +00:00
let thProps = column . getSortByToggleProps ( {
title : undefined ,
} ) ;
2022-06-14 22:57:43 +00:00
if ( column . Filter ) {
// if there is a column filter, we want the `onClick` event listener attached
// just to the child title span so that clicking into the column filter input
// doesn't also sort the column
thProps = omit ( thProps , "onClick" ) ;
}
return (
< th
2022-09-07 16:58:53 +00:00
key = { column . id }
2022-06-14 22:57:43 +00:00
className = { column . id ? ` ${ column . id } __header ` : "" }
{ . . . thProps }
>
{ renderColumnHeader ( column ) }
< / th >
) ;
} ) }
2021-04-04 12:45:24 +00:00
< / tr >
) ) }
< / thead >
< tbody >
2023-02-17 18:25:28 +00:00
{ pageOrRows . map ( ( row : Row ) = > {
2021-04-04 12:45:24 +00:00
prepareRow ( row ) ;
2021-09-10 19:06:37 +00:00
const rowStyles = classnames ( {
"single-row" : disableMultiRowSelect ,
2022-11-02 18:18:08 +00:00
"disable-highlight" : disableHighlightOnHover ,
2021-09-10 19:06:37 +00:00
} ) ;
2021-04-04 12:45:24 +00:00
return (
2021-09-10 19:06:37 +00:00
< tr
className = { rowStyles }
{ . . . row . getRowProps ( {
// @ts-ignore // TS complains about prop not existing
onClick : ( ) = > {
2022-06-10 18:29:45 +00:00
onSingleRowClick &&
disableMultiRowSelect &&
onSingleRowClick ( row ) ;
2021-09-10 19:06:37 +00:00
} ,
} ) }
>
2021-10-04 20:26:10 +00:00
{ row . cells . map ( ( cell : any ) = > {
2021-04-04 12:45:24 +00:00
return (
2021-11-12 22:45:53 +00:00
< td
2022-09-07 16:58:53 +00:00
key = { cell . column . id }
2021-11-12 22:45:53 +00:00
className = {
cell . column . id ? ` ${ cell . column . id } __cell ` : ""
}
{ . . . cell . getCellProps ( ) }
>
{ cell . render ( "Cell" ) }
< / td >
2021-04-04 12:45:24 +00:00
) ;
} ) }
< / tr >
) ;
2021-04-14 16:52:15 +00:00
} ) }
2021-04-04 12:45:24 +00:00
< / tbody >
< / table >
< / div >
2022-01-31 22:24:20 +00:00
< div className = { ` ${ baseClass } __footer ` } >
{ renderFooter && (
< div className = { ` ${ baseClass } __footer-text ` } > { renderFooter ( ) } < / div >
) }
{ isClientSidePagination ? (
< div className = { ` ${ baseClass } __pagination ` } >
< Button
variant = "unstyled"
2023-04-27 13:24:02 +00:00
onClick = { ( ) = > {
onClientSidePaginationChange &&
onClientSidePaginationChange ( pageIndex - 1 ) ;
previousPage ( ) ;
} }
2022-01-31 22:24:20 +00:00
disabled = { ! canPreviousPage }
>
{ previousButton }
< / Button >
< Button
variant = "unstyled"
2023-04-27 13:24:02 +00:00
onClick = { ( ) = > {
onClientSidePaginationChange &&
onClientSidePaginationChange ( pageIndex + 1 ) ;
nextPage ( ) ;
} }
2022-01-31 22:24:20 +00:00
disabled = { ! canNextPage }
>
{ nextButton }
< / Button >
< / div >
) : (
renderPagination && renderPagination ( )
) }
< / div >
2021-04-04 12:45:24 +00:00
< / div >
) ;
} ;
export default DataTable ;