mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-16 04:40:32 +00:00
re-structure to monorepo.
This commit is contained in:
30
packages/webapp/src/components/Datatable/CellForceWidth.tsx
Normal file
30
packages/webapp/src/components/Datatable/CellForceWidth.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { get } from 'lodash';
|
||||
|
||||
import { getForceWidth } from '@/utils';
|
||||
|
||||
export function CellForceWidth({
|
||||
value,
|
||||
column: { forceWidthAccess },
|
||||
row: { original },
|
||||
}) {
|
||||
const forceWidthValue = forceWidthAccess
|
||||
? get(original, forceWidthAccess)
|
||||
: value;
|
||||
|
||||
return <ForceWidth forceValue={forceWidthValue}>{value}</ForceWidth>;
|
||||
}
|
||||
|
||||
export function ForceWidth({ children, forceValue }) {
|
||||
const forceWidthValue = forceValue || children;
|
||||
|
||||
return (
|
||||
<span
|
||||
className={'force-width'}
|
||||
style={{ minWidth: getForceWidth(forceWidthValue) }}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
6
packages/webapp/src/components/Datatable/Cells.tsx
Normal file
6
packages/webapp/src/components/Datatable/Cells.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
|
||||
export function CellTextSpan({ cell: { value } }) {
|
||||
return (<span class="cell-text">{ value }</span>)
|
||||
}
|
||||
231
packages/webapp/src/components/Datatable/DataTable.tsx
Normal file
231
packages/webapp/src/components/Datatable/DataTable.tsx
Normal file
@@ -0,0 +1,231 @@
|
||||
// @ts-nocheck
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import {
|
||||
useTable,
|
||||
useExpanded,
|
||||
useRowSelect,
|
||||
usePagination,
|
||||
useResizeColumns,
|
||||
useSortBy,
|
||||
useFlexLayout,
|
||||
useAsyncDebounce,
|
||||
} from 'react-table';
|
||||
import { useSticky } from 'react-table-sticky';
|
||||
|
||||
import { useUpdateEffect } from '@/hooks';
|
||||
import { saveInvoke } from '@/utils';
|
||||
|
||||
import '@/style/components/DataTable/DataTable.scss';
|
||||
|
||||
import TableNoResultsRow from './TableNoResultsRow';
|
||||
import TableLoadingRow from './TableLoading';
|
||||
import TableHeader from './TableHeader';
|
||||
import TablePage from './TablePage';
|
||||
import TableFooter from './TableFooter';
|
||||
import TableRow from './TableRow';
|
||||
import TableRows from './TableRows';
|
||||
import TableCell from './TableCell';
|
||||
import TableTBody from './TableTBody';
|
||||
import TableContext from './TableContext';
|
||||
import TablePagination from './TablePagination';
|
||||
import TableWrapper from './TableWrapper';
|
||||
|
||||
import TableIndeterminateCheckboxRow from './TableIndeterminateCheckboxRow';
|
||||
import TableIndeterminateCheckboxHeader from './TableIndeterminateCheckboxHeader';
|
||||
|
||||
import { useResizeObserver } from './utils';
|
||||
|
||||
/**
|
||||
* Datatable component.
|
||||
*/
|
||||
export function DataTable(props) {
|
||||
const {
|
||||
columns,
|
||||
data,
|
||||
|
||||
onFetchData,
|
||||
|
||||
onSelectedRowsChange,
|
||||
manualSortBy = false,
|
||||
manualPagination = true,
|
||||
selectionColumn = false,
|
||||
expandSubRows = true,
|
||||
expanded = {},
|
||||
rowClassNames,
|
||||
payload,
|
||||
expandable = false,
|
||||
noInitialFetch = false,
|
||||
|
||||
pagesCount: controlledPageCount,
|
||||
|
||||
// Pagination props.
|
||||
initialPageIndex = 0,
|
||||
initialPageSize = 10,
|
||||
|
||||
updateDebounceTime = 200,
|
||||
selectionColumnWidth = 42,
|
||||
|
||||
autoResetPage,
|
||||
autoResetExpanded,
|
||||
autoResetGroupBy,
|
||||
autoResetSelectedRows,
|
||||
autoResetSortBy,
|
||||
autoResetFilters,
|
||||
autoResetRowState,
|
||||
|
||||
// Components
|
||||
TableHeaderRenderer,
|
||||
TablePageRenderer,
|
||||
TableWrapperRenderer,
|
||||
TableTBodyRenderer,
|
||||
TablePaginationRenderer,
|
||||
TableFooterRenderer,
|
||||
|
||||
onColumnResizing,
|
||||
initialColumnsWidths,
|
||||
|
||||
...restProps
|
||||
} = props;
|
||||
|
||||
const selectionColumnObj = {
|
||||
id: 'selection',
|
||||
disableResizing: true,
|
||||
minWidth: selectionColumnWidth,
|
||||
width: selectionColumnWidth,
|
||||
maxWidth: selectionColumnWidth,
|
||||
skeletonWidthMin: 100,
|
||||
// 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,
|
||||
columnResizing: {
|
||||
columnWidths: initialColumnsWidths || {},
|
||||
},
|
||||
},
|
||||
manualPagination,
|
||||
pageCount: controlledPageCount,
|
||||
getSubRows: (row) => row.children,
|
||||
manualSortBy,
|
||||
expandSubRows,
|
||||
payload,
|
||||
|
||||
autoResetPage,
|
||||
autoResetExpanded,
|
||||
autoResetGroupBy,
|
||||
autoResetSelectedRows,
|
||||
autoResetSortBy,
|
||||
autoResetFilters,
|
||||
autoResetRowState,
|
||||
|
||||
...restProps,
|
||||
},
|
||||
useSortBy,
|
||||
useExpanded,
|
||||
useResizeColumns,
|
||||
useFlexLayout,
|
||||
useSticky,
|
||||
usePagination,
|
||||
useRowSelect,
|
||||
(hooks) => {
|
||||
hooks.visibleColumns.push((columns) => [
|
||||
// Let's make a column for selection
|
||||
...(selectionColumn ? [selectionColumnObj] : []),
|
||||
...columns,
|
||||
]);
|
||||
},
|
||||
);
|
||||
|
||||
const {
|
||||
selectedFlatRows,
|
||||
state: { pageIndex, pageSize, sortBy, selectedRowIds },
|
||||
} = table;
|
||||
|
||||
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]);
|
||||
|
||||
// Column resizing observer.
|
||||
useResizeObserver(table.state, (current, columnWidth, columnsResizing) => {
|
||||
onColumnResizing && onColumnResizing(current, columnWidth, columnsResizing);
|
||||
});
|
||||
|
||||
return (
|
||||
<TableContext.Provider value={{ table, props }}>
|
||||
<TableWrapperRenderer>
|
||||
<TableHeaderRenderer />
|
||||
|
||||
<TableTBodyRenderer>
|
||||
<TablePageRenderer />
|
||||
</TableTBodyRenderer>
|
||||
|
||||
<TableFooterRenderer />
|
||||
</TableWrapperRenderer>
|
||||
|
||||
<TablePaginationRenderer />
|
||||
</TableContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
DataTable.defaultProps = {
|
||||
pagination: false,
|
||||
hidePaginationNoPages: true,
|
||||
hideTableHeader: false,
|
||||
|
||||
size: null,
|
||||
spinnerProps: { size: 30 },
|
||||
|
||||
expandToggleColumn: 1,
|
||||
expandColumnSpace: 0.8,
|
||||
|
||||
autoResetPage: true,
|
||||
autoResetExpanded: true,
|
||||
autoResetGroupBy: true,
|
||||
autoResetSelectedRows: true,
|
||||
autoResetSortBy: true,
|
||||
autoResetFilters: true,
|
||||
autoResetRowState: true,
|
||||
|
||||
TableHeaderRenderer: TableHeader,
|
||||
TableFooterRenderer: TableFooter,
|
||||
TableLoadingRenderer: TableLoadingRow,
|
||||
TablePageRenderer: TablePage,
|
||||
TableRowsRenderer: TableRows,
|
||||
TableRowRenderer: TableRow,
|
||||
TableCellRenderer: TableCell,
|
||||
TableWrapperRenderer: TableWrapper,
|
||||
TableTBodyRenderer: TableTBody,
|
||||
TablePaginationRenderer: TablePagination,
|
||||
TableNoResultsRowRenderer: TableNoResultsRow,
|
||||
|
||||
noResults: '',
|
||||
payload: {},
|
||||
};
|
||||
116
packages/webapp/src/components/Datatable/DatatableEditable.tsx
Normal file
116
packages/webapp/src/components/Datatable/DatatableEditable.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { DataTable } from './DataTable';
|
||||
|
||||
/**
|
||||
* Editable datatable.
|
||||
*/
|
||||
export function DataTableEditable({
|
||||
totalRow = false,
|
||||
actions,
|
||||
name,
|
||||
...tableProps
|
||||
}) {
|
||||
return (
|
||||
<DatatableEditableRoot>
|
||||
<DataTable {...tableProps} />
|
||||
</DatatableEditableRoot>
|
||||
);
|
||||
}
|
||||
|
||||
const DatatableEditableRoot = styled.div`
|
||||
.bp3-form-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.table {
|
||||
border: 1px solid #d2dce2;
|
||||
border-radius: 5px;
|
||||
background-color: #fff;
|
||||
|
||||
.th,
|
||||
.td {
|
||||
border-left: 1px solid #e2e2e2;
|
||||
|
||||
&:first-of-type {
|
||||
border-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.thead {
|
||||
.tr .th {
|
||||
padding: 9px 14px;
|
||||
background-color: #f2f3fb;
|
||||
font-size: 13px;
|
||||
color: #415060;
|
||||
border-bottom: 1px solid #d2dce2;
|
||||
|
||||
&,
|
||||
.inner-resizer {
|
||||
border-left-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
.tbody {
|
||||
.tr .td {
|
||||
border-bottom: 0;
|
||||
border-bottom: 1px solid #d8d8d8;
|
||||
min-height: 38px;
|
||||
padding: 4px 14px;
|
||||
|
||||
&.td-field-type,
|
||||
&.td-button-type {
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
.tr:last-of-type .td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.tr {
|
||||
&:hover .td,
|
||||
.bp3-input {
|
||||
background-color: transparent;
|
||||
}
|
||||
.bp3-form-group:not(.bp3-intent-danger) .bp3-input,
|
||||
.form-group--select-list .bp3-button {
|
||||
border-color: #ffffff;
|
||||
color: #222;
|
||||
border-radius: 3px;
|
||||
text-align: inherit;
|
||||
}
|
||||
.bp3-form-group:not(.bp3-intent-danger) .bp3-input {
|
||||
border-radius: 2px;
|
||||
padding-left: 14px;
|
||||
padding-right: 14px;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2px #116cd0;
|
||||
}
|
||||
}
|
||||
.form-group--select-list .bp3-button {
|
||||
padding-left: 6px;
|
||||
padding-right: 6px;
|
||||
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.form-group--select-list,
|
||||
.bp3-form-group {
|
||||
&.bp3-intent-danger {
|
||||
.bp3-button:not(.bp3-minimal),
|
||||
.bp3-input {
|
||||
border-color: #f7b6b6;
|
||||
}
|
||||
}
|
||||
}
|
||||
.td.actions {
|
||||
.bp3-button {
|
||||
color: #80858f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
230
packages/webapp/src/components/Datatable/Pagination.tsx
Normal file
230
packages/webapp/src/components/Datatable/Pagination.tsx
Normal file
@@ -0,0 +1,230 @@
|
||||
// @ts-nocheck
|
||||
import React, { useReducer, useEffect } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Button, ButtonGroup, Intent, HTMLSelect } from '@blueprintjs/core';
|
||||
import { FormattedMessage as T } from '@/components';
|
||||
import intl from 'react-intl-universal';
|
||||
import PropTypes from 'prop-types';
|
||||
import { range } from 'lodash';
|
||||
import { Icon } from '@/components';
|
||||
|
||||
import '@/style/components/DataTable/Pagination.scss';
|
||||
|
||||
const TYPE = {
|
||||
PAGE_CHANGE: 'PAGE_CHANGE',
|
||||
PAGE_SIZE_CHANGE: 'PAGE_SIZE_CHANGE',
|
||||
INITIALIZE: 'INITIALIZE',
|
||||
};
|
||||
|
||||
const getState = ({ currentPage, size, total }) => {
|
||||
const totalPages = Math.ceil(total / size);
|
||||
const visibleItems = 5;
|
||||
const halfVisibleItems = Math.ceil(visibleItems / 2);
|
||||
|
||||
// create an array of pages to ng-repeat in the pager control
|
||||
let startPage, endPage;
|
||||
if (totalPages <= visibleItems) {
|
||||
// less than {visibleItems} total pages so show
|
||||
startPage = 1;
|
||||
endPage = totalPages;
|
||||
} else {
|
||||
// more than {visibleItems} total pages so calculate start and end pages
|
||||
if (currentPage <= halfVisibleItems) {
|
||||
startPage = 1;
|
||||
endPage = visibleItems;
|
||||
} else if (currentPage + (halfVisibleItems - 1) >= totalPages) {
|
||||
startPage = totalPages - (visibleItems - 1);
|
||||
endPage = totalPages;
|
||||
} else {
|
||||
startPage = currentPage - halfVisibleItems;
|
||||
endPage = currentPage + halfVisibleItems - 1;
|
||||
}
|
||||
}
|
||||
const pages = [...Array(endPage + 1 - startPage).keys()].map(
|
||||
(i) => startPage + i,
|
||||
);
|
||||
|
||||
// Too large or small currentPage
|
||||
let correctCurrentpage = currentPage;
|
||||
if (currentPage > totalPages) correctCurrentpage = totalPages;
|
||||
if (currentPage <= 0) correctCurrentpage = 1;
|
||||
|
||||
return {
|
||||
currentPage: correctCurrentpage,
|
||||
size,
|
||||
total,
|
||||
pages,
|
||||
totalPages,
|
||||
};
|
||||
};
|
||||
|
||||
const reducer = (state, action) => {
|
||||
switch (action.type) {
|
||||
case TYPE.PAGE_CHANGE:
|
||||
return getState({
|
||||
currentPage: action.page,
|
||||
size: state.size,
|
||||
total: state.total,
|
||||
});
|
||||
case TYPE.PAGE_SIZE_CHANGE:
|
||||
return getState({
|
||||
currentPage: state.currentPage,
|
||||
size: action.size,
|
||||
total: state.total,
|
||||
});
|
||||
case TYPE.INITIALIZE:
|
||||
return getState({
|
||||
currentPage: action.page,
|
||||
size: action.size,
|
||||
total: action.total,
|
||||
});
|
||||
default:
|
||||
throw new Error();
|
||||
}
|
||||
};
|
||||
|
||||
export function Pagination({
|
||||
currentPage,
|
||||
total,
|
||||
size,
|
||||
pageSizesOptions = [20, 30, 50, 75, 100, 150],
|
||||
onPageChange,
|
||||
onPageSizeChange,
|
||||
}) {
|
||||
const [state, dispatch] = useReducer(
|
||||
reducer,
|
||||
{ currentPage, total, size },
|
||||
getState,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({
|
||||
type: TYPE.INITIALIZE,
|
||||
total,
|
||||
size,
|
||||
page: currentPage,
|
||||
});
|
||||
}, [total, size, currentPage]);
|
||||
|
||||
return (
|
||||
<div class="pagination">
|
||||
<div class="pagination__buttons-group">
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
disabled={state.currentPage <= 1}
|
||||
onClick={() => {
|
||||
dispatch({ type: 'PAGE_CHANGE', page: state.currentPage - 1 });
|
||||
|
||||
const page = state.currentPage - 1;
|
||||
const { size: pageSize } = state;
|
||||
|
||||
onPageChange({ page, pageSize });
|
||||
}}
|
||||
minimal={true}
|
||||
className={'pagination__item pagination__item--previous'}
|
||||
icon={<Icon icon={'arrow-back-24'} iconSize={12} />}
|
||||
>
|
||||
<T id="previous" />
|
||||
</Button>
|
||||
|
||||
{state.pages.map((page) => (
|
||||
<Button
|
||||
key={page}
|
||||
intent={state.currentPage === page ? Intent.PRIMARY : Intent.NONE}
|
||||
disabled={state.currentPage === page}
|
||||
onClick={() => {
|
||||
dispatch({ type: 'PAGE_CHANGE', page });
|
||||
const { size: pageSize } = state;
|
||||
|
||||
onPageChange({ page, pageSize });
|
||||
}}
|
||||
minimal={true}
|
||||
className={classNames(
|
||||
'pagination__item',
|
||||
'pagination__item--page',
|
||||
{
|
||||
'is-active': state.currentPage === page,
|
||||
},
|
||||
)}
|
||||
>
|
||||
{page}
|
||||
</Button>
|
||||
))}
|
||||
<Button
|
||||
disabled={state.currentPage === state.totalPages}
|
||||
onClick={() => {
|
||||
dispatch({
|
||||
type: 'PAGE_CHANGE',
|
||||
page: state.currentPage + 1,
|
||||
});
|
||||
const page = state.currentPage + 1;
|
||||
const { size: pageSize } = state;
|
||||
|
||||
onPageChange({ page, pageSize });
|
||||
}}
|
||||
minimal={true}
|
||||
className={'pagination__item pagination__item--next'}
|
||||
icon={<Icon icon={'arrow-forward-24'} iconSize={12} />}
|
||||
>
|
||||
<T id="next" />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
|
||||
<div class="pagination__controls">
|
||||
<div class="pagination__goto-control">
|
||||
Go to
|
||||
<HTMLSelect
|
||||
minimal={true}
|
||||
options={range(1, state.totalPages + 1)}
|
||||
value={state.currentPage}
|
||||
onChange={(event) => {
|
||||
const page = parseInt(event.currentTarget.value, 10);
|
||||
const { size: pageSize } = state;
|
||||
|
||||
dispatch({ type: 'PAGE_CHANGE', page });
|
||||
onPageChange({ page, pageSize });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="pagination__pagesize-control">
|
||||
<T id={'page_size'} />
|
||||
<HTMLSelect
|
||||
minimal={true}
|
||||
options={pageSizesOptions}
|
||||
value={size}
|
||||
onChange={(event) => {
|
||||
const pageSize = parseInt(event.currentTarget.value, 10);
|
||||
dispatch({ type: 'PAGE_SIZE_CHANGE', size: pageSize });
|
||||
dispatch({ type: 'PAGE_CHANGE', page: 1 });
|
||||
|
||||
onPageSizeChange({ pageSize, page: 1 });
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pagination__info">
|
||||
{intl.get('showing_current_page_to_total', {
|
||||
currentPage: state.currentPage,
|
||||
totalPages: state.totalPages,
|
||||
total: total,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Pagination.propTypes = {
|
||||
currentPage: PropTypes.number.isRequired,
|
||||
size: PropTypes.number.isRequired,
|
||||
total: PropTypes.number.isRequired,
|
||||
onPageChange: PropTypes.func,
|
||||
onPageSizeChange: PropTypes.func,
|
||||
};
|
||||
|
||||
Pagination.defaultProps = {
|
||||
currentPage: 1,
|
||||
size: 25,
|
||||
};
|
||||
12
packages/webapp/src/components/Datatable/TableBody.tsx
Normal file
12
packages/webapp/src/components/Datatable/TableBody.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
// @ts-nocheck
|
||||
|
||||
|
||||
export default function TableBody({}) {
|
||||
return (
|
||||
<ScrollSyncPane>
|
||||
<div {...getTableBodyProps()} className="tbody">
|
||||
<div class="tbody-inner" style={{ minWidth: totalColumnsWidth }}></div>
|
||||
</div>
|
||||
</ScrollSyncPane>
|
||||
);
|
||||
}
|
||||
115
packages/webapp/src/components/Datatable/TableCell.tsx
Normal file
115
packages/webapp/src/components/Datatable/TableCell.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
// @ts-nocheck
|
||||
import React, { useContext } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { camelCase} from 'lodash';
|
||||
|
||||
import { If, Skeleton } from '@/components';
|
||||
import { useAppIntlContext } from '@/components/AppIntlProvider';
|
||||
import TableContext from './TableContext';
|
||||
import { saveInvoke, ignoreEventFromSelectors } from '@/utils';
|
||||
import { isCellLoading } from './utils';
|
||||
|
||||
const ROW_CLICK_SELECTORS_INGORED = ['.expand-toggle', '.selection-checkbox'];
|
||||
|
||||
/**
|
||||
* Table cell.
|
||||
*/
|
||||
export default function TableCell({ cell, row, index }) {
|
||||
const { index: rowIndex, depth, getToggleRowExpandedProps, isExpanded } = row;
|
||||
const {
|
||||
props: {
|
||||
expandToggleColumn,
|
||||
expandColumnSpace,
|
||||
expandable,
|
||||
cellsLoading,
|
||||
cellsLoadingCoords,
|
||||
onCellClick,
|
||||
},
|
||||
} = useContext(TableContext);
|
||||
|
||||
const isExpandColumn = expandToggleColumn === index;
|
||||
const { skeletonWidthMax = 100, skeletonWidthMin = 40 } = {};
|
||||
|
||||
// Application intl context.
|
||||
const { isRTL } = useAppIntlContext();
|
||||
|
||||
// Detarmines whether the current cell is loading.
|
||||
const cellLoading = isCellLoading(
|
||||
cellsLoading,
|
||||
cellsLoadingCoords,
|
||||
rowIndex,
|
||||
cell.column.id,
|
||||
);
|
||||
|
||||
if (cellLoading) {
|
||||
return (
|
||||
<div
|
||||
{...cell.getCellProps({
|
||||
className: classNames(cell.column.className, 'td'),
|
||||
})}
|
||||
>
|
||||
<Skeleton minWidth={skeletonWidthMin} maxWidth={skeletonWidthMax} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// Handle cell click action.
|
||||
const handleCellClick = (event) => {
|
||||
if (ignoreEventFromSelectors(event, ROW_CLICK_SELECTORS_INGORED)) {
|
||||
return;
|
||||
}
|
||||
saveInvoke(onCellClick, cell, event);
|
||||
};
|
||||
const cellType = camelCase(cell.column.Cell.cellType) || 'text';
|
||||
|
||||
return (
|
||||
<div
|
||||
{...cell.getCellProps({
|
||||
className: classNames(cell.column.className, 'td', {
|
||||
'is-text-overview': cell.column.textOverview,
|
||||
clickable: cell.column.clickable,
|
||||
'align-right': cell.column.align === 'right',
|
||||
'align-center': cell.column.align === 'center',
|
||||
[`td-${cell.column.id}`]: cell.column.id,
|
||||
[`td-${cellType}-type`]: !!cellType,
|
||||
}),
|
||||
onClick: handleCellClick,
|
||||
})}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
{
|
||||
'text-overview': cell.column.textOverview,
|
||||
},
|
||||
'cell-inner',
|
||||
)}
|
||||
style={{
|
||||
[isRTL ? 'paddingRight' : 'paddingLeft']:
|
||||
isExpandColumn && expandable
|
||||
? `${depth * expandColumnSpace}rem`
|
||||
: '',
|
||||
}}
|
||||
>
|
||||
{
|
||||
// Use the row.canExpand and row.getToggleRowExpandedProps prop getter
|
||||
// to build the toggle for expanding a row
|
||||
}
|
||||
<If condition={cell.row.canExpand && expandable && isExpandColumn}>
|
||||
<span
|
||||
{...getToggleRowExpandedProps({
|
||||
className: 'expand-toggle',
|
||||
})}
|
||||
style={{}}
|
||||
>
|
||||
<span
|
||||
className={classNames('expand-arrow', {
|
||||
'is-expanded': isExpanded,
|
||||
})}
|
||||
/>
|
||||
</span>
|
||||
</If>
|
||||
|
||||
{cell.render('Cell')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// @ts-nocheck
|
||||
import { createContext } from 'react';
|
||||
|
||||
export default createContext();
|
||||
21
packages/webapp/src/components/Datatable/TableFastCell.tsx
Normal file
21
packages/webapp/src/components/Datatable/TableFastCell.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
// @ts-nocheck
|
||||
import React, { memo } from 'react';
|
||||
import TableCell from './TableCell';
|
||||
|
||||
export function TableFastCell({ cell, row, index }) {
|
||||
return <TableCell cell={cell} row={row} index={index} />;
|
||||
}
|
||||
|
||||
export default memo(TableFastCell, (prevProps, nextProps) => {
|
||||
if (
|
||||
prevProps.row.canExpand === nextProps.row.canExpand &&
|
||||
prevProps.row.isExpanded === nextProps.row.isExpanded &&
|
||||
prevProps.cell.value === nextProps.cell.value &&
|
||||
prevProps.cell.maxWidth === nextProps.cell.maxWidth &&
|
||||
prevProps.cell.width === nextProps.cell.width
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
37
packages/webapp/src/components/Datatable/TableFooter.tsx
Normal file
37
packages/webapp/src/components/Datatable/TableFooter.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
// @ts-nocheck
|
||||
import React, { useContext } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import TableContext from './TableContext';
|
||||
|
||||
/**
|
||||
* Table footer.
|
||||
*/
|
||||
export default function TableFooter() {
|
||||
const {
|
||||
props: { footer },
|
||||
table: { footerGroups },
|
||||
} = useContext(TableContext);
|
||||
|
||||
// Can't contiunue if the footer is disabled.
|
||||
if (!footer) { return null; }
|
||||
|
||||
return (
|
||||
<div class="tfooter">
|
||||
{footerGroups.map((group) => (
|
||||
<div {...group.getFooterGroupProps({ className: 'tr' })}>
|
||||
{group.headers.map((column) => (
|
||||
<div
|
||||
{...column.getFooterProps({
|
||||
className: classNames(column.className || '', 'td'),
|
||||
})}
|
||||
>
|
||||
<div className={'cell-inner'}>
|
||||
{column.render('Footer')}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
113
packages/webapp/src/components/Datatable/TableHeader.tsx
Normal file
113
packages/webapp/src/components/Datatable/TableHeader.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
// @ts-nocheck
|
||||
import React, { useContext } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { ScrollSyncPane } from 'react-scroll-sync';
|
||||
import { If, MaterialProgressBar } from '@/components';
|
||||
import TableContext from './TableContext';
|
||||
|
||||
function TableHeaderCell({ column, index }) {
|
||||
const {
|
||||
table: { getToggleAllRowsExpandedProps, isAllRowsExpanded },
|
||||
props: { expandable, expandToggleColumn },
|
||||
} = useContext(TableContext);
|
||||
|
||||
return (
|
||||
<div
|
||||
{...column.getHeaderProps({
|
||||
className: classNames(column.className || '', 'th', {
|
||||
[`align-${column.align}`]: column.align,
|
||||
}),
|
||||
})}
|
||||
>
|
||||
<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({
|
||||
className: classNames('cell-inner', {
|
||||
'text-overview': column.textOverview,
|
||||
}),
|
||||
})}
|
||||
>
|
||||
{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>
|
||||
);
|
||||
}
|
||||
|
||||
function TableHeaderGroup({ headerGroup }) {
|
||||
return (
|
||||
<div {...headerGroup.getHeaderGroupProps()} className="tr">
|
||||
{headerGroup.headers.map((column, index) => (
|
||||
<TableHeaderCell key={index} column={column} index={index} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Table header.
|
||||
*/
|
||||
export default function TableHeader() {
|
||||
const {
|
||||
table: { headerGroups, page },
|
||||
props: {
|
||||
TableHeaderSkeletonRenderer,
|
||||
headerLoading,
|
||||
progressBarLoading,
|
||||
hideTableHeader,
|
||||
},
|
||||
} = useContext(TableContext);
|
||||
|
||||
// Can't contiunue if the thead is disabled.
|
||||
if (hideTableHeader) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (headerLoading && TableHeaderSkeletonRenderer) {
|
||||
return <TableHeaderSkeletonRenderer />;
|
||||
}
|
||||
return (
|
||||
<ScrollSyncPane>
|
||||
<div className="thead">
|
||||
<div className={'thead-inner'}>
|
||||
{headerGroups.map((headerGroup, index) => (
|
||||
<TableHeaderGroup key={index} headerGroup={headerGroup} />
|
||||
))}
|
||||
<If condition={progressBarLoading}>
|
||||
<MaterialProgressBar />
|
||||
</If>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollSyncPane>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// @ts-nocheck
|
||||
import React, { useContext } from 'react';
|
||||
import clsx from 'classnames';
|
||||
import TableContext from './TableContext';
|
||||
import { Skeleton } from '@/components';
|
||||
|
||||
function TableHeaderCell({ column }) {
|
||||
const { skeletonWidthMax = 100, skeletonWidthMin = 40 } = column;
|
||||
|
||||
return (
|
||||
<div
|
||||
{...column.getHeaderProps({
|
||||
className: clsx(
|
||||
'th',
|
||||
{
|
||||
[`align-${column.align}`]: column.align,
|
||||
},
|
||||
column.className,
|
||||
),
|
||||
})}
|
||||
>
|
||||
<Skeleton minWidth={skeletonWidthMin} maxWidth={skeletonWidthMax} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Table skeleton rows.
|
||||
*/
|
||||
export function TableSkeletonHeader({}) {
|
||||
const {
|
||||
table: { headerGroups },
|
||||
} = useContext(TableContext);
|
||||
|
||||
return (
|
||||
<div class="thead">
|
||||
{headerGroups.map((headerGroup) => (
|
||||
<div
|
||||
{...headerGroup.getHeaderGroupProps({
|
||||
className: 'tr',
|
||||
})}
|
||||
>
|
||||
{headerGroup.headers.map((column) => (
|
||||
<TableHeaderCell column={column} />
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Checkbox } from '@blueprintjs/core';
|
||||
|
||||
export default function TableIndeterminateCheckboxHeader({
|
||||
getToggleAllRowsSelectedProps,
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
<Checkbox {...getToggleAllRowsSelectedProps()} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Checkbox } from '@blueprintjs/core';
|
||||
import { CellType } from '@/constants';
|
||||
export default function TableIndeterminateCheckboxRow({ row }) {
|
||||
return (
|
||||
<div class="selection-checkbox">
|
||||
<Checkbox {...row.getToggleRowSelectedProps()} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
TableIndeterminateCheckboxRow.cellType = CellType.Field;
|
||||
16
packages/webapp/src/components/Datatable/TableLoading.tsx
Normal file
16
packages/webapp/src/components/Datatable/TableLoading.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { Spinner } from '@blueprintjs/core';
|
||||
|
||||
/**
|
||||
* Table loading component.
|
||||
*/
|
||||
export default function TableLoading({
|
||||
spinnerProps
|
||||
}) {
|
||||
return (
|
||||
<div class="loading">
|
||||
<Spinner {...spinnerProps} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// @ts-nocheck
|
||||
import React, { useContext } from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import TableContext from './TableContext';
|
||||
|
||||
/**
|
||||
* Table no-results row text.
|
||||
*/
|
||||
export default function TableNoResultsRow() {
|
||||
const {
|
||||
props: { noResults },
|
||||
} = useContext(TableContext);
|
||||
|
||||
const noResultText =
|
||||
noResults || intl.get('there_is_no_results_in_the_table');
|
||||
|
||||
return (
|
||||
<div className={'tr no-results'}>
|
||||
<div class="td">{noResultText}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
27
packages/webapp/src/components/Datatable/TablePage.tsx
Normal file
27
packages/webapp/src/components/Datatable/TablePage.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
// @ts-nocheck
|
||||
import React, { useContext } from 'react';
|
||||
import TableContext from './TableContext';
|
||||
|
||||
/**
|
||||
* Table page.
|
||||
*/
|
||||
export default function TablePage() {
|
||||
const {
|
||||
table: { page },
|
||||
props: {
|
||||
spinnerProps,
|
||||
loading,
|
||||
TableRowsRenderer,
|
||||
TableLoadingRenderer,
|
||||
TableNoResultsRowRenderer,
|
||||
},
|
||||
} = useContext(TableContext);
|
||||
|
||||
if (loading) {
|
||||
return <TableLoadingRenderer spinnerProps={spinnerProps} />;
|
||||
}
|
||||
if (page.length === 0) {
|
||||
return <TableNoResultsRowRenderer />;
|
||||
}
|
||||
return (<TableRowsRenderer />);
|
||||
}
|
||||
69
packages/webapp/src/components/Datatable/TablePagination.tsx
Normal file
69
packages/webapp/src/components/Datatable/TablePagination.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
// @ts-nocheck
|
||||
import React, { useCallback, useContext } from 'react';
|
||||
import { If, Pagination } from '@/components';
|
||||
import TableContext from './TableContext';
|
||||
import { saveInvoke } from '@/utils';
|
||||
|
||||
/**
|
||||
* Table pagination.
|
||||
*/
|
||||
export default function TablePagination() {
|
||||
const {
|
||||
table: {
|
||||
gotoPage,
|
||||
setPageSize,
|
||||
pageCount,
|
||||
state: { pageIndex, pageSize },
|
||||
},
|
||||
props: { pagination, loading, onPaginationChange, hidePaginationNoPages },
|
||||
} = useContext(TableContext);
|
||||
|
||||
const triggerOnPaginationChange = useCallback(
|
||||
(payload) => {
|
||||
saveInvoke(onPaginationChange, payload);
|
||||
},
|
||||
[onPaginationChange],
|
||||
);
|
||||
|
||||
// Handles the page changing.
|
||||
const handlePageChange = useCallback(
|
||||
({ page, pageSize }) => {
|
||||
const pageIndex = page - 1;
|
||||
|
||||
gotoPage(pageIndex);
|
||||
triggerOnPaginationChange({ pageIndex, pageSize });
|
||||
},
|
||||
[gotoPage, triggerOnPaginationChange],
|
||||
);
|
||||
|
||||
// Handles the page size changing.
|
||||
const handlePageSizeChange = useCallback(
|
||||
({ pageSize, page }) => {
|
||||
const pageIndex = 0;
|
||||
|
||||
gotoPage(pageIndex);
|
||||
setPageSize(pageSize);
|
||||
|
||||
triggerOnPaginationChange({ pageIndex, pageSize });
|
||||
},
|
||||
[gotoPage, setPageSize, triggerOnPaginationChange],
|
||||
);
|
||||
|
||||
// Detarmines when display the pagination.
|
||||
const showPagination =
|
||||
pagination &&
|
||||
((hidePaginationNoPages && pageCount > 1) || !hidePaginationNoPages) &&
|
||||
!loading;
|
||||
|
||||
return (
|
||||
showPagination && (
|
||||
<Pagination
|
||||
currentPage={pageIndex + 1}
|
||||
total={pageSize * pageCount}
|
||||
size={pageSize}
|
||||
onPageChange={handlePageChange}
|
||||
onPageSizeChange={handlePageSizeChange}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
91
packages/webapp/src/components/Datatable/TableRow.tsx
Normal file
91
packages/webapp/src/components/Datatable/TableRow.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
// @ts-nocheck
|
||||
import React, { useCallback, useContext } from 'react';
|
||||
import { ContextMenu } from '@/components';
|
||||
import classNames from 'classnames';
|
||||
import useContextMenu from 'react-use-context-menu';
|
||||
|
||||
import TableContext from './TableContext';
|
||||
import { saveInvoke, ConditionalWrapper } from '@/utils';
|
||||
|
||||
/**
|
||||
* Table row context wrapper.
|
||||
*/
|
||||
function TableRowContextMenu({ children, row }) {
|
||||
// Table context.
|
||||
const {
|
||||
props: { ContextMenu: ContextMenuContent },
|
||||
table,
|
||||
} = useContext(TableContext);
|
||||
|
||||
const [
|
||||
bindMenu,
|
||||
bindMenuItem,
|
||||
useContextTrigger,
|
||||
{ coords, setVisible, isVisible },
|
||||
] = useContextMenu();
|
||||
|
||||
const [bindTrigger] = useContextTrigger({
|
||||
collect: () => 'Title',
|
||||
});
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
setVisible(false);
|
||||
}, [setVisible]);
|
||||
|
||||
return (
|
||||
<div class="tr-context" {...bindTrigger}>
|
||||
{children}
|
||||
|
||||
<ContextMenu
|
||||
bindMenu={bindMenu}
|
||||
isOpen={isVisible}
|
||||
coords={coords}
|
||||
onClosed={handleClose}
|
||||
>
|
||||
<ContextMenuContent {...table} row={row} />
|
||||
</ContextMenu>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Table row.
|
||||
*/
|
||||
export default function TableRow({ row, className, style }) {
|
||||
const {
|
||||
props: {
|
||||
TableCellRenderer,
|
||||
rowClassNames,
|
||||
ContextMenu: ContextMenuContent,
|
||||
},
|
||||
} = useContext(TableContext);
|
||||
|
||||
return (
|
||||
<div
|
||||
{...row.getRowProps({
|
||||
className: classNames(
|
||||
'tr',
|
||||
{ 'is-expanded': row.isExpanded && row.canExpand },
|
||||
saveInvoke(rowClassNames, row),
|
||||
className,
|
||||
),
|
||||
style,
|
||||
})}
|
||||
>
|
||||
<ConditionalWrapper
|
||||
condition={ContextMenuContent}
|
||||
wrapper={TableRowContextMenu}
|
||||
row={row}
|
||||
>
|
||||
{row.cells.map((cell, index) => (
|
||||
<TableCellRenderer
|
||||
key={index}
|
||||
cell={cell}
|
||||
row={row}
|
||||
index={index + 1}
|
||||
/>
|
||||
))}
|
||||
</ConditionalWrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
18
packages/webapp/src/components/Datatable/TableRows.tsx
Normal file
18
packages/webapp/src/components/Datatable/TableRows.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
// @ts-nocheck
|
||||
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, index) => {
|
||||
prepareRow(row);
|
||||
return <TableRowRenderer key={index} row={row} TableCellRenderer={TableCellRenderer} />;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// @ts-nocheck
|
||||
import React, { useContext } from 'react';
|
||||
import clsx from 'classnames';
|
||||
import TableContext from './TableContext';
|
||||
import { Skeleton } from '../Skeleton';
|
||||
|
||||
/**
|
||||
* Table header cell.
|
||||
*/
|
||||
function TableHeaderCell({ column }) {
|
||||
const { skeletonWidthMax = 100, skeletonWidthMin = 40 } = column;
|
||||
|
||||
return (
|
||||
<div
|
||||
{...column.getHeaderProps({
|
||||
className: clsx(
|
||||
'td',
|
||||
{
|
||||
[`align-${column.align}`]: column.align,
|
||||
},
|
||||
column.className,
|
||||
),
|
||||
})}
|
||||
>
|
||||
<Skeleton minWidth={skeletonWidthMin} maxWidth={skeletonWidthMax} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Table skeleton rows.
|
||||
*/
|
||||
export function TableSkeletonRows({}) {
|
||||
const {
|
||||
table: { headerGroups },
|
||||
} = useContext(TableContext);
|
||||
const skeletonRows = 10;
|
||||
|
||||
return Array.from({ length: skeletonRows }).map(() => {
|
||||
return headerGroups.map((headerGroup) => (
|
||||
<div
|
||||
{...headerGroup.getHeaderGroupProps({
|
||||
className: 'tr',
|
||||
})}
|
||||
>
|
||||
{headerGroup.headers.map((column) => (
|
||||
<TableHeaderCell column={column} />
|
||||
))}
|
||||
</div>
|
||||
));
|
||||
});
|
||||
}
|
||||
22
packages/webapp/src/components/Datatable/TableTBody.tsx
Normal file
22
packages/webapp/src/components/Datatable/TableTBody.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
// @ts-nocheck
|
||||
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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// @ts-nocheck
|
||||
import React, { useContext } from 'react';
|
||||
import { WindowScroller, AutoSizer, List } from 'react-virtualized';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import TableContext from './TableContext';
|
||||
|
||||
/**
|
||||
* Table virtualized list row.
|
||||
*/
|
||||
function TableVirtualizedListRow({ index, isScrolling, isVisible, 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 function TableVirtualizedListRows() {
|
||||
const {
|
||||
table: { page },
|
||||
props: { vListrowHeight, vListOverscanRowCount },
|
||||
} = useContext(TableContext);
|
||||
|
||||
// Dashboard content pane.
|
||||
const dashboardContentPane = React.useMemo(
|
||||
() => document.querySelector(`.${CLASSES.DASHBOARD_CONTENT_PANE}`),
|
||||
[],
|
||||
);
|
||||
|
||||
const rowRenderer = React.useCallback(
|
||||
({ key, ...args }) => <TableVirtualizedListRow {...args} key={key} />,
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<WindowScroller scrollElement={dashboardContentPane}>
|
||||
{({ height, isScrolling, onChildScroll, scrollTop }) => (
|
||||
<AutoSizer disableHeight>
|
||||
{({ width }) => (
|
||||
<List
|
||||
autoHeight={true}
|
||||
className={'List'}
|
||||
height={height}
|
||||
isScrolling={isScrolling}
|
||||
onScroll={onChildScroll}
|
||||
overscanRowCount={vListOverscanRowCount}
|
||||
rowCount={page.length}
|
||||
rowHeight={vListrowHeight}
|
||||
rowRenderer={rowRenderer}
|
||||
scrollTop={scrollTop}
|
||||
width={width}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
)}
|
||||
</WindowScroller>
|
||||
);
|
||||
}
|
||||
48
packages/webapp/src/components/Datatable/TableWrapper.tsx
Normal file
48
packages/webapp/src/components/Datatable/TableWrapper.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
// @ts-nocheck
|
||||
import React, { useContext } from 'react';
|
||||
import clsx 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,
|
||||
styleName,
|
||||
size,
|
||||
},
|
||||
} = useContext(TableContext);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx('bigcapital-datatable', className, {
|
||||
'has-sticky': sticky,
|
||||
'has-pagination': pagination,
|
||||
'is-expandable': expandable,
|
||||
'is-loading': loading,
|
||||
'has-virtualized-rows': virtualizedRows,
|
||||
[`table--${styleName}`]: styleName,
|
||||
})}
|
||||
>
|
||||
<ScrollSync>
|
||||
<div
|
||||
{...getTableProps({ style: { minWidth: 'none' } })}
|
||||
className={clsx('table', {
|
||||
[`table-size--${size}`]: size,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</ScrollSync>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
9
packages/webapp/src/components/Datatable/index.tsx
Normal file
9
packages/webapp/src/components/Datatable/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
// @ts-nocheck
|
||||
export * from './CellForceWidth';
|
||||
export * from './DataTable';
|
||||
export * from './DatatableEditable';
|
||||
export * from './TableHeaderSkeleton'
|
||||
export * from './TableSkeletonRows'
|
||||
export * from './TableVirtualizedRows'
|
||||
export * from './TableFastCell';
|
||||
export * from './Pagination'
|
||||
36
packages/webapp/src/components/Datatable/utils.tsx
Normal file
36
packages/webapp/src/components/Datatable/utils.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
|
||||
export const isCellLoading = (loading, cellsCoords, rowIndex, columnId) => {
|
||||
if (!loading) {
|
||||
return false;
|
||||
}
|
||||
return !cellsCoords
|
||||
? true
|
||||
: cellsCoords.some(
|
||||
(cellCoord) => cellCoord[0] === rowIndex && cellCoord[1] === columnId,
|
||||
);
|
||||
};
|
||||
|
||||
export const useResizeObserver = (state, callback) => {
|
||||
// This Ref will contain the id of the column being resized or undefined
|
||||
const columnResizeRef = React.useRef();
|
||||
|
||||
React.useEffect(() => {
|
||||
// We are interested in calling the resize event only when "state.columnResizing?.isResizingColumn" changes from
|
||||
// a string to undefined, because it indicates that it WAS resizing but it no longer is.
|
||||
if (
|
||||
state.columnResizing &&
|
||||
!state.columnResizing?.isResizingColumn &&
|
||||
columnResizeRef.current
|
||||
) {
|
||||
// Trigger resize event
|
||||
callback(
|
||||
columnResizeRef.current,
|
||||
state.columnResizing.columnWidths[columnResizeRef.current],
|
||||
state.columnResizing,
|
||||
);
|
||||
}
|
||||
columnResizeRef.current = state.columnResizing?.isResizingColumn;
|
||||
}, [callback, state.columnResizing]);
|
||||
};
|
||||
Reference in New Issue
Block a user