-
- {headerColumns.map(column => {
- // if a column is marked as immovable just return regular column
- if (column.props.immovable === "true") return column;
- // keeps track of hidden columns here so columnIndex might not equal i
- return column;
- })}
-
+
+
+ `${index}`)}
+ strategy={horizontalListSortingStrategy}
+ >
+ {children}
+
-
+
);
-}
+};
-class SortableColumns extends Component {
- shouldCancelStart = e => {
+const SortableColumns = ({ className, style, children, moveColumn }) => {
+ const shouldCancelStart = e => {
const className = e.target.className;
// if its an svg then it's a blueprint icon
return (
@@ -66,30 +70,31 @@ class SortableColumns extends Component {
);
};
- onSortEnd = (...args) => {
+ const onSortEnd = (...args) => {
const { oldIndex, newIndex } = args[0];
document.body.classList.remove("drag-active");
- this.props.moveColumn({
+ moveColumn({
oldIndex,
newIndex
});
};
- onSortStart = () => {
+ const onSortStart = () => {
document.body.classList.add("drag-active");
};
- render() {
- return (
-
- );
- }
-}
+ return (
+
+ {children}
+
+ );
+};
export default SortableColumns;
diff --git a/packages/ui/src/DataTable/ThComponent.js b/packages/ui/src/DataTable/ThComponent.js
new file mode 100644
index 00000000..f6cf6d16
--- /dev/null
+++ b/packages/ui/src/DataTable/ThComponent.js
@@ -0,0 +1,43 @@
+import { useSortable } from "@dnd-kit/sortable";
+import { CSS } from "@dnd-kit/utilities";
+import classNames from "classnames";
+
+export const ThComponent = ({
+ toggleSort,
+ immovable,
+ className,
+ children,
+ style,
+ columnindex,
+ ...rest
+}) => {
+ const index = columnindex ?? -1;
+ const { attributes, listeners, setNodeRef, transform, transition } =
+ useSortable({
+ id: `${index}`,
+ disabled: immovable === "true"
+ });
+
+ const sortStyles = {
+ transform: CSS.Transform.toString(transform),
+ transition
+ };
+
+ return (
+
toggleSort && toggleSort(e)}
+ role="columnheader"
+ tabIndex="-1" // Resolves eslint issues without implementing keyboard navigation incorrectly
+ columnindex={columnindex}
+ index={index}
+ {...rest}
+ >
+ {children}
+
+ );
+};
diff --git a/packages/ui/src/DataTable/dataTableEnhancer.js b/packages/ui/src/DataTable/dataTableEnhancer.js
index ca364265..03dc2160 100644
--- a/packages/ui/src/DataTable/dataTableEnhancer.js
+++ b/packages/ui/src/DataTable/dataTableEnhancer.js
@@ -5,300 +5,41 @@
* @property {string} queryName What the props come back on ( by default = modelName + 'Query')
*/
import { reduxForm } from "redux-form";
-
-import { arrayMove } from "@dnd-kit/sortable";
-import { toArray, keyBy, get } from "lodash-es";
-import { withProps, withState, branch, compose } from "recompose";
-import withTableParams from "../DataTable/utils/withTableParams";
-import convertSchema from "../DataTable/utils/convertSchema";
-import { viewColumn, openColumn } from "../DataTable/viewColumn";
+import { branch, compose, withProps } from "recompose";
import pureNoFunc from "../utils/pureNoFunc";
-import tgFormValues from "../utils/tgFormValues";
-import getTableConfigFromStorage from "./utils/getTableConfigFromStorage";
-
+import { withRouter } from "react-router-dom";
+
+/*
+ Right now DataTable is doing the same as withTableParams, so the logic is being
+ run twice. We need to refactor this to make it more DRY.
+ We could do two possible refactorings:
+ - remove withTableParams and just give all the appropiate props to DataTable. This would
+ make the component simpler.
+ - remove the logic from DataTable and just use withTableParams and a new hook called
+ useTableParams. This would be more flexible in the case we want to be able to use
+ pagination in a different component.
+ We should avoid having the component handle all the different
+ cases of input because of the next reasons:
+ 1. It makes the component more complex and harder to understand.
+ 2. It makes the component harder to test.
+ 3. It makes the component harder to reuse.
+ 4. Maintaining the the logic in the component is harder.
+ Keeping the logic and uses simple makes maintaining easier.
+
+ In my opinion, reduxForm could be replaced here with regular redux or even just be taken down.
+ Could be a major simplification, but this needs to be analized with lims for better
+ understanding if it's possible.
+*/
export default compose(
- // maybe we need this in some cases?
- // tgFormValues("reduxFormEntities"),
- // withProps(props => {
- // const entities = props.reduxFormEntities || props.entities;
- // return {
- // _origEntities: props.entities,
- // entities
- // };
- // }),
- //connect to withTableParams here in the dataTable component so that, in the case that the table is not manually connected,
- withTableParams({
- isLocalCall: true
- }),
- branch(
- props => props.showEmptyColumnsByDefault,
- withState("showForcedHiddenColumns", "setShowForcedHidden", true),
- withState("showForcedHiddenColumns", "setShowForcedHidden", false)
- ),
- withProps(ownProps => {
- let propsToUse = ownProps;
- if (!ownProps.isTableParamsConnected) {
- //this is the case where we're hooking up to withTableParams locally, so we need to take the tableParams off the props
- propsToUse = {
- ...ownProps,
- ...ownProps.tableParams
- };
- }
-
- const {
- schema,
- withDisplayOptions,
- syncDisplayOptionsToDb,
- formName,
- tableConfigurations,
- deleteTableConfiguration,
- upsertTableConfiguration,
- upsertFieldOption,
- currentUser,
- isViewable,
- isOpenable,
- entities = [],
- cellRenderer = {},
- showForcedHiddenColumns,
- isSimple,
- isInfinite,
- compact = true,
- extraCompact
- } = propsToUse;
- let schemaToUse = convertSchema(schema);
- let fieldOptsByPath = {};
- let tableConfig = {};
- let resetDefaultVisibility;
- let updateColumnVisibility;
- let persistPageSize;
- let moveColumnPersist;
- let resizePersist;
- let updateTableDisplayDensity;
- let compactToUse = !!compact;
- let extraCompactToUse = !!extraCompact;
-
- if (isViewable) {
- schemaToUse.fields = [viewColumn, ...schemaToUse.fields];
- }
- if (isOpenable) {
- schemaToUse.fields = [openColumn, ...schemaToUse.fields];
- }
- // this must come before handling orderings.
- schemaToUse.fields = schemaToUse.fields.map(field => {
- if (field.placementPath) {
- return {
- ...field,
- sortDisabled:
- field.sortDisabled ||
- (typeof field.path === "string" && field.path.includes(".")),
- path: field.placementPath
- };
- } else {
- return field;
- }
- });
- const hasOptionForForcedHidden =
- withDisplayOptions && (isSimple || isInfinite);
- if (withDisplayOptions) {
- if (syncDisplayOptionsToDb) {
- tableConfig = tableConfigurations && tableConfigurations[0];
- } else {
- tableConfig = getTableConfigFromStorage(formName);
- }
- if (!tableConfig) {
- tableConfig = {
- fieldOptions: []
- };
- }
- if (tableConfig.density) {
- compactToUse = tableConfig.density === "compact";
- }
- if (tableConfig.density) {
- extraCompactToUse = tableConfig.density === "extraCompact";
- }
- const columnOrderings = tableConfig.columnOrderings;
- fieldOptsByPath = keyBy(tableConfig.fieldOptions, "path");
- schemaToUse = {
- ...schemaToUse,
- fields: schemaToUse.fields.map(field => {
- const fieldOpt = fieldOptsByPath[field.path];
- let noValsForField = false;
- // only add this hidden column ability if no paging
- if (!showForcedHiddenColumns && hasOptionForForcedHidden) {
- noValsForField = entities.every(e => {
- const val = get(e, field.path);
- return field.render
- ? !field.render(val, e)
- : cellRenderer[field.path]
- ? !cellRenderer[field.path](val, e)
- : !val;
- });
- }
- if (noValsForField) {
- return {
- ...field,
- isHidden: true,
- isForcedHidden: true
- };
- } else if (fieldOpt) {
- return {
- ...field,
- isHidden: fieldOpt.isHidden
- };
- } else {
- return field;
- }
- })
- };
-
- if (columnOrderings) {
- const fieldsWithOrders = [];
- const fieldsWithoutOrder = [];
- // if a new field has been added since the orderings were set then we want
- // it to be at the end instead of the beginning
- schemaToUse.fields.forEach(field => {
- if (columnOrderings.indexOf(field.path) > -1) {
- fieldsWithOrders.push(field);
- } else {
- fieldsWithoutOrder.push(field);
- }
- });
- schemaToUse.fields = fieldsWithOrders
- .sort(({ path: path1 }, { path: path2 }) => {
- return (
- columnOrderings.indexOf(path1) - columnOrderings.indexOf(path2)
- );
- })
- .concat(fieldsWithoutOrder);
- tableConfig.columnOrderings = schemaToUse.fields.map(f => f.path);
- }
-
- if (syncDisplayOptionsToDb) {
- //sync up to db
- let tableConfigurationId;
- resetDefaultVisibility = function () {
- tableConfigurationId = tableConfig.id;
-
- if (tableConfigurationId) {
- deleteTableConfiguration(tableConfigurationId);
- }
- };
- updateColumnVisibility = function ({ shouldShow, path }) {
- if (tableConfigurationId) {
- const existingFieldOpt = fieldOptsByPath[path] || {};
- upsertFieldOption({
- id: existingFieldOpt.id,
- path,
- isHidden: !shouldShow,
- tableConfigurationId
- });
- } else {
- upsertTableConfiguration({
- userId: currentUser.user.id,
- formName,
- fieldOptions: [
- {
- path,
- isHidden: !shouldShow
- }
- ]
- });
- }
- };
- } else {
- const syncStorage = () => {
- window.localStorage.setItem(formName, JSON.stringify(tableConfig));
- };
-
- //sync display options with localstorage
- resetDefaultVisibility = function () {
- window.localStorage.removeItem(formName);
- };
- updateColumnVisibility = function ({ path, paths, shouldShow }) {
- const newFieldOpts = {
- ...fieldOptsByPath
- };
- const pathsToUse = paths ? paths : [path];
- pathsToUse.forEach(path => {
- newFieldOpts[path] = { path, isHidden: !shouldShow };
- });
- tableConfig.fieldOptions = toArray(newFieldOpts);
- syncStorage();
- };
- updateTableDisplayDensity = function (density) {
- tableConfig.density = density;
- syncStorage();
- };
- persistPageSize = function (pageSize) {
- tableConfig.userSetPageSize = pageSize;
- syncStorage();
- };
- moveColumnPersist = function ({ oldIndex, newIndex }) {
- // we might already have an array of the fields [path1, path2, ..etc]
- const columnOrderings =
- tableConfig.columnOrderings ||
- schemaToUse.fields.map(({ path }) => path); // columnOrderings is [path1, path2, ..etc]
-
- tableConfig.columnOrderings = arrayMove(
- columnOrderings,
- oldIndex,
- newIndex
- );
- syncStorage();
- };
- resizePersist = function (newResized) {
- tableConfig.resized = newResized;
- syncStorage();
- };
- }
- }
- const resized = tableConfig.resized;
- return {
- ...propsToUse,
- schema: schemaToUse,
- compact: compactToUse,
- extraCompact: extraCompactToUse,
- resized,
- resetDefaultVisibility,
- updateColumnVisibility,
- persistPageSize,
- updateTableDisplayDensity,
- resizePersist,
- moveColumnPersist,
- hasOptionForForcedHidden
- };
- }),
- branch(props => !props.noForm, reduxForm({})), //the formName is passed via withTableParams and is often user overridden
- tgFormValues(
- "localStorageForceUpdate",
- "reduxFormQueryParams",
- "reduxFormSearchInput",
- "onlyShowRowsWErrors",
- "reduxFormSelectedEntityIdMap",
- "reduxFormExpandedEntityIdMap",
- "reduxFormSelectedCells",
- "reduxFormEditingCell",
- "reduxFormEditingCellSelectAll",
- "reduxFormCellIdToEditValue",
- "reduxFormEntities",
- "reduxFormCellValidation",
- "reduxFormEntitiesUndoRedoStack"
- ),
- withProps(props => {
- const entities = props.reduxFormEntities || props.entities;
- return {
- _origEntities: props.entities,
- entities
- };
- }),
- // withFields({
- // names: [
- // "localStorageForceUpdate",
- // "reduxFormQueryParams",
- // "reduxFormSearchInput",
- // "reduxFormSelectedEntityIdMap",
- // "reduxFormExpandedEntityIdMap"
- // ]
- // }),
- branch(props => !props.alwaysRerender, pureNoFunc)
+ // Function to make sure we don't rerender unless there are changes
+ // in the params
+ branch(props => !props.alwaysRerender, pureNoFunc),
+ // form prop is needed for redux-form, but we are giving this prop as
+ // formName, so we need to rename it. Previously it was done in the withTableParams,
+ // but now we took it out by default.
+ withProps(({ formName }) => ({ form: formName })),
+ // the formName is passed via withTableParams and is often user overridden
+ branch(props => !props.noForm, reduxForm({})),
+ // don't use withRouter if noRouter is passed!
+ branch(props => !props.noRouter, withRouter)
);
diff --git a/packages/ui/src/DataTable/defaultProps.js b/packages/ui/src/DataTable/defaultProps.js
deleted file mode 100644
index a9bb38bd..00000000
--- a/packages/ui/src/DataTable/defaultProps.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import { noop } from "lodash-es";
-
-// eslint-disable-next-line import/no-anonymous-default-export
-export default {
- //NOTE: DO NOT SET DEFAULTS HERE FOR PROPS THAT GET COMPUTED AS PART OF PRESET GROUPS IN computePresets
- addFilters: noop,
- className: "",
- clearFilters: noop,
- contextMenu: noop,
- disabled: false,
- entities: [],
- extraClasses: "",
- filters: [],
- isCopyable: true,
- isEntityDisabled: noop,
- isLoading: false,
- isSimple: false,
- isSingleSelect: false,
- maxHeight: 600,
- noHeader: false,
- noSelect: false,
- noUserSelect: false,
- onDeselect: noop,
- onMultiRowSelect: noop,
- onRowClick: noop,
- onRowSelect: noop,
- onSingleRowSelect: noop,
- page: 1,
- pageSize: 10,
- reduxFormExpandedEntityIdMap: {},
- reduxFormSearchInput: "",
- reduxFormSelectedEntityIdMap: {},
- removeSingleFilter: noop,
- resized: [],
- resizePersist: noop,
- setFilter: noop,
- setOrder: noop,
- setPage: noop,
- setPageSize: noop,
- setSearchTerm: noop,
- showCount: false,
- style: {},
- withCheckboxes: false,
- withSort: true
-};
diff --git a/packages/ui/src/DataTable/index.js b/packages/ui/src/DataTable/index.js
index ce5eb73a..de28ecb9 100644
--- a/packages/ui/src/DataTable/index.js
+++ b/packages/ui/src/DataTable/index.js
@@ -1,4 +1,11 @@
-import React, { createRef } from "react";
+import React, {
+ useEffect,
+ useRef,
+ useMemo,
+ useState,
+ useCallback,
+ useContext
+} from "react";
import {
invert,
toNumber,
@@ -6,12 +13,10 @@ import {
min,
max,
set,
- map,
toString,
camelCase,
startCase,
noop,
- isEqual,
cloneDeep,
keyBy,
omit,
@@ -21,9 +26,8 @@ import {
padStart,
omitBy,
times,
- some,
- isFunction,
- every
+ toArray,
+ isFunction
} from "lodash-es";
import joinUrl from "url-join";
import {
@@ -36,23 +40,23 @@ import {
Icon,
Intent,
Callout,
- Tooltip
+ Tooltip,
+ useHotkeys
} from "@blueprintjs/core";
-import { arrayMove, useSortable } from "@dnd-kit/sortable";
-import { CSS } from "@dnd-kit/utilities";
+import { arrayMove } from "@dnd-kit/sortable";
import classNames from "classnames";
import scrollIntoView from "dom-scroll-into-view";
import ReactTable from "@teselagen/react-table";
-import { withProps, compose } from "recompose";
import dayjs from "dayjs";
import localizedFormat from "dayjs/plugin/localizedFormat";
import ReactMarkdown from "react-markdown";
import immer, { produceWithPatches, enablePatches, applyPatches } from "immer";
import papaparse from "papaparse";
import remarkGfm from "remark-gfm";
+import { useDispatch, useSelector } from "react-redux";
+import { ThComponent } from "./ThComponent";
import {
- computePresets,
defaultParsePaste,
formatPasteData,
getAllRows,
@@ -71,18 +75,14 @@ import {
handleCopyColumn,
handleCopyHelper,
handleCopyRows,
+ handleCopyTable,
isBottomRightCornerOfRectangle,
isEntityClean,
- removeCleanRows,
stripNumberAtEnd
} from "./utils";
import InfoHelper from "../InfoHelper";
-import { withHotkeys } from "../utils/hotkeyUtils";
import getTextFromEl from "../utils/getTextFromEl";
-import rowClick, {
- changeSelectedEntities,
- finalizeSelection
-} from "./utils/rowClick";
+import rowClick, { finalizeSelection } from "./utils/rowClick";
import PagingTool from "./PagingTool";
import FilterAndSortMenu from "./FilterAndSortMenu";
import SearchBar from "./SearchBar";
@@ -90,8 +90,6 @@ import DisplayOptions from "./DisplayOptions";
import DisabledLoadingComponent from "./DisabledLoadingComponent";
import SortableColumns from "./SortableColumns";
import dataTableEnhancer from "./dataTableEnhancer";
-import defaultProps from "./defaultProps";
-
import "../toastr";
import "@teselagen/react-table/react-table.css";
import "./style.css";
@@ -102,10 +100,20 @@ import { validateTableWideErrors } from "./validateTableWideErrors";
import { editCellHelper } from "./editCellHelper";
import { getCellVal } from "./getCellVal";
import { getVals } from "./getVals";
-import { throwFormError } from "../throwFormError";
import { DropdownCell } from "./DropdownCell";
import { EditableCell } from "./EditabelCell";
import { ColumnFilterMenu } from "./ColumnFilterMenu";
+import getTableConfigFromStorage from "./utils/getTableConfigFromStorage";
+import { viewColumn, openColumn } from "./viewColumn";
+import convertSchema from "./utils/convertSchema";
+import TableFormTrackerContext from "./TableFormTrackerContext";
+import {
+ getCurrentParamsFromUrl,
+ getQueryParams,
+ makeDataTableHandlers,
+ setCurrentParamsOnUrl
+} from "./utils/queryParams";
+
enablePatches();
const PRIMARY_SELECTED_VAL = "main_cell";
@@ -119,791 +127,817 @@ const itemSizeEstimators = {
comfortable: () => 41.34
};
-class DataTable extends React.Component {
- constructor(props) {
- super(props);
-
- this.tableRef = createRef();
- if (this.props.helperProp) {
- this.props.helperProp.updateValidationHelper =
- this.updateValidationHelper;
- this.props.helperProp.addEditableTableEntities =
- this.addEditableTableEntities;
- this.props.helperProp.getEditableTableInfoAndThrowFormError =
- this.getEditableTableInfoAndThrowFormError;
- }
- this.hotkeyEnabler = withHotkeys({
- moveUpARow: {
- global: false,
- combo: "up",
- label: "Move Up a Row",
- onKeyDown: this.handleRowMove("up")
- },
- moveDownARow: {
- global: false,
- combo: "down",
- label: "Move Down a Row",
- onKeyDown: this.handleRowMove("down")
- },
- moveUpARowShift: {
- global: false,
- combo: "up+shift",
- label: "Move Up a Row",
- onKeyDown: this.handleRowMove("up", true)
- },
- ...(props.isCellEditable && {
- enter: {
- global: false,
- combo: "enter",
- label: "Enter -> Start Cell Edit",
- onKeyDown: this.handleEnterStartCellEdit
- },
+const useSelectorOptions = {
+ devModeChecks: { stabilityCheck: "never" }
+};
- cut: {
- global: false,
- combo: "mod+x",
- label: "Cut",
- onKeyDown: this.handleCut
- },
- undo: {
- global: false,
- combo: IS_LINUX ? "alt+z" : "mod+z",
- label: "Undo",
- onKeyDown: this.handleUndo
- },
- redo: {
- global: false,
- combo: IS_LINUX ? "alt+shift+z" : "mod+shift+z",
- label: "Redo",
- onKeyDown: this.handleRedo
- },
- deleteCell: {
- global: false,
- combo: "backspace",
- label: "Delete Cell",
- onKeyDown: this.handleDeleteCell
- }
- }),
- moveDownARowShift: {
- global: false,
- combo: "down+shift",
- label: "Move Down a Row",
- onKeyDown: this.handleRowMove("down", true)
- },
- copyHotkey: {
- global: false,
- combo: "mod + c",
- label: "Copy rows",
- onKeyDown: this.handleCopyHotkey
- },
- selectAllRows: {
- global: false,
- combo: "mod + a",
- label: "Select rows",
- onKeyDown: this.handleSelectAllRows
- }
- });
+const DataTable = ({
+ controlled_pageSize,
+ formName = "tgDataTable",
+ history,
+ isSimple,
+ isLocalCall = true,
+ isTableParamsConnected,
+ noForm,
+ orderByFirstColumn,
+ schema: __schema,
+ showEmptyColumnsByDefault,
+ tableParams,
+ ...ownProps
+}) => {
+ if (isTableParamsConnected && tableParams && !tableParams.entities) {
+ throw new Error(
+ `No entities array detected in tableParams object (
). You need to call withQuery() after withTableParams() like: compose(withTableParams(), withQuery(something)).`
+ );
}
- state = {
- columns: [],
- fullscreen: false,
- // This state prevents the first letter from not being written,
- // when the user starts typing in a cell. It is quite hacky, we should
- // refactor this in the future.
- editableCellInitialValue: ""
- };
- static defaultProps = defaultProps;
+ const dispatch = useDispatch();
+ const tableRef = useRef();
+ const alreadySelected = useRef(false);
+ const [editingCell, setEditingCell] = useState(null);
+ const [onlyShowRowsWErrors, setOnlyShowRowsWErrors] = useState(false);
+ const [editingCellSelectAll, setEditingCellSelectAll] = useState(false);
+ const [entitiesUndoRedoStack, setEntitiesUndoRedoStack] = useState({
+ currentVersion: 0
+ });
+ const [selectedCells, setSelectedCells] = useState({});
+ const _tableConfig = getTableConfigFromStorage(formName);
+ // make user set page size persist
+ const userSetPageSize =
+ _tableConfig?.userSetPageSize && parseInt(_tableConfig.userSetPageSize, 10);
+
+ const formTracker = useContext(TableFormTrackerContext);
+ useEffect(() => {
+ if (formTracker.isActive && !formTracker.formNames.includes(formName)) {
+ formTracker.pushFormName(formName);
+ }
+ }, [formTracker, formName]);
- getPrimarySelectedCellId = () => {
- const { reduxFormSelectedCells = {} } = this.props;
- for (const k of Object.keys(reduxFormSelectedCells)) {
- if (reduxFormSelectedCells[k] === PRIMARY_SELECTED_VAL) {
- return k;
- }
+ const [showForcedHiddenColumns, setShowForcedHidden] = useState(() => {
+ if (showEmptyColumnsByDefault) {
+ return true;
}
+ return false;
+ });
+
+ const reduxFormEntities = useSelector(state => {
+ if (!state.form[formName]) return;
+ return state.form[formName].values?.reduxFormEntities;
+ });
+
+ const {
+ reduxFormSearchInput = "",
+ reduxFormCellValidation,
+ reduxFormSelectedEntityIdMap = {},
+ reduxFormQueryParams = {}
+ } = useSelector(state => {
+ if (!state.form[formName]) return {};
+ return state.form[formName].values || {};
+ }, useSelectorOptions);
+
+ let props = ownProps;
+ if (!isTableParamsConnected) {
+ //this is the case where we're hooking up to withTableParams locally, so we need to take the tableParams off the props
+ props = {
+ ...ownProps,
+ ...tableParams
+ };
+ }
+
+ props.defaults = {
+ pageSize: controlled_pageSize || 25,
+ order: [], // ["-name", "statusCode"] //an array of camelCase display names with - sign to denote reverse
+ searchTerm: "",
+ page: 1,
+ filters: [
+ // filters look like this:
+ // {
+ // selectedFilter: 'textContains', //camel case
+ // filterOn: ccDisplayName, //camel case display name
+ // filterValue: 'thomas',
+ // }
+ ],
+ ...(props.defaults || {})
};
- startCellEdit = (cellId, { shouldSelectAll } = {}) => {
- const {
- change,
- reduxFormSelectedCells = {},
- reduxFormEditingCell
- } = computePresets(this.props);
- const newSelectedCells = { ...reduxFormSelectedCells };
- newSelectedCells[cellId] = PRIMARY_SELECTED_VAL;
- //check if the cell is already selected and editing and if so, don't change it
- if (reduxFormEditingCell === cellId) return;
- change("reduxFormSelectedCells", newSelectedCells);
- change("reduxFormEditingCell", cellId);
- if (shouldSelectAll) {
- //we should select the text
- change("reduxFormEditingCellSelectAll", true);
+ let _schema;
+ if (isFunction(__schema)) _schema = __schema(props);
+ else _schema = __schema;
+ const convertedSchema = convertSchema(_schema);
+
+ if (isLocalCall) {
+ if (!noForm && (!formName || formName === "tgDataTable")) {
+ throw new Error(
+ "Please pass a unique 'formName' prop to the locally connected
component with schema: ",
+ _schema
+ );
}
- };
+ if (orderByFirstColumn && !props.defaults?.order?.length) {
+ const r = [
+ camelCase(
+ convertedSchema.fields[0].displayName ||
+ convertedSchema.fields[0].path
+ )
+ ];
+ props.defaults.order = r;
+ }
+ } else {
+ //in user instantiated withTableParams() call
+ if (!formName || formName === "tgDataTable") {
+ throw new Error(
+ "Please pass a unique 'formName' prop to the withTableParams() with schema: ",
+ _schema
+ );
+ }
+ }
+
+ const {
+ change,
+ doNotCoercePageSize,
+ isInfinite = isSimple || !props.withPaging,
+ syncDisplayOptionsToDb,
+ urlConnected,
+ withSelectedEntities
+ } = props;
+
+ if (!syncDisplayOptionsToDb && userSetPageSize) {
+ props.defaults = props.defaults || {};
+ props.defaults.pageSize = userSetPageSize;
+ }
- handleEnterStartCellEdit = e => {
- e.stopPropagation();
- this.startCellEdit(this.getPrimarySelectedCellId());
+ const selectedEntities = withSelectedEntities
+ ? getRecordsFromIdMap(reduxFormSelectedEntityIdMap)
+ : undefined;
+
+ props = {
+ ...props,
+ ...(withSelectedEntities &&
+ typeof withSelectedEntities === "string" && {
+ [withSelectedEntities]: selectedEntities
+ })
};
- flashTableBorder = () => {
- try {
- const table = this.tableRef.current.tableRef;
- table.classList.add("tgBorderBlue");
+ const currentParams = urlConnected
+ ? getCurrentParamsFromUrl(history.location) //important to use history location and not ownProps.location because for some reason the location path lags one render behind!!
+ : reduxFormQueryParams;
+
+ if (!isTableParamsConnected) {
+ const updateSearch = val => {
setTimeout(() => {
- table.classList.remove("tgBorderBlue");
- }, 300);
- } catch (e) {
- console.error(`err when flashing table border:`, e);
+ dispatch(change(formName, "reduxFormSearchInput", val || ""));
+ });
+ };
+
+ let setNewParams;
+ if (urlConnected) {
+ setNewParams = newParams => {
+ setCurrentParamsOnUrl(newParams, history.replace);
+ dispatch(change(formName, "reduxFormQueryParams", newParams)); //we always will update the redux params as a workaround for withRouter not always working if inside a redux-connected container https://github.com/ReactTraining/react-router/issues/5037
+ };
+ } else {
+ setNewParams = function (newParams) {
+ dispatch(change(formName, "reduxFormQueryParams", newParams));
+ };
}
- };
- formatAndValidateEntities = (
- entities,
- { useDefaultValues, indexToStartAt } = {}
- ) => {
- const { schema } = this.props;
- const editableFields = schema.fields.filter(f => !f.isNotEditable);
- const validationErrors = {};
-
- const newEnts = immer(entities, entities => {
- entities.forEach((e, index) => {
- editableFields.forEach(columnSchema => {
- if (useDefaultValues) {
- if (e[columnSchema.path] === undefined) {
- if (isFunction(columnSchema.defaultValue)) {
- e[columnSchema.path] = columnSchema.defaultValue(
- index + indexToStartAt,
- e
- );
- } else e[columnSchema.path] = columnSchema.defaultValue;
- }
- }
- //mutative
- const { error } = editCellHelper({
- entity: e,
- columnSchema,
- newVal: e[columnSchema.path]
- });
- if (error) {
- const rowId = getIdOrCodeOrIndex(e, index);
- validationErrors[`${rowId}:${columnSchema.path}`] = error;
- }
- });
- });
+ const bindThese = makeDataTableHandlers({
+ setNewParams,
+ updateSearch,
+ defaults: props.defaults,
+ onlyOneFilter: props.onlyOneFilter
});
- return {
- newEnts,
- validationErrors
- };
- };
- updateValidation = (entities, newCellValidate) => {
- const { change, schema } = computePresets(this.props);
- const tableWideErr = validateTableWideErrors({
- entities,
- schema,
- newCellValidate,
- props: this.props
+ const boundDispatchProps = {};
+ //bind currentParams to actions
+ Object.keys(bindThese).forEach(function (key) {
+ const action = bindThese[key];
+ boundDispatchProps[key] = function (...args) {
+ action(...args, currentParams);
+ };
});
- change("reduxFormCellValidation", tableWideErr);
- this.forceUpdate();
- };
- handleUndo = () => {
- const {
- change,
- entities,
- reduxFormEntitiesUndoRedoStack = { currentVersion: 0 }
- } = this.props;
+ const changeFormValue = (...args) => dispatch(change(formName, ...args));
- if (reduxFormEntitiesUndoRedoStack.currentVersion > 0) {
- this.flashTableBorder();
- const nextState = applyPatches(
- entities,
- reduxFormEntitiesUndoRedoStack[
- reduxFormEntitiesUndoRedoStack.currentVersion
- ].inversePatches
- );
- const { newEnts, validationErrors } =
- this.formatAndValidateEntities(nextState);
- change("reduxFormEntities", newEnts);
- this.updateValidation(newEnts, validationErrors);
- change("reduxFormEntitiesUndoRedoStack", {
- ...reduxFormEntitiesUndoRedoStack,
- currentVersion: reduxFormEntitiesUndoRedoStack.currentVersion - 1
- });
- }
+ props.tableParams = {
+ changeFormValue,
+ selectedEntities,
+ ...tableParams,
+ ...props,
+ ...boundDispatchProps,
+ isTableParamsConnected: true //let the table know not to do local sorting/filtering etc.
+ };
+ }
+
+ props = {
+ ...props,
+ ...props.tableParams
};
- handleRedo = () => {
- const {
- change,
- entities,
- reduxFormEntitiesUndoRedoStack = { currentVersion: 0 }
- } = this.props;
+ const additionalFilterToUse =
+ typeof props.additionalFilter === "function"
+ ? props.additionalFilter.bind(this, ownProps)
+ : () => props.additionalFilter;
- const nextV = reduxFormEntitiesUndoRedoStack.currentVersion + 1;
- if (reduxFormEntitiesUndoRedoStack[nextV]) {
- this.flashTableBorder();
- const nextState = applyPatches(
- entities,
- reduxFormEntitiesUndoRedoStack[nextV].patches
- );
- const { newEnts, validationErrors } =
- this.formatAndValidateEntities(nextState);
- change("reduxFormEntities", newEnts);
- this.updateValidation(newEnts, validationErrors);
- change("reduxFormEntitiesUndoRedoStack", {
- ...reduxFormEntitiesUndoRedoStack,
- currentVersion: nextV
- });
- }
+ const additionalOrFilterToUse =
+ typeof props.additionalOrFilter === "function"
+ ? props.additionalOrFilter.bind(this, ownProps)
+ : () => props.additionalOrFilter;
+
+ currentParams.searchTerm = reduxFormSearchInput;
+
+ props = {
+ ...props,
+ ...getQueryParams({
+ doNotCoercePageSize,
+ currentParams,
+ entities: props.entities, // for local table
+ urlConnected,
+ defaults: props.defaults,
+ schema: convertedSchema,
+ isInfinite,
+ isLocalCall,
+ additionalFilter: additionalFilterToUse,
+ additionalOrFilter: additionalOrFilterToUse,
+ noOrderError: props.noOrderError,
+ isCodeModel: props.isCodeModel,
+ ownProps: props
+ })
};
- updateFromProps = (oldProps, newProps) => {
- const {
- selectedIds,
- entities = [],
- isEntityDisabled,
- expandAllByDefault,
- selectAllByDefault,
- reduxFormSelectedEntityIdMap,
- reduxFormExpandedEntityIdMap,
- change
- } = newProps;
-
- const idMap = reduxFormSelectedEntityIdMap;
-
- //handle programatic filter adding
- if (!isEqual(newProps.additionalFilters, oldProps.additionalFilters)) {
- newProps.addFilters(newProps.additionalFilters);
+ const {
+ addFilters = noop,
+ additionalFilters,
+ additionalFooterButtons,
+ autoFocusSearch,
+ cellRenderer,
+ children: maybeChildren,
+ className = "",
+ clearFilters = noop,
+ compact: _compact = true,
+ compactPaging,
+ contextMenu = noop,
+ controlled_hasNextPage,
+ controlled_onRefresh,
+ controlled_page,
+ controlled_setPage,
+ controlled_setPageSize,
+ controlled_total,
+ currentUser,
+ deleteTableConfiguration,
+ disabled = false,
+ disableSetPageSize,
+ doNotShowEmptyRows,
+ doNotValidateUntouchedRows,
+ entities: _origEntities = [],
+ entitiesAcrossPages,
+ entityCount,
+ errorParsingUrlString,
+ expandAllByDefault,
+ extraClasses = "",
+ extraCompact: _extraCompact,
+ filters = [],
+ fragment,
+ getCellHoverText,
+ getRowClassName,
+ hideColumnHeader,
+ hideDisplayOptionsIcon,
+ hidePageSizeWhenPossible = isSimple ? !props.withPaging : false,
+ hideSelectedCount = isSimple,
+ hideSetPageSize,
+ hideTotalPages,
+ initialSelectedIds,
+ isCellEditable,
+ isCopyable = true,
+ isEntityDisabled = noop,
+ isLoading = false,
+ isOpenable,
+ isSingleSelect = false,
+ isViewable,
+ keepSelectionOnPageChange,
+ leftOfSearchBarItems,
+ maxHeight = 600,
+ minimalStyle,
+ mustClickCheckboxToSelect,
+ noDeselectAll,
+ noFooter = isSimple ? props.withPaging : false,
+ noFullscreenButton = isSimple,
+ noHeader = false,
+ noPadding = isSimple,
+ noRowsFoundMessage,
+ noSelect = false,
+ noUserSelect = false,
+ onDeselect = noop,
+ onDoubleClick,
+ onMultiRowSelect = noop,
+ onRefresh,
+ onRowClick = noop,
+ onRowSelect = noop,
+ onSingleRowSelect = noop,
+ order,
+ page = 1,
+ pageSize: _pageSize = 10,
+ pagingDisabled,
+ ReactTableProps = {},
+ removeSingleFilter = noop,
+ safeQuery,
+ searchMenuButton,
+ searchTerm,
+ selectAllByDefault,
+ setNewParams,
+ setOrder = noop,
+ setPage = noop,
+ setPageSize = noop,
+ setSearchTerm = noop,
+ shouldShowSubComponent,
+ showCount = false,
+ style = {},
+ SubComponent,
+ subHeader,
+ tableConfigurations,
+ tableName,
+ topLeftItems,
+ upsertFieldOption,
+ upsertTableConfiguration,
+ variables,
+ withCheckboxes = false,
+ withDisplayOptions,
+ withExpandAndCollapseAllButton,
+ withFilter = !isSimple,
+ withPaging = !isSimple,
+ withSearch = !isSimple,
+ withSelectAll,
+ withSort = true,
+ withTitle = !isSimple
+ } = props;
+
+ // We need to memoize the entities so that we don't rerender the table
+ const entities = reduxFormEntities?.length
+ ? reduxFormEntities
+ : _origEntities;
+
+ const [tableConfig, setTableConfig] = useState({ fieldOptions: [] });
+
+ useEffect(() => {
+ let newTableConfig = {};
+ if (withDisplayOptions) {
+ if (syncDisplayOptionsToDb) {
+ newTableConfig = tableConfigurations && tableConfigurations[0];
+ } else {
+ newTableConfig = getTableConfigFromStorage(formName);
+ }
+ if (!newTableConfig) {
+ newTableConfig = {
+ fieldOptions: []
+ };
+ }
}
- if (!isEqual(newProps.schema, oldProps.schema)) {
- const { schema = {} } = newProps;
- const columns = schema.fields
- ? schema.fields.reduce(function (columns, field, i) {
- if (field.isHidden) {
- return columns;
- }
- return columns.concat({
- ...field,
- columnIndex: i
- });
- }, [])
- : [];
- this.setState({ columns });
+ setTableConfig(newTableConfig);
+ }, [
+ formName,
+ syncDisplayOptionsToDb,
+ tableConfigurations,
+ withDisplayOptions
+ ]);
+
+ const { schema } = useMemo(() => {
+ const schema = convertSchema(_schema);
+ if (isViewable) {
+ schema.fields = [viewColumn, ...schema.fields];
}
- let selectedIdsToUse = selectedIds;
- let newIdMap;
- //handle selecting all or expanding all
- if (
- (selectAllByDefault || expandAllByDefault) &&
- !isEqual(
- (newProps.entities || []).map(({ id }) => id),
- oldProps.entities && oldProps.entities.map(({ id }) => id)
- )
- ) {
- if (
- selectAllByDefault &&
- !this.alreadySelected &&
- entities &&
- entities.length
- ) {
- this.alreadySelected = true;
- newIdMap = {
- ...(entities || []).reduce((acc, entity) => {
- acc[entity.id || entity.code] = { entity, time: Date.now() };
- return acc;
- }, {}),
- ...(reduxFormSelectedEntityIdMap || {})
+ if (isOpenable) {
+ schema.fields = [openColumn, ...schema.fields];
+ }
+ // this must come before handling orderings.
+ schema.fields = schema.fields.map(field => {
+ if (field.placementPath) {
+ return {
+ ...field,
+ sortDisabled:
+ field.sortDisabled ||
+ (typeof field.path === "string" && field.path.includes(".")),
+ path: field.placementPath
};
- selectedIdsToUse = map(newIdMap, (a, key) => key);
- }
- if (expandAllByDefault) {
- change("reduxFormExpandedEntityIdMap", {
- ...(entities || []).reduce((acc, e) => {
- acc[e.id || e.code] = true;
- return acc;
- }, {}),
- ...(reduxFormExpandedEntityIdMap || {})
- });
+ } else {
+ return field;
}
- }
+ });
- // handle programmatic selection and scrolling
- const { selectedIds: oldSelectedIds } = oldProps;
- if (isEqual(selectedIdsToUse, oldSelectedIds)) {
- // if not changing selectedIds then we just want to make sure selected entities
- // stored in redux are in proper format
- // if selected ids have changed then it will handle redux selection
- const tableScrollElement =
- this.tableRef.current.tableRef.getElementsByClassName("rt-table")[0];
- const {
- entities: oldEntities = [],
- reduxFormSelectedEntityIdMap: oldIdMap
- } = oldProps;
- const reloaded = oldProps.isLoading && !this.props.isLoading;
- const entitiesHaveChanged =
- oldEntities.length !== entities.length ||
- getIdOrCodeOrIndex(entities[0] || {}) !==
- getIdOrCodeOrIndex(oldEntities[0] || {});
- // if switching pages or searching the table we want to reset the scrollbar
- if (tableScrollElement.scrollTop > 0 && !this.props.isCellEditable) {
- if (reloaded || entitiesHaveChanged) {
- tableScrollElement.scrollTop = 0;
- }
- }
- // re-index entities in redux form so that sorting will be correct in withSelectedEntities
- if (change) {
- if (entitiesHaveChanged && (!isEmpty(oldIdMap) || !isEmpty(idMap))) {
- changeSelectedEntities({ idMap, entities, change });
- } else if (
- !isEmpty(idMap) &&
- idMap[Object.keys(idMap)[0]] &&
- idMap[Object.keys(idMap)[0]].rowIndex === undefined
+ if (withDisplayOptions) {
+ const fieldOptsByPath = keyBy(tableConfig.fieldOptions, "path");
+ schema.fields = schema.fields.map(field => {
+ const fieldOpt = fieldOptsByPath[field.path];
+ let noValsForField = false;
+ // only add this hidden column ability if no paging
+ if (
+ !showForcedHiddenColumns &&
+ withDisplayOptions &&
+ (isSimple || isInfinite)
) {
- // if programmatically selected will still want the order to match the table sorting.
- changeSelectedEntities({ idMap, entities, change });
+ noValsForField = entities.every(e => {
+ const val = get(e, field.path);
+ return field.render
+ ? !field.render(val, e)
+ : cellRenderer[field.path]
+ ? !cellRenderer[field.path](val, e)
+ : !val;
+ });
+ }
+ if (noValsForField) {
+ return {
+ ...field,
+ isHidden: true,
+ isForcedHidden: true
+ };
+ } else if (fieldOpt) {
+ return {
+ ...field,
+ isHidden: fieldOpt.isHidden
+ };
+ } else {
+ return field;
}
+ });
+
+ const columnOrderings = tableConfig.columnOrderings;
+ if (columnOrderings) {
+ const fieldsWithOrders = [];
+ const fieldsWithoutOrder = [];
+ // if a new field has been added since the orderings were set then we want
+ // it to be at the end instead of the beginning
+ schema.fields.forEach(field => {
+ if (columnOrderings.indexOf(field.path) > -1) {
+ fieldsWithOrders.push(field);
+ } else {
+ fieldsWithoutOrder.push(field);
+ }
+ });
+ schema.fields = fieldsWithOrders
+ .sort(({ path: path1 }, { path: path2 }) => {
+ return (
+ columnOrderings.indexOf(path1) - columnOrderings.indexOf(path2)
+ );
+ })
+ .concat(fieldsWithoutOrder);
+ setTableConfig(prev => ({
+ ...prev,
+ columnOrderings: schema.fields.map(f => f.path)
+ }));
}
- } else {
- const idArray = Array.isArray(selectedIdsToUse)
- ? selectedIdsToUse
- : [selectedIdsToUse];
- const selectedEntities = entities.filter(
- e => idArray.indexOf(getIdOrCodeOrIndex(e)) > -1 && !isEntityDisabled(e)
- );
- newIdMap =
- newIdMap ||
- selectedEntities.reduce((acc, entity) => {
- acc[getIdOrCodeOrIndex(entity)] = { entity };
- return acc;
- }, {});
- change("reduxFormExpandedEntityIdMap", newIdMap);
- finalizeSelection({ idMap: newIdMap, entities, props: newProps });
- const idToScrollTo = idArray[0];
- if (!idToScrollTo && idToScrollTo !== 0) return;
- const entityIndexToScrollTo = entities.findIndex(
- e => e.id === idToScrollTo || e.code === idToScrollTo
- );
- if (entityIndexToScrollTo === -1 || !this.tableRef.current) return;
- const tableBody =
- this.tableRef.current.tableRef.querySelector(".rt-tbody");
- if (!tableBody) return;
- const rowEl =
- tableBody.getElementsByClassName("rt-tr-group")[entityIndexToScrollTo];
- if (!rowEl) return;
- setTimeout(() => {
- //we need to delay for a teeny bit to make sure the table has drawn
- rowEl &&
- tableBody &&
- scrollIntoView(rowEl, tableBody, {
- alignWithTop: true
- });
- }, 0);
}
- };
+ return { schema };
+ }, [
+ _schema,
+ cellRenderer,
+ entities,
+ isInfinite,
+ isOpenable,
+ isSimple,
+ isViewable,
+ showForcedHiddenColumns,
+ tableConfig,
+ withDisplayOptions
+ ]);
+
+ const {
+ moveColumnPersist,
+ persistPageSize,
+ resetDefaultVisibility,
+ resizePersist,
+ updateColumnVisibility,
+ updateTableDisplayDensity
+ } = useMemo(() => {
+ let resetDefaultVisibility;
+ let updateColumnVisibility;
+ let updateTableDisplayDensity;
+ let persistPageSize;
+ let moveColumnPersist;
+ let resizePersist = noop;
+
+ if (withDisplayOptions) {
+ const fieldOptsByPath = keyBy(tableConfig.fieldOptions, "path");
+ if (syncDisplayOptionsToDb) {
+ // sync up to db
+ // There must be a better way to set this variable...
+ let tableConfigurationId;
+ resetDefaultVisibility = function () {
+ tableConfigurationId = tableConfig.id;
+ if (tableConfigurationId) {
+ deleteTableConfiguration(tableConfigurationId);
+ }
+ };
+ updateColumnVisibility = function ({ shouldShow, path }) {
+ if (tableConfigurationId) {
+ const existingFieldOpt = fieldOptsByPath[path] || {};
+ upsertFieldOption({
+ id: existingFieldOpt.id,
+ path,
+ isHidden: !shouldShow,
+ tableConfigurationId
+ });
+ } else {
+ upsertTableConfiguration({
+ userId: currentUser.user.id,
+ formName,
+ fieldOptions: [
+ {
+ path,
+ isHidden: !shouldShow
+ }
+ ]
+ });
+ }
+ };
+ } else {
+ const syncStorage = newTableConfig => {
+ setTableConfig(newTableConfig);
+ window.localStorage.setItem(formName, JSON.stringify(newTableConfig));
+ };
- formatAndValidateTableInitial = () => {
- const {
- _origEntities,
- entities,
- initialEntities,
- change,
- reduxFormCellValidation
- } = this.props;
- const { newEnts, validationErrors } = this.formatAndValidateEntities(
- initialEntities ||
- (entities && entities.length ? entities : _origEntities)
- );
- change("reduxFormEntities", newEnts);
- const toKeep = {};
- //on the initial load we want to keep any async table wide errors
- forEach(reduxFormCellValidation, (v, k) => {
- if (v && v._isTableAsyncWideError) {
- toKeep[k] = v;
+ //sync display options with localstorage
+ resetDefaultVisibility = function () {
+ setTableConfig({ fieldOptions: [] });
+ window.localStorage.removeItem(formName);
+ };
+ updateColumnVisibility = function ({ path, paths, shouldShow }) {
+ const newFieldOpts = {
+ ...fieldOptsByPath
+ };
+ const pathsToUse = paths ? paths : [path];
+ pathsToUse.forEach(path => {
+ newFieldOpts[path] = { path, isHidden: !shouldShow };
+ });
+ syncStorage({ ...tableConfig, fieldOptions: toArray(newFieldOpts) });
+ };
+ updateTableDisplayDensity = function (density) {
+ syncStorage({ ...tableConfig, density: density });
+ };
+ persistPageSize = function (pageSize) {
+ syncStorage({ ...tableConfig, userSetPageSize: pageSize });
+ };
+ moveColumnPersist = function ({ oldIndex, newIndex }) {
+ // we might already have an array of the fields [path1, path2, ..etc]
+ const columnOrderings =
+ tableConfig.columnOrderings ||
+ schema.fields.map(({ path }) => path); // columnOrderings is [path1, path2, ..etc]
+ syncStorage({
+ ...tableConfig,
+ columnOrderings: arrayMove(columnOrderings, oldIndex, newIndex)
+ });
+ };
+ resizePersist = function (newResized) {
+ syncStorage({ ...tableConfig, resized: newResized });
+ };
}
- });
- this.updateValidation(newEnts, {
- ...toKeep,
- ...validationErrors
- });
- };
+ }
+ return {
+ moveColumnPersist,
+ persistPageSize,
+ resetDefaultVisibility,
+ resizePersist,
+ updateColumnVisibility,
+ updateTableDisplayDensity
+ };
+ }, [
+ currentUser?.user?.id,
+ deleteTableConfiguration,
+ formName,
+ schema.fields,
+ syncDisplayOptionsToDb,
+ tableConfig,
+ upsertFieldOption,
+ upsertTableConfiguration,
+ withDisplayOptions
+ ]);
+
+ let compact = _compact;
+ let extraCompact = _extraCompact;
+
+ if (withDisplayOptions && tableConfig.density) {
+ compact = tableConfig.density === "compact";
+ extraCompact = tableConfig.density === "extraCompact";
+ }
- updateEntitiesHelper = (ents, fn) => {
- const { change, reduxFormEntitiesUndoRedoStack = { currentVersion: 0 } } =
- this.props;
- const [nextState, patches, inversePatches] = produceWithPatches(ents, fn);
- if (!inversePatches.length) return;
- const thatNewNew = [...nextState];
- thatNewNew.isDirty = true;
- change("reduxFormEntities", thatNewNew);
- change("reduxFormEntitiesUndoRedoStack", {
- ...omitBy(reduxFormEntitiesUndoRedoStack, (v, k) => {
- return toNumber(k) > reduxFormEntitiesUndoRedoStack.currentVersion + 1;
- }),
- currentVersion: reduxFormEntitiesUndoRedoStack.currentVersion + 1,
- [reduxFormEntitiesUndoRedoStack.currentVersion + 1]: {
- inversePatches,
- patches
- }
- });
- };
+ const resized = tableConfig.resized || [];
- handlePaste = e => {
- const {
- isCellEditable,
- reduxFormSelectedCells,
- reduxFormCellValidation,
- change,
- schema,
- entities
- } = computePresets(this.props);
+ const pageSize = controlled_pageSize || _pageSize;
- if (isCellEditable) {
- if (isEmpty(reduxFormSelectedCells)) return;
- try {
- let pasteData = [];
- let toPaste;
- if (window.clipboardData && window.clipboardData.getData) {
- // IE
- toPaste = window.clipboardData.getData("Text");
- } else if (e.clipboardData && e.clipboardData.getData) {
- toPaste = e.clipboardData.getData("text/plain");
+ const [expandedEntityIdMap, setExpandedEntityIdMap] = useState(() => {
+ const initialExpandedEntityIdMap = {};
+ if (expandAllByDefault) {
+ entities.forEach(entity => {
+ initialExpandedEntityIdMap[entity.id || entity.code] = true;
+ });
+ }
+ return initialExpandedEntityIdMap;
+ });
+
+ const updateValidation = useCallback(
+ (entities, newCellValidate) => {
+ const tableWideErr = validateTableWideErrors({
+ entities,
+ schema,
+ newCellValidate
+ });
+ change("reduxFormCellValidation", tableWideErr);
+ },
+ [change, schema]
+ );
+
+ const updateEntitiesHelper = useCallback(
+ (ents, fn) => {
+ const [nextState, patches, inversePatches] = produceWithPatches(ents, fn);
+ if (!inversePatches.length) return;
+ const thatNewNew = [...nextState];
+ thatNewNew.isDirty = true;
+ change("reduxFormEntities", thatNewNew);
+ setEntitiesUndoRedoStack(prev => ({
+ ...omitBy(prev, (v, k) => {
+ return toNumber(k) > prev.currentVersion + 1;
+ }),
+ currentVersion: prev.currentVersion + 1,
+ [prev.currentVersion + 1]: {
+ inversePatches,
+ patches
}
- const jsonToPaste = e.clipboardData.getData("application/json");
- try {
- const pastedJson = [];
- JSON.parse(jsonToPaste).forEach(row => {
- const newRow = [];
- Object.values(row).forEach(cell => {
- const cellVal = JSON.parse(cell);
- newRow.push(cellVal);
+ }));
+ },
+ [change]
+ );
+
+ const formatAndValidateEntities = useCallback(
+ (entities, { useDefaultValues, indexToStartAt } = {}) => {
+ const editableFields = schema.fields.filter(f => !f.isNotEditable);
+ const validationErrors = {};
+
+ const newEnts = immer(entities, entities => {
+ entities.forEach((e, index) => {
+ editableFields.forEach(columnSchema => {
+ if (useDefaultValues) {
+ if (e[columnSchema.path] === undefined) {
+ if (isFunction(columnSchema.defaultValue)) {
+ e[columnSchema.path] = columnSchema.defaultValue(
+ index + indexToStartAt,
+ e
+ );
+ } else e[columnSchema.path] = columnSchema.defaultValue;
+ }
+ }
+ //mutative
+ const { error } = editCellHelper({
+ entity: e,
+ columnSchema,
+ newVal: e[columnSchema.path]
});
- pastedJson.push(newRow);
+ if (error) {
+ const rowId = getIdOrCodeOrIndex(e, index);
+ validationErrors[`${rowId}:${columnSchema.path}`] = error;
+ }
});
- pasteData = pastedJson;
- // try to remove the header row if it exists
+ });
+ });
+ return {
+ newEnts,
+ validationErrors
+ };
+ },
+ [schema.fields]
+ );
+
+ const handleRowMove = useCallback(
+ (type, shiftHeld) => e => {
+ e.preventDefault();
+ e.stopPropagation();
+ let newIdMap = {};
+ const lastSelectedEnt = getLastSelectedEntity(
+ reduxFormSelectedEntityIdMap
+ );
+
+ if (noSelect) return;
+ if (lastSelectedEnt) {
+ let lastSelectedIndex = entities.findIndex(
+ ent => ent === lastSelectedEnt
+ );
+ if (lastSelectedIndex === -1) {
+ if (lastSelectedEnt.id !== undefined) {
+ lastSelectedIndex = entities.findIndex(
+ ent => ent.id === lastSelectedEnt.id
+ );
+ } else if (lastSelectedEnt.code !== undefined) {
+ lastSelectedIndex = entities.findIndex(
+ ent => ent.code === lastSelectedEnt.code
+ );
+ }
+ }
+ if (lastSelectedIndex === -1) {
+ return;
+ }
+ const newEntToSelect = getNewEntToSelect({
+ type,
+ lastSelectedIndex,
+ entities,
+ isEntityDisabled
+ });
+
+ if (!newEntToSelect) return;
+ if (shiftHeld && !isSingleSelect) {
if (
- pasteData[0] &&
- pasteData[0][0] &&
- pasteData[0][0].__isHeaderCell
+ reduxFormSelectedEntityIdMap[
+ newEntToSelect.id || newEntToSelect.code
+ ]
) {
- pasteData = pasteData.slice(1);
- }
- } catch (e) {
- if (toPaste.includes(",")) {
- //try papaparsing it out as a csv if it contains commas
- try {
- const { data, errors } = papaparse.parse(toPaste, {
- header: false
- });
- if (data?.length && !errors?.length) {
- pasteData = data;
+ //the entity being moved to has already been selected
+ newIdMap = omit(reduxFormSelectedEntityIdMap, [
+ lastSelectedEnt.id || lastSelectedEnt.code
+ ]);
+ newIdMap[newEntToSelect.id || newEntToSelect.code].time =
+ Date.now() + 1;
+ } else {
+ //the entity being moved to has NOT been selected yet
+ newIdMap = {
+ ...reduxFormSelectedEntityIdMap,
+ [newEntToSelect.id || newEntToSelect.code]: {
+ entity: newEntToSelect,
+ time: Date.now()
}
- } catch (error) {
- console.error(`error p982qhgpf9qh`, error);
- }
+ };
}
+ } else {
+ //no shiftHeld
+ newIdMap[newEntToSelect.id || newEntToSelect.code] = {
+ entity: newEntToSelect,
+ time: Date.now()
+ };
}
- pasteData = pasteData.length ? pasteData : defaultParsePaste(toPaste);
-
- if (!pasteData || !pasteData.length) return;
+ }
- if (pasteData.length === 1 && pasteData[0].length === 1) {
- const newCellValidate = {
- ...reduxFormCellValidation
- };
- // single paste value, fill all cells with value
- const newVal = pasteData[0][0];
- this.updateEntitiesHelper(entities, entities => {
- const entityIdToEntity = getEntityIdToEntity(entities);
- Object.keys(reduxFormSelectedCells).forEach(cellId => {
- const [rowId, path] = cellId.split(":");
-
- const entity = entityIdToEntity[rowId].e;
- delete entity._isClean;
- const { error } = editCellHelper({
- entity,
- path,
- schema,
- newVal: formatPasteData({ newVal, path, schema })
- });
- if (error) {
- newCellValidate[cellId] = error;
- } else {
- delete newCellValidate[cellId];
- }
- });
- this.updateValidation(entities, newCellValidate);
- });
- } else {
- // handle paste in same format
- const primarySelectedCell = this.getPrimarySelectedCellId();
- if (primarySelectedCell) {
- const newCellValidate = {
- ...reduxFormCellValidation
- };
-
- const newSelectedCells = { ...reduxFormSelectedCells };
- this.updateEntitiesHelper(entities, entities => {
- const entityIdToEntity = getEntityIdToEntity(entities);
- const [rowId, primaryCellPath] = primarySelectedCell.split(":");
- const primaryEntityInfo = entityIdToEntity[rowId];
- const startIndex = primaryEntityInfo.i;
- const endIndex = primaryEntityInfo.i + pasteData.length;
- for (let i = startIndex; i < endIndex; i++) {
- if (!entities[i]) {
- entities[i] = { id: nanoid() };
- }
- }
- const entitiesToManipulate = entities.slice(startIndex, endIndex);
- const pathToIndex = getFieldPathToIndex(schema);
- const indexToPath = invert(pathToIndex);
- const startCellIndex = pathToIndex[primaryCellPath];
- pasteData.forEach((row, i) => {
- row.forEach((newVal, j) => {
- if (newVal) {
- const cellIndexToChange = startCellIndex + j;
- const entity = entitiesToManipulate[i];
- if (entity) {
- delete entity._isClean;
- const path = indexToPath[cellIndexToChange];
- if (path) {
- const { error } = editCellHelper({
- entity,
- path,
- schema,
- newVal: formatPasteData({
- newVal,
- path,
- schema
- })
- });
- const cellId = `${getIdOrCodeOrIndex(entity)}:${path}`;
- if (!newSelectedCells[cellId]) {
- newSelectedCells[cellId] = true;
- }
- if (error) {
- newCellValidate[cellId] = error;
- } else {
- delete newCellValidate[cellId];
- }
- }
- }
- }
- });
- });
- this.updateValidation(entities, newCellValidate);
- });
- change("reduxFormSelectedCells", newSelectedCells);
- }
- }
- } catch (error) {
- console.error(`error:`, error);
- }
- }
- };
-
- componentDidMount() {
- const {
- isCellEditable,
- entities = [],
- isLoading,
- showForcedHiddenColumns,
- setShowForcedHidden
- } = this.props;
- isCellEditable && this.formatAndValidateTableInitial();
- this.updateFromProps({}, computePresets(this.props));
- document.addEventListener("paste", this.handlePaste);
-
- if (!entities.length && !isLoading && !showForcedHiddenColumns) {
- setShowForcedHidden(true);
- }
- // const table = this.tableRef.current.tableRef;
- // let theads = table.getElementsByClassName("rt-thead");
- // let tbody = table.getElementsByClassName("rt-tbody")[0];
-
- // tbody.addEventListener("scroll", () => {
- // for (let i = 0; i < theads.length; i++) {
- // theads.item(i).scrollLeft = tbody.scrollLeft;
- // }
- // });
- }
-
- componentDidUpdate(oldProps) {
- // const tableBody = table.querySelector(".rt-tbody");
- // const headerNode = table.querySelector(".rt-thead.-header");
- // if (headerNode) headerNode.style.overflowY = "inherit";
- // if (tableBody && tableBody.scrollHeight > tableBody.clientHeight) {
- // if (headerNode) {
- // headerNode.style.overflowY = "scroll";
- // headerNode.style.overflowX = "hidden";
- // }
- // }
-
- this.updateFromProps(computePresets(oldProps), computePresets(this.props));
-
- // comment in to test what is causing re-render
- // Object.entries(this.props).forEach(
- // ([key, val]) =>
- // oldProps[key] !== val && console.info(`Prop '${key}' changed`)
- // );
- }
-
- componentWillUnmount() {
- document.removeEventListener("paste", this.handlePaste);
- }
-
- handleRowMove = (type, shiftHeld) => e => {
- e.preventDefault();
- e.stopPropagation();
- const props = computePresets(this.props);
- const {
- noSelect,
- entities,
- reduxFormSelectedEntityIdMap: idMap,
- isEntityDisabled,
- isSingleSelect
- } = props;
- let newIdMap = {};
- const lastSelectedEnt = getLastSelectedEntity(idMap);
-
- if (noSelect) return;
- if (lastSelectedEnt) {
- let lastSelectedIndex = entities.findIndex(
- ent => ent === lastSelectedEnt
- );
- if (lastSelectedIndex === -1) {
- if (lastSelectedEnt.id !== undefined) {
- lastSelectedIndex = entities.findIndex(
- ent => ent.id === lastSelectedEnt.id
- );
- } else if (lastSelectedEnt.code !== undefined) {
- lastSelectedIndex = entities.findIndex(
- ent => ent.code === lastSelectedEnt.code
- );
- }
- }
- if (lastSelectedIndex === -1) {
- return;
- }
- const newEntToSelect = getNewEntToSelect({
- type,
- lastSelectedIndex,
+ finalizeSelection({
+ idMap: newIdMap,
entities,
- isEntityDisabled
- });
-
- if (!newEntToSelect) return;
- if (shiftHeld && !isSingleSelect) {
- if (idMap[newEntToSelect.id || newEntToSelect.code]) {
- //the entity being moved to has already been selected
- newIdMap = omit(idMap, [lastSelectedEnt.id || lastSelectedEnt.code]);
- newIdMap[newEntToSelect.id || newEntToSelect.code].time =
- Date.now() + 1;
- } else {
- //the entity being moved to has NOT been selected yet
- newIdMap = {
- ...idMap,
- [newEntToSelect.id || newEntToSelect.code]: {
- entity: newEntToSelect,
- time: Date.now()
- }
- };
+ props: {
+ onDeselect,
+ onSingleRowSelect,
+ onMultiRowSelect,
+ noDeselectAll,
+ onRowSelect,
+ noSelect,
+ change
}
- } else {
- //no shiftHeld
- newIdMap[newEntToSelect.id || newEntToSelect.code] = {
- entity: newEntToSelect,
- time: Date.now()
- };
- }
- }
-
- finalizeSelection({
- idMap: newIdMap,
- entities,
- props
- });
- };
-
- handleCopyHotkey = e => {
- const { isCellEditable, reduxFormSelectedEntityIdMap } = computePresets(
- this.props
- );
-
- if (isCellEditable) {
- this.handleCopySelectedCells(e);
- } else {
- this.handleCopySelectedRows(
- getRecordsFromIdMap(reduxFormSelectedEntityIdMap),
- e
- );
- }
- };
-
- handleSelectAllRows = e => {
- const {
+ });
+ },
+ [
change,
- isEntityDisabled,
entities,
+ isEntityDisabled,
isSingleSelect,
- isCellEditable,
- schema
- } = computePresets(this.props);
- if (isSingleSelect) return;
- e.preventDefault();
-
- if (isCellEditable) {
- const schemaPaths = schema.fields.map(f => f.path);
- const newSelectedCells = {};
- entities.forEach((entity, i) => {
- if (isEntityDisabled(entity)) return;
- const entityId = getIdOrCodeOrIndex(entity, i);
- schemaPaths.forEach(p => {
- newSelectedCells[`${entityId}:${p}`] = true;
- });
- });
- change("reduxFormSelectedCells", newSelectedCells);
- } else {
- const newIdMap = {};
+ noDeselectAll,
+ noSelect,
+ onDeselect,
+ onMultiRowSelect,
+ onRowSelect,
+ onSingleRowSelect,
+ reduxFormSelectedEntityIdMap
+ ]
+ );
- entities.forEach((entity, i) => {
- if (isEntityDisabled(entity)) return;
- const entityId = getIdOrCodeOrIndex(entity, i);
- newIdMap[entityId] = { entity };
- });
- finalizeSelection({
- idMap: newIdMap,
- entities,
- props: computePresets(this.props)
- });
+ const primarySelectedCellId = useMemo(() => {
+ for (const k of Object.keys(selectedCells)) {
+ if (selectedCells[k] === PRIMARY_SELECTED_VAL) {
+ return k;
+ }
}
- };
-
- updateValidationHelper = () => {
- const { entities, reduxFormCellValidation } = computePresets(this.props);
- this.updateValidation(entities, reduxFormCellValidation);
- };
-
- handleDeleteCell = () => {
- const {
- reduxFormSelectedCells,
- reduxFormCellValidation,
- schema,
- entities
- } = computePresets(this.props);
+ }, [selectedCells]);
+
+ const startCellEdit = useCallback(
+ (cellId, { shouldSelectAll } = {}) => {
+ //check if the cell is already selected and editing and if so, don't change it
+ if (editingCell === cellId) return;
+ setSelectedCells(prev => ({ ...prev, [cellId]: PRIMARY_SELECTED_VAL }));
+ setEditingCell(cellId);
+ if (shouldSelectAll) {
+ //we should select the text
+ setEditingCellSelectAll(true);
+ }
+ },
+ [editingCell]
+ );
+
+ const handleEnterStartCellEdit = useCallback(
+ e => {
+ e.stopPropagation();
+ startCellEdit(primarySelectedCellId);
+ },
+ [primarySelectedCellId, startCellEdit]
+ );
+
+ const handleDeleteCell = useCallback(() => {
const newCellValidate = {
...reduxFormCellValidation
};
- if (isEmpty(reduxFormSelectedCells)) return;
+ if (isEmpty(selectedCells)) return;
const rowIds = [];
- this.updateEntitiesHelper(entities, entities => {
+ updateEntitiesHelper(entities, entities => {
const entityIdToEntity = getEntityIdToEntity(entities);
- Object.keys(reduxFormSelectedCells).forEach(cellId => {
+ Object.keys(selectedCells).forEach(cellId => {
const [rowId, path] = cellId.split(":");
rowIds.push(rowId);
const entity = entityIdToEntity[rowId].e;
@@ -920,1243 +954,893 @@ class DataTable extends React.Component {
delete newCellValidate[cellId];
}
});
- this.updateValidation(entities, newCellValidate);
+ updateValidation(entities, newCellValidate);
});
- };
-
- handleCut = e => {
- this.handleDeleteCell();
- this.handleCopyHotkey(e);
- };
-
- handleCopyTable = (e, opts) => {
- try {
- const allRowEls = getAllRows(e);
- if (!allRowEls) return;
- handleCopyRows(allRowEls, {
- ...opts,
- onFinishMsg: "Table Copied"
- });
- } catch (error) {
- console.error(`error:`, error);
- window.toastr.error("Error copying rows.");
- }
- };
-
- handleCopySelectedCells = e => {
- const {
- entities = [],
- reduxFormSelectedCells,
- schema
- } = computePresets(this.props);
- // if the current selection is consecutive cells then copy with
- // tabs between. if not then just select primary selected cell
- if (isEmpty(reduxFormSelectedCells)) return;
- const pathToIndex = getFieldPathToIndex(schema);
- const entityIdToEntity = getEntityIdToEntity(entities);
- const selectionGrid = [];
- let firstRowIndex;
- let firstCellIndex;
- Object.keys(reduxFormSelectedCells).forEach(key => {
- const [rowId, path] = key.split(":");
- const eInfo = entityIdToEntity[rowId];
- if (eInfo) {
- if (firstRowIndex === undefined || eInfo.i < firstRowIndex) {
- firstRowIndex = eInfo.i;
+ }, [
+ entities,
+ reduxFormCellValidation,
+ schema,
+ selectedCells,
+ updateEntitiesHelper,
+ updateValidation
+ ]);
+
+ const handleCopySelectedCells = useCallback(
+ e => {
+ // if the current selection is consecutive cells then copy with
+ // tabs between. if not then just select primary selected cell
+ if (isEmpty(selectedCells)) return;
+ const pathToIndex = getFieldPathToIndex(schema);
+ const entityIdToEntity = getEntityIdToEntity(entities);
+ const selectionGrid = [];
+ let firstRowIndex;
+ let firstCellIndex;
+ Object.keys(selectedCells).forEach(key => {
+ const [rowId, path] = key.split(":");
+ const eInfo = entityIdToEntity[rowId];
+ if (eInfo) {
+ if (firstRowIndex === undefined || eInfo.i < firstRowIndex) {
+ firstRowIndex = eInfo.i;
+ }
+ if (!selectionGrid[eInfo.i]) {
+ selectionGrid[eInfo.i] = [];
+ }
+ const cellIndex = pathToIndex[path];
+ if (firstCellIndex === undefined || cellIndex < firstCellIndex) {
+ firstCellIndex = cellIndex;
+ }
+ selectionGrid[eInfo.i][cellIndex] = true;
}
- if (!selectionGrid[eInfo.i]) {
- selectionGrid[eInfo.i] = [];
+ });
+ if (firstRowIndex === undefined) return;
+ const allRows = getAllRows(e);
+ let fullCellText = "";
+ const fullJson = [];
+ times(selectionGrid.length, i => {
+ const row = selectionGrid[i];
+ if (fullCellText) {
+ fullCellText += "\n";
}
- const cellIndex = pathToIndex[path];
- if (firstCellIndex === undefined || cellIndex < firstCellIndex) {
- firstCellIndex = cellIndex;
+ if (!row) {
+ return;
+ } else {
+ const jsonRow = [];
+ // ignore header
+ let [rowCopyText, json] = getRowCopyText(allRows[i + 1]);
+ rowCopyText = rowCopyText.split("\t");
+ times(row.length, i => {
+ const cell = row[i];
+ if (cell) {
+ fullCellText += rowCopyText[i];
+ jsonRow.push(json[i]);
+ }
+ if (i !== row.length - 1 && i >= firstCellIndex)
+ fullCellText += "\t";
+ });
+ fullJson.push(jsonRow);
}
- selectionGrid[eInfo.i][cellIndex] = true;
- }
- });
- if (firstRowIndex === undefined) return;
- const allRows = getAllRows(e);
- let fullCellText = "";
- const fullJson = [];
- times(selectionGrid.length, i => {
- const row = selectionGrid[i];
- if (fullCellText) {
- fullCellText += "\n";
+ });
+ if (!fullCellText) return window.toastr.warning("No text to copy");
+
+ handleCopyHelper(fullCellText, fullJson, "Selected cells copied");
+ },
+ [entities, selectedCells, schema]
+ );
+
+ const handleCopySelectedRows = useCallback(
+ (selectedRecords, e) => {
+ const idToIndex = entities.reduce((acc, e, i) => {
+ acc[e.id || e.code] = i;
+ return acc;
+ }, {});
+
+ //index 0 of the table is the column titles
+ //must add 1 to rowNum
+ const rowNumbersToCopy = selectedRecords
+ .map(rec => idToIndex[rec.id || rec.code] + 1)
+ .sort();
+
+ if (!rowNumbersToCopy.length) return;
+ rowNumbersToCopy.unshift(0); //add in the header row
+ try {
+ const allRowEls = getAllRows(e);
+ if (!allRowEls) return;
+ const rowEls = rowNumbersToCopy.map(i => allRowEls[i]);
+
+ handleCopyRows(rowEls, {
+ onFinishMsg: "Selected rows copied"
+ });
+ } catch (error) {
+ console.error(`error:`, error);
+ window.toastr.error("Error copying rows.");
}
- if (!row) {
- return;
+ },
+ [entities]
+ );
+
+ const handleCopyHotkey = useCallback(
+ e => {
+ if (isCellEditable) {
+ handleCopySelectedCells(e);
} else {
- const jsonRow = [];
- // ignore header
- let [rowCopyText, json] = getRowCopyText(allRows[i + 1]);
- rowCopyText = rowCopyText.split("\t");
- times(row.length, i => {
- const cell = row[i];
- if (cell) {
- fullCellText += rowCopyText[i];
- jsonRow.push(json[i]);
- }
- if (i !== row.length - 1 && i >= firstCellIndex) fullCellText += "\t";
- });
- fullJson.push(jsonRow);
+ handleCopySelectedRows(
+ getRecordsFromIdMap(reduxFormSelectedEntityIdMap),
+ e
+ );
}
- });
- if (!fullCellText) return window.toastr.warning("No text to copy");
-
- handleCopyHelper(fullCellText, fullJson, "Selected cells copied");
- };
-
- handleCopySelectedRows = (selectedRecords, e) => {
- const { entities = [] } = computePresets(this.props);
- const idToIndex = entities.reduce((acc, e, i) => {
- acc[e.id || e.code] = i;
- return acc;
- }, {});
-
- //index 0 of the table is the column titles
- //must add 1 to rowNum
- const rowNumbersToCopy = selectedRecords
- .map(rec => idToIndex[rec.id || rec.code] + 1)
- .sort();
-
- if (!rowNumbersToCopy.length) return;
- rowNumbersToCopy.unshift(0); //add in the header row
+ },
+ [
+ handleCopySelectedCells,
+ handleCopySelectedRows,
+ isCellEditable,
+ reduxFormSelectedEntityIdMap
+ ]
+ );
+
+ const handleCut = useCallback(
+ e => {
+ handleDeleteCell();
+ handleCopyHotkey(e);
+ },
+ [handleCopyHotkey, handleDeleteCell]
+ );
+
+ const flashTableBorder = () => {
try {
- const allRowEls = getAllRows(e);
- if (!allRowEls) return;
- const rowEls = rowNumbersToCopy.map(i => allRowEls[i]);
-
- handleCopyRows(rowEls, {
- onFinishMsg: "Selected rows copied"
- });
- } catch (error) {
- console.error(`error:`, error);
- window.toastr.error("Error copying rows.");
+ const table = tableRef.current.tableRef;
+ table.classList.add("tgBorderBlue");
+ setTimeout(() => {
+ table.classList.remove("tgBorderBlue");
+ }, 300);
+ } catch (e) {
+ console.error(`err when flashing table border:`, e);
}
};
- moveColumn = ({ oldIndex, newIndex }) => {
- const { columns } = this.state;
- let oldStateColumnIndex, newStateColumnIndex;
- columns.forEach((column, i) => {
- if (oldIndex === column.columnIndex) oldStateColumnIndex = i;
- if (newIndex === column.columnIndex) newStateColumnIndex = i;
- });
- // because it is all handled in state we need
- // to perform the move and update the columnIndices
- // because they are used for the sortable columns
- const newColumns = arrayMove(
- columns,
- oldStateColumnIndex,
- newStateColumnIndex
- ).map((column, i) => {
- return {
- ...column,
- columnIndex: i
- };
- });
- this.setState({
- columns: newColumns
- });
- };
-
- getTheadComponent = props => {
- const {
- withDisplayOptions,
- moveColumnPersist,
- syncDisplayOptionsToDb,
- change
- } = computePresets(this.props);
- let moveColumnPersistToUse = moveColumnPersist;
- if (moveColumnPersist && withDisplayOptions && !syncDisplayOptionsToDb) {
- //little hack to make localstorage changes get reflected in UI (we force an update to get the enhancers to run again :)
- moveColumnPersistToUse = (...args) => {
- moveColumnPersist(...args);
- change("localStorageForceUpdate", Math.random());
- };
+ const handleUndo = useCallback(() => {
+ if (entitiesUndoRedoStack.currentVersion > 0) {
+ flashTableBorder();
+ const nextState = applyPatches(
+ entities,
+ entitiesUndoRedoStack[entitiesUndoRedoStack.currentVersion]
+ .inversePatches
+ );
+ const { newEnts, validationErrors } =
+ formatAndValidateEntities(nextState);
+ setEntitiesUndoRedoStack(prev => ({
+ ...prev,
+ currentVersion: prev.currentVersion - 1
+ }));
+ updateValidation(newEnts, validationErrors);
+ change("reduxFormEntities", newEnts);
}
- return (
-
- );
- };
-
- getThComponent = compose(
- withProps(props => {
- const { columnindex } = props;
- return {
- index: columnindex ?? -1
- };
- })
- )(({ toggleSort, immovable, className, children, style, ...rest }) => {
- const { attributes, listeners, setNodeRef, transform, transition } =
- useSortable({
- id: `${rest.index}`,
- disabled: immovable === "true"
- });
-
- const sortStyles = {
- transform: CSS.Transform.toString(transform),
- transition
- };
-
- return (
-
toggleSort && toggleSort(e)}
- role="columnheader"
- tabIndex="-1" // Resolves eslint issues without implementing keyboard navigation incorrectly
- {...rest}
- >
- {children}
-
- );
- });
-
- addEntitiesToSelection = entities => {
- const propPresets = computePresets(this.props);
- const { isEntityDisabled, reduxFormSelectedEntityIdMap } = propPresets;
- const idMap = reduxFormSelectedEntityIdMap || {};
- const newIdMap = cloneDeep(idMap) || {};
- entities.forEach((entity, i) => {
- if (isEntityDisabled(entity)) return;
- const entityId = getIdOrCodeOrIndex(entity, i);
- newIdMap[entityId] = { entity };
- });
- finalizeSelection({
- idMap: newIdMap,
- entities,
- props: propPresets
- });
- };
+ }, [
+ change,
+ entities,
+ formatAndValidateEntities,
+ entitiesUndoRedoStack,
+ updateValidation
+ ]);
+
+ const handleRedo = useCallback(() => {
+ const nextV = entitiesUndoRedoStack.currentVersion + 1;
+ if (entitiesUndoRedoStack[nextV]) {
+ flashTableBorder();
+ const nextState = applyPatches(
+ entities,
+ entitiesUndoRedoStack[nextV].patches
+ );
+ const { newEnts, validationErrors } =
+ formatAndValidateEntities(nextState);
+ change("reduxFormEntities", newEnts);
+ updateValidation(newEnts, validationErrors);
+ setEntitiesUndoRedoStack(prev => ({
+ ...prev,
+ currentVersion: nextV
+ }));
+ }
+ }, [
+ change,
+ entities,
+ formatAndValidateEntities,
+ entitiesUndoRedoStack,
+ updateValidation
+ ]);
+
+ const handleSelectAllRows = useCallback(
+ e => {
+ if (isSingleSelect) return;
+ e.preventDefault();
+
+ if (isCellEditable) {
+ const schemaPaths = schema.fields.map(f => f.path);
+ const newSelectedCells = {};
+ entities.forEach((entity, i) => {
+ if (isEntityDisabled(entity)) return;
+ const entityId = getIdOrCodeOrIndex(entity, i);
+ schemaPaths.forEach(p => {
+ newSelectedCells[`${entityId}:${p}`] = true;
+ });
+ });
+ setSelectedCells(newSelectedCells);
+ } else {
+ const newIdMap = {};
- render() {
- const { fullscreen } = this.state;
- const propPresets = computePresets(this.props);
- const {
- extraClasses,
- className,
- tableName,
- isLoading,
- searchTerm,
- setSearchTerm,
- clearFilters,
- hidePageSizeWhenPossible,
- doNotShowEmptyRows,
- withTitle,
- withSearch,
- withPaging,
- isInfinite,
- disabled,
- noHeader,
- noFooter,
- noPadding,
- noFullscreenButton,
- withDisplayOptions,
- resized,
- resizePersist,
- updateColumnVisibility,
- persistPageSize,
- updateTableDisplayDensity,
+ entities.forEach((entity, i) => {
+ if (isEntityDisabled(entity)) return;
+ const entityId = getIdOrCodeOrIndex(entity, i);
+ newIdMap[entityId] = { entity };
+ });
+ finalizeSelection({
+ idMap: newIdMap,
+ entities,
+ props: {
+ onDeselect,
+ onSingleRowSelect,
+ onMultiRowSelect,
+ noDeselectAll,
+ onRowSelect,
+ noSelect,
+ change
+ }
+ });
+ }
+ },
+ [
change,
- syncDisplayOptionsToDb,
- resetDefaultVisibility,
- maxHeight,
- style,
- pageSize,
- formName,
- reduxFormSearchInput,
- reduxFormSelectedEntityIdMap,
- reduxFormExpandedEntityIdMap,
- schema,
- filters,
- errorParsingUrlString,
- hideDisplayOptionsIcon,
- compact,
- extraCompact,
- compactPaging,
- entityCount,
- showCount,
- isSingleSelect,
- noSelect,
- noRowsFoundMessage,
- SubComponent,
- shouldShowSubComponent,
- ReactTableProps = {},
- hideSelectedCount,
- hideColumnHeader,
- subHeader,
- isViewable,
- minimalStyle,
entities,
- onlyShowRowsWErrors,
- reduxFormCellValidation,
- entitiesAcrossPages,
- children: maybeChildren,
- topLeftItems,
- leftOfSearchBarItems,
- currentParams,
- hasOptionForForcedHidden,
- showForcedHiddenColumns,
- searchMenuButton,
- setShowForcedHidden,
- autoFocusSearch,
- additionalFooterButtons,
+ isCellEditable,
isEntityDisabled,
- isLocalCall,
- withSelectAll,
- variables,
- fragment,
- safeQuery,
- isCellEditable
- } = propPresets;
-
- if (withSelectAll && !safeQuery) {
- throw new Error("safeQuery is needed for selecting all table records");
- }
- let updateColumnVisibilityToUse = updateColumnVisibility;
- let persistPageSizeToUse = persistPageSize;
- let updateTableDisplayDensityToUse = updateTableDisplayDensity;
- let resetDefaultVisibilityToUse = resetDefaultVisibility;
- if (withDisplayOptions && !syncDisplayOptionsToDb) {
- //little hack to make localstorage changes get reflected in UI (we force an update to get the enhancers to run again :)
-
- const wrapUpdate =
- fn =>
- (...args) => {
- fn(...args);
- change("localStorageForceUpdate", Math.random());
- };
- updateColumnVisibilityToUse = wrapUpdate(updateColumnVisibility);
- updateTableDisplayDensityToUse = wrapUpdate(updateTableDisplayDensity);
- resetDefaultVisibilityToUse = wrapUpdate(resetDefaultVisibility);
- persistPageSizeToUse = wrapUpdate(persistPageSize);
- }
- let compactClassName = "";
- if (compactPaging) {
- compactClassName += " tg-compact-paging";
- }
- compactClassName += extraCompact
- ? " tg-extra-compact-table"
- : compact
- ? " tg-compact-table"
- : "";
-
- const hasFilters =
- filters.length ||
- searchTerm ||
- schema.fields.some(
- field => field.filterIsActive && field.filterIsActive(currentParams)
- );
- const additionalFilterKeys = schema.fields.reduce((acc, field) => {
- if (field.filterKey) acc.push(field.filterKey);
- return acc;
- }, []);
- const filtersOnNonDisplayedFields = [];
- if (filters && filters.length) {
- schema.fields.forEach(({ isHidden, displayName, path }) => {
- const ccDisplayName = camelCase(displayName || path);
- if (isHidden) {
- filters.forEach(filter => {
- if (filter.filterOn === ccDisplayName) {
- filtersOnNonDisplayedFields.push({
- ...filter,
- displayName
- });
+ isSingleSelect,
+ noDeselectAll,
+ noSelect,
+ onDeselect,
+ onMultiRowSelect,
+ onRowSelect,
+ onSingleRowSelect,
+ schema.fields
+ ]
+ );
+
+ const hotKeys = useMemo(
+ () => [
+ {
+ global: false,
+ combo: "up",
+ label: "Move Up a Row",
+ onKeyDown: handleRowMove("up")
+ },
+ {
+ global: false,
+ combo: "down",
+ label: "Move Down a Row",
+ onKeyDown: handleRowMove("down")
+ },
+ {
+ global: false,
+ combo: "up+shift",
+ label: "Move Up a Row",
+ onKeyDown: handleRowMove("up", true)
+ },
+ ...(isCellEditable
+ ? [
+ {
+ global: false,
+ combo: "enter",
+ label: "Enter -> Start Cell Edit",
+ onKeyDown: handleEnterStartCellEdit
+ },
+ {
+ global: false,
+ combo: "mod+x",
+ label: "Cut",
+ onKeyDown: handleCut
+ },
+ {
+ global: false,
+ combo: IS_LINUX ? "alt+z" : "mod+z",
+ label: "Undo",
+ onKeyDown: handleUndo
+ },
+ {
+ global: false,
+ combo: IS_LINUX ? "alt+shift+z" : "mod+shift+z",
+ label: "Redo",
+ onKeyDown: handleRedo
+ },
+ {
+ global: false,
+ combo: "backspace",
+ label: "Delete Cell",
+ onKeyDown: handleDeleteCell
}
- });
+ ]
+ : []),
+ {
+ global: false,
+ combo: "down+shift",
+ label: "Move Down a Row",
+ onKeyDown: handleRowMove("down", true)
+ },
+ {
+ global: false,
+ combo: "mod + c",
+ label: "Copy rows",
+ onKeyDown: handleCopyHotkey
+ },
+ {
+ global: false,
+ combo: "mod + a",
+ label: "Select rows",
+ onKeyDown: handleSelectAllRows
+ }
+ ],
+ [
+ handleCopyHotkey,
+ handleCut,
+ handleDeleteCell,
+ handleEnterStartCellEdit,
+ handleRedo,
+ handleRowMove,
+ handleSelectAllRows,
+ handleUndo,
+ isCellEditable
+ ]
+ );
+
+ const { handleKeyDown, handleKeyUp } = useHotkeys(hotKeys);
+ const [columns, setColumns] = useState([]);
+ const [fullscreen, setFullscreen] = useState(false);
+ const [selectingAll, setSelectingAll] = useState(false);
+
+ // We need to fix the useSelector not to make everything rerender everytime.
+ // Also we need to make sure formatAndValidateEntities gives a constant output
+ // from the same input.
+ useEffect(() => {
+ const formatAndValidateTableInitial = () => {
+ const { newEnts, validationErrors } = formatAndValidateEntities(entities);
+ const toKeep = {};
+ //on the initial load we want to keep any async table wide errors
+ forEach(reduxFormCellValidation, (v, k) => {
+ if (v && v._isTableAsyncWideError) {
+ toKeep[k] = v;
}
});
- }
- const numRows = isInfinite ? entities.length : pageSize;
- const idMap = reduxFormSelectedEntityIdMap || {};
- const selectedRowCount = Object.keys(idMap).filter(
- key => idMap[key]
- ).length;
-
- let rowsToShow = doNotShowEmptyRows
- ? Math.min(numRows, entities.length)
- : numRows;
- // if there are no entities then provide enough space to show
- // no rows found message
- if (entities.length === 0 && rowsToShow < 3) rowsToShow = 3;
- const expandedRows = entities.reduce((acc, row, index) => {
- const rowId = getIdOrCodeOrIndex(row, index);
- acc[index] = reduxFormExpandedEntityIdMap[rowId];
- return acc;
- }, {});
- let children = maybeChildren;
- if (children && typeof children === "function") {
- children = children(propPresets);
- }
- const showHeader = (withTitle || withSearch || children) && !noHeader;
- const toggleFullscreenButton = (
-
{
- this.setState({
- fullscreen: !this.state.fullscreen
- });
- }}
- />
- );
+ change("reduxFormEntities", newEnts);
+ updateValidation(newEnts, {
+ ...toKeep,
+ ...validationErrors
+ });
+ };
+ isCellEditable && formatAndValidateTableInitial();
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [change, isCellEditable]);
+
+ const handlePaste = useCallback(
+ e => {
+ if (isCellEditable) {
+ if (isEmpty(selectedCells)) return;
+ try {
+ let pasteData = [];
+ let toPaste;
+ if (window.clipboardData && window.clipboardData.getData) {
+ // IE
+ toPaste = window.clipboardData.getData("Text");
+ } else if (e.clipboardData && e.clipboardData.getData) {
+ toPaste = e.clipboardData.getData("text/plain");
+ }
+ const jsonToPaste = e.clipboardData.getData("application/json");
+ try {
+ const pastedJson = [];
+ JSON.parse(jsonToPaste).forEach(row => {
+ const newRow = [];
+ Object.values(row).forEach(cell => {
+ const cellVal = JSON.parse(cell);
+ newRow.push(cellVal);
+ });
+ pastedJson.push(newRow);
+ });
+ pasteData = pastedJson;
+ // try to remove the header row if it exists
+ if (
+ pasteData[0] &&
+ pasteData[0][0] &&
+ pasteData[0][0].__isHeaderCell
+ ) {
+ pasteData = pasteData.slice(1);
+ }
+ } catch (e) {
+ if (toPaste.includes(",")) {
+ //try papaparsing it out as a csv if it contains commas
+ try {
+ const { data, errors } = papaparse.parse(toPaste, {
+ header: false
+ });
+ if (data?.length && !errors?.length) {
+ pasteData = data;
+ }
+ } catch (error) {
+ console.error(`error p982qhgpf9qh`, error);
+ }
+ }
+ }
+ pasteData = pasteData.length ? pasteData : defaultParsePaste(toPaste);
- let showSelectAll = false;
- let showClearAll = false;
- // we want to show select all if every row on the current page is selected
- // and not every row across all pages are already selected.
- if (!isInfinite) {
- const canShowSelectAll =
- withSelectAll ||
- (entitiesAcrossPages && numRows < entitiesAcrossPages.length);
- if (canShowSelectAll) {
- // could all be disabled
- let atLeastOneRowOnCurrentPageSelected = false;
- const allRowsOnCurrentPageSelected = entities.every(e => {
- const rowId = getIdOrCodeOrIndex(e);
- const selected = idMap[rowId] || isEntityDisabled(e);
- if (selected) atLeastOneRowOnCurrentPageSelected = true;
- return selected;
- });
- if (
- atLeastOneRowOnCurrentPageSelected &&
- allRowsOnCurrentPageSelected
- ) {
- let everyEntitySelected;
- if (isLocalCall) {
- everyEntitySelected = entitiesAcrossPages.every(e => {
- const rowId = getIdOrCodeOrIndex(e);
- return idMap[rowId] || isEntityDisabled(e);
+ if (!pasteData || !pasteData.length) return;
+
+ if (pasteData.length === 1 && pasteData[0].length === 1) {
+ const newCellValidate = {
+ ...reduxFormCellValidation
+ };
+ // single paste value, fill all cells with value
+ const newVal = pasteData[0][0];
+ updateEntitiesHelper(entities, entities => {
+ const entityIdToEntity = getEntityIdToEntity(entities);
+ Object.keys(selectedCells).forEach(cellId => {
+ const [rowId, path] = cellId.split(":");
+
+ const entity = entityIdToEntity[rowId].e;
+ delete entity._isClean;
+ const { error } = editCellHelper({
+ entity,
+ path,
+ schema,
+ newVal: formatPasteData({ newVal, path, schema })
+ });
+ if (error) {
+ newCellValidate[cellId] = error;
+ } else {
+ delete newCellValidate[cellId];
+ }
+ });
+ updateValidation(entities, newCellValidate);
});
} else {
- everyEntitySelected = entityCount <= selectedRowCount;
- }
- if (everyEntitySelected) {
- showClearAll = selectedRowCount;
+ // handle paste in same format
+ if (primarySelectedCellId) {
+ const newCellValidate = {
+ ...reduxFormCellValidation
+ };
+
+ const newSelectedCells = { ...selectedCells };
+ updateEntitiesHelper(entities, entities => {
+ const entityIdToEntity = getEntityIdToEntity(entities);
+ const [rowId, primaryCellPath] =
+ primarySelectedCellId.split(":");
+ const primaryEntityInfo = entityIdToEntity[rowId];
+ const startIndex = primaryEntityInfo.i;
+ const endIndex = primaryEntityInfo.i + pasteData.length;
+ for (let i = startIndex; i < endIndex; i++) {
+ if (!entities[i]) {
+ entities[i] = { id: nanoid() };
+ }
+ }
+ const entitiesToManipulate = entities.slice(
+ startIndex,
+ endIndex
+ );
+ const pathToIndex = getFieldPathToIndex(schema);
+ const indexToPath = invert(pathToIndex);
+ const startCellIndex = pathToIndex[primaryCellPath];
+ pasteData.forEach((row, i) => {
+ row.forEach((newVal, j) => {
+ if (newVal) {
+ const cellIndexToChange = startCellIndex + j;
+ const entity = entitiesToManipulate[i];
+ if (entity) {
+ delete entity._isClean;
+ const path = indexToPath[cellIndexToChange];
+ if (path) {
+ const { error } = editCellHelper({
+ entity,
+ path,
+ schema,
+ newVal: formatPasteData({
+ newVal,
+ path,
+ schema
+ })
+ });
+ const cellId = `${getIdOrCodeOrIndex(entity)}:${path}`;
+ if (!newSelectedCells[cellId]) {
+ newSelectedCells[cellId] = true;
+ }
+ if (error) {
+ newCellValidate[cellId] = error;
+ } else {
+ delete newCellValidate[cellId];
+ }
+ }
+ }
+ }
+ });
+ });
+ updateValidation(entities, newCellValidate);
+ });
+ setSelectedCells(newSelectedCells);
+ }
}
- // only show if not all selected
- showSelectAll = !everyEntitySelected;
+ } catch (error) {
+ console.error(`error:`, error);
}
}
- }
+ },
+ [
+ entities,
+ isCellEditable,
+ primarySelectedCellId,
+ reduxFormCellValidation,
+ schema,
+ selectedCells,
+ updateEntitiesHelper,
+ updateValidation
+ ]
+ );
+
+ useEffect(() => {
+ document.addEventListener("paste", handlePaste);
+ return () => {
+ document.removeEventListener("paste", handlePaste);
+ };
+ }, [handlePaste]);
- const showNumSelected = !noSelect && !isSingleSelect && !hideSelectedCount;
- let selectedAndTotalMessage = "";
- if (showNumSelected) {
- selectedAndTotalMessage += `${selectedRowCount} Selected `;
- }
- if (showCount && showNumSelected) {
- selectedAndTotalMessage += `/ `;
+ useEffect(() => {
+ if (!entities.length && !isLoading && !showForcedHiddenColumns) {
+ setShowForcedHidden(true);
}
- if (showCount) {
- selectedAndTotalMessage += `${entityCount || 0} Total`;
+ }, [
+ entities.length,
+ isLoading,
+ setShowForcedHidden,
+ showForcedHiddenColumns
+ ]);
+
+ useEffect(() => {
+ addFilters(additionalFilters);
+ }, [addFilters, additionalFilters]);
+
+ useEffect(() => {
+ setColumns(
+ schema.fields
+ ? schema.fields.reduce(function (columns, field, i) {
+ if (field.isHidden) {
+ return columns;
+ }
+ return columns.concat({
+ ...field,
+ columnIndex: i
+ });
+ }, [])
+ : []
+ );
+ }, [schema?.fields]);
+
+ const setSelectedIds = useCallback(
+ (selectedIds, scrollToFirst) => {
+ const idArray = Array.isArray(selectedIds) ? selectedIds : [selectedIds];
+ const selectedEntities = entities.filter(
+ e => idArray.indexOf(getIdOrCodeOrIndex(e)) > -1 && !isEntityDisabled(e)
+ );
+ const newIdMap = selectedEntities.reduce((acc, entity) => {
+ acc[getIdOrCodeOrIndex(entity)] = { entity };
+ return acc;
+ }, {});
+ setExpandedEntityIdMap(newIdMap);
+ finalizeSelection({
+ idMap: newIdMap,
+ entities,
+ props: {
+ onDeselect,
+ onSingleRowSelect,
+ onMultiRowSelect,
+ noDeselectAll,
+ onRowSelect,
+ noSelect,
+ change
+ }
+ });
+ // This option could be eliminated, keeping it because it was prior in the
+ // code, but it is a fuctionality not needed
+ if (scrollToFirst) {
+ const idToScrollTo = idArray[0];
+ if (!idToScrollTo && idToScrollTo !== 0) return;
+ const entityIndexToScrollTo = entities.findIndex(
+ e => e.id === idToScrollTo || e.code === idToScrollTo
+ );
+ if (entityIndexToScrollTo === -1 || !tableRef.current) return;
+ const tableBody = tableRef.current.tableRef.querySelector(".rt-tbody");
+ if (!tableBody) return;
+ const rowEl =
+ tableBody.getElementsByClassName("rt-tr-group")[
+ entityIndexToScrollTo
+ ];
+ if (!rowEl) return;
+ setTimeout(() => {
+ //we need to delay for a teeny bit to make sure the table has drawn
+ rowEl &&
+ tableBody &&
+ scrollIntoView(rowEl, tableBody, {
+ alignWithTop: true
+ });
+ }, 0);
+ }
+ },
+ [
+ change,
+ entities,
+ isEntityDisabled,
+ noDeselectAll,
+ noSelect,
+ onDeselect,
+ onMultiRowSelect,
+ onRowSelect,
+ onSingleRowSelect
+ ]
+ );
+
+ useEffect(() => {
+ if (alreadySelected.current) return;
+ alreadySelected.current = true;
+ if (initialSelectedIds) {
+ setSelectedIds(initialSelectedIds);
+ return;
}
- if (selectedAndTotalMessage) {
- selectedAndTotalMessage = {selectedAndTotalMessage}
;
+ if (selectAllByDefault && entities && entities.length) {
+ setSelectedIds(entities.map(getIdOrCodeOrIndex));
}
+ }, [entities, initialSelectedIds, selectAllByDefault, setSelectedIds]);
- const shouldShowPaging =
- !isInfinite &&
- withPaging &&
- (hidePageSizeWhenPossible ? entityCount > pageSize : true);
+ useEffect(() => {
+ if (!alreadySelected.current) return;
+ if (initialSelectedIds) {
+ setSelectedIds(initialSelectedIds);
+ return;
+ }
+ }, [initialSelectedIds, setSelectedIds]);
- let SubComponentToUse;
- if (SubComponent) {
- SubComponentToUse = row => {
- let shouldShow = true;
- if (shouldShowSubComponent) {
- shouldShow = shouldShowSubComponent(row.original);
- }
- if (shouldShow) {
- return SubComponent(row);
- }
+ const moveColumn = ({ oldIndex, newIndex }) => {
+ let oldStateColumnIndex, newStateColumnIndex;
+ columns.forEach((column, i) => {
+ if (oldIndex === column.columnIndex) oldStateColumnIndex = i;
+ if (newIndex === column.columnIndex) newStateColumnIndex = i;
+ });
+ // because it is all handled in state we need
+ // to perform the move and update the columnIndices
+ // because they are used for the sortable columns
+ const newColumns = arrayMove(
+ columns,
+ oldStateColumnIndex,
+ newStateColumnIndex
+ ).map((column, i) => {
+ return {
+ ...column,
+ columnIndex: i
};
- }
- let nonDisplayedFilterComp;
- if (filtersOnNonDisplayedFields.length) {
- const content = filtersOnNonDisplayedFields.map(
- ({ displayName, path, selectedFilter, filterValue }) => {
- let filterValToDisplay = filterValue;
- if (selectedFilter === "inList") {
- filterValToDisplay = Array.isArray(filterValToDisplay)
- ? filterValToDisplay
- : filterValToDisplay && filterValToDisplay.split(";");
- }
- if (Array.isArray(filterValToDisplay)) {
- filterValToDisplay = filterValToDisplay.join(", ");
+ });
+ setColumns(newColumns);
+ };
+
+ const TheadComponent = ({ className, style, children }) => (
+
+ {children}
+
+ );
+
+ const addEntitiesToSelection = entities => {
+ const idMap = reduxFormSelectedEntityIdMap || {};
+ const newIdMap = cloneDeep(idMap) || {};
+ entities.forEach((entity, i) => {
+ if (isEntityDisabled(entity)) return;
+ const entityId = getIdOrCodeOrIndex(entity, i);
+ newIdMap[entityId] = { entity };
+ });
+ finalizeSelection({
+ idMap: newIdMap,
+ entities,
+ props: {
+ onDeselect,
+ onSingleRowSelect,
+ onMultiRowSelect,
+ noDeselectAll,
+ onRowSelect,
+ noSelect,
+ change
+ }
+ });
+ };
+
+ const renderColumnHeader = column => {
+ const {
+ displayName,
+ description,
+ isUnique,
+ sortDisabled,
+ filterDisabled,
+ columnFilterDisabled,
+ renderTitleInner,
+ filterIsActive = noop,
+ noTitle,
+ isNotEditable,
+ type,
+ path
+ } = column;
+ const columnDataType = column.type;
+ const isActionColumn = columnDataType === "action";
+ const disableSorting =
+ sortDisabled ||
+ isActionColumn ||
+ (!isLocalCall && typeof path === "string" && path.includes(".")) ||
+ columnDataType === "color";
+ const disableFiltering =
+ filterDisabled ||
+ columnDataType === "color" ||
+ isActionColumn ||
+ columnFilterDisabled;
+ const ccDisplayName = camelCase(displayName || path);
+ let columnTitle = displayName || startCase(camelCase(path));
+ if (isActionColumn) columnTitle = "";
+
+ const currentFilter =
+ filters &&
+ !!filters.length &&
+ filters.filter(({ filterOn }) => {
+ return filterOn === ccDisplayName;
+ })[0];
+ const filterActiveForColumn =
+ !!currentFilter || filterIsActive(currentParams);
+ let ordering;
+ if (order && order.length) {
+ order.forEach(order => {
+ const orderField = order.replace("-", "");
+ if (orderField === ccDisplayName) {
+ if (orderField === order) {
+ ordering = "asc";
+ } else {
+ ordering = "desc";
}
- return (
-
- {displayName || startCase(camelCase(path))}{" "}
- {lowerCase(selectedFilter)} {filterValToDisplay}
-
- );
- }
- );
- nonDisplayedFilterComp = (
-
-
- Active filters on hidden columns:
-
-
- {content}
-
- }
- >
-
-
-
- );
- }
- let filteredEnts = entities;
-
- if (onlyShowRowsWErrors) {
- const rowToErrorMap = {};
- forEach(reduxFormCellValidation, (err, cellId) => {
- if (err) {
- const [rowId] = cellId.split(":");
- rowToErrorMap[rowId] = true;
}
});
- filteredEnts = entities.filter(e => {
- return rowToErrorMap[e.id];
- });
}
- return (
- // eslint-disable-next-line no-undef
-