mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-17 05:10:31 +00:00
feat: optimize accounts performance.
feat: optimize alerts architecture. feat: optimize datatable architecture. feat: optimize datatable style.
This commit is contained in:
@@ -14,7 +14,7 @@ export default function AccountsSelectList({
|
||||
onAccountSelected,
|
||||
disabled = false,
|
||||
popoverFill = false,
|
||||
filterByRootTypes = [],
|
||||
filterByParentTypes = [],
|
||||
filterByTypes = [],
|
||||
filterByNormal,
|
||||
buttonProps = {}
|
||||
@@ -23,23 +23,23 @@ export default function AccountsSelectList({
|
||||
const filteredAccounts = useMemo(() => {
|
||||
let filteredAccounts = [...accounts];
|
||||
|
||||
if (!isEmpty(filterByRootTypes)) {
|
||||
if (!isEmpty(filterByParentTypes)) {
|
||||
filteredAccounts = filteredAccounts.filter(
|
||||
(account) => filterByRootTypes.indexOf(account.type.root_type) !== -1,
|
||||
(account) => filterByParentTypes.indexOf(account.account_parent_type) !== -1,
|
||||
);
|
||||
}
|
||||
if (!isEmpty(filterByTypes)) {
|
||||
filteredAccounts = filteredAccounts.filter(
|
||||
(account) => filterByTypes.indexOf(account.type.key) !== -1,
|
||||
(account) => filterByTypes.indexOf(account.account_type) !== -1,
|
||||
);
|
||||
}
|
||||
if (!isEmpty(filterByNormal)) {
|
||||
filteredAccounts = filteredAccounts.filter(
|
||||
(account) => filterByTypes.indexOf(account.type.normal) === filterByNormal,
|
||||
(account) => filterByTypes.indexOf(account.account_normal) === filterByNormal,
|
||||
);
|
||||
}
|
||||
return filteredAccounts;
|
||||
}, [accounts, filterByRootTypes, filterByTypes, filterByNormal]);
|
||||
}, [accounts, filterByParentTypes, filterByTypes, filterByNormal]);
|
||||
|
||||
// Find initial account object to set it as default account in initial render.
|
||||
const initialAccount = useMemo(
|
||||
|
||||
@@ -119,7 +119,7 @@ export default function AccountsSuggestField({
|
||||
inputProps={{ placeholder: defaultSelectText }}
|
||||
resetOnClose={true}
|
||||
fill={true}
|
||||
popoverProps={{ minimal: true }}
|
||||
popoverProps={{ minimal: true, boundary: 'window' }}
|
||||
inputValueRenderer={handleInputValueRenderer}
|
||||
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Position, Toaster, Intent } from "@blueprintjs/core";
|
||||
|
||||
const AppToaster = Toaster.create({
|
||||
position: Position.TOP,
|
||||
position: Position.RIGHT_BOTTOM,
|
||||
intent: Intent.WARNING,
|
||||
});
|
||||
|
||||
|
||||
@@ -94,8 +94,7 @@ export default function ContactsSuggestField({
|
||||
selectedItem={selecetedContact}
|
||||
inputProps={{ placeholder: defaultTextSelect }}
|
||||
resetOnClose={true}
|
||||
// fill={true}
|
||||
popoverProps={{ minimal: true }}
|
||||
popoverProps={{ minimal: true, boundary: 'window' }}
|
||||
inputValueRenderer={handleInputValueRenderer}
|
||||
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||
|
||||
@@ -9,7 +9,7 @@ function DashboardSplitPane({
|
||||
sidebarExpended,
|
||||
children
|
||||
}) {
|
||||
const initialSize = 190;
|
||||
const initialSize = 200;
|
||||
|
||||
const [defaultSize, setDefaultSize] = useState(
|
||||
parseInt(localStorage.getItem('dashboard-size'), 10) || initialSize,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
MenuDivider,
|
||||
Button,
|
||||
Popover,
|
||||
Position,
|
||||
} from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from 'react-intl';
|
||||
|
||||
@@ -50,7 +51,7 @@ function DashboardTopbarUser({ requestLogout, user }) {
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover content={userAvatarDropMenu}>
|
||||
<Popover content={userAvatarDropMenu} position={Position.BOTTOM}>
|
||||
<Button>
|
||||
<div className="user-text">
|
||||
{firstLettersArgs(user.first_name, user.last_name)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useRef, useCallback, useMemo } from 'react';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import {
|
||||
useTable,
|
||||
useExpanded,
|
||||
@@ -9,104 +9,101 @@ import {
|
||||
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';
|
||||
import { saveInvoke } from 'utils';
|
||||
|
||||
import 'style/components/DataTable/DataTable.scss';
|
||||
|
||||
const IndeterminateCheckbox = React.forwardRef(
|
||||
({ indeterminate, ...rest }, ref) => {
|
||||
return <Checkbox indeterminate={indeterminate} {...rest} />;
|
||||
},
|
||||
);
|
||||
import TableNoResultsRow from './Datatable/TableNoResultsRow';
|
||||
import TableLoadingRow from './Datatable/TableLoading';
|
||||
import TableHeader from './Datatable/TableHeader';
|
||||
import TablePage from './Datatable/TablePage';
|
||||
import TableRow from './Datatable/TableRow';
|
||||
import TableRows from './Datatable/TableRows';
|
||||
import TableCell from './Datatable/TableCell';
|
||||
import TableTBody from './Datatable/TableTBody';
|
||||
import TableContext from './Datatable/TableContext';
|
||||
import TablePagination from './Datatable/TablePagination';
|
||||
import TableWrapper from './Datatable/TableWrapper';
|
||||
|
||||
export default function DataTable({
|
||||
columns,
|
||||
data,
|
||||
import TableIndeterminateCheckboxRow from './Datatable/TableIndeterminateCheckboxRow';
|
||||
import TableIndeterminateCheckboxHeader from './Datatable/TableIndeterminateCheckboxHeader';
|
||||
|
||||
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,
|
||||
}) {
|
||||
/**
|
||||
* Datatable component.
|
||||
*/
|
||||
export default function DataTable(props) {
|
||||
const {
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
prepareRow,
|
||||
page,
|
||||
rows,
|
||||
selectedFlatRows,
|
||||
getToggleAllRowsExpandedProps,
|
||||
isAllRowsExpanded,
|
||||
totalColumnsWidth,
|
||||
columns,
|
||||
data,
|
||||
|
||||
// page,
|
||||
pageCount,
|
||||
canPreviousPage,
|
||||
canNextPage,
|
||||
gotoPage,
|
||||
previousPage,
|
||||
nextPage,
|
||||
setPageSize,
|
||||
onFetchData,
|
||||
|
||||
// Get the state from the instance
|
||||
state: { pageIndex, pageSize, sortBy, selectedRowIds },
|
||||
} = useTable(
|
||||
onSelectedRowsChange,
|
||||
manualSortBy = false,
|
||||
manualPagination = true,
|
||||
selectionColumn = false,
|
||||
expandSubRows = true,
|
||||
expanded = {},
|
||||
rowClassNames,
|
||||
payload,
|
||||
expandable = false,
|
||||
expandToggleColumn = 2,
|
||||
noInitialFetch = false,
|
||||
|
||||
pagesCount: controlledPageCount,
|
||||
|
||||
// Pagination props.
|
||||
initialPageIndex = 0,
|
||||
initialPageSize = 10,
|
||||
rowContextMenu,
|
||||
expandColumnSpace = 1.5,
|
||||
|
||||
updateDebounceTime = 200,
|
||||
selectionColumnWidth = 42,
|
||||
|
||||
autoResetPage,
|
||||
autoResetExpanded,
|
||||
autoResetGroupBy,
|
||||
autoResetSelectedRows,
|
||||
autoResetSortBy,
|
||||
autoResetFilters,
|
||||
autoResetRowState,
|
||||
|
||||
// Components
|
||||
TableHeaderRenderer,
|
||||
TablePageRenderer,
|
||||
TableWrapperRenderer,
|
||||
TableTBodyRenderer,
|
||||
TablePaginationRenderer,
|
||||
} = props;
|
||||
|
||||
const selectionColumnObj = {
|
||||
id: 'selection',
|
||||
disableResizing: true,
|
||||
minWidth: selectionColumnWidth,
|
||||
width: selectionColumnWidth,
|
||||
maxWidth: selectionColumnWidth,
|
||||
// The header can use the table's getToggleAllRowsSelectedProps method
|
||||
// to render a checkbox
|
||||
Header: TableIndeterminateCheckboxHeader,
|
||||
// The cell can use the individual row's getToggleRowSelectedProps method
|
||||
// to the render a checkbox
|
||||
Cell: TableIndeterminateCheckboxRow,
|
||||
className: 'selection',
|
||||
...(typeof selectionColumn === 'object' ? selectionColumn : {}),
|
||||
};
|
||||
|
||||
const table = useTable(
|
||||
{
|
||||
columns,
|
||||
data,
|
||||
initialState: {
|
||||
pageIndex: initialPageIndex,
|
||||
pageSize: initialPageSize,
|
||||
expanded
|
||||
expanded,
|
||||
},
|
||||
manualPagination,
|
||||
pageCount: controlledPageCount,
|
||||
@@ -133,49 +130,23 @@ export default function DataTable({
|
||||
(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 }) => (
|
||||
<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 ? [selectionColumnObj] : []),
|
||||
...columns,
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
const {
|
||||
selectedFlatRows,
|
||||
state: { pageIndex, pageSize, sortBy, selectedRowIds },
|
||||
} = table;
|
||||
|
||||
const isInitialMount = useRef(noInitialFetch);
|
||||
const onFetchDataDebounced = useAsyncDebounce(
|
||||
(...args) => {
|
||||
saveInvoke(onFetchData, ...args);
|
||||
},
|
||||
updateDebounceTime,
|
||||
);
|
||||
|
||||
const onFetchDataDebounced = useAsyncDebounce((...args) => {
|
||||
saveInvoke(onFetchData, ...args);
|
||||
}, updateDebounceTime);
|
||||
|
||||
// When these table states change, fetch new data!
|
||||
useEffect(() => {
|
||||
if (isInitialMount.current) {
|
||||
@@ -189,285 +160,42 @@ export default function DataTable({
|
||||
saveInvoke(onSelectedRowsChange, selectedFlatRows);
|
||||
}, [selectedRowIds, onSelectedRowsChange]);
|
||||
|
||||
// Renders table cell.
|
||||
const RenderCell = useCallback(
|
||||
({ row, cell, column, index }) => (
|
||||
<ConditionalWrapper
|
||||
condition={expandToggleColumn === index && expandable}
|
||||
wrapper={(children) => (
|
||||
<div
|
||||
style={{
|
||||
'padding-left': `${row.depth * expandColumnSpace}rem`,
|
||||
}}
|
||||
className={'expend-padding'}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
{
|
||||
// Use the row.canExpand and row.getToggleRowExpandedProps prop getter
|
||||
// to build the toggle for expanding a row
|
||||
}
|
||||
<If
|
||||
condition={
|
||||
row.canExpand && expandable && index === expandToggleColumn
|
||||
}
|
||||
>
|
||||
<span
|
||||
{...row.getToggleRowExpandedProps({ className: 'expand-toggle' })}
|
||||
>
|
||||
<span
|
||||
className={classnames({
|
||||
'arrow-down': row.isExpanded,
|
||||
'arrow-right': !row.isExpanded,
|
||||
})}
|
||||
/>
|
||||
</span>
|
||||
</If>
|
||||
|
||||
<ConditionalWrapper
|
||||
condition={cell.column.textOverview}
|
||||
wrapper={(children) => (
|
||||
<span class="text-overview">{ children }</span>
|
||||
)}>
|
||||
{cell.render('Cell')}
|
||||
</ConditionalWrapper>
|
||||
</ConditionalWrapper>
|
||||
),
|
||||
[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 (
|
||||
<div
|
||||
{...row.getRowProps({
|
||||
className: classnames(
|
||||
'tr',
|
||||
{
|
||||
'is-expanded': row.isExpanded && row.canExpand,
|
||||
},
|
||||
rowClasses,
|
||||
),
|
||||
style,
|
||||
})}
|
||||
>
|
||||
{row.cells.map((cell, i) => {
|
||||
const index = i + 1;
|
||||
return (
|
||||
<div
|
||||
{...cell.getCellProps({
|
||||
className: classnames(
|
||||
cell.column.className,
|
||||
'td',
|
||||
{
|
||||
'is-text-overview': cell.column.textOverview,
|
||||
}
|
||||
),
|
||||
})}
|
||||
onContextMenu={handleRowContextMenu(cell, row)}
|
||||
>
|
||||
{RenderCell({ cell, row, index })}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
[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 ? (
|
||||
<FixedSizeList
|
||||
height={fixedSizeHeight}
|
||||
itemCount={rows.length}
|
||||
itemSize={fixedItemSize}
|
||||
>
|
||||
{RenderVirtualizedRows}
|
||||
</FixedSizeList>
|
||||
) : (
|
||||
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 (
|
||||
<div
|
||||
className={classnames('bigcapital-datatable', className, {
|
||||
'has-sticky': sticky,
|
||||
'has-pagination': pagination,
|
||||
'is-expandable': expandable,
|
||||
'is-loading': loading,
|
||||
'has-virtualized-rows': virtualizedRows,
|
||||
})}
|
||||
>
|
||||
<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.getHeaderProps({
|
||||
className: classnames(column.className || '', 'th'),
|
||||
})}
|
||||
>
|
||||
<If
|
||||
condition={
|
||||
expandable && index + 1 === expandToggleColumn
|
||||
}
|
||||
>
|
||||
<span
|
||||
{...getToggleAllRowsExpandedProps()}
|
||||
className="expand-toggle"
|
||||
>
|
||||
<span
|
||||
className={classnames({
|
||||
'arrow-down': isAllRowsExpanded,
|
||||
'arrow-right': !isAllRowsExpanded,
|
||||
})}
|
||||
/>
|
||||
</span>
|
||||
</If>
|
||||
<TableContext.Provider value={{ table, props }}>
|
||||
<TableWrapperRenderer>
|
||||
<TableHeaderRenderer />
|
||||
|
||||
<div {...column.getSortByToggleProps()}>
|
||||
{column.render('Header')}
|
||||
<TableTBodyRenderer>
|
||||
<TablePageRenderer />
|
||||
</TableTBodyRenderer>
|
||||
</TableWrapperRenderer>
|
||||
|
||||
<If condition={column.isSorted}>
|
||||
<span
|
||||
className={classnames(
|
||||
{
|
||||
'sort-icon--desc': column.isSortedDesc,
|
||||
'sort-icon--asc': !column.isSortedDesc,
|
||||
},
|
||||
'sort-icon',
|
||||
)}
|
||||
></span>
|
||||
</If>
|
||||
</div>
|
||||
|
||||
{column.canResize && (
|
||||
<div
|
||||
{...column.getResizerProps()}
|
||||
className={`resizer ${
|
||||
column.isResizing ? 'isResizing' : ''
|
||||
}`}
|
||||
>
|
||||
<div class="inner-resizer" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollSyncPane>
|
||||
|
||||
<ScrollSyncPane>
|
||||
<div {...getTableBodyProps()} className="tbody">
|
||||
<div class="tbody-inner" style={{ minWidth: totalColumnsWidth }}>
|
||||
<Choose>
|
||||
<Choose.When condition={loading}>
|
||||
<div class="loading">
|
||||
<Spinner {...spinnerProps} />
|
||||
</div>
|
||||
</Choose.When>
|
||||
|
||||
<Choose.Otherwise>
|
||||
{RenderTBody()}
|
||||
|
||||
<If condition={page.length === 0}>
|
||||
<div className={'tr no-results'}>
|
||||
<div class="td">{noResults}</div>
|
||||
</div>
|
||||
</If>
|
||||
</Choose.Otherwise>
|
||||
</Choose>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollSyncPane>
|
||||
</div>
|
||||
</ScrollSync>
|
||||
|
||||
<If condition={pagination && !loading}>
|
||||
<Pagination
|
||||
initialPage={pageIndex + 1}
|
||||
total={pageSize * pageCount}
|
||||
size={pageSize}
|
||||
onPageChange={handlePageChange}
|
||||
onPageSizeChange={handlePageSizeChange}
|
||||
/>
|
||||
</If>
|
||||
</div>
|
||||
<TablePaginationRenderer />
|
||||
</TableContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
DataTable.defaultProps = {
|
||||
pagination: false,
|
||||
spinnerProps: { size: 30 },
|
||||
|
||||
autoResetPage: true,
|
||||
autoResetExpanded: true,
|
||||
autoResetGroupBy: true,
|
||||
autoResetSelectedRows: true,
|
||||
autoResetSortBy: true,
|
||||
autoResetFilters: true,
|
||||
autoResetRowState: true,
|
||||
|
||||
TableHeaderRenderer: TableHeader,
|
||||
TableLoadingRenderer: TableLoadingRow,
|
||||
TablePageRenderer: TablePage,
|
||||
TableRowsRenderer: TableRows,
|
||||
TableRowRenderer: TableRow,
|
||||
TableCellRenderer: TableCell,
|
||||
TableWrapperRenderer: TableWrapper,
|
||||
TableTBodyRenderer: TableTBody,
|
||||
TablePaginationRenderer: TablePagination,
|
||||
TableNoResultsRowRenderer: TableNoResultsRow,
|
||||
};
|
||||
11
client/src/components/Datatable/TableBody.js
Normal file
11
client/src/components/Datatable/TableBody.js
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
|
||||
export default function TableBody({}) {
|
||||
return (
|
||||
<ScrollSyncPane>
|
||||
<div {...getTableBodyProps()} className="tbody">
|
||||
<div class="tbody-inner" style={{ minWidth: totalColumnsWidth }}></div>
|
||||
</div>
|
||||
</ScrollSyncPane>
|
||||
);
|
||||
}
|
||||
52
client/src/components/Datatable/TableCell.js
Normal file
52
client/src/components/Datatable/TableCell.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import React, { useContext } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { If } from 'components';
|
||||
import { ConditionalWrapper } from 'utils';
|
||||
import TableContext from './TableContext';
|
||||
|
||||
/**
|
||||
* Tabl cell.
|
||||
*/
|
||||
export default function TableCell({ cell, row, index }) {
|
||||
const {
|
||||
props: { expandToggleColumn, expandable }
|
||||
} = useContext(TableContext);
|
||||
|
||||
return (
|
||||
<div
|
||||
{...cell.getCellProps({
|
||||
className: classNames(cell.column.className, 'td', {
|
||||
'is-text-overview': cell.column.textOverview,
|
||||
}),
|
||||
})}
|
||||
>
|
||||
{
|
||||
// Use the row.canExpand and row.getToggleRowExpandedProps prop getter
|
||||
// to build the toggle for expanding a row
|
||||
}
|
||||
<If
|
||||
condition={
|
||||
cell.row.canExpand && expandable && index === expandToggleColumn
|
||||
}
|
||||
>
|
||||
<span
|
||||
{...row.getToggleRowExpandedProps({ className: 'expand-toggle' })}
|
||||
>
|
||||
<span
|
||||
className={classNames({
|
||||
'arrow-down': row.isExpanded,
|
||||
'arrow-right': !row.isExpanded,
|
||||
})}
|
||||
/>
|
||||
</span>
|
||||
</If>
|
||||
|
||||
<ConditionalWrapper
|
||||
condition={cell.column.textOverview}
|
||||
wrapper={(children) => <span class="text-overview">{children}</span>}
|
||||
>
|
||||
{cell.render('Cell')}
|
||||
</ConditionalWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
3
client/src/components/Datatable/TableContext.js
Normal file
3
client/src/components/Datatable/TableContext.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export default createContext();
|
||||
18
client/src/components/Datatable/TableFastCell.js
Normal file
18
client/src/components/Datatable/TableFastCell.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import React, { memo } from 'react';
|
||||
import TableCell from './TableCell';
|
||||
|
||||
function TableFastCell({ cell, row, index }) {
|
||||
return <TableCell cell={cell} row={row} index={index} />;
|
||||
}
|
||||
|
||||
export default memo(TableFastCell, (prevProps, nextProps) => {
|
||||
if (
|
||||
prevProps.cell.value === nextProps.cell.value &&
|
||||
prevProps.cell.maxWidth === nextProps.cell.maxWidth &&
|
||||
prevProps.cell.width === nextProps.cell.width
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
82
client/src/components/Datatable/TableHeader.js
Normal file
82
client/src/components/Datatable/TableHeader.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import React, { useContext } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { ScrollSyncPane } from 'react-scroll-sync';
|
||||
import { If } from 'components';
|
||||
import TableContext from './TableContext';
|
||||
|
||||
function TableHeaderGroup({ headerGroup }) {
|
||||
const {
|
||||
table: { getToggleAllRowsExpandedProps, isAllRowsExpanded },
|
||||
props: { expandable, expandToggleColumn },
|
||||
} = useContext(TableContext);
|
||||
|
||||
return (
|
||||
<div {...headerGroup.getHeaderGroupProps()} className="tr">
|
||||
{headerGroup.headers.map((column, index) => (
|
||||
<div
|
||||
{...column.getHeaderProps({
|
||||
className: classNames(column.className || '', 'th'),
|
||||
})}
|
||||
>
|
||||
<If condition={expandable && index + 1 === expandToggleColumn}>
|
||||
<span
|
||||
{...getToggleAllRowsExpandedProps()}
|
||||
className="expand-toggle"
|
||||
>
|
||||
<span
|
||||
className={classNames({
|
||||
'arrow-down': isAllRowsExpanded,
|
||||
'arrow-right': !isAllRowsExpanded,
|
||||
})}
|
||||
/>
|
||||
</span>
|
||||
</If>
|
||||
|
||||
<div {...column.getSortByToggleProps()}>
|
||||
{column.render('Header')}
|
||||
|
||||
<If condition={column.isSorted}>
|
||||
<span
|
||||
className={classNames(
|
||||
{
|
||||
'sort-icon--desc': column.isSortedDesc,
|
||||
'sort-icon--asc': !column.isSortedDesc,
|
||||
},
|
||||
'sort-icon',
|
||||
)}
|
||||
></span>
|
||||
</If>
|
||||
</div>
|
||||
|
||||
{column.canResize && (
|
||||
<div
|
||||
{...column.getResizerProps()}
|
||||
className={`resizer ${column.isResizing ? 'isResizing' : ''}`}
|
||||
>
|
||||
<div class="inner-resizer" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Table header.
|
||||
*/
|
||||
export default function TableHeader() {
|
||||
const {
|
||||
table: { headerGroups },
|
||||
} = useContext(TableContext);
|
||||
|
||||
return (
|
||||
<ScrollSyncPane>
|
||||
<div className="thead">
|
||||
{headerGroups.map((headerGroup) => (
|
||||
<TableHeaderGroup headerGroup={headerGroup} />
|
||||
))}
|
||||
</div>
|
||||
</ScrollSyncPane>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import { Checkbox } from '@blueprintjs/core';
|
||||
|
||||
export default function TableIndeterminateCheckboxHeader({
|
||||
getToggleAllRowsSelectedProps,
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<Checkbox {...getToggleAllRowsSelectedProps()} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from 'react';
|
||||
import { Checkbox } from '@blueprintjs/core';
|
||||
|
||||
export default function TableIndeterminateCheckboxRow({ row }) {
|
||||
return (
|
||||
<div>
|
||||
<Checkbox {...row.getToggleRowSelectedProps()} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
15
client/src/components/Datatable/TableLoading.js
Normal file
15
client/src/components/Datatable/TableLoading.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import { Spinner } from '@blueprintjs/core';
|
||||
|
||||
/**
|
||||
* Table loading component.
|
||||
*/
|
||||
export default function TableLoading({
|
||||
spinnerProps
|
||||
}) {
|
||||
return (
|
||||
<div class="loading">
|
||||
<Spinner {...spinnerProps} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
17
client/src/components/Datatable/TableNoResultsRow.js
Normal file
17
client/src/components/Datatable/TableNoResultsRow.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import React, { useContext } from 'react';
|
||||
import TableContext from './TableContext';
|
||||
|
||||
/**
|
||||
* Table no-results row text.
|
||||
*/
|
||||
export default function TableNoResultsRow() {
|
||||
const {
|
||||
props: { noResults }
|
||||
} = useContext(TableContext);
|
||||
|
||||
return (
|
||||
<div className={'tr no-results'}>
|
||||
<div class="td">{ noResults }</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
26
client/src/components/Datatable/TablePage.js
Normal file
26
client/src/components/Datatable/TablePage.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import React, { useContext } from 'react';
|
||||
import TableContext from './TableContext';
|
||||
|
||||
/**
|
||||
* Table page.
|
||||
*/
|
||||
export default function TablePage() {
|
||||
const {
|
||||
table: { page },
|
||||
props: {
|
||||
spinnerProps,
|
||||
loading,
|
||||
TableRowsRenderer,
|
||||
TableLoadingRenderer,
|
||||
TableNoResultsRow,
|
||||
},
|
||||
} = useContext(TableContext);
|
||||
|
||||
if (loading) {
|
||||
return <TableLoadingRenderer spinnerProps={spinnerProps} />;
|
||||
}
|
||||
if (page.length === 0) {
|
||||
return <TableNoResultsRow />;
|
||||
}
|
||||
return (<TableRowsRenderer />);
|
||||
}
|
||||
45
client/src/components/Datatable/TablePagination.js
Normal file
45
client/src/components/Datatable/TablePagination.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import React, { useCallback, useContext } from 'react';
|
||||
import { If, Pagination } from 'components';
|
||||
import TableContext from './TableContext';
|
||||
|
||||
/**
|
||||
* Table pagination.
|
||||
*/
|
||||
export default function TablePagination({}) {
|
||||
const {
|
||||
table: {
|
||||
gotoPage,
|
||||
setPageSize,
|
||||
pageCount,
|
||||
state: { pageIndex, pageSize },
|
||||
},
|
||||
props: { pagination, loading },
|
||||
} = useContext(TableContext);
|
||||
|
||||
const handlePageChange = useCallback(
|
||||
(currentPage) => {
|
||||
gotoPage(currentPage - 1);
|
||||
},
|
||||
[gotoPage],
|
||||
);
|
||||
|
||||
const handlePageSizeChange = useCallback(
|
||||
(pageSize, currentPage) => {
|
||||
gotoPage(0);
|
||||
setPageSize(pageSize);
|
||||
},
|
||||
[gotoPage, setPageSize],
|
||||
);
|
||||
|
||||
return (
|
||||
<If condition={pagination && !loading}>
|
||||
<Pagination
|
||||
initialPage={pageIndex + 1}
|
||||
total={pageSize * pageCount}
|
||||
size={pageSize}
|
||||
onPageChange={handlePageChange}
|
||||
onPageSizeChange={handlePageSizeChange}
|
||||
/>
|
||||
</If>
|
||||
);
|
||||
}
|
||||
51
client/src/components/Datatable/TableRow.js
Normal file
51
client/src/components/Datatable/TableRow.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import React, { useContext } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { ContextMenu } from '@blueprintjs/core';
|
||||
|
||||
import TableContext from './TableContext';
|
||||
import { saveInvoke } from 'utils';
|
||||
|
||||
/**
|
||||
* Table row.
|
||||
*/
|
||||
export default function TableRow({ row, className, style }) {
|
||||
const {
|
||||
props: { TableCellRenderer, rowContextMenu, rowClassNames },
|
||||
} = useContext(TableContext);
|
||||
|
||||
// Handle rendering row context menu.
|
||||
const handleRowContextMenu = (row) => (e) => {
|
||||
if (typeof rowContextMenu === 'function') {
|
||||
e.preventDefault();
|
||||
const tr = e.currentTarget.closest('.tr');
|
||||
tr.classList.add('is-context-menu-active');
|
||||
|
||||
const DropdownEl = rowContextMenu({ row });
|
||||
|
||||
ContextMenu.show(DropdownEl, { left: e.clientX, top: e.clientY }, () => {
|
||||
tr.classList.remove('is-context-menu-active');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
{...row.getRowProps({
|
||||
className: classNames(
|
||||
'tr',
|
||||
{
|
||||
'is-expanded': row.isExpanded && row.canExpand,
|
||||
},
|
||||
saveInvoke(rowClassNames, row),
|
||||
className,
|
||||
),
|
||||
style,
|
||||
onContextMenu: handleRowContextMenu(row)
|
||||
})}
|
||||
>
|
||||
{row.cells.map((cell, index) => (
|
||||
<TableCellRenderer cell={cell} row={row} index={index + 1} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
17
client/src/components/Datatable/TableRows.js
Normal file
17
client/src/components/Datatable/TableRows.js
Normal file
@@ -0,0 +1,17 @@
|
||||
import React, { useContext } from "react";
|
||||
import TableContext from "./TableContext";
|
||||
|
||||
/**
|
||||
* Table rows.
|
||||
*/
|
||||
export default function TableRows() {
|
||||
const {
|
||||
table: { prepareRow, page },
|
||||
props: { TableRowRenderer, TableCellRenderer },
|
||||
} = useContext(TableContext);
|
||||
|
||||
return page.map((row) => {
|
||||
prepareRow(row);
|
||||
return <TableRowRenderer row={row} TableCellRenderer={TableCellRenderer} />;
|
||||
});
|
||||
}
|
||||
21
client/src/components/Datatable/TableTBody.js
Normal file
21
client/src/components/Datatable/TableTBody.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { ScrollSyncPane } from 'react-scroll-sync';
|
||||
import TableContext from './TableContext';
|
||||
|
||||
export default function TableTBody({
|
||||
children
|
||||
}) {
|
||||
const {
|
||||
table: { getTableBodyProps }
|
||||
} = useContext(TableContext);
|
||||
|
||||
return (
|
||||
<ScrollSyncPane>
|
||||
<div {...getTableBodyProps()} className="tbody">
|
||||
<div class="tbody-inner">
|
||||
{ children }
|
||||
</div>
|
||||
</div>
|
||||
</ScrollSyncPane>
|
||||
);
|
||||
}
|
||||
66
client/src/components/Datatable/TableVirtualizedRows.js
Normal file
66
client/src/components/Datatable/TableVirtualizedRows.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { WindowScroller, AutoSizer, List } from 'react-virtualized';
|
||||
import { CLASSES } from 'common/classes';
|
||||
import TableContext from './TableContext';
|
||||
|
||||
function TableVirtualizedListRow({
|
||||
index,
|
||||
isScrolling,
|
||||
isVisible,
|
||||
key,
|
||||
style,
|
||||
}) {
|
||||
const {
|
||||
table: { page, prepareRow },
|
||||
props: { TableRowRenderer },
|
||||
} = useContext(TableContext);
|
||||
|
||||
const row = page[index];
|
||||
prepareRow(row);
|
||||
|
||||
return <TableRowRenderer row={row} style={style} />;
|
||||
}
|
||||
|
||||
/**
|
||||
* Table virtualized list rows.
|
||||
*/
|
||||
export default function TableVirtualizedListRows() {
|
||||
const {
|
||||
table: { page },
|
||||
props: { vListrowHeight, vListOverscanRowCount }
|
||||
} = useContext(TableContext);
|
||||
|
||||
// Dashboard content pane.
|
||||
const dashboardContentPane = document.querySelector(
|
||||
`.${CLASSES.DASHBOARD_CONTENT_PANE}`,
|
||||
);
|
||||
return (
|
||||
<WindowScroller scrollElement={dashboardContentPane}>
|
||||
{({ height, isScrolling, registerChild, onChildScroll, scrollTop }) => (
|
||||
<div className={'WindowScrollerWrapper'}>
|
||||
<AutoSizer disableHeight>
|
||||
{({ width }) => (
|
||||
<div ref={registerChild}>
|
||||
<List
|
||||
autoHeight={true}
|
||||
className={'List'}
|
||||
height={height}
|
||||
isScrolling={isScrolling}
|
||||
onScroll={onChildScroll}
|
||||
overscanRowCount={vListOverscanRowCount}
|
||||
rowCount={page.length}
|
||||
rowHeight={vListrowHeight}
|
||||
rowRenderer={({ ...args }) => {
|
||||
return <TableVirtualizedListRow {...args} />;
|
||||
}}
|
||||
scrollTop={scrollTop}
|
||||
width={width}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
)}
|
||||
</WindowScroller>
|
||||
);
|
||||
}
|
||||
35
client/src/components/Datatable/TableWrapper.js
Normal file
35
client/src/components/Datatable/TableWrapper.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import React, { useContext } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { ScrollSync } from 'react-scroll-sync';
|
||||
import TableContext from './TableContext';
|
||||
|
||||
/**
|
||||
* Table wrapper.
|
||||
*/
|
||||
export default function TableWrapper({ children }) {
|
||||
const {
|
||||
table: { getTableProps },
|
||||
props: { sticky, pagination, loading, expandable, virtualizedRows, className },
|
||||
} = useContext(TableContext);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames('bigcapital-datatable', className, {
|
||||
'has-sticky': sticky,
|
||||
'has-pagination': pagination,
|
||||
'is-expandable': expandable,
|
||||
'is-loading': loading,
|
||||
'has-virtualized-rows': virtualizedRows,
|
||||
})}
|
||||
>
|
||||
<ScrollSync>
|
||||
<div
|
||||
{...getTableProps({ style: { minWidth: 'none' } })}
|
||||
className="table"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</ScrollSync>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -100,7 +100,7 @@ export default function ItemsSuggestField({
|
||||
inputProps={{ placeholder: defautlSelectText }}
|
||||
resetOnClose={true}
|
||||
fill={true}
|
||||
popoverProps={{ minimal: true }}
|
||||
popoverProps={{ minimal: true, boundary: 'window' }}
|
||||
className={classNames(CLASSES.FORM_GROUP_LIST_SELECT, {
|
||||
[CLASSES.SELECT_LIST_FILL_POPOVER]: popoverFill,
|
||||
})}
|
||||
|
||||
@@ -45,6 +45,9 @@ import PageFormBigNumber from './PageFormBigNumber';
|
||||
import AccountsMultiSelect from './AccountsMultiSelect';
|
||||
import CustomersMultiSelect from './CustomersMultiSelect';
|
||||
|
||||
|
||||
import TableFastCell from './Datatable/TableFastCell';
|
||||
|
||||
const Hint = FieldHint;
|
||||
|
||||
export {
|
||||
@@ -93,5 +96,7 @@ export {
|
||||
PageFormBigNumber,
|
||||
AccountsMultiSelect,
|
||||
DataTableEditable,
|
||||
CustomersMultiSelect
|
||||
CustomersMultiSelect,
|
||||
|
||||
TableFastCell,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user