mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 12:50:38 +00:00
- Data table sticky header.
- Mini sidebar toggle. - Refactor withDashboard and withDashboardActions.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React, {useEffect, useRef, useCallback} from 'react';
|
||||
import React, { useEffect, useRef, useCallback } from 'react';
|
||||
import {
|
||||
useTable,
|
||||
useExpanded,
|
||||
@@ -6,19 +6,22 @@ import {
|
||||
usePagination,
|
||||
useResizeColumns,
|
||||
useSortBy,
|
||||
useFlexLayout
|
||||
useFlexLayout,
|
||||
} from 'react-table';
|
||||
import { Checkbox, Spinner } from '@blueprintjs/core';
|
||||
import classnames from 'classnames';
|
||||
import { FixedSizeList } from 'react-window'
|
||||
import { FixedSizeList } from 'react-window';
|
||||
import { useSticky } from 'react-table-sticky';
|
||||
import { ScrollSync, ScrollSyncPane } from 'react-scroll-sync';
|
||||
|
||||
import { ConditionalWrapper } from 'utils';
|
||||
import { useUpdateEffect } from 'hooks';
|
||||
import { If } from 'components';
|
||||
|
||||
const IndeterminateCheckbox = React.forwardRef(
|
||||
({ indeterminate, ...rest }, ref) => {
|
||||
return (<Checkbox indeterminate={indeterminate} {...rest} />);
|
||||
}
|
||||
return <Checkbox indeterminate={indeterminate} {...rest} />;
|
||||
},
|
||||
);
|
||||
|
||||
export default function DataTable({
|
||||
@@ -34,11 +37,11 @@ export default function DataTable({
|
||||
noResults = 'This report does not contain any data.',
|
||||
expanded = {},
|
||||
rowClassNames,
|
||||
stickyHeader = true,
|
||||
sticky = false,
|
||||
virtualizedRows = false,
|
||||
fixedSizeHeight = 100,
|
||||
fixedItemSize = 30,
|
||||
payload,
|
||||
payload,
|
||||
expandable = false,
|
||||
expandToggleColumn = 2,
|
||||
noInitialFetch = false,
|
||||
@@ -54,9 +57,10 @@ export default function DataTable({
|
||||
selectedFlatRows,
|
||||
getToggleAllRowsExpandedProps,
|
||||
isAllRowsExpanded,
|
||||
totalColumnsWidth,
|
||||
|
||||
// Get the state from the instance
|
||||
state: { pageIndex, pageSize, sortBy, selectedRowIds},
|
||||
state: { pageIndex, pageSize, sortBy, selectedRowIds },
|
||||
} = useTable(
|
||||
{
|
||||
columns,
|
||||
@@ -67,7 +71,7 @@ export default function DataTable({
|
||||
// This means we'll also have to provide our own
|
||||
// pageCount.
|
||||
// pageCount: controlledPageCount,
|
||||
getSubRows: row => row.children,
|
||||
getSubRows: (row) => row.children,
|
||||
manualSortBy,
|
||||
expandSubRows,
|
||||
payload,
|
||||
@@ -79,111 +83,150 @@ export default function DataTable({
|
||||
usePagination,
|
||||
useResizeColumns,
|
||||
useFlexLayout,
|
||||
hooks => {
|
||||
hooks.visibleColumns.push(columns => [
|
||||
useSticky,
|
||||
(hooks) => {
|
||||
hooks.visibleColumns.push((columns) => [
|
||||
// Let's make a column for selection
|
||||
...(selectionColumn) ? [{
|
||||
id: 'selection',
|
||||
disableResizing: true,
|
||||
minWidth: 42,
|
||||
width: 42,
|
||||
maxWidth: 42,
|
||||
// The header can use the table's getToggleAllRowsSelectedProps method
|
||||
// to render a checkbox
|
||||
Header: ({ getToggleAllRowsSelectedProps }) => (
|
||||
<div>
|
||||
<IndeterminateCheckbox {...getToggleAllRowsSelectedProps()} />
|
||||
</div>
|
||||
),
|
||||
// The cell can use the individual row's getToggleRowSelectedProps method
|
||||
// to the render a checkbox
|
||||
Cell: ({ row }) => (
|
||||
<div>
|
||||
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
|
||||
</div>
|
||||
),
|
||||
className: 'selection',
|
||||
...(typeof selectionColumn === 'object') ? selectionColumn : {},
|
||||
}] : [],
|
||||
...(selectionColumn
|
||||
? [
|
||||
{
|
||||
id: 'selection',
|
||||
disableResizing: true,
|
||||
minWidth: 42,
|
||||
width: 42,
|
||||
maxWidth: 42,
|
||||
// The header can use the table's getToggleAllRowsSelectedProps method
|
||||
// to render a checkbox
|
||||
Header: ({ getToggleAllRowsSelectedProps }) => (
|
||||
<div>
|
||||
<IndeterminateCheckbox
|
||||
{...getToggleAllRowsSelectedProps()}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
// The cell can use the individual row's getToggleRowSelectedProps method
|
||||
// to the render a checkbox
|
||||
Cell: ({ row }) => (
|
||||
<div>
|
||||
<IndeterminateCheckbox
|
||||
{...row.getToggleRowSelectedProps()}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
className: 'selection',
|
||||
...(typeof selectionColumn === 'object' ? selectionColumn : {}),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...columns,
|
||||
])
|
||||
}
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
const isInitialMount = useRef(noInitialFetch);
|
||||
|
||||
|
||||
// When these table states change, fetch new data!
|
||||
useEffect(() => {
|
||||
if (isInitialMount.current) {
|
||||
isInitialMount.current = false;
|
||||
} else {
|
||||
onFetchData && onFetchData({ pageIndex, pageSize, sortBy })
|
||||
onFetchData && onFetchData({ pageIndex, pageSize, sortBy });
|
||||
}
|
||||
}, [pageIndex, pageSize, sortBy,onFetchData]);
|
||||
}, [pageIndex, pageSize, sortBy, onFetchData]);
|
||||
|
||||
useUpdateEffect(() => {
|
||||
onSelectedRowsChange && onSelectedRowsChange(selectedFlatRows);
|
||||
}, [selectedRowIds, onSelectedRowsChange]);
|
||||
|
||||
// Renders table cell.
|
||||
const RenderCell = useCallback(({ row, cell, index }) => (
|
||||
<ConditionalWrapper
|
||||
condition={expandToggleColumn === index && expandable}
|
||||
wrapper={(children) => (<div style={{
|
||||
'padding-left': `${row.depth * 1.5}rem`,
|
||||
}}>{children}</div>)}
|
||||
>
|
||||
{
|
||||
// Use the row.canExpand and row.getToggleRowExpandedProps prop getter
|
||||
// to build the toggle for expanding a row
|
||||
(row.canExpand && expandable && index === expandToggleColumn) && (
|
||||
<span {...row.getToggleRowExpandedProps({
|
||||
className: 'expand-toggle',
|
||||
})}>
|
||||
<span className={classnames({
|
||||
'arrow-down': row.isExpanded,
|
||||
'arrow-right': !row.isExpanded,
|
||||
})} />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
{ cell.render('Cell') }
|
||||
</ConditionalWrapper>
|
||||
), [expandable, expandToggleColumn]);
|
||||
const RenderCell = useCallback(
|
||||
({ row, cell, index }) => (
|
||||
<ConditionalWrapper
|
||||
condition={expandToggleColumn === index && expandable}
|
||||
wrapper={(children) => (
|
||||
<div
|
||||
style={{
|
||||
'padding-left': `${row.depth * 1.5}rem`,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
{
|
||||
// Use the row.canExpand and row.getToggleRowExpandedProps prop getter
|
||||
// to build the toggle for expanding a row
|
||||
row.canExpand && expandable && index === expandToggleColumn && (
|
||||
<span
|
||||
{...row.getToggleRowExpandedProps({
|
||||
className: 'expand-toggle',
|
||||
})}
|
||||
>
|
||||
<span
|
||||
className={classnames({
|
||||
'arrow-down': row.isExpanded,
|
||||
'arrow-right': !row.isExpanded,
|
||||
})}
|
||||
/>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
{cell.render('Cell')}
|
||||
</ConditionalWrapper>
|
||||
),
|
||||
[expandable, expandToggleColumn],
|
||||
);
|
||||
|
||||
// Renders table row.
|
||||
const RenderRow = useCallback(({ style = {}, row }) => {
|
||||
prepareRow(row);
|
||||
const rowClasses = rowClassNames && rowClassNames(row);
|
||||
const RenderRow = useCallback(
|
||||
({ style = {}, row }) => {
|
||||
prepareRow(row);
|
||||
const rowClasses = rowClassNames && rowClassNames(row);
|
||||
|
||||
return (
|
||||
<div {...row.getRowProps({
|
||||
className: classnames('tr', rowClasses), style
|
||||
})}>
|
||||
{row.cells.map((cell, i) => {
|
||||
const index = i + 1;
|
||||
return <div {...cell.getCellProps({
|
||||
className: classnames(cell.column.className || '', 'td'),
|
||||
})}>
|
||||
{ RenderCell({ cell, row, index }) }
|
||||
</div>
|
||||
})}
|
||||
</div>);
|
||||
}, [prepareRow, rowClassNames, expandable, RenderCell, expandToggleColumn]);
|
||||
return (
|
||||
<div
|
||||
{...row.getRowProps({
|
||||
className: classnames('tr', rowClasses),
|
||||
style,
|
||||
})}
|
||||
>
|
||||
{row.cells.map((cell, i) => {
|
||||
const index = i + 1;
|
||||
return (
|
||||
<div
|
||||
{...cell.getCellProps({
|
||||
className: classnames(cell.column.className || '', 'td'),
|
||||
})}
|
||||
>
|
||||
{RenderCell({ cell, row, index })}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[prepareRow, rowClassNames, expandable, RenderCell, expandToggleColumn],
|
||||
);
|
||||
|
||||
// Renders virtualize circle table rows.
|
||||
const RenderVirtualizedRows = useCallback(({ index, style }) => {
|
||||
const row = rows[index];
|
||||
return RenderRow({ row, style });
|
||||
}, [RenderRow, rows]);
|
||||
const RenderVirtualizedRows = useCallback(
|
||||
({ index, style }) => {
|
||||
const row = rows[index];
|
||||
return RenderRow({ row, style });
|
||||
},
|
||||
[RenderRow, rows],
|
||||
);
|
||||
|
||||
const RenderPage = useCallback(({ style, index } = {}) => {
|
||||
return page.map((row, index) => RenderRow({ row }));
|
||||
}, [RenderRow, page]);
|
||||
const RenderPage = useCallback(
|
||||
({ style, index } = {}) => {
|
||||
return page.map((row, index) => RenderRow({ row }));
|
||||
},
|
||||
[RenderRow, page],
|
||||
);
|
||||
|
||||
// Renders fixed size tbody.
|
||||
const RenderTBody = useCallback(() => {
|
||||
return (virtualizedRows) ? (
|
||||
return virtualizedRows ? (
|
||||
<FixedSizeList
|
||||
height={fixedSizeHeight}
|
||||
itemCount={rows.length}
|
||||
@@ -191,79 +234,109 @@ export default function DataTable({
|
||||
>
|
||||
{RenderVirtualizedRows}
|
||||
</FixedSizeList>
|
||||
) : RenderPage();
|
||||
}, [fixedSizeHeight, rows, fixedItemSize, virtualizedRows,
|
||||
RenderVirtualizedRows, RenderPage]);
|
||||
) : (
|
||||
RenderPage()
|
||||
);
|
||||
}, [
|
||||
fixedSizeHeight,
|
||||
rows,
|
||||
fixedItemSize,
|
||||
virtualizedRows,
|
||||
RenderVirtualizedRows,
|
||||
RenderPage,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div className={classnames(
|
||||
'bigcapital-datatable',
|
||||
className,
|
||||
{
|
||||
'has-sticky-header': stickyHeader,
|
||||
<div
|
||||
className={classnames('bigcapital-datatable', className, {
|
||||
'has-sticky': sticky,
|
||||
'is-expandable': expandable,
|
||||
'has-virtualized-rows': virtualizedRows,
|
||||
})}>
|
||||
<div {...getTableProps()} className="table">
|
||||
<div className="thead">
|
||||
{headerGroups.map(headerGroup => (
|
||||
<div {...headerGroup.getHeaderGroupProps()} className="tr">
|
||||
{headerGroup.headers.map((column, index) => (
|
||||
<div {...column.getHeaderProps({
|
||||
className: classnames(column.className || '', 'th'),
|
||||
})}>
|
||||
{(expandable && (index + 1) === expandToggleColumn) && (
|
||||
<span
|
||||
{...getToggleAllRowsExpandedProps()}
|
||||
className="expand-toggle">
|
||||
<span className={classnames({
|
||||
'arrow-down': isAllRowsExpanded,
|
||||
'arrow-right': !isAllRowsExpanded,
|
||||
})} />
|
||||
</span>)}
|
||||
|
||||
<div {...column.getSortByToggleProps()}>
|
||||
{column.render('Header')}
|
||||
|
||||
{column.isSorted && (
|
||||
<span className={classnames({
|
||||
'sort-icon--desc': column.isSortedDesc,
|
||||
'sort-icon--asc': !column.isSortedDesc,
|
||||
}, 'sort-icon')}>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{column.canResize && (
|
||||
})}
|
||||
>
|
||||
<ScrollSync>
|
||||
<div
|
||||
{...getTableProps({ style: { minWidth: 'none' } })}
|
||||
className="table"
|
||||
>
|
||||
<ScrollSyncPane>
|
||||
<div className="thead">
|
||||
{headerGroups.map((headerGroup) => (
|
||||
<div {...headerGroup.getHeaderGroupProps()} className="tr">
|
||||
{headerGroup.headers.map((column, index) => (
|
||||
<div
|
||||
{...column.getResizerProps()}
|
||||
className={`resizer ${
|
||||
column.isResizing ? 'isResizing' : ''
|
||||
}`}>
|
||||
<div class="inner-resizer" />
|
||||
{...column.getHeaderProps({
|
||||
className: classnames(column.className || '', 'th'),
|
||||
})}
|
||||
>
|
||||
{expandable && index + 1 === expandToggleColumn && (
|
||||
<span
|
||||
{...getToggleAllRowsExpandedProps()}
|
||||
className="expand-toggle"
|
||||
>
|
||||
<span
|
||||
className={classnames({
|
||||
'arrow-down': isAllRowsExpanded,
|
||||
'arrow-right': !isAllRowsExpanded,
|
||||
})}
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
|
||||
<div {...column.getSortByToggleProps()}>
|
||||
{column.render('Header')}
|
||||
|
||||
{column.isSorted && (
|
||||
<span
|
||||
className={classnames(
|
||||
{
|
||||
'sort-icon--desc': column.isSortedDesc,
|
||||
'sort-icon--asc': !column.isSortedDesc,
|
||||
},
|
||||
'sort-icon',
|
||||
)}
|
||||
></span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{column.canResize && (
|
||||
<div
|
||||
{...column.getResizerProps()}
|
||||
className={`resizer ${
|
||||
column.isResizing ? 'isResizing' : ''
|
||||
}`}
|
||||
>
|
||||
<div class="inner-resizer" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div {...getTableBodyProps()} className="tbody">
|
||||
<If condition={!loading}>
|
||||
{ RenderTBody() }
|
||||
</If>
|
||||
|
||||
<If condition={!loading && (page.length === 0)}>
|
||||
<div className={'tr no-results'}>
|
||||
<div class="td">{ noResults }</div>
|
||||
</div>
|
||||
</If>
|
||||
</ScrollSyncPane>
|
||||
|
||||
<If condition={loading}>
|
||||
<div class="loading"><Spinner size={spinnerProps.size} /></div>
|
||||
</If>
|
||||
<ScrollSyncPane>
|
||||
<div {...getTableBodyProps()} className="tbody">
|
||||
<div class="tbody-inner" style={{ minWidth: totalColumnsWidth }}>
|
||||
<If condition={!loading}>{RenderTBody()}</If>
|
||||
|
||||
<If condition={!loading && page.length === 0}>
|
||||
<div className={'tr no-results'}>
|
||||
<div class="td">{noResults}</div>
|
||||
</div>
|
||||
</If>
|
||||
|
||||
<If condition={loading}>
|
||||
<div class="loading">
|
||||
<Spinner size={spinnerProps.size} />
|
||||
</div>
|
||||
</If>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollSyncPane>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollSync>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user