@coreui/react-pro
Version:
UI Components Library for React.js
350 lines (347 loc) • 21.5 kB
JavaScript
import { __rest } from '../../node_modules/tslib/tslib.es6.js';
import React, { forwardRef, useRef, useState, useMemo, useEffect } from 'react';
import PropTypes from 'prop-types';
import { CIcon } from '../../node_modules/@coreui/icons-react/dist/index.esm.js';
import { CElementCover } from '../element-cover/CElementCover.js';
import '../form/CForm.js';
import '../form/CFormCheck.js';
import '../form/CFormControlValidation.js';
import '../form/CFormControlWrapper.js';
import '../form/CFormFeedback.js';
import '../form/CFormFloating.js';
import { CFormInput } from '../form/CFormInput.js';
import { CFormLabel } from '../form/CFormLabel.js';
import '../form/CFormRange.js';
import { CFormSelect } from '../form/CFormSelect.js';
import '../form/CFormSwitch.js';
import '../form/CFormText.js';
import '../form/CFormTextarea.js';
import '../form/CInputGroup.js';
import '../form/CInputGroupText.js';
import { CSmartPagination } from '../smart-pagination/CSmartPagination.js';
import { CTable } from '../table/CTable.js';
import '../table/CTableBody.js';
import '../table/CTableCaption.js';
import { CTableDataCell } from '../table/CTableDataCell.js';
import { CTableFoot } from '../table/CTableFoot.js';
import '../table/CTableHead.js';
import '../table/CTableHeaderCell.js';
import { CTableRow } from '../table/CTableRow.js';
import { CSmartTableBody } from './CSmartTableBody.js';
import { CSmartTableHead } from './CSmartTableHead.js';
import isObjectInArray from '../../utils/isObjectInArray.js';
import { ITEM_INTERNAL_KEYS } from './consts.js';
import { getColumnNames, getColumnNamesFromItems, filterColumns, filterTable, sortItems, isSortable } from './utils.js';
import { cilSwapVertical } from '../../node_modules/@coreui/icons/dist/esm/free/cil-swap-vertical.js';
import { cilArrowTop } from '../../node_modules/@coreui/icons/dist/esm/free/cil-arrow-top.js';
import { cilArrowBottom } from '../../node_modules/@coreui/icons/dist/esm/free/cil-arrow-bottom.js';
import { cilFilterX } from '../../node_modules/@coreui/icons/dist/esm/free/cil-filter-x.js';
const CSmartTable = forwardRef((_a, ref) => {
var { activePage = 1, cleaner, clickableRows, columnFilter, columnFilterValue, // TODO: consider to use only columnFilter prop
columns, columnSorter, elementCover, footer, header = true, items = [], itemsNumber, itemsPerPage = 10, itemsPerPageLabel = 'Items per page:', itemsPerPageOptions = [5, 10, 20, 50], itemsPerPageSelect, loading, noItemsLabel = 'No items found', onActivePageChange, onColumnFilterChange, onFilteredItemsChange, onItemsPerPageChange, onRowChecked, onRowClick, onSelectAll, onSelectedItemsChange, onSorterChange, onTableFilterChange, pagination, paginationProps, scopedColumns, selected, selectable, selectAll = true, sorterValue, sortingIcon = React.createElement(CIcon, { width: 18, icon: cilSwapVertical, key: "csv" }), sortingIconAscending = React.createElement(CIcon, { width: 18, icon: cilArrowTop, key: "cat" }), sortingIconDescending = React.createElement(CIcon, { width: 18, icon: cilArrowBottom, key: "cab" }), tableBodyProps, tableFootProps, tableFilter, tableFilterLabel = 'Filter:', tableFilterPlaceholder = 'type string...', tableFilterValue, tableHeadProps, tableProps } = _a, rest = __rest(_a, ["activePage", "cleaner", "clickableRows", "columnFilter", "columnFilterValue", "columns", "columnSorter", "elementCover", "footer", "header", "items", "itemsNumber", "itemsPerPage", "itemsPerPageLabel", "itemsPerPageOptions", "itemsPerPageSelect", "loading", "noItemsLabel", "onActivePageChange", "onColumnFilterChange", "onFilteredItemsChange", "onItemsPerPageChange", "onRowChecked", "onRowClick", "onSelectAll", "onSelectedItemsChange", "onSorterChange", "onTableFilterChange", "pagination", "paginationProps", "scopedColumns", "selected", "selectable", "selectAll", "sorterValue", "sortingIcon", "sortingIconAscending", "sortingIconDescending", "tableBodyProps", "tableFootProps", "tableFilter", "tableFilterLabel", "tableFilterPlaceholder", "tableFilterValue", "tableHeadProps", "tableProps"]);
const mountedRef = useRef(false);
const [_activePage, setActivePage] = useState(activePage);
const [_items, setItems] = useState([]);
const [_itemsPerPage, setItemsPerPage] = useState(itemsPerPage);
const [_selected, setSelected] = useState([]);
const [columnFilterState, setColumnFilterState] = useState({});
const [selectedAll, setSelectedAll] = useState();
const [sorterState, setSorterState] = useState([]);
const [tableFilterState, setTableFilterState] = useState(tableFilterValue !== null && tableFilterValue !== void 0 ? tableFilterValue : '');
const _itemsNumber = useMemo(() => itemsNumber !== null && itemsNumber !== void 0 ? itemsNumber : items.length, [itemsNumber, items.length]);
useEffect(() => {
setActivePage(activePage);
}, [activePage]);
useEffect(() => {
if (items.length < _itemsPerPage * _activePage - _itemsPerPage) {
setActivePage(1);
}
const selected = [];
for (const item of items) {
if (item._selected) {
const _item = Object.assign({}, item);
for (const key of ITEM_INTERNAL_KEYS) {
delete _item[key]; // Remove internal keys
}
selected.push(_item); // Add cleaned item to selected array
}
}
if (selected.length > 0) {
setSelected([..._selected, ...selected]);
}
if (Array.isArray(items)) {
setItems([...items]);
}
}, [JSON.stringify(items)]);
useEffect(() => {
Array.isArray(selected) && setSelected(selected);
}, [JSON.stringify(selected)]);
useEffect(() => {
columnFilterValue && setColumnFilterState(columnFilterValue);
}, [JSON.stringify(columnFilterValue)]);
useEffect(() => {
sorterValue && setSorterState(Array.isArray(sorterValue) ? sorterValue : [sorterValue]);
}, [JSON.stringify(sorterValue)]);
useEffect(() => setItemsPerPage(itemsPerPage), [itemsPerPage]);
useEffect(() => {
mountedRef.current && onActivePageChange && onActivePageChange(_activePage);
}, [_activePage]);
useEffect(() => {
mountedRef.current && onItemsPerPageChange && onItemsPerPageChange(_itemsPerPage);
itemsPerPage !== _itemsPerPage && setActivePage(1); // TODO: set proper page after _itemsPerPage update
}, [_itemsPerPage]);
useEffect(() => {
const multiple = typeof columnSorter === 'object' && columnSorter.multiple;
mountedRef.current &&
sorterState &&
onSorterChange &&
onSorterChange(multiple ? sorterState : sorterState[0]);
}, [sorterState]);
useEffect(() => {
mountedRef.current && onColumnFilterChange && onColumnFilterChange(columnFilterState);
}, [columnFilterState]);
useEffect(() => {
mountedRef.current && onTableFilterChange && onTableFilterChange(tableFilterState);
}, [tableFilterState]);
useEffect(() => {
if (selectable) {
onSelectedItemsChange && onSelectedItemsChange(_selected);
if (_selected.length === _itemsNumber) {
setSelectedAll(true);
return;
}
if (_selected.length === 0) {
setSelectedAll(false);
return;
}
if (_selected.length > 0 && _selected.length !== _itemsNumber) {
setSelectedAll('indeterminate');
}
}
}, [JSON.stringify(_selected), _itemsNumber]);
const columnNames = useMemo(() => getColumnNames(columns, _items), [columns, _items]);
const itemsDataColumns = useMemo(() => columnNames.filter((name) => getColumnNamesFromItems(_items).includes(name)), [columnNames, _items]);
const filteredColumns = useMemo(() => filterColumns(_items, columnFilter, columnFilterState, itemsDataColumns), [columnFilterState, _items]);
const filteredTable = useMemo(() => filterTable(filteredColumns, tableFilter, tableFilterState, itemsDataColumns), [tableFilterState, tableFilterValue, filteredColumns]);
const sortedItems = useMemo(() => sortItems(columns, columnSorter, filteredTable, itemsDataColumns, sorterState), [columnSorter, sorterState, filteredTable]);
const numberOfPages = _itemsPerPage ? Math.ceil(sortedItems.length / _itemsPerPage) : 1;
const firstItemOnActivePageIndex = _activePage ? (_activePage - 1) * _itemsPerPage : 0;
const currentItems = _activePage
? sortedItems.slice(firstItemOnActivePageIndex, firstItemOnActivePageIndex + _itemsPerPage)
: sortedItems;
useEffect(() => {
mountedRef.current && onFilteredItemsChange && onFilteredItemsChange(sortedItems);
}, [JSON.stringify(sortedItems)]);
const handleClean = () => {
setTableFilterState('');
setColumnFilterState({});
setSorterState([]);
};
const handleColumnFilterChange = (colName, value, type) => {
const isLazy = columnFilter && typeof columnFilter === 'object' && columnFilter.lazy === true;
if ((isLazy && type === 'input') || (!isLazy && type === 'change')) {
return;
}
setActivePage(1);
setColumnFilterState((prevState) => {
const newState = Object.assign({}, prevState);
if (value === '') {
delete newState[colName];
}
else {
newState[colName] = value;
}
return newState;
});
};
const handleItemsPerPageChange = (event) => {
if (typeof itemsPerPageSelect !== 'object' ||
(typeof itemsPerPageSelect === 'object' && !itemsPerPageSelect.external)) {
setItemsPerPage(Number(event.target.value));
}
};
const handleRowChecked = (item, value) => {
onRowChecked === null || onRowChecked === void 0 ? void 0 : onRowChecked(item, value);
if (value && !isObjectInArray(_selected, item, ITEM_INTERNAL_KEYS)) {
setSelected((prevSelected) => [...prevSelected, item]);
return;
}
setSelected((prevSelected) => prevSelected.filter((_item) => !isObjectInArray([_item], item, ITEM_INTERNAL_KEYS)));
};
const handleSelectAllChecked = () => {
onSelectAll === null || onSelectAll === void 0 ? void 0 : onSelectAll();
if (selectedAll === true) {
setSelected(_items.filter((item) => item._selectable === false));
return;
}
if (selectAll && typeof selectAll === 'object' && selectAll.external) {
return;
}
const selectable = _items.filter((item) => item._selectable !== false || item._selected === true);
if (selectable.length === _selected.length) {
setSelected(_items.filter((item) => item._selectable === false && item._selected === true));
return;
}
const selected = selectable.map((item) => {
return Object.assign({}, item);
});
setSelected(selected.map((item) => {
for (const key of ITEM_INTERNAL_KEYS) {
delete item[key];
}
return item;
}));
};
const handleSorterChange = (column, index, order) => {
if (!isSortable(index, columns, columnSorter, itemsDataColumns, columnNames)) {
return;
}
const existingColumnState = sorterState.find((x) => x.column === column);
const multiple = typeof columnSorter === 'object' && columnSorter.multiple;
// If the column already has a sort state
if (existingColumnState) {
// No need to update if the order is already the same
if (existingColumnState.state === order) {
return;
}
// Remove the column from sorting if resetable and descending
if (typeof columnSorter === 'object' &&
columnSorter.resetable &&
existingColumnState.state === 'desc' &&
order !== 'asc') {
setSorterState(multiple ? sorterState.filter((x) => x.column !== column) : []);
}
else {
// Toggle between ascending and descending
const newState = {
column,
state: order || (existingColumnState.state === 'asc' ? 'desc' : 'asc'),
};
setSorterState(multiple
? sorterState.map((item) => (item.column === column ? newState : item))
: [newState]);
}
}
else {
// If the column is not yet sorted, add it with the default or provided order
const newSorter = { column, state: order || 'asc' };
setSorterState(multiple ? [...sorterState, newSorter] : [newSorter]);
}
};
const handleTableFilterChange = (value, type) => {
const isLazy = tableFilter && typeof tableFilter === 'object' && tableFilter.lazy === true;
if ((isLazy && type === 'input') || (!isLazy && type === 'change')) {
return;
}
setActivePage(1);
setTableFilterState(value);
};
useEffect(() => {
mountedRef.current = true;
}, []);
return (React.createElement(React.Fragment, null,
React.createElement("div", Object.assign({}, rest, { ref: ref }), (itemsPerPageSelect || tableFilter || cleaner) && (React.createElement("div", { className: "row my-2 mx-0" }, (tableFilter || cleaner) && (React.createElement(React.Fragment, null,
React.createElement("div", { className: "col-auto p-0" }, tableFilter && (React.createElement("div", { className: "row mb-2" },
React.createElement(CFormLabel, { className: "col-sm-auto col-form-label" }, tableFilterLabel),
React.createElement("div", { className: "col-sm-auto" },
React.createElement(CFormInput, { onInput: (e) => {
handleTableFilterChange(e.target.value, 'input');
}, onChange: (e) => {
handleTableFilterChange(e.target.value, 'change');
}, placeholder: tableFilterPlaceholder, value: tableFilterState || '' }))))),
React.createElement("div", { className: "col-auto p-0" }, cleaner && (React.createElement("button", Object.assign({ type: "button", className: "btn btn-transparent" }, (!(tableFilterState ||
sorterState.length > 0 ||
Object.values(columnFilterState).join('')) && {
disabled: true,
tabIndex: -1,
}), { onClick: () => handleClean(), onKeyDown: (event) => {
if (event.key === 'Enter')
handleClean();
} }),
React.createElement(CIcon, { width: 18, icon: cilFilterX }))))))))),
React.createElement("div", { className: "position-relative" },
React.createElement(CTable, Object.assign({}, tableProps),
header && (React.createElement(CSmartTableHead, Object.assign({}, tableHeadProps, { columnFilter: columnFilter, columnFilterState: columnFilterState, columns: columns !== null && columns !== void 0 ? columns : columnNames, columnSorter: columnSorter, items: _items, selectable: selectable, selectAll: selectAll, selectedAll: selectedAll, sorterState: sorterState, sortingIcon: sortingIcon, sortingIconAscending: sortingIconAscending, sortingIconDescending: sortingIconDescending, handleFilterOnChange: (key, event) => handleColumnFilterChange(key, event, 'change'), handleFilterOnInput: (key, event) => handleColumnFilterChange(key, event, 'input'), handleOnCustomFilterChange: (key, event) => handleColumnFilterChange(key, event), handleSelectAllChecked: () => handleSelectAllChecked(), handleSort: (key, index, order) => handleSorterChange(key, index, order) }))),
React.createElement(CSmartTableBody, Object.assign({ clickableRows: clickableRows, columnNames: columnNames, columns: columns !== null && columns !== void 0 ? columns : columnNames, currentItems: currentItems, firstItemOnActivePageIndex: firstItemOnActivePageIndex, noItemsLabel: noItemsLabel, onRowClick: (item, index, columnName, event) => clickableRows && onRowClick && onRowClick(item, index, columnName, event), onRowChecked: (item, value) => handleRowChecked(item, value), scopedColumns: scopedColumns, selectable: selectable, selected: _selected }, tableBodyProps)),
typeof footer === 'boolean' && footer && (React.createElement(CSmartTableHead, Object.assign({ as: CTableFoot }, tableFootProps, { columnFilter: false, columnSorter: false, columns: columns !== null && columns !== void 0 ? columns : columnNames, items: _items, handleSelectAllChecked: () => handleSelectAllChecked(), selectable: selectable, selectAll: selectAll, selectedAll: selectedAll, showGroups: false }))),
Array.isArray(footer) && (React.createElement(CTableFoot, Object.assign({}, tableFootProps),
React.createElement(CTableRow, null, footer.map((item, index) => (React.createElement(CTableDataCell, Object.assign({}, (typeof item === 'object' && item._props && Object.assign({}, item._props)), { key: index }), typeof item === 'object' ? item.label : item))))))),
loading && (React.createElement(CElementCover, { boundaries: [
{ sides: ['top'], query: 'tbody' },
{ sides: ['bottom'], query: 'tbody' },
] }, elementCover))),
(pagination || itemsPerPageSelect) && (React.createElement("div", { className: "row" },
React.createElement("div", { className: "col" }, ((pagination && numberOfPages > 1) ||
(paginationProps && paginationProps.pages > 1)) && (React.createElement(CSmartPagination, Object.assign({ activePage: _activePage, onActivePageChange: (page) => {
pagination && typeof pagination === 'object' && pagination.external
? onActivePageChange && onActivePageChange(page)
: setActivePage(page);
}, pages: numberOfPages }, paginationProps)))),
React.createElement("div", { className: "col-auto ms-auto" }, itemsPerPageSelect && (React.createElement("div", { className: "row" },
React.createElement(CFormLabel, { className: "col-auto col-form-label" }, itemsPerPageLabel),
React.createElement("div", { className: "col-auto" },
React.createElement(CFormSelect, { defaultValue: _itemsPerPage, onChange: (event) => handleItemsPerPageChange(event) }, itemsPerPageOptions &&
itemsPerPageOptions.map((number, index) => {
return (React.createElement("option", { value: number, key: index }, number));
}))))))))));
});
CSmartTable.propTypes = {
activePage: PropTypes.number,
cleaner: PropTypes.bool,
clickableRows: PropTypes.bool,
columnFilter: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
columnFilterValue: PropTypes.object,
columns: PropTypes.array,
columnSorter: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
elementCover: PropTypes.node,
footer: PropTypes.oneOfType([PropTypes.bool, PropTypes.array]),
header: PropTypes.bool,
items: PropTypes.array,
itemsNumber: PropTypes.number,
itemsPerPage: PropTypes.number,
itemsPerPageLabel: PropTypes.string,
itemsPerPageOptions: PropTypes.array,
itemsPerPageSelect: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
loading: PropTypes.bool,
noItemsLabel: PropTypes.oneOfType([PropTypes.string, PropTypes.node]),
onActivePageChange: PropTypes.func,
onColumnFilterChange: PropTypes.func,
onFilteredItemsChange: PropTypes.func,
onItemsPerPageChange: PropTypes.func,
onRowChecked: PropTypes.func,
onRowClick: PropTypes.func,
onSelectAll: PropTypes.func,
onSelectedItemsChange: PropTypes.func,
onSorterChange: PropTypes.func, // TODO: change to `onColumnSorterChange` in v6
onTableFilterChange: PropTypes.func,
pagination: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
paginationProps: PropTypes.any, // TODO: update
scopedColumns: PropTypes.object,
selectable: PropTypes.bool,
selectAll: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
selected: PropTypes.array,
sorterValue: PropTypes.oneOfType([
PropTypes.shape({
column: PropTypes.string.isRequired,
state: PropTypes.oneOf(['asc', 'desc', 0]).isRequired,
}),
PropTypes.arrayOf(PropTypes.shape({
column: PropTypes.string.isRequired,
state: PropTypes.oneOf(['asc', 'desc', 0]).isRequired,
}).isRequired),
]),
sortingIcon: PropTypes.node,
sortingIconAscending: PropTypes.node,
sortingIconDescending: PropTypes.node,
tableBodyProps: PropTypes.object,
tableFootProps: PropTypes.object,
tableFilter: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
tableFilterLabel: PropTypes.string,
tableFilterPlaceholder: PropTypes.string,
tableFilterValue: PropTypes.string,
tableHeadProps: PropTypes.object,
tableProps: PropTypes.object,
};
CSmartTable.displayName = 'CSmartTable';
export { CSmartTable };
//# sourceMappingURL=CSmartTable.js.map