@teselagen/ui 0.0.11 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/cypress.config.ts +6 -0
- package/index.html +12 -0
- package/package.json +5 -11
- package/project.json +74 -0
- package/src/AdvancedOptions.js +33 -0
- package/src/AdvancedOptions.spec.js +24 -0
- package/src/AssignDefaultsModeContext.js +21 -0
- package/src/AsyncValidateFieldSpinner/index.js +12 -0
- package/src/BlueprintError/index.js +14 -0
- package/src/BounceLoader/index.js +16 -0
- package/src/BounceLoader/style.css +45 -0
- package/src/CollapsibleCard/index.js +92 -0
- package/src/CollapsibleCard/style.css +21 -0
- package/src/DNALoader/index.js +20 -0
- package/src/DNALoader/style.css +251 -0
- package/src/DataTable/CellDragHandle.js +130 -0
- package/src/DataTable/DisabledLoadingComponent.js +15 -0
- package/src/DataTable/DisplayOptions.js +218 -0
- package/src/DataTable/FilterAndSortMenu.js +397 -0
- package/src/DataTable/PagingTool.js +232 -0
- package/src/DataTable/SearchBar.js +57 -0
- package/src/DataTable/SortableColumns.js +53 -0
- package/src/DataTable/TableFormTrackerContext.js +10 -0
- package/src/DataTable/dataTableEnhancer.js +291 -0
- package/src/DataTable/defaultFormatters.js +32 -0
- package/src/DataTable/defaultProps.js +45 -0
- package/src/DataTable/defaultValidators.js +40 -0
- package/src/DataTable/editCellHelper.js +44 -0
- package/src/DataTable/getCellVal.js +20 -0
- package/src/DataTable/getVals.js +8 -0
- package/src/DataTable/index.js +3537 -0
- package/src/DataTable/isTruthy.js +12 -0
- package/src/DataTable/isValueEmpty.js +3 -0
- package/src/DataTable/style.css +600 -0
- package/src/DataTable/utils/computePresets.js +42 -0
- package/src/DataTable/utils/convertSchema.js +69 -0
- package/src/DataTable/utils/getIdOrCodeOrIndex.js +9 -0
- package/src/DataTable/utils/getTableConfigFromStorage.js +5 -0
- package/src/DataTable/utils/queryParams.js +1032 -0
- package/src/DataTable/utils/rowClick.js +156 -0
- package/src/DataTable/utils/selection.js +8 -0
- package/src/DataTable/utils/withSelectedEntities.js +65 -0
- package/src/DataTable/utils/withTableParams.js +328 -0
- package/src/DataTable/validateTableWideErrors.js +135 -0
- package/src/DataTable/viewColumn.js +37 -0
- package/src/DialogFooter/index.js +79 -0
- package/src/DialogFooter/style.css +9 -0
- package/src/DropdownButton.js +36 -0
- package/src/FillWindow.css +6 -0
- package/src/FillWindow.js +69 -0
- package/src/FormComponents/Uploader.js +1197 -0
- package/src/FormComponents/getNewName.js +31 -0
- package/src/FormComponents/index.js +1384 -0
- package/src/FormComponents/itemUpload.js +84 -0
- package/src/FormComponents/sortify.js +73 -0
- package/src/FormComponents/style.css +247 -0
- package/src/FormComponents/tryToMatchSchemas.js +222 -0
- package/src/FormComponents/utils.js +6 -0
- package/src/HotkeysDialog/index.js +79 -0
- package/src/HotkeysDialog/style.css +54 -0
- package/src/InfoHelper/index.js +83 -0
- package/src/InfoHelper/style.css +7 -0
- package/src/IntentText/index.js +18 -0
- package/src/Loading/index.js +74 -0
- package/src/Loading/style.css +4 -0
- package/src/MatchHeaders.js +223 -0
- package/src/MenuBar/index.js +416 -0
- package/src/MenuBar/style.css +45 -0
- package/src/PromptUnsavedChanges/index.js +40 -0
- package/src/ResizableDraggableDialog/index.js +138 -0
- package/src/ResizableDraggableDialog/style.css +42 -0
- package/src/ScrollToTop/index.js +72 -0
- package/src/SimpleStepViz.js +26 -0
- package/src/TgSelect/index.js +465 -0
- package/src/TgSelect/style.css +34 -0
- package/src/TgSuggest/index.js +121 -0
- package/src/Timeline/TimelineEvent.js +31 -0
- package/src/Timeline/index.js +22 -0
- package/src/Timeline/style.css +29 -0
- package/src/UploadCsvWizard.css +4 -0
- package/src/UploadCsvWizard.js +731 -0
- package/src/autoTooltip.js +89 -0
- package/src/constants.js +1 -0
- package/src/customIcons.js +361 -0
- package/src/enhancers/withDialog/index.js +196 -0
- package/src/enhancers/withDialog/tg_modalState.js +46 -0
- package/src/enhancers/withField.js +20 -0
- package/src/enhancers/withFields.js +11 -0
- package/src/enhancers/withLocalStorage.js +11 -0
- package/src/index.js +76 -0
- package/src/rerenderOnWindowResize.js +27 -0
- package/src/showAppSpinner.js +12 -0
- package/src/showConfirmationDialog/index.js +116 -0
- package/src/showDialogOnDocBody.js +37 -0
- package/src/style.css +214 -0
- package/src/toastr.js +92 -0
- package/src/typeToCommonType.js +6 -0
- package/src/useDialog.js +64 -0
- package/src/utils/S3Download.js +14 -0
- package/src/utils/adHoc.js +10 -0
- package/src/utils/basicHandleActionsWithFullState.js +14 -0
- package/src/utils/combineReducersWithFullState.js +14 -0
- package/src/utils/commandControls.js +83 -0
- package/src/utils/commandUtils.js +112 -0
- package/src/utils/determineBlackOrWhiteTextColor.js +4 -0
- package/src/utils/getDayjsFormatter.js +35 -0
- package/src/utils/getTextFromEl.js +28 -0
- package/src/utils/handlerHelpers.js +30 -0
- package/src/utils/hotkeyUtils.js +129 -0
- package/src/utils/menuUtils.js +402 -0
- package/src/utils/popoverOverflowModifiers.js +11 -0
- package/src/utils/pureNoFunc.js +31 -0
- package/src/utils/renderOnDoc.js +29 -0
- package/src/utils/showProgressToast.js +22 -0
- package/src/utils/tagUtils.js +45 -0
- package/src/utils/tgFormValues.js +32 -0
- package/src/utils/withSelectTableRecords.js +38 -0
- package/src/utils/withStore.js +10 -0
- package/src/wrapDialog.js +112 -0
- package/tsconfig.json +4 -0
- package/vite.config.ts +7 -0
- package/index.mjs +0 -109378
- package/index.umd.js +0 -109381
- package/style.css +0 -10421
|
@@ -0,0 +1,3537 @@
|
|
|
1
|
+
/* eslint react/jsx-no-bind: 0 */
|
|
2
|
+
import React, { useState } from "react";
|
|
3
|
+
import ReactDOM from "react-dom";
|
|
4
|
+
import { arrayMove } from "react-sortable-hoc";
|
|
5
|
+
import copy from "copy-to-clipboard";
|
|
6
|
+
import {
|
|
7
|
+
invert,
|
|
8
|
+
toNumber,
|
|
9
|
+
isEmpty,
|
|
10
|
+
min,
|
|
11
|
+
max,
|
|
12
|
+
flatMap,
|
|
13
|
+
set,
|
|
14
|
+
map,
|
|
15
|
+
toString,
|
|
16
|
+
camelCase,
|
|
17
|
+
startCase,
|
|
18
|
+
noop,
|
|
19
|
+
isEqual,
|
|
20
|
+
cloneDeep,
|
|
21
|
+
keyBy,
|
|
22
|
+
omit,
|
|
23
|
+
forEach,
|
|
24
|
+
lowerCase,
|
|
25
|
+
get,
|
|
26
|
+
padStart,
|
|
27
|
+
omitBy,
|
|
28
|
+
times
|
|
29
|
+
} from "lodash";
|
|
30
|
+
import joinUrl from "url-join";
|
|
31
|
+
|
|
32
|
+
import {
|
|
33
|
+
Button,
|
|
34
|
+
Menu,
|
|
35
|
+
MenuItem,
|
|
36
|
+
Classes,
|
|
37
|
+
ContextMenu,
|
|
38
|
+
Checkbox,
|
|
39
|
+
Icon,
|
|
40
|
+
Popover,
|
|
41
|
+
Intent,
|
|
42
|
+
Callout,
|
|
43
|
+
Tooltip
|
|
44
|
+
} from "@blueprintjs/core";
|
|
45
|
+
import classNames from "classnames";
|
|
46
|
+
import scrollIntoView from "dom-scroll-into-view";
|
|
47
|
+
import { SortableElement } from "react-sortable-hoc";
|
|
48
|
+
import ReactTable from "@teselagen/react-table";
|
|
49
|
+
import { withProps, branch, compose } from "recompose";
|
|
50
|
+
import dayjs from "dayjs";
|
|
51
|
+
import localizedFormat from "dayjs/plugin/localizedFormat";
|
|
52
|
+
import ReactMarkdown from "react-markdown";
|
|
53
|
+
import immer, { produceWithPatches, enablePatches, applyPatches } from "immer";
|
|
54
|
+
import papaparse from "papaparse";
|
|
55
|
+
import remarkGfm from "remark-gfm";
|
|
56
|
+
|
|
57
|
+
import TgSelect from "../TgSelect";
|
|
58
|
+
import { withHotkeys } from "../utils/hotkeyUtils";
|
|
59
|
+
import InfoHelper from "../InfoHelper";
|
|
60
|
+
import getTextFromEl from "../utils/getTextFromEl";
|
|
61
|
+
import { getSelectedRowsFromEntities } from "./utils/selection";
|
|
62
|
+
import rowClick, {
|
|
63
|
+
changeSelectedEntities,
|
|
64
|
+
finalizeSelection
|
|
65
|
+
} from "./utils/rowClick";
|
|
66
|
+
import PagingTool from "./PagingTool";
|
|
67
|
+
import FilterAndSortMenu from "./FilterAndSortMenu";
|
|
68
|
+
import getIdOrCodeOrIndex from "./utils/getIdOrCodeOrIndex";
|
|
69
|
+
import SearchBar from "./SearchBar";
|
|
70
|
+
import DisplayOptions from "./DisplayOptions";
|
|
71
|
+
import DisabledLoadingComponent from "./DisabledLoadingComponent";
|
|
72
|
+
import SortableColumns from "./SortableColumns";
|
|
73
|
+
import computePresets from "./utils/computePresets";
|
|
74
|
+
import dataTableEnhancer from "./dataTableEnhancer";
|
|
75
|
+
import defaultProps from "./defaultProps";
|
|
76
|
+
|
|
77
|
+
import "../toastr";
|
|
78
|
+
import "@teselagen/react-table/react-table.css";
|
|
79
|
+
import "./style.css";
|
|
80
|
+
import { getRecordsFromIdMap } from "./utils/withSelectedEntities";
|
|
81
|
+
import { CellDragHandle } from "./CellDragHandle";
|
|
82
|
+
import { nanoid } from "nanoid";
|
|
83
|
+
import { SwitchField } from "../FormComponents";
|
|
84
|
+
import { validateTableWideErrors } from "./validateTableWideErrors";
|
|
85
|
+
import { editCellHelper } from "./editCellHelper";
|
|
86
|
+
import { getCellVal } from "./getCellVal";
|
|
87
|
+
import { getVals } from "./getVals";
|
|
88
|
+
enablePatches();
|
|
89
|
+
|
|
90
|
+
const PRIMARY_SELECTED_VAL = "main_cell";
|
|
91
|
+
|
|
92
|
+
dayjs.extend(localizedFormat);
|
|
93
|
+
const IS_LINUX = window.navigator.platform.toLowerCase().search("linux") > -1;
|
|
94
|
+
class DataTable extends React.Component {
|
|
95
|
+
constructor(props) {
|
|
96
|
+
super(props);
|
|
97
|
+
this.hotkeyEnabler = withHotkeys({
|
|
98
|
+
moveUpARow: {
|
|
99
|
+
global: false,
|
|
100
|
+
combo: "up",
|
|
101
|
+
label: "Move Up a Row",
|
|
102
|
+
onKeyDown: this.handleRowMove("up")
|
|
103
|
+
},
|
|
104
|
+
moveDownARow: {
|
|
105
|
+
global: false,
|
|
106
|
+
combo: "down",
|
|
107
|
+
label: "Move Down a Row",
|
|
108
|
+
onKeyDown: this.handleRowMove("down")
|
|
109
|
+
},
|
|
110
|
+
moveUpARowShift: {
|
|
111
|
+
global: false,
|
|
112
|
+
combo: "up+shift",
|
|
113
|
+
label: "Move Up a Row",
|
|
114
|
+
onKeyDown: this.handleRowMove("up", true)
|
|
115
|
+
},
|
|
116
|
+
...(props.isCellEditable && {
|
|
117
|
+
enter: {
|
|
118
|
+
global: false,
|
|
119
|
+
combo: "enter",
|
|
120
|
+
label: "Enter -> Start Cell Edit",
|
|
121
|
+
onKeyDown: this.handleEnterStartCellEdit
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
cut: {
|
|
125
|
+
global: false,
|
|
126
|
+
combo: "mod+x",
|
|
127
|
+
label: "Cut",
|
|
128
|
+
onKeyDown: this.handleCut
|
|
129
|
+
},
|
|
130
|
+
undo: {
|
|
131
|
+
global: false,
|
|
132
|
+
combo: IS_LINUX ? "alt+z" : "mod+z",
|
|
133
|
+
label: "Undo",
|
|
134
|
+
onKeyDown: this.handleUndo
|
|
135
|
+
},
|
|
136
|
+
redo: {
|
|
137
|
+
global: false,
|
|
138
|
+
combo: IS_LINUX ? "alt+shift+z" : "mod+shift+z",
|
|
139
|
+
label: "Redo",
|
|
140
|
+
onKeyDown: this.handleRedo
|
|
141
|
+
},
|
|
142
|
+
deleteCell: {
|
|
143
|
+
global: false,
|
|
144
|
+
combo: "backspace",
|
|
145
|
+
label: "Delete Cell",
|
|
146
|
+
onKeyDown: this.handleDeleteCell
|
|
147
|
+
}
|
|
148
|
+
}),
|
|
149
|
+
moveDownARowShift: {
|
|
150
|
+
global: false,
|
|
151
|
+
combo: "down+shift",
|
|
152
|
+
label: "Move Down a Row",
|
|
153
|
+
onKeyDown: this.handleRowMove("down", true)
|
|
154
|
+
},
|
|
155
|
+
copyHotkey: {
|
|
156
|
+
global: false,
|
|
157
|
+
combo: "mod + c",
|
|
158
|
+
label: "Copy rows",
|
|
159
|
+
onKeyDown: this.handleCopyHotkey
|
|
160
|
+
},
|
|
161
|
+
selectAllRows: {
|
|
162
|
+
global: false,
|
|
163
|
+
combo: "mod + a",
|
|
164
|
+
label: "Select rows",
|
|
165
|
+
onKeyDown: this.handleSelectAllRows
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
state = {
|
|
170
|
+
columns: [],
|
|
171
|
+
fullscreen: false
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
static defaultProps = defaultProps;
|
|
175
|
+
|
|
176
|
+
toggleFullscreen = () => {
|
|
177
|
+
this.setState({
|
|
178
|
+
fullscreen: !this.state.fullscreen
|
|
179
|
+
});
|
|
180
|
+
};
|
|
181
|
+
handleEnterStartCellEdit = e => {
|
|
182
|
+
e.stopPropagation();
|
|
183
|
+
this.startCellEdit(this.getPrimarySelectedCellId());
|
|
184
|
+
};
|
|
185
|
+
flashTableBorder = () => {
|
|
186
|
+
try {
|
|
187
|
+
const table = ReactDOM.findDOMNode(this.table);
|
|
188
|
+
table.classList.add("tgBorderBlue");
|
|
189
|
+
setTimeout(() => {
|
|
190
|
+
table.classList.remove("tgBorderBlue");
|
|
191
|
+
}, 300);
|
|
192
|
+
} catch (e) {
|
|
193
|
+
console.error(`err when flashing table border:`, e);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
handleUndo = () => {
|
|
197
|
+
const {
|
|
198
|
+
change,
|
|
199
|
+
entities,
|
|
200
|
+
reduxFormEntitiesUndoRedoStack = { currentVersion: 0 }
|
|
201
|
+
} = this.props;
|
|
202
|
+
|
|
203
|
+
if (reduxFormEntitiesUndoRedoStack.currentVersion > 0) {
|
|
204
|
+
this.flashTableBorder();
|
|
205
|
+
const nextState = applyPatches(
|
|
206
|
+
entities,
|
|
207
|
+
reduxFormEntitiesUndoRedoStack[
|
|
208
|
+
reduxFormEntitiesUndoRedoStack.currentVersion
|
|
209
|
+
].inversePatches
|
|
210
|
+
);
|
|
211
|
+
const { newEnts, validationErrors } = this.formatAndValidateEntities(
|
|
212
|
+
nextState
|
|
213
|
+
);
|
|
214
|
+
change("reduxFormEntities", newEnts);
|
|
215
|
+
this.updateValidation(newEnts, validationErrors);
|
|
216
|
+
change("reduxFormEntitiesUndoRedoStack", {
|
|
217
|
+
...reduxFormEntitiesUndoRedoStack,
|
|
218
|
+
currentVersion: reduxFormEntitiesUndoRedoStack.currentVersion - 1
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
handleRedo = () => {
|
|
223
|
+
const {
|
|
224
|
+
change,
|
|
225
|
+
entities,
|
|
226
|
+
reduxFormEntitiesUndoRedoStack = { currentVersion: 0 }
|
|
227
|
+
} = this.props;
|
|
228
|
+
|
|
229
|
+
const nextV = reduxFormEntitiesUndoRedoStack.currentVersion + 1;
|
|
230
|
+
if (reduxFormEntitiesUndoRedoStack[nextV]) {
|
|
231
|
+
this.flashTableBorder();
|
|
232
|
+
const nextState = applyPatches(
|
|
233
|
+
entities,
|
|
234
|
+
reduxFormEntitiesUndoRedoStack[nextV].patches
|
|
235
|
+
);
|
|
236
|
+
const { newEnts, validationErrors } = this.formatAndValidateEntities(
|
|
237
|
+
nextState
|
|
238
|
+
);
|
|
239
|
+
change("reduxFormEntities", newEnts);
|
|
240
|
+
this.updateValidation(newEnts, validationErrors);
|
|
241
|
+
change("reduxFormEntitiesUndoRedoStack", {
|
|
242
|
+
...reduxFormEntitiesUndoRedoStack,
|
|
243
|
+
currentVersion: nextV
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
updateFromProps = (oldProps, newProps) => {
|
|
248
|
+
const {
|
|
249
|
+
selectedIds,
|
|
250
|
+
entities = [],
|
|
251
|
+
isEntityDisabled,
|
|
252
|
+
expandAllByDefault,
|
|
253
|
+
selectAllByDefault,
|
|
254
|
+
reduxFormSelectedEntityIdMap,
|
|
255
|
+
reduxFormExpandedEntityIdMap,
|
|
256
|
+
change
|
|
257
|
+
} = newProps;
|
|
258
|
+
const table = ReactDOM.findDOMNode(this.table);
|
|
259
|
+
|
|
260
|
+
const idMap = reduxFormSelectedEntityIdMap;
|
|
261
|
+
|
|
262
|
+
//handle programatic filter adding
|
|
263
|
+
if (!isEqual(newProps.additionalFilters, oldProps.additionalFilters)) {
|
|
264
|
+
newProps.addFilters(newProps.additionalFilters);
|
|
265
|
+
}
|
|
266
|
+
if (!isEqual(newProps.schema, oldProps.schema)) {
|
|
267
|
+
const { schema = {} } = newProps;
|
|
268
|
+
const columns = schema.fields
|
|
269
|
+
? schema.fields.reduce(function(columns, field, i) {
|
|
270
|
+
if (field.isHidden) {
|
|
271
|
+
return columns;
|
|
272
|
+
}
|
|
273
|
+
return columns.concat({
|
|
274
|
+
...field,
|
|
275
|
+
columnIndex: i
|
|
276
|
+
});
|
|
277
|
+
}, [])
|
|
278
|
+
: [];
|
|
279
|
+
this.setState({ columns });
|
|
280
|
+
}
|
|
281
|
+
let selectedIdsToUse = selectedIds;
|
|
282
|
+
let newIdMap;
|
|
283
|
+
//handle selecting all or expanding all
|
|
284
|
+
if (
|
|
285
|
+
(selectAllByDefault || expandAllByDefault) &&
|
|
286
|
+
!isEqual(
|
|
287
|
+
(newProps.entities || []).map(({ id }) => id),
|
|
288
|
+
oldProps.entities && oldProps.entities.map(({ id }) => id)
|
|
289
|
+
)
|
|
290
|
+
) {
|
|
291
|
+
if (
|
|
292
|
+
selectAllByDefault &&
|
|
293
|
+
!this.alreadySelected &&
|
|
294
|
+
entities &&
|
|
295
|
+
entities.length
|
|
296
|
+
) {
|
|
297
|
+
this.alreadySelected = true;
|
|
298
|
+
newIdMap = {
|
|
299
|
+
...(entities || []).reduce((acc, entity) => {
|
|
300
|
+
acc[entity.id || entity.code] = { entity, time: Date.now() };
|
|
301
|
+
return acc;
|
|
302
|
+
}, {}),
|
|
303
|
+
...(reduxFormSelectedEntityIdMap || {})
|
|
304
|
+
};
|
|
305
|
+
selectedIdsToUse = map(newIdMap, (a, key) => key);
|
|
306
|
+
}
|
|
307
|
+
if (expandAllByDefault) {
|
|
308
|
+
change("reduxFormExpandedEntityIdMap", {
|
|
309
|
+
...(entities || []).reduce((acc, e) => {
|
|
310
|
+
acc[e.id || e.code] = true;
|
|
311
|
+
return acc;
|
|
312
|
+
}, {}),
|
|
313
|
+
...(reduxFormExpandedEntityIdMap || {})
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// handle programmatic selection and scrolling
|
|
319
|
+
const { selectedIds: oldSelectedIds } = oldProps;
|
|
320
|
+
if (isEqual(selectedIdsToUse, oldSelectedIds)) {
|
|
321
|
+
// if not changing selectedIds then we just want to make sure selected entities
|
|
322
|
+
// stored in redux are in proper format
|
|
323
|
+
// if selected ids have changed then it will handle redux selection
|
|
324
|
+
const tableScrollElement = table.getElementsByClassName("rt-table")[0];
|
|
325
|
+
const {
|
|
326
|
+
entities: oldEntities = [],
|
|
327
|
+
reduxFormSelectedEntityIdMap: oldIdMap
|
|
328
|
+
} = oldProps;
|
|
329
|
+
const reloaded = oldProps.isLoading && !this.props.isLoading;
|
|
330
|
+
const entitiesHaveChanged =
|
|
331
|
+
oldEntities.length !== entities.length ||
|
|
332
|
+
getIdOrCodeOrIndex(entities[0] || {}) !==
|
|
333
|
+
getIdOrCodeOrIndex(oldEntities[0] || {});
|
|
334
|
+
// if switching pages or searching the table we want to reset the scrollbar
|
|
335
|
+
if (tableScrollElement.scrollTop > 0 && !this.props.isCellEditable) {
|
|
336
|
+
if (reloaded || entitiesHaveChanged) {
|
|
337
|
+
tableScrollElement.scrollTop = 0;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
// re-index entities in redux form so that sorting will be correct in withSelectedEntities
|
|
341
|
+
if (change) {
|
|
342
|
+
if (entitiesHaveChanged && (!isEmpty(oldIdMap) || !isEmpty(idMap))) {
|
|
343
|
+
changeSelectedEntities({ idMap, entities, change });
|
|
344
|
+
} else if (
|
|
345
|
+
!isEmpty(idMap) &&
|
|
346
|
+
idMap[Object.keys(idMap)[0]] &&
|
|
347
|
+
idMap[Object.keys(idMap)[0]].rowIndex === undefined
|
|
348
|
+
) {
|
|
349
|
+
// if programmatically selected will still want the order to match the table sorting.
|
|
350
|
+
changeSelectedEntities({ idMap, entities, change });
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
const idArray = Array.isArray(selectedIdsToUse)
|
|
355
|
+
? selectedIdsToUse
|
|
356
|
+
: [selectedIdsToUse];
|
|
357
|
+
const selectedEntities = entities.filter(
|
|
358
|
+
e => idArray.indexOf(getIdOrCodeOrIndex(e)) > -1 && !isEntityDisabled(e)
|
|
359
|
+
);
|
|
360
|
+
newIdMap =
|
|
361
|
+
newIdMap ||
|
|
362
|
+
selectedEntities.reduce((acc, entity) => {
|
|
363
|
+
acc[getIdOrCodeOrIndex(entity)] = { entity };
|
|
364
|
+
return acc;
|
|
365
|
+
}, {});
|
|
366
|
+
change("reduxFormExpandedEntityIdMap", newIdMap);
|
|
367
|
+
finalizeSelection({ idMap: newIdMap, entities, props: newProps });
|
|
368
|
+
const idToScrollTo = idArray[0];
|
|
369
|
+
if (!idToScrollTo && idToScrollTo !== 0) return;
|
|
370
|
+
const entityIndexToScrollTo = entities.findIndex(
|
|
371
|
+
e => e.id === idToScrollTo || e.code === idToScrollTo
|
|
372
|
+
);
|
|
373
|
+
if (entityIndexToScrollTo === -1 || !table) return;
|
|
374
|
+
const tableBody = table.querySelector(".rt-tbody");
|
|
375
|
+
if (!tableBody) return;
|
|
376
|
+
const rowEl = tableBody.getElementsByClassName("rt-tr-group")[
|
|
377
|
+
entityIndexToScrollTo
|
|
378
|
+
];
|
|
379
|
+
if (!rowEl) return;
|
|
380
|
+
setTimeout(() => {
|
|
381
|
+
//we need to delay for a teeny bit to make sure the table has drawn
|
|
382
|
+
rowEl &&
|
|
383
|
+
tableBody &&
|
|
384
|
+
scrollIntoView(rowEl, tableBody, {
|
|
385
|
+
alignWithTop: true
|
|
386
|
+
});
|
|
387
|
+
}, 0);
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
formatAndValidateEntities = entities => {
|
|
391
|
+
const { schema } = this.props;
|
|
392
|
+
const editableFields = schema.fields.filter(f => !f.isNotEditable);
|
|
393
|
+
const validationErrors = {};
|
|
394
|
+
|
|
395
|
+
const newEnts = immer(entities, entities => {
|
|
396
|
+
entities.forEach((e, index) => {
|
|
397
|
+
editableFields.forEach(columnSchema => {
|
|
398
|
+
//mutative
|
|
399
|
+
const { error } = editCellHelper({
|
|
400
|
+
entity: e,
|
|
401
|
+
columnSchema,
|
|
402
|
+
newVal: e[columnSchema.path]
|
|
403
|
+
});
|
|
404
|
+
if (error) {
|
|
405
|
+
const rowId = getIdOrCodeOrIndex(e, index);
|
|
406
|
+
validationErrors[`${rowId}:${columnSchema.path}`] = error;
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
});
|
|
411
|
+
return {
|
|
412
|
+
newEnts,
|
|
413
|
+
validationErrors
|
|
414
|
+
};
|
|
415
|
+
};
|
|
416
|
+
formatAndValidateTableInitial = () => {
|
|
417
|
+
const {
|
|
418
|
+
_origEntities: entities,
|
|
419
|
+
initialEntities,
|
|
420
|
+
change,
|
|
421
|
+
reduxFormCellValidation
|
|
422
|
+
} = this.props;
|
|
423
|
+
const { newEnts, validationErrors } = this.formatAndValidateEntities(
|
|
424
|
+
initialEntities || entities
|
|
425
|
+
);
|
|
426
|
+
change("reduxFormEntities", newEnts);
|
|
427
|
+
const toKeep = {};
|
|
428
|
+
//on the initial load we want to keep any async table wide errors
|
|
429
|
+
forEach(reduxFormCellValidation, (v, k) => {
|
|
430
|
+
if (v && v._isTableAsyncWideError) {
|
|
431
|
+
toKeep[k] = v;
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
this.updateValidation(newEnts, {
|
|
435
|
+
...toKeep,
|
|
436
|
+
...validationErrors
|
|
437
|
+
});
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
componentDidMount() {
|
|
441
|
+
const {
|
|
442
|
+
isCellEditable,
|
|
443
|
+
entities = [],
|
|
444
|
+
isLoading,
|
|
445
|
+
showForcedHiddenColumns,
|
|
446
|
+
setShowForcedHidden
|
|
447
|
+
} = this.props;
|
|
448
|
+
isCellEditable && this.formatAndValidateTableInitial();
|
|
449
|
+
this.updateFromProps({}, computePresets(this.props));
|
|
450
|
+
document.addEventListener("paste", this.handlePaste);
|
|
451
|
+
|
|
452
|
+
if (!entities.length && !isLoading && !showForcedHiddenColumns) {
|
|
453
|
+
setShowForcedHidden(true);
|
|
454
|
+
}
|
|
455
|
+
// const table = ReactDOM.findDOMNode(this.table);
|
|
456
|
+
// let theads = table.getElementsByClassName("rt-thead");
|
|
457
|
+
// let tbody = table.getElementsByClassName("rt-tbody")[0];
|
|
458
|
+
|
|
459
|
+
// tbody.addEventListener("scroll", () => {
|
|
460
|
+
// for (let i = 0; i < theads.length; i++) {
|
|
461
|
+
// theads.item(i).scrollLeft = tbody.scrollLeft;
|
|
462
|
+
// }
|
|
463
|
+
// });
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
componentDidUpdate(oldProps) {
|
|
467
|
+
// const tableBody = table.querySelector(".rt-tbody");
|
|
468
|
+
// const headerNode = table.querySelector(".rt-thead.-header");
|
|
469
|
+
// if (headerNode) headerNode.style.overflowY = "inherit";
|
|
470
|
+
// if (tableBody && tableBody.scrollHeight > tableBody.clientHeight) {
|
|
471
|
+
// if (headerNode) {
|
|
472
|
+
// headerNode.style.overflowY = "scroll";
|
|
473
|
+
// headerNode.style.overflowX = "hidden";
|
|
474
|
+
// }
|
|
475
|
+
// }
|
|
476
|
+
|
|
477
|
+
this.updateFromProps(computePresets(oldProps), computePresets(this.props));
|
|
478
|
+
|
|
479
|
+
// comment in to test what is causing re-render
|
|
480
|
+
// Object.entries(this.props).forEach(
|
|
481
|
+
// ([key, val]) =>
|
|
482
|
+
// oldProps[key] !== val && console.info(`Prop '${key}' changed`)
|
|
483
|
+
// );
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
componentWillUnmount() {
|
|
487
|
+
document.removeEventListener("paste", this.handlePaste);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
handleRowMove = (type, shiftHeld) => e => {
|
|
491
|
+
e.preventDefault();
|
|
492
|
+
e.stopPropagation();
|
|
493
|
+
const props = computePresets(this.props);
|
|
494
|
+
const {
|
|
495
|
+
noSelect,
|
|
496
|
+
entities,
|
|
497
|
+
reduxFormSelectedEntityIdMap: idMap,
|
|
498
|
+
isEntityDisabled,
|
|
499
|
+
isSingleSelect
|
|
500
|
+
} = props;
|
|
501
|
+
let newIdMap = {};
|
|
502
|
+
const lastSelectedEnt = getLastSelectedEntity(idMap);
|
|
503
|
+
|
|
504
|
+
if (noSelect) return;
|
|
505
|
+
if (lastSelectedEnt) {
|
|
506
|
+
let lastSelectedIndex = entities.findIndex(
|
|
507
|
+
ent => ent === lastSelectedEnt
|
|
508
|
+
);
|
|
509
|
+
if (lastSelectedIndex === -1) {
|
|
510
|
+
if (lastSelectedEnt.id !== undefined) {
|
|
511
|
+
lastSelectedIndex = entities.findIndex(
|
|
512
|
+
ent => ent.id === lastSelectedEnt.id
|
|
513
|
+
);
|
|
514
|
+
} else if (lastSelectedEnt.code !== undefined) {
|
|
515
|
+
lastSelectedIndex = entities.findIndex(
|
|
516
|
+
ent => ent.code === lastSelectedEnt.code
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (lastSelectedIndex === -1) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
const newEntToSelect = getNewEntToSelect({
|
|
524
|
+
type,
|
|
525
|
+
lastSelectedIndex,
|
|
526
|
+
entities,
|
|
527
|
+
isEntityDisabled
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
if (!newEntToSelect) return;
|
|
531
|
+
if (shiftHeld && !isSingleSelect) {
|
|
532
|
+
if (idMap[newEntToSelect.id || newEntToSelect.code]) {
|
|
533
|
+
//the entity being moved to has already been selected
|
|
534
|
+
newIdMap = omit(idMap, [lastSelectedEnt.id || lastSelectedEnt.code]);
|
|
535
|
+
newIdMap[newEntToSelect.id || newEntToSelect.code].time =
|
|
536
|
+
Date.now() + 1;
|
|
537
|
+
} else {
|
|
538
|
+
//the entity being moved to has NOT been selected yet
|
|
539
|
+
newIdMap = {
|
|
540
|
+
...idMap,
|
|
541
|
+
[newEntToSelect.id || newEntToSelect.code]: {
|
|
542
|
+
entity: newEntToSelect,
|
|
543
|
+
time: Date.now()
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
} else {
|
|
548
|
+
//no shiftHeld
|
|
549
|
+
newIdMap[newEntToSelect.id || newEntToSelect.code] = {
|
|
550
|
+
entity: newEntToSelect,
|
|
551
|
+
time: Date.now()
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
finalizeSelection({
|
|
557
|
+
idMap: newIdMap,
|
|
558
|
+
entities,
|
|
559
|
+
props
|
|
560
|
+
});
|
|
561
|
+
};
|
|
562
|
+
handleCopyHotkey = e => {
|
|
563
|
+
const { isCellEditable, reduxFormSelectedEntityIdMap } = computePresets(
|
|
564
|
+
this.props
|
|
565
|
+
);
|
|
566
|
+
|
|
567
|
+
if (isCellEditable) {
|
|
568
|
+
this.handleCopySelectedCells(e);
|
|
569
|
+
} else {
|
|
570
|
+
this.handleCopySelectedRows(
|
|
571
|
+
getRecordsFromIdMap(reduxFormSelectedEntityIdMap),
|
|
572
|
+
e
|
|
573
|
+
);
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
getPrimarySelectedCellId = () => {
|
|
578
|
+
const { reduxFormSelectedCells = {} } = this.props;
|
|
579
|
+
for (const k of Object.keys(reduxFormSelectedCells)) {
|
|
580
|
+
if (reduxFormSelectedCells[k] === PRIMARY_SELECTED_VAL) {
|
|
581
|
+
return k;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
handlePaste = e => {
|
|
587
|
+
const {
|
|
588
|
+
isCellEditable,
|
|
589
|
+
reduxFormSelectedCells,
|
|
590
|
+
reduxFormCellValidation,
|
|
591
|
+
change,
|
|
592
|
+
schema,
|
|
593
|
+
entities
|
|
594
|
+
} = computePresets(this.props);
|
|
595
|
+
|
|
596
|
+
if (isCellEditable) {
|
|
597
|
+
if (isEmpty(reduxFormSelectedCells)) return;
|
|
598
|
+
try {
|
|
599
|
+
let pasteData = [];
|
|
600
|
+
let toPaste;
|
|
601
|
+
if (window.clipboardData && window.clipboardData.getData) {
|
|
602
|
+
// IE
|
|
603
|
+
toPaste = window.clipboardData.getData("Text");
|
|
604
|
+
} else if (e.clipboardData && e.clipboardData.getData) {
|
|
605
|
+
toPaste = e.clipboardData.getData("text/plain");
|
|
606
|
+
}
|
|
607
|
+
if (toPaste.includes(",")) {
|
|
608
|
+
//try papaparsing it out as a csv if it contains commas
|
|
609
|
+
try {
|
|
610
|
+
const { data, errors } = papaparse.parse(toPaste, {
|
|
611
|
+
header: false
|
|
612
|
+
});
|
|
613
|
+
if (data?.length && !errors?.length) {
|
|
614
|
+
pasteData = data;
|
|
615
|
+
}
|
|
616
|
+
} catch (error) {
|
|
617
|
+
console.error(`error p982qhgpf9qh`, error);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
pasteData = pasteData.length ? pasteData : defaultParsePaste(toPaste);
|
|
621
|
+
|
|
622
|
+
if (!pasteData || !pasteData.length) return;
|
|
623
|
+
|
|
624
|
+
if (pasteData.length === 1 && pasteData[0].length === 1) {
|
|
625
|
+
const newCellValidate = {
|
|
626
|
+
...reduxFormCellValidation
|
|
627
|
+
};
|
|
628
|
+
// single paste value, fill all cells with value
|
|
629
|
+
const newVal = pasteData[0][0];
|
|
630
|
+
this.updateEntitiesHelper(entities, entities => {
|
|
631
|
+
const entityIdToEntity = getEntityIdToEntity(entities);
|
|
632
|
+
Object.keys(reduxFormSelectedCells).forEach(cellId => {
|
|
633
|
+
const [rowId, path] = cellId.split(":");
|
|
634
|
+
const entity = entityIdToEntity[rowId].e;
|
|
635
|
+
delete entity._isClean;
|
|
636
|
+
const { error } = editCellHelper({
|
|
637
|
+
entity,
|
|
638
|
+
path,
|
|
639
|
+
schema,
|
|
640
|
+
newVal
|
|
641
|
+
});
|
|
642
|
+
if (error) {
|
|
643
|
+
newCellValidate[cellId] = error;
|
|
644
|
+
} else {
|
|
645
|
+
delete newCellValidate[cellId];
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
this.updateValidation(entities, newCellValidate);
|
|
649
|
+
});
|
|
650
|
+
} else {
|
|
651
|
+
// handle paste in same format
|
|
652
|
+
const primarySelectedCell = this.getPrimarySelectedCellId();
|
|
653
|
+
if (primarySelectedCell) {
|
|
654
|
+
const newCellValidate = {
|
|
655
|
+
...reduxFormCellValidation
|
|
656
|
+
};
|
|
657
|
+
|
|
658
|
+
const newSelectedCells = { ...reduxFormSelectedCells };
|
|
659
|
+
this.updateEntitiesHelper(entities, entities => {
|
|
660
|
+
const entityIdToEntity = getEntityIdToEntity(entities);
|
|
661
|
+
const [rowId, primaryCellPath] = primarySelectedCell.split(":");
|
|
662
|
+
const primaryEntityInfo = entityIdToEntity[rowId];
|
|
663
|
+
const startIndex = primaryEntityInfo.i;
|
|
664
|
+
const endIndex = primaryEntityInfo.i + pasteData.length;
|
|
665
|
+
for (let i = startIndex; i < endIndex; i++) {
|
|
666
|
+
if (!entities[i]) {
|
|
667
|
+
entities[i] = { id: nanoid() };
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
const entitiesToManipulate = entities.slice(startIndex, endIndex);
|
|
671
|
+
const pathToIndex = getFieldPathToIndex(schema);
|
|
672
|
+
const indexToPath = invert(pathToIndex);
|
|
673
|
+
const startCellIndex = pathToIndex[primaryCellPath];
|
|
674
|
+
pasteData.forEach((row, i) => {
|
|
675
|
+
row.forEach((cell, j) => {
|
|
676
|
+
if (cell) {
|
|
677
|
+
const cellIndexToChange = startCellIndex + j;
|
|
678
|
+
const entity = entitiesToManipulate[i];
|
|
679
|
+
if (entity) {
|
|
680
|
+
delete entity._isClean;
|
|
681
|
+
const path = indexToPath[cellIndexToChange];
|
|
682
|
+
if (path) {
|
|
683
|
+
const { error } = editCellHelper({
|
|
684
|
+
entity,
|
|
685
|
+
path,
|
|
686
|
+
schema,
|
|
687
|
+
newVal: cell
|
|
688
|
+
});
|
|
689
|
+
const cellId = `${getIdOrCodeOrIndex(entity)}:${path}`;
|
|
690
|
+
if (!newSelectedCells[cellId]) {
|
|
691
|
+
newSelectedCells[cellId] = true;
|
|
692
|
+
}
|
|
693
|
+
if (error) {
|
|
694
|
+
newCellValidate[cellId] = error;
|
|
695
|
+
} else {
|
|
696
|
+
delete newCellValidate[cellId];
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
});
|
|
703
|
+
this.updateValidation(entities, newCellValidate);
|
|
704
|
+
});
|
|
705
|
+
change("reduxFormSelectedCells", newSelectedCells);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
} catch (error) {
|
|
709
|
+
console.error(`error:`, error);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
handleSelectAllRows = e => {
|
|
714
|
+
const {
|
|
715
|
+
change,
|
|
716
|
+
isEntityDisabled,
|
|
717
|
+
entities,
|
|
718
|
+
isSingleSelect,
|
|
719
|
+
isCellEditable,
|
|
720
|
+
schema
|
|
721
|
+
} = computePresets(this.props);
|
|
722
|
+
if (isSingleSelect) return;
|
|
723
|
+
e.preventDefault();
|
|
724
|
+
|
|
725
|
+
if (isCellEditable) {
|
|
726
|
+
const schemaPaths = schema.fields.map(f => f.path);
|
|
727
|
+
const newSelectedCells = {};
|
|
728
|
+
entities.forEach((entity, i) => {
|
|
729
|
+
if (isEntityDisabled(entity)) return;
|
|
730
|
+
const entityId = getIdOrCodeOrIndex(entity, i);
|
|
731
|
+
schemaPaths.forEach(p => {
|
|
732
|
+
newSelectedCells[`${entityId}:${p}`] = true;
|
|
733
|
+
});
|
|
734
|
+
});
|
|
735
|
+
change("reduxFormSelectedCells", newSelectedCells);
|
|
736
|
+
} else {
|
|
737
|
+
const newIdMap = {};
|
|
738
|
+
|
|
739
|
+
entities.forEach((entity, i) => {
|
|
740
|
+
if (isEntityDisabled(entity)) return;
|
|
741
|
+
const entityId = getIdOrCodeOrIndex(entity, i);
|
|
742
|
+
newIdMap[entityId] = { entity };
|
|
743
|
+
});
|
|
744
|
+
finalizeSelection({
|
|
745
|
+
idMap: newIdMap,
|
|
746
|
+
entities,
|
|
747
|
+
props: computePresets(this.props)
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
|
|
752
|
+
updateValidation = (entities, newCellValidate) => {
|
|
753
|
+
const { change, schema } = computePresets(this.props);
|
|
754
|
+
change(
|
|
755
|
+
"reduxFormCellValidation",
|
|
756
|
+
validateTableWideErrors({ entities, schema, newCellValidate })
|
|
757
|
+
);
|
|
758
|
+
};
|
|
759
|
+
handleDeleteCell = () => {
|
|
760
|
+
const {
|
|
761
|
+
reduxFormSelectedCells,
|
|
762
|
+
reduxFormCellValidation,
|
|
763
|
+
schema,
|
|
764
|
+
entities
|
|
765
|
+
} = computePresets(this.props);
|
|
766
|
+
const newCellValidate = {
|
|
767
|
+
...reduxFormCellValidation
|
|
768
|
+
};
|
|
769
|
+
if (isEmpty(reduxFormSelectedCells)) return;
|
|
770
|
+
const rowIds = [];
|
|
771
|
+
this.updateEntitiesHelper(entities, entities => {
|
|
772
|
+
const entityIdToEntity = getEntityIdToEntity(entities);
|
|
773
|
+
Object.keys(reduxFormSelectedCells).forEach(cellId => {
|
|
774
|
+
const [rowId, path] = cellId.split(":");
|
|
775
|
+
rowIds.push(rowId);
|
|
776
|
+
const entity = entityIdToEntity[rowId].e;
|
|
777
|
+
delete entity._isClean;
|
|
778
|
+
const { error } = editCellHelper({
|
|
779
|
+
entity,
|
|
780
|
+
path,
|
|
781
|
+
schema,
|
|
782
|
+
newVal: ""
|
|
783
|
+
});
|
|
784
|
+
if (error) {
|
|
785
|
+
newCellValidate[cellId] = error;
|
|
786
|
+
} else {
|
|
787
|
+
delete newCellValidate[cellId];
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
this.updateValidation(entities, newCellValidate);
|
|
791
|
+
});
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
handleCut = e => {
|
|
795
|
+
this.handleDeleteCell();
|
|
796
|
+
this.handleCopyHotkey(e);
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
getCellCopyText = cellWrapper => {
|
|
800
|
+
const text = cellWrapper && cellWrapper.getAttribute("data-copy-text");
|
|
801
|
+
|
|
802
|
+
const toRet = text || cellWrapper.textContent || "";
|
|
803
|
+
return toRet;
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
handleCopyRow = rowEl => {
|
|
807
|
+
//takes in a row element
|
|
808
|
+
const text = this.getRowCopyText(rowEl);
|
|
809
|
+
if (!text) return window.toastr.warning("No text to copy");
|
|
810
|
+
this.handleCopyHelper(text, "Row Copied");
|
|
811
|
+
};
|
|
812
|
+
handleCopyColumn = (e, cellWrapper) => {
|
|
813
|
+
const cellType = cellWrapper.getAttribute("data-test");
|
|
814
|
+
const allRowEls = getAllRows(e);
|
|
815
|
+
if (!allRowEls) return;
|
|
816
|
+
const textToCopy = map(allRowEls, rowEl =>
|
|
817
|
+
this.getRowCopyText(rowEl, { cellType })
|
|
818
|
+
)
|
|
819
|
+
.filter(text => text)
|
|
820
|
+
.join("\n");
|
|
821
|
+
if (!textToCopy) return window.toastr.warning("No text to copy");
|
|
822
|
+
|
|
823
|
+
this.handleCopyHelper(textToCopy, "Column copied");
|
|
824
|
+
};
|
|
825
|
+
updateEntitiesHelper = (ents, fn) => {
|
|
826
|
+
const {
|
|
827
|
+
change,
|
|
828
|
+
reduxFormEntitiesUndoRedoStack = { currentVersion: 0 }
|
|
829
|
+
} = this.props;
|
|
830
|
+
const [nextState, patches, inversePatches] = produceWithPatches(ents, fn);
|
|
831
|
+
if (!inversePatches.length) return;
|
|
832
|
+
const thatNewNew = [...nextState];
|
|
833
|
+
thatNewNew.isDirty = true;
|
|
834
|
+
change("reduxFormEntities", thatNewNew);
|
|
835
|
+
change("reduxFormEntitiesUndoRedoStack", {
|
|
836
|
+
...omitBy(reduxFormEntitiesUndoRedoStack, (v, k) => {
|
|
837
|
+
return toNumber(k) > reduxFormEntitiesUndoRedoStack.currentVersion + 1;
|
|
838
|
+
}),
|
|
839
|
+
currentVersion: reduxFormEntitiesUndoRedoStack.currentVersion + 1,
|
|
840
|
+
[reduxFormEntitiesUndoRedoStack.currentVersion + 1]: {
|
|
841
|
+
inversePatches,
|
|
842
|
+
patches
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
};
|
|
846
|
+
|
|
847
|
+
getRowCopyText = (rowEl, { cellType } = {}) => {
|
|
848
|
+
//takes in a row element
|
|
849
|
+
if (!rowEl) return;
|
|
850
|
+
return flatMap(rowEl.children, cellEl => {
|
|
851
|
+
const cellChild = cellEl.querySelector(`[data-copy-text]`);
|
|
852
|
+
if (!cellChild) {
|
|
853
|
+
if (cellType) return []; //strip it
|
|
854
|
+
return; //just leave it blank
|
|
855
|
+
}
|
|
856
|
+
if (cellType && cellChild.getAttribute("data-test") !== cellType) {
|
|
857
|
+
return [];
|
|
858
|
+
}
|
|
859
|
+
return this.getCellCopyText(cellChild);
|
|
860
|
+
}).join("\t");
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
handleCopyHelper = (stringToCopy, message) => {
|
|
864
|
+
const copyHandler = e => {
|
|
865
|
+
e.preventDefault();
|
|
866
|
+
e.clipboardData.setData("text/plain", stringToCopy);
|
|
867
|
+
};
|
|
868
|
+
document.addEventListener("copy", copyHandler);
|
|
869
|
+
!window.Cypress &&
|
|
870
|
+
copy(stringToCopy, {
|
|
871
|
+
// keep this so that pasting into spreadsheets works.
|
|
872
|
+
format: "text/plain"
|
|
873
|
+
});
|
|
874
|
+
document.removeEventListener("copy", copyHandler);
|
|
875
|
+
window.toastr.success(message);
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
handleCopyTable = e => {
|
|
879
|
+
try {
|
|
880
|
+
const allRowEls = getAllRows(e);
|
|
881
|
+
if (!allRowEls) return;
|
|
882
|
+
//get row elements and call this.handleCopyRow for each
|
|
883
|
+
const textToCopy = map(allRowEls, rowEl => this.getRowCopyText(rowEl))
|
|
884
|
+
.filter(text => text)
|
|
885
|
+
.join("\n");
|
|
886
|
+
if (!textToCopy) return window.toastr.warning("No text to copy");
|
|
887
|
+
|
|
888
|
+
this.handleCopyHelper(textToCopy, "Table copied");
|
|
889
|
+
} catch (error) {
|
|
890
|
+
console.error(`error:`, error);
|
|
891
|
+
window.toastr.error("Error copying rows.");
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
handleCopySelectedCells = e => {
|
|
895
|
+
const { entities = [], reduxFormSelectedCells, schema } = computePresets(
|
|
896
|
+
this.props
|
|
897
|
+
);
|
|
898
|
+
// if the current selection is consecutive cells then copy with
|
|
899
|
+
// tabs between. if not then just select primary selected cell
|
|
900
|
+
if (isEmpty(reduxFormSelectedCells)) return;
|
|
901
|
+
const pathToIndex = getFieldPathToIndex(schema);
|
|
902
|
+
const entityIdToEntity = getEntityIdToEntity(entities);
|
|
903
|
+
const selectionGrid = [];
|
|
904
|
+
let firstRowIndex;
|
|
905
|
+
let firstCellIndex;
|
|
906
|
+
Object.keys(reduxFormSelectedCells).forEach(key => {
|
|
907
|
+
const [rowId, path] = key.split(":");
|
|
908
|
+
const eInfo = entityIdToEntity[rowId];
|
|
909
|
+
if (eInfo) {
|
|
910
|
+
if (firstRowIndex === undefined || eInfo.i < firstRowIndex) {
|
|
911
|
+
firstRowIndex = eInfo.i;
|
|
912
|
+
}
|
|
913
|
+
if (!selectionGrid[eInfo.i]) {
|
|
914
|
+
selectionGrid[eInfo.i] = [];
|
|
915
|
+
}
|
|
916
|
+
const cellIndex = pathToIndex[path];
|
|
917
|
+
if (firstCellIndex === undefined || cellIndex < firstCellIndex) {
|
|
918
|
+
firstCellIndex = cellIndex;
|
|
919
|
+
}
|
|
920
|
+
selectionGrid[eInfo.i][cellIndex] = true;
|
|
921
|
+
}
|
|
922
|
+
});
|
|
923
|
+
if (firstRowIndex === undefined) return;
|
|
924
|
+
const allRows = getAllRows(e);
|
|
925
|
+
let fullCellText = "";
|
|
926
|
+
times(selectionGrid.length, i => {
|
|
927
|
+
const row = selectionGrid[i];
|
|
928
|
+
if (fullCellText) {
|
|
929
|
+
fullCellText += "\n";
|
|
930
|
+
}
|
|
931
|
+
if (!row) {
|
|
932
|
+
return;
|
|
933
|
+
} else {
|
|
934
|
+
// ignore header
|
|
935
|
+
const rowCopyText = this.getRowCopyText(allRows[i + 1]).split("\t");
|
|
936
|
+
times(row.length, i => {
|
|
937
|
+
const cell = row[i];
|
|
938
|
+
if (cell) {
|
|
939
|
+
fullCellText += rowCopyText[i];
|
|
940
|
+
}
|
|
941
|
+
if (i !== row.length - 1 && i >= firstCellIndex) fullCellText += "\t";
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
});
|
|
945
|
+
if (!fullCellText) return window.toastr.warning("No text to copy");
|
|
946
|
+
|
|
947
|
+
this.handleCopyHelper(fullCellText, "Selected cells copied");
|
|
948
|
+
};
|
|
949
|
+
|
|
950
|
+
handleCopySelectedRows = (selectedRecords, e) => {
|
|
951
|
+
const { entities = [] } = computePresets(this.props);
|
|
952
|
+
const idToIndex = entities.reduce((acc, e, i) => {
|
|
953
|
+
acc[e.id || e.code] = i;
|
|
954
|
+
return acc;
|
|
955
|
+
}, {});
|
|
956
|
+
|
|
957
|
+
//index 0 of the table is the column titles
|
|
958
|
+
//must add 1 to rowNum
|
|
959
|
+
const rowNumbersToCopy = selectedRecords
|
|
960
|
+
.map(rec => idToIndex[rec.id || rec.code] + 1)
|
|
961
|
+
.sort();
|
|
962
|
+
|
|
963
|
+
if (!rowNumbersToCopy.length) return;
|
|
964
|
+
rowNumbersToCopy.unshift(0); //add in the header row
|
|
965
|
+
try {
|
|
966
|
+
const allRowEls = getAllRows(e);
|
|
967
|
+
if (!allRowEls) return;
|
|
968
|
+
const rowEls = rowNumbersToCopy.map(i => allRowEls[i]);
|
|
969
|
+
|
|
970
|
+
//get row elements and call this.handleCopyRow for each const rowEls = this.getRowEls(rowNumbersToCopy)
|
|
971
|
+
const textToCopy = map(rowEls, rowEl => this.getRowCopyText(rowEl))
|
|
972
|
+
.filter(text => text)
|
|
973
|
+
.join("\n");
|
|
974
|
+
if (!textToCopy) return window.toastr.warning("No text to copy");
|
|
975
|
+
|
|
976
|
+
this.handleCopyHelper(textToCopy, "Selected rows copied");
|
|
977
|
+
} catch (error) {
|
|
978
|
+
console.error(`error:`, error);
|
|
979
|
+
window.toastr.error("Error copying rows.");
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
|
|
983
|
+
moveColumn = ({ oldIndex, newIndex }) => {
|
|
984
|
+
const { columns } = this.state;
|
|
985
|
+
let oldStateColumnIndex, newStateColumnIndex;
|
|
986
|
+
columns.forEach((column, i) => {
|
|
987
|
+
if (oldIndex === column.columnIndex) oldStateColumnIndex = i;
|
|
988
|
+
if (newIndex === column.columnIndex) newStateColumnIndex = i;
|
|
989
|
+
});
|
|
990
|
+
// because it is all handled in state we need
|
|
991
|
+
// to perform the move and update the columnIndices
|
|
992
|
+
// because they are used for the sortable columns
|
|
993
|
+
const newColumns = arrayMove(
|
|
994
|
+
columns,
|
|
995
|
+
oldStateColumnIndex,
|
|
996
|
+
newStateColumnIndex
|
|
997
|
+
).map((column, i) => {
|
|
998
|
+
return {
|
|
999
|
+
...column,
|
|
1000
|
+
columnIndex: i
|
|
1001
|
+
};
|
|
1002
|
+
});
|
|
1003
|
+
this.setState({
|
|
1004
|
+
columns: newColumns
|
|
1005
|
+
});
|
|
1006
|
+
};
|
|
1007
|
+
|
|
1008
|
+
getTheadComponent = props => {
|
|
1009
|
+
const {
|
|
1010
|
+
withDisplayOptions,
|
|
1011
|
+
moveColumnPersist,
|
|
1012
|
+
syncDisplayOptionsToDb,
|
|
1013
|
+
change
|
|
1014
|
+
} = computePresets(this.props);
|
|
1015
|
+
let moveColumnPersistToUse = moveColumnPersist;
|
|
1016
|
+
if (moveColumnPersist && withDisplayOptions && !syncDisplayOptionsToDb) {
|
|
1017
|
+
//little hack to make localstorage changes get reflected in UI (we force an update to get the enhancers to run again :)
|
|
1018
|
+
moveColumnPersistToUse = (...args) => {
|
|
1019
|
+
moveColumnPersist(...args);
|
|
1020
|
+
change("localStorageForceUpdate", Math.random());
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
return (
|
|
1024
|
+
<SortableColumns
|
|
1025
|
+
{...props}
|
|
1026
|
+
withDisplayOptions={withDisplayOptions}
|
|
1027
|
+
moveColumn={moveColumnPersistToUse || this.moveColumn}
|
|
1028
|
+
/>
|
|
1029
|
+
);
|
|
1030
|
+
};
|
|
1031
|
+
getThComponent = compose(
|
|
1032
|
+
withProps(props => {
|
|
1033
|
+
const { columnindex } = props;
|
|
1034
|
+
return {
|
|
1035
|
+
index: columnindex || 0
|
|
1036
|
+
};
|
|
1037
|
+
}),
|
|
1038
|
+
branch(({ immovable }) => "true" !== immovable, SortableElement)
|
|
1039
|
+
)(({ toggleSort, className, children, ...rest }) => (
|
|
1040
|
+
<div
|
|
1041
|
+
className={classNames("rt-th", className)}
|
|
1042
|
+
onClick={e => toggleSort && toggleSort(e)}
|
|
1043
|
+
role="columnheader"
|
|
1044
|
+
tabIndex="-1" // Resolves eslint issues without implementing keyboard navigation incorrectly
|
|
1045
|
+
{...rest}
|
|
1046
|
+
>
|
|
1047
|
+
{children}
|
|
1048
|
+
</div>
|
|
1049
|
+
));
|
|
1050
|
+
|
|
1051
|
+
addEntitiesToSelection = entities => {
|
|
1052
|
+
const propPresets = computePresets(this.props);
|
|
1053
|
+
const { isEntityDisabled, reduxFormSelectedEntityIdMap } = propPresets;
|
|
1054
|
+
const idMap = reduxFormSelectedEntityIdMap || {};
|
|
1055
|
+
const newIdMap = cloneDeep(idMap) || {};
|
|
1056
|
+
entities.forEach((entity, i) => {
|
|
1057
|
+
if (isEntityDisabled(entity)) return;
|
|
1058
|
+
const entityId = getIdOrCodeOrIndex(entity, i);
|
|
1059
|
+
newIdMap[entityId] = { entity };
|
|
1060
|
+
});
|
|
1061
|
+
finalizeSelection({
|
|
1062
|
+
idMap: newIdMap,
|
|
1063
|
+
entities,
|
|
1064
|
+
props: propPresets
|
|
1065
|
+
});
|
|
1066
|
+
};
|
|
1067
|
+
|
|
1068
|
+
render() {
|
|
1069
|
+
const { fullscreen } = this.state;
|
|
1070
|
+
const propPresets = computePresets(this.props);
|
|
1071
|
+
const {
|
|
1072
|
+
extraClasses,
|
|
1073
|
+
className,
|
|
1074
|
+
tableName,
|
|
1075
|
+
isLoading,
|
|
1076
|
+
searchTerm,
|
|
1077
|
+
setSearchTerm,
|
|
1078
|
+
clearFilters,
|
|
1079
|
+
hidePageSizeWhenPossible,
|
|
1080
|
+
doNotShowEmptyRows,
|
|
1081
|
+
withTitle,
|
|
1082
|
+
withSearch,
|
|
1083
|
+
withPaging,
|
|
1084
|
+
isInfinite,
|
|
1085
|
+
disabled,
|
|
1086
|
+
noHeader,
|
|
1087
|
+
noFooter,
|
|
1088
|
+
noPadding,
|
|
1089
|
+
noFullscreenButton,
|
|
1090
|
+
withDisplayOptions,
|
|
1091
|
+
resized,
|
|
1092
|
+
resizePersist,
|
|
1093
|
+
updateColumnVisibility,
|
|
1094
|
+
persistPageSize,
|
|
1095
|
+
updateTableDisplayDensity,
|
|
1096
|
+
change,
|
|
1097
|
+
syncDisplayOptionsToDb,
|
|
1098
|
+
resetDefaultVisibility,
|
|
1099
|
+
maxHeight,
|
|
1100
|
+
style,
|
|
1101
|
+
pageSize,
|
|
1102
|
+
formName,
|
|
1103
|
+
reduxFormSearchInput,
|
|
1104
|
+
reduxFormSelectedEntityIdMap,
|
|
1105
|
+
reduxFormExpandedEntityIdMap,
|
|
1106
|
+
schema,
|
|
1107
|
+
filters,
|
|
1108
|
+
errorParsingUrlString,
|
|
1109
|
+
hideDisplayOptionsIcon,
|
|
1110
|
+
compact,
|
|
1111
|
+
extraCompact,
|
|
1112
|
+
compactPaging,
|
|
1113
|
+
entityCount,
|
|
1114
|
+
showCount,
|
|
1115
|
+
isSingleSelect,
|
|
1116
|
+
noSelect,
|
|
1117
|
+
noRowsFoundMessage,
|
|
1118
|
+
SubComponent,
|
|
1119
|
+
shouldShowSubComponent,
|
|
1120
|
+
ReactTableProps = {},
|
|
1121
|
+
hideSelectedCount,
|
|
1122
|
+
hideColumnHeader,
|
|
1123
|
+
subHeader,
|
|
1124
|
+
isViewable,
|
|
1125
|
+
minimalStyle,
|
|
1126
|
+
entities,
|
|
1127
|
+
onlyShowRowsWErrors,
|
|
1128
|
+
reduxFormCellValidation,
|
|
1129
|
+
entitiesAcrossPages,
|
|
1130
|
+
children: maybeChildren,
|
|
1131
|
+
topLeftItems,
|
|
1132
|
+
leftOfSearchBarItems,
|
|
1133
|
+
currentParams,
|
|
1134
|
+
hasOptionForForcedHidden,
|
|
1135
|
+
showForcedHiddenColumns,
|
|
1136
|
+
searchMenuButton,
|
|
1137
|
+
setShowForcedHidden,
|
|
1138
|
+
autoFocusSearch,
|
|
1139
|
+
additionalFooterButtons,
|
|
1140
|
+
isEntityDisabled,
|
|
1141
|
+
isLocalCall,
|
|
1142
|
+
withSelectAll,
|
|
1143
|
+
variables,
|
|
1144
|
+
fragment,
|
|
1145
|
+
safeQuery,
|
|
1146
|
+
isCellEditable
|
|
1147
|
+
} = propPresets;
|
|
1148
|
+
|
|
1149
|
+
if (withSelectAll && !safeQuery) {
|
|
1150
|
+
throw new Error("safeQuery is needed for selecting all table records");
|
|
1151
|
+
}
|
|
1152
|
+
let updateColumnVisibilityToUse = updateColumnVisibility;
|
|
1153
|
+
let persistPageSizeToUse = persistPageSize;
|
|
1154
|
+
let updateTableDisplayDensityToUse = updateTableDisplayDensity;
|
|
1155
|
+
let resetDefaultVisibilityToUse = resetDefaultVisibility;
|
|
1156
|
+
if (withDisplayOptions && !syncDisplayOptionsToDb) {
|
|
1157
|
+
//little hack to make localstorage changes get reflected in UI (we force an update to get the enhancers to run again :)
|
|
1158
|
+
|
|
1159
|
+
const wrapUpdate = fn => (...args) => {
|
|
1160
|
+
fn(...args);
|
|
1161
|
+
change("localStorageForceUpdate", Math.random());
|
|
1162
|
+
};
|
|
1163
|
+
updateColumnVisibilityToUse = wrapUpdate(updateColumnVisibility);
|
|
1164
|
+
updateTableDisplayDensityToUse = wrapUpdate(updateTableDisplayDensity);
|
|
1165
|
+
resetDefaultVisibilityToUse = wrapUpdate(resetDefaultVisibility);
|
|
1166
|
+
persistPageSizeToUse = wrapUpdate(persistPageSize);
|
|
1167
|
+
}
|
|
1168
|
+
let compactClassName = "";
|
|
1169
|
+
if (compactPaging) {
|
|
1170
|
+
compactClassName += " tg-compact-paging";
|
|
1171
|
+
}
|
|
1172
|
+
compactClassName += extraCompact
|
|
1173
|
+
? " tg-extra-compact-table"
|
|
1174
|
+
: compact
|
|
1175
|
+
? " tg-compact-table"
|
|
1176
|
+
: "";
|
|
1177
|
+
|
|
1178
|
+
const hasFilters =
|
|
1179
|
+
filters.length ||
|
|
1180
|
+
searchTerm ||
|
|
1181
|
+
schema.fields.some(
|
|
1182
|
+
field => field.filterIsActive && field.filterIsActive(currentParams)
|
|
1183
|
+
);
|
|
1184
|
+
const additionalFilterKeys = schema.fields.reduce((acc, field) => {
|
|
1185
|
+
if (field.filterKey) acc.push(field.filterKey);
|
|
1186
|
+
return acc;
|
|
1187
|
+
}, []);
|
|
1188
|
+
const filtersOnNonDisplayedFields = [];
|
|
1189
|
+
if (filters && filters.length) {
|
|
1190
|
+
schema.fields.forEach(({ isHidden, displayName, path }) => {
|
|
1191
|
+
const ccDisplayName = camelCase(displayName || path);
|
|
1192
|
+
if (isHidden) {
|
|
1193
|
+
filters.forEach(filter => {
|
|
1194
|
+
if (filter.filterOn === ccDisplayName) {
|
|
1195
|
+
filtersOnNonDisplayedFields.push({
|
|
1196
|
+
...filter,
|
|
1197
|
+
displayName
|
|
1198
|
+
});
|
|
1199
|
+
}
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
const numRows = isInfinite ? entities.length : pageSize;
|
|
1205
|
+
const idMap = reduxFormSelectedEntityIdMap || {};
|
|
1206
|
+
const selectedRowCount = Object.keys(idMap).filter(key => idMap[key])
|
|
1207
|
+
.length;
|
|
1208
|
+
|
|
1209
|
+
let rowsToShow = doNotShowEmptyRows
|
|
1210
|
+
? Math.min(numRows, entities.length)
|
|
1211
|
+
: numRows;
|
|
1212
|
+
// if there are no entities then provide enough space to show
|
|
1213
|
+
// no rows found message
|
|
1214
|
+
if (entities.length === 0 && rowsToShow < 3) rowsToShow = 3;
|
|
1215
|
+
const expandedRows = entities.reduce((acc, row, index) => {
|
|
1216
|
+
const rowId = getIdOrCodeOrIndex(row, index);
|
|
1217
|
+
acc[index] = reduxFormExpandedEntityIdMap[rowId];
|
|
1218
|
+
return acc;
|
|
1219
|
+
}, {});
|
|
1220
|
+
let children = maybeChildren;
|
|
1221
|
+
if (children && typeof children === "function") {
|
|
1222
|
+
children = children(propPresets);
|
|
1223
|
+
}
|
|
1224
|
+
const showHeader = (withTitle || withSearch || children) && !noHeader;
|
|
1225
|
+
const toggleFullscreenButton = (
|
|
1226
|
+
<Button
|
|
1227
|
+
icon="fullscreen"
|
|
1228
|
+
active={fullscreen}
|
|
1229
|
+
minimal
|
|
1230
|
+
onClick={this.toggleFullscreen}
|
|
1231
|
+
/>
|
|
1232
|
+
);
|
|
1233
|
+
|
|
1234
|
+
let showSelectAll = false;
|
|
1235
|
+
let showClearAll = false;
|
|
1236
|
+
// we want to show select all if every row on the current page is selected
|
|
1237
|
+
// and not every row across all pages are already selected.
|
|
1238
|
+
if (!isInfinite) {
|
|
1239
|
+
const canShowSelectAll =
|
|
1240
|
+
withSelectAll ||
|
|
1241
|
+
(entitiesAcrossPages && numRows < entitiesAcrossPages.length);
|
|
1242
|
+
if (canShowSelectAll) {
|
|
1243
|
+
// could all be disabled
|
|
1244
|
+
let atLeastOneRowOnCurrentPageSelected = false;
|
|
1245
|
+
const allRowsOnCurrentPageSelected = entities.every(e => {
|
|
1246
|
+
const rowId = getIdOrCodeOrIndex(e);
|
|
1247
|
+
const selected = idMap[rowId] || isEntityDisabled(e);
|
|
1248
|
+
if (selected) atLeastOneRowOnCurrentPageSelected = true;
|
|
1249
|
+
return selected;
|
|
1250
|
+
});
|
|
1251
|
+
if (
|
|
1252
|
+
atLeastOneRowOnCurrentPageSelected &&
|
|
1253
|
+
allRowsOnCurrentPageSelected
|
|
1254
|
+
) {
|
|
1255
|
+
let everyEntitySelected;
|
|
1256
|
+
if (isLocalCall) {
|
|
1257
|
+
everyEntitySelected = entitiesAcrossPages.every(e => {
|
|
1258
|
+
const rowId = getIdOrCodeOrIndex(e);
|
|
1259
|
+
return idMap[rowId] || isEntityDisabled(e);
|
|
1260
|
+
});
|
|
1261
|
+
} else {
|
|
1262
|
+
everyEntitySelected = entityCount <= selectedRowCount;
|
|
1263
|
+
}
|
|
1264
|
+
if (everyEntitySelected) {
|
|
1265
|
+
showClearAll = selectedRowCount;
|
|
1266
|
+
}
|
|
1267
|
+
// only show if not all selected
|
|
1268
|
+
showSelectAll = !everyEntitySelected;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
const showNumSelected = !noSelect && !isSingleSelect && !hideSelectedCount;
|
|
1274
|
+
let selectedAndTotalMessage = "";
|
|
1275
|
+
if (showNumSelected) {
|
|
1276
|
+
selectedAndTotalMessage += `${selectedRowCount} Selected `;
|
|
1277
|
+
}
|
|
1278
|
+
if (showCount && showNumSelected) {
|
|
1279
|
+
selectedAndTotalMessage += `/ `;
|
|
1280
|
+
}
|
|
1281
|
+
if (showCount) {
|
|
1282
|
+
selectedAndTotalMessage += `${entityCount || 0} Total`;
|
|
1283
|
+
}
|
|
1284
|
+
if (selectedAndTotalMessage) {
|
|
1285
|
+
selectedAndTotalMessage = <div>{selectedAndTotalMessage}</div>;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
const shouldShowPaging =
|
|
1289
|
+
!isInfinite &&
|
|
1290
|
+
withPaging &&
|
|
1291
|
+
(hidePageSizeWhenPossible ? entityCount > pageSize : true);
|
|
1292
|
+
|
|
1293
|
+
let SubComponentToUse;
|
|
1294
|
+
if (SubComponent) {
|
|
1295
|
+
SubComponentToUse = row => {
|
|
1296
|
+
let shouldShow = true;
|
|
1297
|
+
if (shouldShowSubComponent) {
|
|
1298
|
+
shouldShow = shouldShowSubComponent(row.original);
|
|
1299
|
+
}
|
|
1300
|
+
if (shouldShow) {
|
|
1301
|
+
return SubComponent(row);
|
|
1302
|
+
}
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
let nonDisplayedFilterComp;
|
|
1306
|
+
if (filtersOnNonDisplayedFields.length) {
|
|
1307
|
+
const content = filtersOnNonDisplayedFields.map(
|
|
1308
|
+
({ displayName, path, selectedFilter, filterValue }) => {
|
|
1309
|
+
let filterValToDisplay = filterValue;
|
|
1310
|
+
if (selectedFilter === "inList") {
|
|
1311
|
+
filterValToDisplay = Array.isArray(filterValToDisplay)
|
|
1312
|
+
? filterValToDisplay
|
|
1313
|
+
: filterValToDisplay && filterValToDisplay.split(".");
|
|
1314
|
+
}
|
|
1315
|
+
if (Array.isArray(filterValToDisplay)) {
|
|
1316
|
+
filterValToDisplay = filterValToDisplay.join(", ");
|
|
1317
|
+
}
|
|
1318
|
+
return (
|
|
1319
|
+
<div
|
|
1320
|
+
key={displayName || startCase(camelCase(path))}
|
|
1321
|
+
className="tg-filter-on-non-displayed-field"
|
|
1322
|
+
>
|
|
1323
|
+
{displayName || startCase(camelCase(path))}{" "}
|
|
1324
|
+
{lowerCase(selectedFilter)} {filterValToDisplay}
|
|
1325
|
+
</div>
|
|
1326
|
+
);
|
|
1327
|
+
}
|
|
1328
|
+
);
|
|
1329
|
+
nonDisplayedFilterComp = (
|
|
1330
|
+
<div style={{ marginRight: 5, marginLeft: "auto" }}>
|
|
1331
|
+
<Tooltip
|
|
1332
|
+
content={
|
|
1333
|
+
<div>
|
|
1334
|
+
Active filters on hidden columns:
|
|
1335
|
+
<br />
|
|
1336
|
+
<br />
|
|
1337
|
+
{content}
|
|
1338
|
+
</div>
|
|
1339
|
+
}
|
|
1340
|
+
>
|
|
1341
|
+
<Icon icon="filter-list" />
|
|
1342
|
+
</Tooltip>
|
|
1343
|
+
</div>
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
let filteredEnts = entities;
|
|
1347
|
+
|
|
1348
|
+
if (onlyShowRowsWErrors) {
|
|
1349
|
+
const rowToErrorMap = {};
|
|
1350
|
+
forEach(reduxFormCellValidation, (err, cellId) => {
|
|
1351
|
+
if (err) {
|
|
1352
|
+
const [rowId] = cellId.split(":");
|
|
1353
|
+
rowToErrorMap[rowId] = true;
|
|
1354
|
+
}
|
|
1355
|
+
});
|
|
1356
|
+
filteredEnts = entities.filter(e => {
|
|
1357
|
+
return rowToErrorMap[e.id];
|
|
1358
|
+
});
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
return (
|
|
1362
|
+
// eslint-disable-next-line no-undef
|
|
1363
|
+
<this.hotkeyEnabler>
|
|
1364
|
+
<div
|
|
1365
|
+
className={classNames(
|
|
1366
|
+
"data-table-container",
|
|
1367
|
+
extraClasses,
|
|
1368
|
+
className,
|
|
1369
|
+
compactClassName,
|
|
1370
|
+
{
|
|
1371
|
+
fullscreen,
|
|
1372
|
+
in_cypress_test: window.Cypress, //tnr: this is a hack to make cypress be able to correctly click the table without the sticky header getting in the way. remove me once https://github.com/cypress-io/cypress/issues/871 is fixed
|
|
1373
|
+
"dt-isViewable": isViewable,
|
|
1374
|
+
"dt-minimalStyle": minimalStyle,
|
|
1375
|
+
"no-padding": noPadding,
|
|
1376
|
+
"hide-column-header": hideColumnHeader
|
|
1377
|
+
}
|
|
1378
|
+
)}
|
|
1379
|
+
>
|
|
1380
|
+
<div
|
|
1381
|
+
className="data-table-container-inner"
|
|
1382
|
+
{...(isCellEditable && {
|
|
1383
|
+
tabIndex: -1,
|
|
1384
|
+
onKeyDown: e => {
|
|
1385
|
+
const isArrowKey =
|
|
1386
|
+
(e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode === 9;
|
|
1387
|
+
if (isArrowKey) {
|
|
1388
|
+
const { schema, entities } = computePresets(this.props);
|
|
1389
|
+
const left = e.keyCode === 37;
|
|
1390
|
+
const up = e.keyCode === 38;
|
|
1391
|
+
const down = e.keyCode === 40;
|
|
1392
|
+
let cellIdToUse = this.getPrimarySelectedCellId();
|
|
1393
|
+
const pathToIndex = getFieldPathToIndex(schema);
|
|
1394
|
+
const entityMap = getEntityIdToEntity(entities);
|
|
1395
|
+
if (!cellIdToUse) return;
|
|
1396
|
+
const {
|
|
1397
|
+
isRect,
|
|
1398
|
+
firstCellIndex,
|
|
1399
|
+
lastCellIndex,
|
|
1400
|
+
lastRowIndex,
|
|
1401
|
+
firstRowIndex
|
|
1402
|
+
} = this.isSelectionARectangle();
|
|
1403
|
+
|
|
1404
|
+
if (isRect) {
|
|
1405
|
+
const [rowId, columnPath] = cellIdToUse.split(":");
|
|
1406
|
+
|
|
1407
|
+
const columnIndex = pathToIndex[columnPath];
|
|
1408
|
+
const indexToPath = invert(pathToIndex);
|
|
1409
|
+
// we want to grab the cell opposite to the primary selected cell
|
|
1410
|
+
if (
|
|
1411
|
+
firstCellIndex === columnIndex &&
|
|
1412
|
+
firstRowIndex === entityMap[rowId]?.i
|
|
1413
|
+
) {
|
|
1414
|
+
cellIdToUse = `${entities[lastRowIndex].id}:${indexToPath[lastCellIndex]}`;
|
|
1415
|
+
} else if (
|
|
1416
|
+
firstCellIndex === columnIndex &&
|
|
1417
|
+
lastRowIndex === entityMap[rowId]?.i
|
|
1418
|
+
) {
|
|
1419
|
+
cellIdToUse = `${entities[firstRowIndex].id}:${indexToPath[lastCellIndex]}`;
|
|
1420
|
+
} else if (
|
|
1421
|
+
lastCellIndex === columnIndex &&
|
|
1422
|
+
firstRowIndex === entityMap[rowId]?.i
|
|
1423
|
+
) {
|
|
1424
|
+
cellIdToUse = `${entities[lastRowIndex].id}:${indexToPath[firstCellIndex]}`;
|
|
1425
|
+
} else {
|
|
1426
|
+
cellIdToUse = `${entities[firstRowIndex].id}:${indexToPath[firstCellIndex]}`;
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
if (!cellIdToUse) return;
|
|
1430
|
+
const [rowId, columnPath] = cellIdToUse.split(":");
|
|
1431
|
+
const columnIndex = pathToIndex[columnPath];
|
|
1432
|
+
|
|
1433
|
+
const { i: rowIndex } = entityMap[rowId];
|
|
1434
|
+
|
|
1435
|
+
const {
|
|
1436
|
+
cellIdAbove,
|
|
1437
|
+
cellIdToRight,
|
|
1438
|
+
cellIdBelow,
|
|
1439
|
+
cellIdToLeft
|
|
1440
|
+
} = getCellInfo({
|
|
1441
|
+
columnIndex,
|
|
1442
|
+
columnPath,
|
|
1443
|
+
rowId,
|
|
1444
|
+
schema,
|
|
1445
|
+
entities,
|
|
1446
|
+
rowIndex,
|
|
1447
|
+
isEntityDisabled,
|
|
1448
|
+
entity: entityMap[rowId].e
|
|
1449
|
+
});
|
|
1450
|
+
const nextCellId = up
|
|
1451
|
+
? cellIdAbove
|
|
1452
|
+
: down
|
|
1453
|
+
? cellIdBelow
|
|
1454
|
+
: left
|
|
1455
|
+
? cellIdToLeft
|
|
1456
|
+
: cellIdToRight;
|
|
1457
|
+
|
|
1458
|
+
e.stopPropagation();
|
|
1459
|
+
e.preventDefault();
|
|
1460
|
+
if (!nextCellId) return;
|
|
1461
|
+
// this.handleCellBlur();
|
|
1462
|
+
// this.finishCellEdit
|
|
1463
|
+
if (
|
|
1464
|
+
document.activeElement?.parentElement?.classList.contains(
|
|
1465
|
+
"rt-td"
|
|
1466
|
+
)
|
|
1467
|
+
) {
|
|
1468
|
+
document.activeElement.blur();
|
|
1469
|
+
}
|
|
1470
|
+
this.handleCellClick({
|
|
1471
|
+
event: e,
|
|
1472
|
+
cellId: nextCellId
|
|
1473
|
+
});
|
|
1474
|
+
}
|
|
1475
|
+
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
|
1476
|
+
const cellId = this.getPrimarySelectedCellId();
|
|
1477
|
+
if (!cellId) return;
|
|
1478
|
+
const entityIdToEntity = getEntityIdToEntity(entities);
|
|
1479
|
+
const [rowId] = cellId.split(":");
|
|
1480
|
+
if (!rowId) return;
|
|
1481
|
+
const entity = entityIdToEntity[rowId].e;
|
|
1482
|
+
if (!entity) return;
|
|
1483
|
+
const rowDisabled = isEntityDisabled(entity);
|
|
1484
|
+
const isNum = e.keyCode >= 48 && e.keyCode <= 57;
|
|
1485
|
+
const isLetter = e.keyCode >= 65 && e.keyCode <= 90;
|
|
1486
|
+
if (!isNum && !isLetter) return;
|
|
1487
|
+
if (rowDisabled) return;
|
|
1488
|
+
this.startCellEdit(cellId, { shouldSelectAll: true });
|
|
1489
|
+
e.stopPropagation();
|
|
1490
|
+
// e.preventDefault();
|
|
1491
|
+
}
|
|
1492
|
+
})}
|
|
1493
|
+
>
|
|
1494
|
+
{isCellEditable && entities.length > 50 && (
|
|
1495
|
+
<SwitchField
|
|
1496
|
+
name="onlyShowRowsWErrors"
|
|
1497
|
+
inlineLabel={true}
|
|
1498
|
+
label="Only Show Rows With Errors"
|
|
1499
|
+
/>
|
|
1500
|
+
)}
|
|
1501
|
+
{showHeader && (
|
|
1502
|
+
<div className="data-table-header">
|
|
1503
|
+
<div className="data-table-title-and-buttons">
|
|
1504
|
+
{tableName && withTitle && (
|
|
1505
|
+
<span className="data-table-title">{tableName}</span>
|
|
1506
|
+
)}
|
|
1507
|
+
{children}
|
|
1508
|
+
{topLeftItems}
|
|
1509
|
+
</div>
|
|
1510
|
+
{errorParsingUrlString && (
|
|
1511
|
+
<Callout
|
|
1512
|
+
icon="error"
|
|
1513
|
+
style={{
|
|
1514
|
+
width: "unset"
|
|
1515
|
+
}}
|
|
1516
|
+
intent={Intent.WARNING}
|
|
1517
|
+
>
|
|
1518
|
+
Error parsing URL
|
|
1519
|
+
</Callout>
|
|
1520
|
+
)}
|
|
1521
|
+
{nonDisplayedFilterComp}
|
|
1522
|
+
{withSearch && (
|
|
1523
|
+
<div className="data-table-search-and-clear-filter-container">
|
|
1524
|
+
{leftOfSearchBarItems}
|
|
1525
|
+
{hasFilters ? (
|
|
1526
|
+
<Tooltip content="Clear Filters">
|
|
1527
|
+
<Button
|
|
1528
|
+
minimal
|
|
1529
|
+
intent="danger"
|
|
1530
|
+
icon="filter-remove"
|
|
1531
|
+
disabled={disabled}
|
|
1532
|
+
className="data-table-clear-filters"
|
|
1533
|
+
onClick={() => {
|
|
1534
|
+
clearFilters(additionalFilterKeys);
|
|
1535
|
+
}}
|
|
1536
|
+
/>
|
|
1537
|
+
</Tooltip>
|
|
1538
|
+
) : (
|
|
1539
|
+
""
|
|
1540
|
+
)}
|
|
1541
|
+
<SearchBar
|
|
1542
|
+
{...{
|
|
1543
|
+
reduxFormSearchInput,
|
|
1544
|
+
setSearchTerm,
|
|
1545
|
+
loading: isLoading,
|
|
1546
|
+
searchMenuButton,
|
|
1547
|
+
disabled,
|
|
1548
|
+
autoFocusSearch
|
|
1549
|
+
}}
|
|
1550
|
+
/>
|
|
1551
|
+
</div>
|
|
1552
|
+
)}
|
|
1553
|
+
</div>
|
|
1554
|
+
)}
|
|
1555
|
+
{subHeader}
|
|
1556
|
+
{showSelectAll && !isSingleSelect && (
|
|
1557
|
+
<div
|
|
1558
|
+
style={{
|
|
1559
|
+
marginTop: 5,
|
|
1560
|
+
marginBottom: 5,
|
|
1561
|
+
display: "flex",
|
|
1562
|
+
alignItems: "center"
|
|
1563
|
+
}}
|
|
1564
|
+
>
|
|
1565
|
+
All items on this page are selected.{" "}
|
|
1566
|
+
<Button
|
|
1567
|
+
small
|
|
1568
|
+
minimal
|
|
1569
|
+
intent="primary"
|
|
1570
|
+
text={`Select all ${entityCount ||
|
|
1571
|
+
entitiesAcrossPages.length} items`}
|
|
1572
|
+
loading={this.state.selectingAll}
|
|
1573
|
+
onClick={async () => {
|
|
1574
|
+
if (withSelectAll) {
|
|
1575
|
+
// this will be by querying for everything
|
|
1576
|
+
this.setState({
|
|
1577
|
+
selectingAll: true
|
|
1578
|
+
});
|
|
1579
|
+
try {
|
|
1580
|
+
const allEntities = await safeQuery(fragment, {
|
|
1581
|
+
variables: {
|
|
1582
|
+
filter: variables.filter,
|
|
1583
|
+
sort: variables.sort
|
|
1584
|
+
},
|
|
1585
|
+
canCancel: true
|
|
1586
|
+
});
|
|
1587
|
+
this.addEntitiesToSelection(allEntities);
|
|
1588
|
+
} catch (error) {
|
|
1589
|
+
console.error(`error:`, error);
|
|
1590
|
+
window.toastr.error("Error selecting all constructs");
|
|
1591
|
+
}
|
|
1592
|
+
this.setState({
|
|
1593
|
+
selectingAll: false
|
|
1594
|
+
});
|
|
1595
|
+
} else {
|
|
1596
|
+
this.addEntitiesToSelection(entitiesAcrossPages);
|
|
1597
|
+
}
|
|
1598
|
+
}}
|
|
1599
|
+
/>
|
|
1600
|
+
</div>
|
|
1601
|
+
)}
|
|
1602
|
+
{showClearAll > 0 && (
|
|
1603
|
+
<div
|
|
1604
|
+
style={{
|
|
1605
|
+
marginTop: 5,
|
|
1606
|
+
marginBottom: 5,
|
|
1607
|
+
display: "flex",
|
|
1608
|
+
alignItems: "center"
|
|
1609
|
+
}}
|
|
1610
|
+
>
|
|
1611
|
+
All {showClearAll} items are selected.{" "}
|
|
1612
|
+
<Button
|
|
1613
|
+
small
|
|
1614
|
+
minimal
|
|
1615
|
+
intent="primary"
|
|
1616
|
+
text="Clear Selection"
|
|
1617
|
+
onClick={() => {
|
|
1618
|
+
finalizeSelection({
|
|
1619
|
+
idMap: {},
|
|
1620
|
+
entities,
|
|
1621
|
+
props: computePresets(this.props)
|
|
1622
|
+
});
|
|
1623
|
+
}}
|
|
1624
|
+
/>
|
|
1625
|
+
</div>
|
|
1626
|
+
)}
|
|
1627
|
+
<ReactTable
|
|
1628
|
+
data={filteredEnts}
|
|
1629
|
+
ref={n => {
|
|
1630
|
+
if (n) this.table = n;
|
|
1631
|
+
}}
|
|
1632
|
+
additionalBodyEl={
|
|
1633
|
+
isCellEditable &&
|
|
1634
|
+
!onlyShowRowsWErrors && (
|
|
1635
|
+
<Button
|
|
1636
|
+
icon="add"
|
|
1637
|
+
style={{ marginTop: "auto" }}
|
|
1638
|
+
onClick={() => {
|
|
1639
|
+
this.insertRows({ numRows: 10, appendToBottom: true });
|
|
1640
|
+
}}
|
|
1641
|
+
minimal
|
|
1642
|
+
>
|
|
1643
|
+
Add 10 Rows
|
|
1644
|
+
</Button>
|
|
1645
|
+
)
|
|
1646
|
+
}
|
|
1647
|
+
className={classNames({
|
|
1648
|
+
isCellEditable,
|
|
1649
|
+
"tg-table-loading": isLoading,
|
|
1650
|
+
"tg-table-disabled": disabled
|
|
1651
|
+
})}
|
|
1652
|
+
itemSizeEstimator={
|
|
1653
|
+
extraCompact
|
|
1654
|
+
? itemSizeEstimators.compact
|
|
1655
|
+
: compact
|
|
1656
|
+
? itemSizeEstimators.normal
|
|
1657
|
+
: itemSizeEstimators.comfortable
|
|
1658
|
+
}
|
|
1659
|
+
TfootComponent={() => {
|
|
1660
|
+
return <button>hasdfasdf</button>;
|
|
1661
|
+
}}
|
|
1662
|
+
columns={this.renderColumns()}
|
|
1663
|
+
pageSize={rowsToShow}
|
|
1664
|
+
expanded={expandedRows}
|
|
1665
|
+
showPagination={false}
|
|
1666
|
+
sortable={false}
|
|
1667
|
+
loading={isLoading || disabled}
|
|
1668
|
+
defaultResized={resized}
|
|
1669
|
+
onResizedChange={(newResized = []) => {
|
|
1670
|
+
const resizedToUse = newResized.map(column => {
|
|
1671
|
+
// have a min width of 50 so that columns don't disappear
|
|
1672
|
+
if (column.value < 50) {
|
|
1673
|
+
return {
|
|
1674
|
+
...column,
|
|
1675
|
+
value: 50
|
|
1676
|
+
};
|
|
1677
|
+
} else {
|
|
1678
|
+
return column;
|
|
1679
|
+
}
|
|
1680
|
+
});
|
|
1681
|
+
resizePersist(resizedToUse);
|
|
1682
|
+
}}
|
|
1683
|
+
TheadComponent={this.getTheadComponent}
|
|
1684
|
+
ThComponent={this.getThComponent}
|
|
1685
|
+
getTrGroupProps={this.getTableRowProps}
|
|
1686
|
+
getTdProps={this.getTableCellProps}
|
|
1687
|
+
NoDataComponent={({ children }) =>
|
|
1688
|
+
isLoading ? null : (
|
|
1689
|
+
<div className="rt-noData">
|
|
1690
|
+
{noRowsFoundMessage || children}
|
|
1691
|
+
</div>
|
|
1692
|
+
)
|
|
1693
|
+
}
|
|
1694
|
+
LoadingComponent={props => (
|
|
1695
|
+
<DisabledLoadingComponent {...{ ...props, disabled }} />
|
|
1696
|
+
)}
|
|
1697
|
+
style={{
|
|
1698
|
+
maxHeight,
|
|
1699
|
+
minHeight: 150,
|
|
1700
|
+
...style
|
|
1701
|
+
}}
|
|
1702
|
+
SubComponent={SubComponentToUse}
|
|
1703
|
+
{...ReactTableProps}
|
|
1704
|
+
/>
|
|
1705
|
+
|
|
1706
|
+
{!noFooter && (
|
|
1707
|
+
<div
|
|
1708
|
+
className="data-table-footer"
|
|
1709
|
+
style={{
|
|
1710
|
+
justifyContent:
|
|
1711
|
+
!showNumSelected && !showCount
|
|
1712
|
+
? "flex-end"
|
|
1713
|
+
: "space-between"
|
|
1714
|
+
}}
|
|
1715
|
+
>
|
|
1716
|
+
{selectedAndTotalMessage}
|
|
1717
|
+
<div style={{ display: "flex", flexWrap: "wrap" }}>
|
|
1718
|
+
{additionalFooterButtons}
|
|
1719
|
+
{!noFullscreenButton && toggleFullscreenButton}
|
|
1720
|
+
{withDisplayOptions && (
|
|
1721
|
+
<DisplayOptions
|
|
1722
|
+
compact={compact}
|
|
1723
|
+
extraCompact={extraCompact}
|
|
1724
|
+
disabled={disabled}
|
|
1725
|
+
hideDisplayOptionsIcon={hideDisplayOptionsIcon}
|
|
1726
|
+
resetDefaultVisibility={resetDefaultVisibilityToUse}
|
|
1727
|
+
updateColumnVisibility={updateColumnVisibilityToUse}
|
|
1728
|
+
updateTableDisplayDensity={updateTableDisplayDensityToUse}
|
|
1729
|
+
showForcedHiddenColumns={showForcedHiddenColumns}
|
|
1730
|
+
setShowForcedHidden={setShowForcedHidden}
|
|
1731
|
+
hasOptionForForcedHidden={hasOptionForForcedHidden}
|
|
1732
|
+
formName={formName}
|
|
1733
|
+
schema={schema}
|
|
1734
|
+
/>
|
|
1735
|
+
)}
|
|
1736
|
+
{shouldShowPaging && (
|
|
1737
|
+
<PagingTool
|
|
1738
|
+
{...propPresets}
|
|
1739
|
+
persistPageSize={persistPageSizeToUse}
|
|
1740
|
+
/>
|
|
1741
|
+
)}
|
|
1742
|
+
</div>
|
|
1743
|
+
</div>
|
|
1744
|
+
)}
|
|
1745
|
+
</div>
|
|
1746
|
+
</div>
|
|
1747
|
+
</this.hotkeyEnabler>
|
|
1748
|
+
);
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
getTableRowProps = (state, rowInfo) => {
|
|
1752
|
+
const {
|
|
1753
|
+
reduxFormSelectedEntityIdMap,
|
|
1754
|
+
reduxFormExpandedEntityIdMap,
|
|
1755
|
+
withCheckboxes,
|
|
1756
|
+
onDoubleClick,
|
|
1757
|
+
history,
|
|
1758
|
+
mustClickCheckboxToSelect,
|
|
1759
|
+
entities,
|
|
1760
|
+
isEntityDisabled,
|
|
1761
|
+
change,
|
|
1762
|
+
getRowClassName,
|
|
1763
|
+
isCellEditable
|
|
1764
|
+
} = computePresets(this.props);
|
|
1765
|
+
if (!rowInfo) {
|
|
1766
|
+
return {
|
|
1767
|
+
className: "no-row-data"
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
const entity = rowInfo.original;
|
|
1771
|
+
const rowId = getIdOrCodeOrIndex(entity, rowInfo.index);
|
|
1772
|
+
const rowSelected = reduxFormSelectedEntityIdMap[rowId];
|
|
1773
|
+
const isExpanded = reduxFormExpandedEntityIdMap[rowId];
|
|
1774
|
+
const rowDisabled = isEntityDisabled(entity);
|
|
1775
|
+
const dataId = entity.id || entity.code;
|
|
1776
|
+
return {
|
|
1777
|
+
onClick: e => {
|
|
1778
|
+
if (isCellEditable) return;
|
|
1779
|
+
// if checkboxes are activated or row expander is clicked don't select row
|
|
1780
|
+
if (e.target.matches(".tg-expander, .tg-expander *")) {
|
|
1781
|
+
change("reduxFormExpandedEntityIdMap", {
|
|
1782
|
+
...reduxFormExpandedEntityIdMap,
|
|
1783
|
+
[rowId]: !isExpanded
|
|
1784
|
+
});
|
|
1785
|
+
return;
|
|
1786
|
+
} else if (
|
|
1787
|
+
e.target.closest(".tg-react-table-checkbox-cell-container")
|
|
1788
|
+
) {
|
|
1789
|
+
return;
|
|
1790
|
+
} else if (mustClickCheckboxToSelect) {
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
if (e.detail > 1) {
|
|
1794
|
+
return; //cancel multiple quick clicks
|
|
1795
|
+
}
|
|
1796
|
+
rowClick(e, rowInfo, entities, computePresets(this.props));
|
|
1797
|
+
},
|
|
1798
|
+
//row right click
|
|
1799
|
+
onContextMenu: e => {
|
|
1800
|
+
e.preventDefault();
|
|
1801
|
+
if (rowId === undefined || rowDisabled || isCellEditable) return;
|
|
1802
|
+
const oldIdMap = cloneDeep(reduxFormSelectedEntityIdMap) || {};
|
|
1803
|
+
let newIdMap;
|
|
1804
|
+
if (withCheckboxes) {
|
|
1805
|
+
newIdMap = oldIdMap;
|
|
1806
|
+
} else {
|
|
1807
|
+
// if we are not using checkboxes we need to make sure
|
|
1808
|
+
// that the id of the record gets added to the id map
|
|
1809
|
+
newIdMap = oldIdMap[rowId] ? oldIdMap : { [rowId]: { entity } };
|
|
1810
|
+
|
|
1811
|
+
// tgreen: this will refresh the selection with fresh data. The entities in redux might not be up to date
|
|
1812
|
+
const keyedEntities = keyBy(entities, getIdOrCodeOrIndex);
|
|
1813
|
+
forEach(newIdMap, (val, key) => {
|
|
1814
|
+
const freshEntity = keyedEntities[key];
|
|
1815
|
+
if (freshEntity) {
|
|
1816
|
+
newIdMap[key] = { ...newIdMap[key], entity: freshEntity };
|
|
1817
|
+
}
|
|
1818
|
+
});
|
|
1819
|
+
|
|
1820
|
+
finalizeSelection({
|
|
1821
|
+
idMap: newIdMap,
|
|
1822
|
+
entities,
|
|
1823
|
+
props: computePresets(this.props)
|
|
1824
|
+
});
|
|
1825
|
+
}
|
|
1826
|
+
this.showContextMenu(e, newIdMap);
|
|
1827
|
+
},
|
|
1828
|
+
className: classNames(
|
|
1829
|
+
"with-row-data",
|
|
1830
|
+
getRowClassName && getRowClassName(rowInfo, state, this.props),
|
|
1831
|
+
{
|
|
1832
|
+
disabled: rowDisabled,
|
|
1833
|
+
selected: rowSelected && !withCheckboxes
|
|
1834
|
+
}
|
|
1835
|
+
),
|
|
1836
|
+
"data-test-id": dataId === undefined ? rowInfo.index : dataId,
|
|
1837
|
+
"data-index": rowInfo.index,
|
|
1838
|
+
onDoubleClick: e => {
|
|
1839
|
+
if (rowDisabled) return;
|
|
1840
|
+
this.dblClickTriggered = true;
|
|
1841
|
+
onDoubleClick &&
|
|
1842
|
+
onDoubleClick(rowInfo.original, rowInfo.index, history, e);
|
|
1843
|
+
}
|
|
1844
|
+
};
|
|
1845
|
+
};
|
|
1846
|
+
|
|
1847
|
+
startCellEdit = (cellId, { shouldSelectAll } = {}) => {
|
|
1848
|
+
const {
|
|
1849
|
+
change,
|
|
1850
|
+
reduxFormSelectedCells = {},
|
|
1851
|
+
reduxFormEditingCell
|
|
1852
|
+
} = computePresets(this.props);
|
|
1853
|
+
const newSelectedCells = { ...reduxFormSelectedCells };
|
|
1854
|
+
newSelectedCells[cellId] = PRIMARY_SELECTED_VAL;
|
|
1855
|
+
//check if the cell is already selected and editing and if so, don't change it
|
|
1856
|
+
if (reduxFormEditingCell === cellId) return;
|
|
1857
|
+
change("reduxFormSelectedCells", newSelectedCells);
|
|
1858
|
+
change("reduxFormEditingCell", cellId);
|
|
1859
|
+
if (shouldSelectAll) {
|
|
1860
|
+
//we should select the text
|
|
1861
|
+
change("reduxFormEditingCellSelectAll", true);
|
|
1862
|
+
}
|
|
1863
|
+
};
|
|
1864
|
+
|
|
1865
|
+
getTableCellProps = (state, rowInfo, column) => {
|
|
1866
|
+
const {
|
|
1867
|
+
entities,
|
|
1868
|
+
schema,
|
|
1869
|
+
doNotValidateUntouchedRows,
|
|
1870
|
+
reduxFormEditingCell,
|
|
1871
|
+
isCellEditable,
|
|
1872
|
+
reduxFormCellValidation,
|
|
1873
|
+
reduxFormSelectedCells = {},
|
|
1874
|
+
isEntityDisabled,
|
|
1875
|
+
change
|
|
1876
|
+
} = computePresets(this.props);
|
|
1877
|
+
if (!isCellEditable) return {}; //only allow cell selection to do stuff here
|
|
1878
|
+
if (!rowInfo) return {};
|
|
1879
|
+
const entity = rowInfo.original;
|
|
1880
|
+
const rowIndex = rowInfo.index;
|
|
1881
|
+
const rowId = getIdOrCodeOrIndex(entity, rowIndex);
|
|
1882
|
+
const {
|
|
1883
|
+
cellId,
|
|
1884
|
+
cellIdAbove,
|
|
1885
|
+
cellIdToRight,
|
|
1886
|
+
cellIdBelow,
|
|
1887
|
+
cellIdToLeft,
|
|
1888
|
+
rowDisabled,
|
|
1889
|
+
columnIndex
|
|
1890
|
+
} = getCellInfo({
|
|
1891
|
+
columnIndex: column.index,
|
|
1892
|
+
columnPath: column.path,
|
|
1893
|
+
rowId,
|
|
1894
|
+
schema,
|
|
1895
|
+
entities,
|
|
1896
|
+
rowIndex,
|
|
1897
|
+
isEntityDisabled,
|
|
1898
|
+
entity
|
|
1899
|
+
});
|
|
1900
|
+
const _isClean = entity._isClean && doNotValidateUntouchedRows;
|
|
1901
|
+
|
|
1902
|
+
const err = !_isClean && reduxFormCellValidation[cellId];
|
|
1903
|
+
let selectedTopBorder,
|
|
1904
|
+
selectedRightBorder,
|
|
1905
|
+
selectedBottomBorder,
|
|
1906
|
+
selectedLeftBorder;
|
|
1907
|
+
if (reduxFormSelectedCells[cellId]) {
|
|
1908
|
+
selectedTopBorder = !reduxFormSelectedCells[cellIdAbove];
|
|
1909
|
+
selectedRightBorder = !reduxFormSelectedCells[cellIdToRight];
|
|
1910
|
+
selectedBottomBorder = !reduxFormSelectedCells[cellIdBelow];
|
|
1911
|
+
selectedLeftBorder = !reduxFormSelectedCells[cellIdToLeft];
|
|
1912
|
+
}
|
|
1913
|
+
const isPrimarySelected =
|
|
1914
|
+
reduxFormSelectedCells[cellId] === PRIMARY_SELECTED_VAL;
|
|
1915
|
+
const className = classNames({
|
|
1916
|
+
isSelectedCell: reduxFormSelectedCells[cellId],
|
|
1917
|
+
isPrimarySelected,
|
|
1918
|
+
isSecondarySelected: reduxFormSelectedCells[cellId] === true,
|
|
1919
|
+
noSelectedTopBorder: !selectedTopBorder,
|
|
1920
|
+
isCleanRow: _isClean,
|
|
1921
|
+
noSelectedRightBorder: !selectedRightBorder,
|
|
1922
|
+
noSelectedBottomBorder: !selectedBottomBorder,
|
|
1923
|
+
noSelectedLeftBorder: !selectedLeftBorder,
|
|
1924
|
+
isDropdownCell: column.type === "dropdown",
|
|
1925
|
+
isEditingCell: reduxFormEditingCell === cellId,
|
|
1926
|
+
hasCellError: !!err,
|
|
1927
|
+
"no-data-tip": reduxFormSelectedCells[cellId]
|
|
1928
|
+
});
|
|
1929
|
+
return {
|
|
1930
|
+
onDoubleClick: () => {
|
|
1931
|
+
// cell double click
|
|
1932
|
+
if (rowDisabled) return;
|
|
1933
|
+
this.startCellEdit(cellId);
|
|
1934
|
+
},
|
|
1935
|
+
...(err && {
|
|
1936
|
+
"data-tip": err?.message || err
|
|
1937
|
+
}),
|
|
1938
|
+
onContextMenu: e => {
|
|
1939
|
+
if (!isPrimarySelected) {
|
|
1940
|
+
const primaryCellId = this.getPrimarySelectedCellId();
|
|
1941
|
+
const newSelectedCells = { ...reduxFormSelectedCells };
|
|
1942
|
+
if (primaryCellId) {
|
|
1943
|
+
newSelectedCells[primaryCellId] = true;
|
|
1944
|
+
}
|
|
1945
|
+
newSelectedCells[cellId] = PRIMARY_SELECTED_VAL;
|
|
1946
|
+
change("reduxFormSelectedCells", newSelectedCells);
|
|
1947
|
+
}
|
|
1948
|
+
setTimeout(() => {
|
|
1949
|
+
//need a timeout so reduxFormSelectedCells is up to date in the context menu
|
|
1950
|
+
this.showContextMenu(e);
|
|
1951
|
+
}, 0);
|
|
1952
|
+
},
|
|
1953
|
+
onClick: event => {
|
|
1954
|
+
this.handleCellClick({
|
|
1955
|
+
event,
|
|
1956
|
+
cellId,
|
|
1957
|
+
rowDisabled,
|
|
1958
|
+
rowIndex,
|
|
1959
|
+
columnIndex
|
|
1960
|
+
});
|
|
1961
|
+
},
|
|
1962
|
+
className
|
|
1963
|
+
};
|
|
1964
|
+
};
|
|
1965
|
+
|
|
1966
|
+
handleCellClick = ({ event, cellId }) => {
|
|
1967
|
+
if (!cellId) return;
|
|
1968
|
+
// cell click, cellclick
|
|
1969
|
+
const {
|
|
1970
|
+
entities,
|
|
1971
|
+
schema,
|
|
1972
|
+
change,
|
|
1973
|
+
reduxFormEditingCell,
|
|
1974
|
+
reduxFormSelectedCells = {},
|
|
1975
|
+
isEntityDisabled
|
|
1976
|
+
} = computePresets(this.props);
|
|
1977
|
+
const [rowId, cellPath] = cellId.split(":");
|
|
1978
|
+
const entityMap = getEntityIdToEntity(entities);
|
|
1979
|
+
const { e: entity, i: rowIndex } = entityMap[rowId];
|
|
1980
|
+
const pathToIndex = getFieldPathToIndex(schema);
|
|
1981
|
+
const columnIndex = pathToIndex[cellPath];
|
|
1982
|
+
const rowDisabled = isEntityDisabled(entity);
|
|
1983
|
+
|
|
1984
|
+
if (rowDisabled) return;
|
|
1985
|
+
let newSelectedCells = {
|
|
1986
|
+
...reduxFormSelectedCells
|
|
1987
|
+
};
|
|
1988
|
+
if (newSelectedCells[cellId] && !event.shiftKey) {
|
|
1989
|
+
// don't deselect if editing
|
|
1990
|
+
if (reduxFormEditingCell === cellId) return;
|
|
1991
|
+
if (event.metaKey) {
|
|
1992
|
+
delete newSelectedCells[cellId];
|
|
1993
|
+
} else {
|
|
1994
|
+
newSelectedCells = {};
|
|
1995
|
+
newSelectedCells[cellId] = PRIMARY_SELECTED_VAL;
|
|
1996
|
+
}
|
|
1997
|
+
} else {
|
|
1998
|
+
const primarySelectedCellId = this.getPrimarySelectedCellId();
|
|
1999
|
+
if (event.metaKey) {
|
|
2000
|
+
if (isEmpty(newSelectedCells)) {
|
|
2001
|
+
newSelectedCells[cellId] = PRIMARY_SELECTED_VAL;
|
|
2002
|
+
} else {
|
|
2003
|
+
newSelectedCells[cellId] = PRIMARY_SELECTED_VAL;
|
|
2004
|
+
if (primarySelectedCellId)
|
|
2005
|
+
newSelectedCells[primarySelectedCellId] = true;
|
|
2006
|
+
}
|
|
2007
|
+
} else if (event.shiftKey) {
|
|
2008
|
+
if (primarySelectedCellId) {
|
|
2009
|
+
const [rowId, colPath] = primarySelectedCellId.split(":");
|
|
2010
|
+
const primaryRowIndex = entities.findIndex((e, i) => {
|
|
2011
|
+
return getIdOrCodeOrIndex(e, i) === rowId;
|
|
2012
|
+
});
|
|
2013
|
+
const fieldToIndex = getFieldPathToIndex(schema);
|
|
2014
|
+
const primaryColIndex = fieldToIndex[colPath];
|
|
2015
|
+
|
|
2016
|
+
if (primaryRowIndex === -1 || primaryColIndex === -1) {
|
|
2017
|
+
newSelectedCells = {};
|
|
2018
|
+
newSelectedCells[cellId] = PRIMARY_SELECTED_VAL;
|
|
2019
|
+
} else {
|
|
2020
|
+
const minRowIndex = min([primaryRowIndex, rowIndex]);
|
|
2021
|
+
const minColIndex = min([primaryColIndex, columnIndex]);
|
|
2022
|
+
const maxRowIndex = max([primaryRowIndex, rowIndex]);
|
|
2023
|
+
const maxColIndex = max([primaryColIndex, columnIndex]);
|
|
2024
|
+
const entitiesBetweenRows = entities.slice(
|
|
2025
|
+
minRowIndex,
|
|
2026
|
+
maxRowIndex + 1
|
|
2027
|
+
);
|
|
2028
|
+
const fieldsBetweenCols = schema.fields.slice(
|
|
2029
|
+
minColIndex,
|
|
2030
|
+
maxColIndex + 1
|
|
2031
|
+
);
|
|
2032
|
+
newSelectedCells = {
|
|
2033
|
+
[primarySelectedCellId]: PRIMARY_SELECTED_VAL
|
|
2034
|
+
};
|
|
2035
|
+
entitiesBetweenRows.forEach(e => {
|
|
2036
|
+
const rowId = getIdOrCodeOrIndex(e, entities.indexOf(e));
|
|
2037
|
+
fieldsBetweenCols.forEach(f => {
|
|
2038
|
+
const cellId = `${rowId}:${f.path}`;
|
|
2039
|
+
if (!newSelectedCells[cellId]) newSelectedCells[cellId] = true;
|
|
2040
|
+
});
|
|
2041
|
+
});
|
|
2042
|
+
// newSelectedCells[cellId] = PRIMARY_SELECTED_VAL;
|
|
2043
|
+
// newSelectedCells[primarySelectedCellId] = true;
|
|
2044
|
+
}
|
|
2045
|
+
} else {
|
|
2046
|
+
newSelectedCells = {};
|
|
2047
|
+
newSelectedCells[cellId] = PRIMARY_SELECTED_VAL;
|
|
2048
|
+
}
|
|
2049
|
+
} else {
|
|
2050
|
+
newSelectedCells = {};
|
|
2051
|
+
newSelectedCells[cellId] = PRIMARY_SELECTED_VAL;
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2055
|
+
change("reduxFormSelectedCells", newSelectedCells);
|
|
2056
|
+
};
|
|
2057
|
+
renderCheckboxHeader = () => {
|
|
2058
|
+
const {
|
|
2059
|
+
reduxFormSelectedEntityIdMap,
|
|
2060
|
+
isSingleSelect,
|
|
2061
|
+
noSelect,
|
|
2062
|
+
noUserSelect,
|
|
2063
|
+
entities,
|
|
2064
|
+
isEntityDisabled
|
|
2065
|
+
} = computePresets(this.props);
|
|
2066
|
+
const checkedRows = getSelectedRowsFromEntities(
|
|
2067
|
+
entities,
|
|
2068
|
+
reduxFormSelectedEntityIdMap
|
|
2069
|
+
);
|
|
2070
|
+
const checkboxProps = {
|
|
2071
|
+
checked: false,
|
|
2072
|
+
indeterminate: false
|
|
2073
|
+
};
|
|
2074
|
+
const notDisabledEntityCount = entities.reduce((acc, e) => {
|
|
2075
|
+
return isEntityDisabled(e) ? acc : acc + 1;
|
|
2076
|
+
}, 0);
|
|
2077
|
+
if (checkedRows.length === notDisabledEntityCount) {
|
|
2078
|
+
//tnr: maybe this will need to change if we want enable select all across pages
|
|
2079
|
+
checkboxProps.checked = notDisabledEntityCount !== 0;
|
|
2080
|
+
} else {
|
|
2081
|
+
if (checkedRows.length) {
|
|
2082
|
+
checkboxProps.indeterminate = true;
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
|
|
2086
|
+
return !isSingleSelect ? (
|
|
2087
|
+
<Checkbox
|
|
2088
|
+
disabled={noSelect || noUserSelect}
|
|
2089
|
+
/* eslint-disable react/jsx-no-bind */
|
|
2090
|
+
onChange={() => {
|
|
2091
|
+
const newIdMap = cloneDeep(reduxFormSelectedEntityIdMap) || {};
|
|
2092
|
+
entities.forEach((entity, i) => {
|
|
2093
|
+
if (isEntityDisabled(entity)) return;
|
|
2094
|
+
const entityId = getIdOrCodeOrIndex(entity, i);
|
|
2095
|
+
if (checkboxProps.checked) {
|
|
2096
|
+
delete newIdMap[entityId];
|
|
2097
|
+
} else {
|
|
2098
|
+
newIdMap[entityId] = { entity };
|
|
2099
|
+
}
|
|
2100
|
+
});
|
|
2101
|
+
|
|
2102
|
+
finalizeSelection({
|
|
2103
|
+
idMap: newIdMap,
|
|
2104
|
+
entities,
|
|
2105
|
+
props: computePresets(this.props)
|
|
2106
|
+
});
|
|
2107
|
+
}}
|
|
2108
|
+
/* eslint-enable react/jsx-no-bind */
|
|
2109
|
+
{...checkboxProps}
|
|
2110
|
+
/>
|
|
2111
|
+
) : null;
|
|
2112
|
+
};
|
|
2113
|
+
|
|
2114
|
+
renderCheckboxCell = row => {
|
|
2115
|
+
const rowIndex = row.index;
|
|
2116
|
+
const {
|
|
2117
|
+
reduxFormSelectedEntityIdMap,
|
|
2118
|
+
noSelect,
|
|
2119
|
+
noUserSelect,
|
|
2120
|
+
entities,
|
|
2121
|
+
isEntityDisabled
|
|
2122
|
+
} = computePresets(this.props);
|
|
2123
|
+
const checkedRows = getSelectedRowsFromEntities(
|
|
2124
|
+
entities,
|
|
2125
|
+
reduxFormSelectedEntityIdMap
|
|
2126
|
+
);
|
|
2127
|
+
|
|
2128
|
+
const isSelected = checkedRows.some(rowNum => {
|
|
2129
|
+
return rowNum === rowIndex;
|
|
2130
|
+
});
|
|
2131
|
+
if (rowIndex >= entities.length) {
|
|
2132
|
+
return <div />;
|
|
2133
|
+
}
|
|
2134
|
+
const entity = entities[rowIndex];
|
|
2135
|
+
return (
|
|
2136
|
+
<Checkbox
|
|
2137
|
+
disabled={noSelect || noUserSelect || isEntityDisabled(entity)}
|
|
2138
|
+
onClick={e => {
|
|
2139
|
+
rowClick(e, row, entities, computePresets(this.props));
|
|
2140
|
+
}}
|
|
2141
|
+
checked={isSelected}
|
|
2142
|
+
/>
|
|
2143
|
+
);
|
|
2144
|
+
};
|
|
2145
|
+
|
|
2146
|
+
finishCellEdit = (cellId, newVal, doNotStopEditing) => {
|
|
2147
|
+
const {
|
|
2148
|
+
entities = [],
|
|
2149
|
+
change,
|
|
2150
|
+
schema,
|
|
2151
|
+
reduxFormCellValidation
|
|
2152
|
+
} = computePresets(this.props);
|
|
2153
|
+
const [rowId, path] = cellId.split(":");
|
|
2154
|
+
!doNotStopEditing && change("reduxFormEditingCell", null);
|
|
2155
|
+
this.updateEntitiesHelper(entities, entities => {
|
|
2156
|
+
const entity = entities.find((e, i) => {
|
|
2157
|
+
return getIdOrCodeOrIndex(e, i) === rowId;
|
|
2158
|
+
});
|
|
2159
|
+
delete entity._isClean;
|
|
2160
|
+
const { error } = editCellHelper({
|
|
2161
|
+
entity,
|
|
2162
|
+
path,
|
|
2163
|
+
schema,
|
|
2164
|
+
newVal
|
|
2165
|
+
});
|
|
2166
|
+
this.updateValidation(entities, {
|
|
2167
|
+
...reduxFormCellValidation,
|
|
2168
|
+
[cellId]: error
|
|
2169
|
+
});
|
|
2170
|
+
});
|
|
2171
|
+
!doNotStopEditing && this.refocusTable();
|
|
2172
|
+
};
|
|
2173
|
+
|
|
2174
|
+
cancelCellEdit = () => {
|
|
2175
|
+
const { change } = computePresets(this.props);
|
|
2176
|
+
change("reduxFormEditingCell", null);
|
|
2177
|
+
this.refocusTable();
|
|
2178
|
+
};
|
|
2179
|
+
refocusTable = () => {
|
|
2180
|
+
setTimeout(() => {
|
|
2181
|
+
const table = ReactDOM.findDOMNode(this.table)?.closest(
|
|
2182
|
+
".data-table-container>div"
|
|
2183
|
+
);
|
|
2184
|
+
table?.focus();
|
|
2185
|
+
}, 0);
|
|
2186
|
+
};
|
|
2187
|
+
|
|
2188
|
+
isSelectionARectangle = () => {
|
|
2189
|
+
const { entities, reduxFormSelectedCells, schema } = computePresets(
|
|
2190
|
+
this.props
|
|
2191
|
+
);
|
|
2192
|
+
if (
|
|
2193
|
+
reduxFormSelectedCells &&
|
|
2194
|
+
Object.keys(reduxFormSelectedCells).length > 1
|
|
2195
|
+
) {
|
|
2196
|
+
const pathToIndex = getFieldPathToIndex(schema);
|
|
2197
|
+
const entityMap = getEntityIdToEntity(entities);
|
|
2198
|
+
// let primaryCellId;
|
|
2199
|
+
let selectionGrid = [];
|
|
2200
|
+
let firstCellIndex;
|
|
2201
|
+
let lastCellIndex;
|
|
2202
|
+
let lastRowIndex;
|
|
2203
|
+
let firstRowIndex;
|
|
2204
|
+
const selectedPaths = [];
|
|
2205
|
+
Object.keys(reduxFormSelectedCells).forEach(key => {
|
|
2206
|
+
// if (reduxFormSelectedCells[key] === PRIMARY_SELECTED_VAL) {
|
|
2207
|
+
// primaryCellId = key;
|
|
2208
|
+
// }
|
|
2209
|
+
const [rowId, cellPath] = key.split(":");
|
|
2210
|
+
if (!selectedPaths.includes(cellPath)) selectedPaths.push(cellPath);
|
|
2211
|
+
const cellIndex = pathToIndex[cellPath];
|
|
2212
|
+
const ent = entityMap[rowId];
|
|
2213
|
+
if (!ent) return;
|
|
2214
|
+
const { i } = ent;
|
|
2215
|
+
if (firstRowIndex === undefined || i < firstRowIndex) {
|
|
2216
|
+
firstRowIndex = i;
|
|
2217
|
+
}
|
|
2218
|
+
if (lastRowIndex === undefined || i > lastRowIndex) {
|
|
2219
|
+
lastRowIndex = i;
|
|
2220
|
+
}
|
|
2221
|
+
if (!selectionGrid[i]) selectionGrid[i] = [];
|
|
2222
|
+
selectionGrid[i][cellIndex] = { cellId: key, rowIndex: i, cellIndex };
|
|
2223
|
+
if (firstCellIndex === undefined || cellIndex < firstCellIndex) {
|
|
2224
|
+
firstCellIndex = cellIndex;
|
|
2225
|
+
}
|
|
2226
|
+
if (lastCellIndex === undefined || cellIndex > lastCellIndex) {
|
|
2227
|
+
lastCellIndex = cellIndex;
|
|
2228
|
+
}
|
|
2229
|
+
});
|
|
2230
|
+
selectionGrid = selectionGrid.slice(firstRowIndex);
|
|
2231
|
+
let isRectangle = true;
|
|
2232
|
+
for (let i = 0; i < selectionGrid.length; i++) {
|
|
2233
|
+
const row = selectionGrid[i];
|
|
2234
|
+
if (!row) {
|
|
2235
|
+
isRectangle = false;
|
|
2236
|
+
break;
|
|
2237
|
+
} else {
|
|
2238
|
+
for (let j = firstCellIndex; j < row.length; j++) {
|
|
2239
|
+
if (!row[j]) {
|
|
2240
|
+
isRectangle = false;
|
|
2241
|
+
break;
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
if (isRectangle) {
|
|
2248
|
+
return {
|
|
2249
|
+
isRect: true,
|
|
2250
|
+
selectedPaths,
|
|
2251
|
+
selectionGrid,
|
|
2252
|
+
lastRowIndex,
|
|
2253
|
+
lastCellIndex,
|
|
2254
|
+
firstCellIndex,
|
|
2255
|
+
firstRowIndex,
|
|
2256
|
+
entityMap,
|
|
2257
|
+
pathToIndex
|
|
2258
|
+
};
|
|
2259
|
+
} else {
|
|
2260
|
+
return {};
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
return {};
|
|
2264
|
+
};
|
|
2265
|
+
|
|
2266
|
+
renderColumns = () => {
|
|
2267
|
+
const {
|
|
2268
|
+
isCellEditable,
|
|
2269
|
+
cellRenderer,
|
|
2270
|
+
withCheckboxes,
|
|
2271
|
+
SubComponent,
|
|
2272
|
+
shouldShowSubComponent,
|
|
2273
|
+
entities,
|
|
2274
|
+
reduxFormEditingCellSelectAll,
|
|
2275
|
+
isEntityDisabled,
|
|
2276
|
+
getCellHoverText,
|
|
2277
|
+
withExpandAndCollapseAllButton,
|
|
2278
|
+
reduxFormExpandedEntityIdMap,
|
|
2279
|
+
change,
|
|
2280
|
+
reduxFormSelectedCells,
|
|
2281
|
+
reduxFormEditingCell
|
|
2282
|
+
} = computePresets(this.props);
|
|
2283
|
+
const { columns } = this.state;
|
|
2284
|
+
if (!columns.length) {
|
|
2285
|
+
return columns;
|
|
2286
|
+
}
|
|
2287
|
+
const columnsToRender = [];
|
|
2288
|
+
if (SubComponent) {
|
|
2289
|
+
columnsToRender.push({
|
|
2290
|
+
...(withExpandAndCollapseAllButton && {
|
|
2291
|
+
Header: () => {
|
|
2292
|
+
const showCollapseAll =
|
|
2293
|
+
Object.values(reduxFormExpandedEntityIdMap).filter(i => i)
|
|
2294
|
+
.length === entities.length;
|
|
2295
|
+
return (
|
|
2296
|
+
<InfoHelper
|
|
2297
|
+
content={showCollapseAll ? "Collapse All" : "Expand All"}
|
|
2298
|
+
isButton
|
|
2299
|
+
minimal
|
|
2300
|
+
small
|
|
2301
|
+
style={{ padding: 2 }}
|
|
2302
|
+
popoverProps={{
|
|
2303
|
+
modifiers: {
|
|
2304
|
+
preventOverflow: { enabled: false },
|
|
2305
|
+
hide: { enabled: false }
|
|
2306
|
+
}
|
|
2307
|
+
}}
|
|
2308
|
+
onClick={() => {
|
|
2309
|
+
showCollapseAll
|
|
2310
|
+
? change("reduxFormExpandedEntityIdMap", {})
|
|
2311
|
+
: change(
|
|
2312
|
+
"reduxFormExpandedEntityIdMap",
|
|
2313
|
+
entities.reduce((acc, e) => {
|
|
2314
|
+
acc[e.id] = true;
|
|
2315
|
+
return acc;
|
|
2316
|
+
}, {})
|
|
2317
|
+
);
|
|
2318
|
+
}}
|
|
2319
|
+
className={classNames("tg-expander-all")}
|
|
2320
|
+
icon={showCollapseAll ? "chevron-down" : "chevron-right"}
|
|
2321
|
+
/>
|
|
2322
|
+
);
|
|
2323
|
+
}
|
|
2324
|
+
}),
|
|
2325
|
+
expander: true,
|
|
2326
|
+
Expander: ({ isExpanded, original: record }) => {
|
|
2327
|
+
let shouldShow = true;
|
|
2328
|
+
if (shouldShowSubComponent) {
|
|
2329
|
+
shouldShow = shouldShowSubComponent(record);
|
|
2330
|
+
}
|
|
2331
|
+
if (!shouldShow) return null;
|
|
2332
|
+
return (
|
|
2333
|
+
<Button
|
|
2334
|
+
className={classNames(
|
|
2335
|
+
"tg-expander",
|
|
2336
|
+
Classes.MINIMAL,
|
|
2337
|
+
Classes.SMALL
|
|
2338
|
+
)}
|
|
2339
|
+
icon={isExpanded ? "chevron-down" : "chevron-right"}
|
|
2340
|
+
/>
|
|
2341
|
+
);
|
|
2342
|
+
}
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
if (withCheckboxes) {
|
|
2347
|
+
columnsToRender.push({
|
|
2348
|
+
Header: this.renderCheckboxHeader,
|
|
2349
|
+
Cell: this.renderCheckboxCell,
|
|
2350
|
+
width: 35,
|
|
2351
|
+
resizable: false,
|
|
2352
|
+
getHeaderProps: () => {
|
|
2353
|
+
return {
|
|
2354
|
+
className: "tg-react-table-checkbox-header-container",
|
|
2355
|
+
immovable: "true"
|
|
2356
|
+
};
|
|
2357
|
+
},
|
|
2358
|
+
getProps: () => {
|
|
2359
|
+
return {
|
|
2360
|
+
className: "tg-react-table-checkbox-cell-container"
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2363
|
+
});
|
|
2364
|
+
}
|
|
2365
|
+
|
|
2366
|
+
columns.forEach(column => {
|
|
2367
|
+
const tableColumn = {
|
|
2368
|
+
...column,
|
|
2369
|
+
Header: this.renderColumnHeader(column),
|
|
2370
|
+
accessor: column.path,
|
|
2371
|
+
getHeaderProps: () => ({
|
|
2372
|
+
// needs to be a string because it is getting passed
|
|
2373
|
+
// to the dom
|
|
2374
|
+
immovable: column.immovable ? "true" : "false",
|
|
2375
|
+
columnindex: column.columnIndex
|
|
2376
|
+
})
|
|
2377
|
+
};
|
|
2378
|
+
let noEllipsis = column.noEllipsis;
|
|
2379
|
+
if (column.width) {
|
|
2380
|
+
tableColumn.width = column.width;
|
|
2381
|
+
}
|
|
2382
|
+
if (cellRenderer && cellRenderer[column.path]) {
|
|
2383
|
+
tableColumn.Cell = row => {
|
|
2384
|
+
const val = cellRenderer[column.path](
|
|
2385
|
+
row.value,
|
|
2386
|
+
row.original,
|
|
2387
|
+
row,
|
|
2388
|
+
this.props
|
|
2389
|
+
);
|
|
2390
|
+
return val;
|
|
2391
|
+
};
|
|
2392
|
+
} else if (column.render) {
|
|
2393
|
+
tableColumn.Cell = row => {
|
|
2394
|
+
const val = column.render(row.value, row.original, row, this.props);
|
|
2395
|
+
return val;
|
|
2396
|
+
};
|
|
2397
|
+
} else if (column.type === "timestamp") {
|
|
2398
|
+
tableColumn.Cell = props => {
|
|
2399
|
+
return props.value ? dayjs(props.value).format("lll") : "";
|
|
2400
|
+
};
|
|
2401
|
+
} else if (column.type === "color") {
|
|
2402
|
+
tableColumn.Cell = props => {
|
|
2403
|
+
return props.value ? (
|
|
2404
|
+
<div
|
|
2405
|
+
style={{
|
|
2406
|
+
height: 20,
|
|
2407
|
+
width: 40,
|
|
2408
|
+
background: props.value,
|
|
2409
|
+
border: "1px solid #182026",
|
|
2410
|
+
borderRadius: 5
|
|
2411
|
+
}}
|
|
2412
|
+
/>
|
|
2413
|
+
) : (
|
|
2414
|
+
""
|
|
2415
|
+
);
|
|
2416
|
+
};
|
|
2417
|
+
} else if (column.type === "boolean") {
|
|
2418
|
+
if (isCellEditable) {
|
|
2419
|
+
tableColumn.Cell = props => (props.value ? "True" : "False");
|
|
2420
|
+
} else {
|
|
2421
|
+
tableColumn.Cell = props => (
|
|
2422
|
+
<Icon
|
|
2423
|
+
className={classNames({
|
|
2424
|
+
[Classes.TEXT_MUTED]: !props.value
|
|
2425
|
+
})}
|
|
2426
|
+
icon={props.value ? "tick" : "cross"}
|
|
2427
|
+
/>
|
|
2428
|
+
);
|
|
2429
|
+
}
|
|
2430
|
+
} else if (column.type === "markdown") {
|
|
2431
|
+
tableColumn.Cell = props => (
|
|
2432
|
+
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
|
2433
|
+
{props.value}
|
|
2434
|
+
</ReactMarkdown>
|
|
2435
|
+
);
|
|
2436
|
+
} else {
|
|
2437
|
+
tableColumn.Cell = props => props.value;
|
|
2438
|
+
}
|
|
2439
|
+
const oldFunc = tableColumn.Cell;
|
|
2440
|
+
|
|
2441
|
+
tableColumn.Cell = (...args) => {
|
|
2442
|
+
const [row] = args;
|
|
2443
|
+
const rowId = getIdOrCodeOrIndex(row.original, row.index);
|
|
2444
|
+
const cellId = `${rowId}:${row.column.path}`;
|
|
2445
|
+
let val = oldFunc(...args);
|
|
2446
|
+
const oldVal = val;
|
|
2447
|
+
const text = this.getCopyTextForCell(val, row, column);
|
|
2448
|
+
const isBool = column.type === "boolean";
|
|
2449
|
+
if (isCellEditable && isBool) {
|
|
2450
|
+
val = (
|
|
2451
|
+
<Checkbox
|
|
2452
|
+
disabled={isEntityDisabled(row.original)}
|
|
2453
|
+
className="tg-cell-edit-boolean-checkbox"
|
|
2454
|
+
checked={oldVal === "True"}
|
|
2455
|
+
onChange={e => {
|
|
2456
|
+
const checked = e.target.checked;
|
|
2457
|
+
this.finishCellEdit(cellId, checked);
|
|
2458
|
+
}}
|
|
2459
|
+
/>
|
|
2460
|
+
);
|
|
2461
|
+
noEllipsis = true;
|
|
2462
|
+
} else {
|
|
2463
|
+
if (reduxFormEditingCell === cellId) {
|
|
2464
|
+
if (column.type === "dropdown" || column.type === "dropdownMulti") {
|
|
2465
|
+
return (
|
|
2466
|
+
<DropdownCell
|
|
2467
|
+
isMulti={column.type === "dropdownMulti"}
|
|
2468
|
+
initialValue={text}
|
|
2469
|
+
options={getVals(column.values)}
|
|
2470
|
+
finishEdit={(newVal, doNotStopEditing) => {
|
|
2471
|
+
this.finishCellEdit(cellId, newVal, doNotStopEditing);
|
|
2472
|
+
}}
|
|
2473
|
+
cancelEdit={this.cancelCellEdit}
|
|
2474
|
+
></DropdownCell>
|
|
2475
|
+
);
|
|
2476
|
+
} else {
|
|
2477
|
+
return (
|
|
2478
|
+
<EditableCell
|
|
2479
|
+
stopSelectAll={() =>
|
|
2480
|
+
change("reduxFormEditingCellSelectAll", false)
|
|
2481
|
+
}
|
|
2482
|
+
shouldSelectAll={reduxFormEditingCellSelectAll}
|
|
2483
|
+
cancelEdit={this.cancelCellEdit}
|
|
2484
|
+
isNumeric={column.type === "number"}
|
|
2485
|
+
initialValue={text}
|
|
2486
|
+
finishEdit={newVal => {
|
|
2487
|
+
this.finishCellEdit(cellId, newVal);
|
|
2488
|
+
}}
|
|
2489
|
+
></EditableCell>
|
|
2490
|
+
);
|
|
2491
|
+
}
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
|
|
2495
|
+
//wrap the original tableColumn.Cell function in another div in order to add a title attribute
|
|
2496
|
+
let title = text;
|
|
2497
|
+
if (getCellHoverText) title = getCellHoverText(...args);
|
|
2498
|
+
else if (column.getTitleAttr) title = column.getTitleAttr(...args);
|
|
2499
|
+
const isSelectedCell = reduxFormSelectedCells?.[cellId];
|
|
2500
|
+
// if (isSelectedCell) {
|
|
2501
|
+
// const [rowId2, path] = cellId.split(":");
|
|
2502
|
+
// const selectedEnt = entities.find((e, i) => {
|
|
2503
|
+
// return getIdOrCodeOrIndex(e, i) === rowId2;
|
|
2504
|
+
// });
|
|
2505
|
+
// }
|
|
2506
|
+
|
|
2507
|
+
const {
|
|
2508
|
+
isRect,
|
|
2509
|
+
selectionGrid,
|
|
2510
|
+
lastRowIndex,
|
|
2511
|
+
lastCellIndex,
|
|
2512
|
+
entityMap,
|
|
2513
|
+
pathToIndex
|
|
2514
|
+
} = this.isSelectionARectangle();
|
|
2515
|
+
return (
|
|
2516
|
+
<>
|
|
2517
|
+
<div
|
|
2518
|
+
style={{
|
|
2519
|
+
...(!noEllipsis && {
|
|
2520
|
+
textOverflow: "ellipsis",
|
|
2521
|
+
overflow: "hidden"
|
|
2522
|
+
})
|
|
2523
|
+
}}
|
|
2524
|
+
data-test={"tgCell_" + column.path}
|
|
2525
|
+
className="tg-cell-wrapper"
|
|
2526
|
+
data-copy-text={text}
|
|
2527
|
+
title={title || undefined}
|
|
2528
|
+
>
|
|
2529
|
+
{val}
|
|
2530
|
+
</div>
|
|
2531
|
+
{isCellEditable &&
|
|
2532
|
+
(column.type === "dropdown" ||
|
|
2533
|
+
column.type === "dropdownMulti") && (
|
|
2534
|
+
<Icon
|
|
2535
|
+
icon="caret-down"
|
|
2536
|
+
style={{
|
|
2537
|
+
position: "absolute",
|
|
2538
|
+
right: 5,
|
|
2539
|
+
opacity: 0.3
|
|
2540
|
+
}}
|
|
2541
|
+
className="cell-edit-dropdown"
|
|
2542
|
+
onClick={() => {
|
|
2543
|
+
this.startCellEdit(cellId);
|
|
2544
|
+
}}
|
|
2545
|
+
/>
|
|
2546
|
+
)}
|
|
2547
|
+
|
|
2548
|
+
{isSelectedCell &&
|
|
2549
|
+
(isRect
|
|
2550
|
+
? this.isBottomRightCornerOfRectangle({
|
|
2551
|
+
cellId,
|
|
2552
|
+
selectionGrid,
|
|
2553
|
+
lastRowIndex,
|
|
2554
|
+
lastCellIndex,
|
|
2555
|
+
entityMap,
|
|
2556
|
+
pathToIndex
|
|
2557
|
+
})
|
|
2558
|
+
: isSelectedCell === PRIMARY_SELECTED_VAL) && (
|
|
2559
|
+
<CellDragHandle
|
|
2560
|
+
key={cellId}
|
|
2561
|
+
thisTable={this.table}
|
|
2562
|
+
cellId={cellId}
|
|
2563
|
+
isSelectionARectangle={this.isSelectionARectangle}
|
|
2564
|
+
onDragEnd={this.onDragEnd}
|
|
2565
|
+
></CellDragHandle>
|
|
2566
|
+
)}
|
|
2567
|
+
</>
|
|
2568
|
+
);
|
|
2569
|
+
};
|
|
2570
|
+
|
|
2571
|
+
columnsToRender.push(tableColumn);
|
|
2572
|
+
});
|
|
2573
|
+
return columnsToRender;
|
|
2574
|
+
};
|
|
2575
|
+
isBottomRightCornerOfRectangle = ({
|
|
2576
|
+
cellId,
|
|
2577
|
+
selectionGrid,
|
|
2578
|
+
lastRowIndex,
|
|
2579
|
+
lastCellIndex,
|
|
2580
|
+
entityMap,
|
|
2581
|
+
pathToIndex
|
|
2582
|
+
}) => {
|
|
2583
|
+
selectionGrid.forEach(row => {
|
|
2584
|
+
// remove undefineds from start of row
|
|
2585
|
+
while (row[0] === undefined && row.length) row.shift();
|
|
2586
|
+
});
|
|
2587
|
+
const [rowId, cellPath] = cellId.split(":");
|
|
2588
|
+
const ent = entityMap[rowId];
|
|
2589
|
+
if (!ent) return;
|
|
2590
|
+
const { i } = ent;
|
|
2591
|
+
const cellIndex = pathToIndex[cellPath];
|
|
2592
|
+
const isBottomRight = i === lastRowIndex && cellIndex === lastCellIndex;
|
|
2593
|
+
return isBottomRight;
|
|
2594
|
+
};
|
|
2595
|
+
|
|
2596
|
+
onDragEnd = cellsToSelect => {
|
|
2597
|
+
const {
|
|
2598
|
+
entities,
|
|
2599
|
+
schema,
|
|
2600
|
+
reduxFormCellValidation,
|
|
2601
|
+
change,
|
|
2602
|
+
reduxFormSelectedCells
|
|
2603
|
+
} = this.props;
|
|
2604
|
+
const primaryCellId = this.getPrimarySelectedCellId();
|
|
2605
|
+
const [primaryRowId, primaryCellPath] = primaryCellId.split(":");
|
|
2606
|
+
const pathToField = getFieldPathToField(schema);
|
|
2607
|
+
const { selectedPaths, selectionGrid } = this.isSelectionARectangle();
|
|
2608
|
+
let allSelectedPaths = selectedPaths;
|
|
2609
|
+
if (!allSelectedPaths) {
|
|
2610
|
+
allSelectedPaths = [primaryCellPath];
|
|
2611
|
+
}
|
|
2612
|
+
|
|
2613
|
+
this.updateEntitiesHelper(entities, entities => {
|
|
2614
|
+
let newReduxFormSelectedCells;
|
|
2615
|
+
if (selectedPaths) {
|
|
2616
|
+
newReduxFormSelectedCells = {
|
|
2617
|
+
...reduxFormSelectedCells
|
|
2618
|
+
};
|
|
2619
|
+
} else {
|
|
2620
|
+
newReduxFormSelectedCells = {
|
|
2621
|
+
[primaryCellId]: PRIMARY_SELECTED_VAL
|
|
2622
|
+
};
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
const newCellValidate = {
|
|
2626
|
+
...reduxFormCellValidation
|
|
2627
|
+
};
|
|
2628
|
+
const entityMap = getEntityIdToEntity(entities);
|
|
2629
|
+
const { e: selectedEnt } = entityMap[primaryRowId];
|
|
2630
|
+
const firstCellToSelectRowIndex =
|
|
2631
|
+
entityMap[cellsToSelect[0]?.split(":")[0]]?.i;
|
|
2632
|
+
const pathToIndex = getFieldPathToIndex(schema);
|
|
2633
|
+
|
|
2634
|
+
allSelectedPaths.forEach(selectedPath => {
|
|
2635
|
+
const column = pathToField[selectedPath];
|
|
2636
|
+
|
|
2637
|
+
const selectedCellVal = getCellVal(selectedEnt, selectedPath, column);
|
|
2638
|
+
const cellIndexOfSelectedPath = pathToIndex[selectedPath];
|
|
2639
|
+
let incrementStart;
|
|
2640
|
+
let incrementPrefix;
|
|
2641
|
+
let incrementPad = 0;
|
|
2642
|
+
if (column.type === "string" || column.type === "number") {
|
|
2643
|
+
const cellNumStr = getNumberStrAtEnd(selectedCellVal);
|
|
2644
|
+
const cellNum = Number(cellNumStr);
|
|
2645
|
+
const entityAbovePrimaryCell =
|
|
2646
|
+
entities[entityMap[primaryRowId].i - 1];
|
|
2647
|
+
if (cellNumStr !== null && !isNaN(cellNum)) {
|
|
2648
|
+
if (
|
|
2649
|
+
entityAbovePrimaryCell &&
|
|
2650
|
+
(!selectionGrid || selectionGrid.length <= 1)
|
|
2651
|
+
) {
|
|
2652
|
+
const cellAboveVal = get(
|
|
2653
|
+
entityAbovePrimaryCell,
|
|
2654
|
+
selectedPath,
|
|
2655
|
+
""
|
|
2656
|
+
);
|
|
2657
|
+
const cellAboveNumStr = getNumberStrAtEnd(cellAboveVal);
|
|
2658
|
+
const cellAboveNum = Number(cellAboveNumStr);
|
|
2659
|
+
if (!isNaN(cellAboveNum)) {
|
|
2660
|
+
const isIncremental = cellNum - cellAboveNum === 1;
|
|
2661
|
+
if (isIncremental) {
|
|
2662
|
+
const cellTextNoNum = stripNumberAtEnd(selectedCellVal);
|
|
2663
|
+
const sameText =
|
|
2664
|
+
stripNumberAtEnd(cellAboveVal) === cellTextNoNum;
|
|
2665
|
+
if (sameText) {
|
|
2666
|
+
incrementStart = cellNum + 1;
|
|
2667
|
+
incrementPrefix = cellTextNoNum || "";
|
|
2668
|
+
if (cellNumStr && cellNumStr.startsWith("0")) {
|
|
2669
|
+
incrementPad = cellNumStr.length;
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
if (incrementStart === undefined) {
|
|
2676
|
+
const draggingDown =
|
|
2677
|
+
firstCellToSelectRowIndex > selectionGrid?.[0][0].rowIndex;
|
|
2678
|
+
if (selectedPaths && draggingDown) {
|
|
2679
|
+
let checkIncrement;
|
|
2680
|
+
let prefix;
|
|
2681
|
+
let maybePad;
|
|
2682
|
+
// determine if all the cells in this column of the selectionGrid are incrementing
|
|
2683
|
+
const allAreIncrementing = selectionGrid.every(row => {
|
|
2684
|
+
// see if cell is selected
|
|
2685
|
+
const cellInfo = row[cellIndexOfSelectedPath];
|
|
2686
|
+
if (!cellInfo) return false;
|
|
2687
|
+
const { cellId } = cellInfo;
|
|
2688
|
+
const [rowId] = cellId.split(":");
|
|
2689
|
+
const cellVal = getCellVal(
|
|
2690
|
+
entityMap[rowId].e,
|
|
2691
|
+
selectedPath,
|
|
2692
|
+
pathToField[selectedPath]
|
|
2693
|
+
);
|
|
2694
|
+
const cellNumStr = getNumberStrAtEnd(cellVal);
|
|
2695
|
+
const cellNum = Number(cellNumStr);
|
|
2696
|
+
const cellTextNoNum = stripNumberAtEnd(cellVal);
|
|
2697
|
+
if (cellNumStr.startsWith("0")) {
|
|
2698
|
+
maybePad = cellNumStr.length;
|
|
2699
|
+
}
|
|
2700
|
+
if (cellTextNoNum && !prefix) {
|
|
2701
|
+
prefix = cellTextNoNum;
|
|
2702
|
+
}
|
|
2703
|
+
if (cellTextNoNum && prefix !== cellTextNoNum) {
|
|
2704
|
+
return false;
|
|
2705
|
+
}
|
|
2706
|
+
if (!isNaN(cellNum)) {
|
|
2707
|
+
if (!checkIncrement) {
|
|
2708
|
+
checkIncrement = cellNum;
|
|
2709
|
+
return true;
|
|
2710
|
+
} else {
|
|
2711
|
+
return ++checkIncrement === cellNum;
|
|
2712
|
+
}
|
|
2713
|
+
} else {
|
|
2714
|
+
return false;
|
|
2715
|
+
}
|
|
2716
|
+
});
|
|
2717
|
+
|
|
2718
|
+
if (allAreIncrementing) {
|
|
2719
|
+
incrementStart = checkIncrement + 1;
|
|
2720
|
+
incrementPrefix = prefix || "";
|
|
2721
|
+
incrementPad = maybePad;
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
let firstSelectedCellRowIndex;
|
|
2729
|
+
if (selectionGrid) {
|
|
2730
|
+
selectionGrid[0].some(cell => {
|
|
2731
|
+
if (cell) {
|
|
2732
|
+
firstSelectedCellRowIndex = cell.rowIndex;
|
|
2733
|
+
return true;
|
|
2734
|
+
}
|
|
2735
|
+
return false;
|
|
2736
|
+
});
|
|
2737
|
+
}
|
|
2738
|
+
|
|
2739
|
+
cellsToSelect.forEach(cellId => {
|
|
2740
|
+
const [rowId, cellPath] = cellId.split(":");
|
|
2741
|
+
if (cellPath !== selectedPath) return;
|
|
2742
|
+
newReduxFormSelectedCells[cellId] = true;
|
|
2743
|
+
const { e: entityToUpdate, i: rowIndex } = entityMap[rowId] || {};
|
|
2744
|
+
if (entityToUpdate) {
|
|
2745
|
+
delete entityToUpdate._isClean;
|
|
2746
|
+
let newVal;
|
|
2747
|
+
if (incrementStart !== undefined) {
|
|
2748
|
+
const num = incrementStart++;
|
|
2749
|
+
newVal = incrementPrefix + padStart(num, incrementPad, "0");
|
|
2750
|
+
} else {
|
|
2751
|
+
if (selectionGrid && selectionGrid.length > 1) {
|
|
2752
|
+
// if there are multiple cells selected then we want to copy them repeating
|
|
2753
|
+
// ex: if we have 1,2,3 selected and we drag for 5 more rows we want it to
|
|
2754
|
+
// be 1,2,3,1,2 for the new row cells in this column
|
|
2755
|
+
const draggingDown = rowIndex > firstSelectedCellRowIndex;
|
|
2756
|
+
const cellIndex = pathToIndex[cellPath];
|
|
2757
|
+
let cellIdToCopy;
|
|
2758
|
+
if (draggingDown) {
|
|
2759
|
+
const { cellId } = selectionGrid[
|
|
2760
|
+
(rowIndex - firstSelectedCellRowIndex) %
|
|
2761
|
+
selectionGrid.length
|
|
2762
|
+
].find(g => g && g.cellIndex === cellIndex);
|
|
2763
|
+
cellIdToCopy = cellId;
|
|
2764
|
+
} else {
|
|
2765
|
+
const lastIndexInGrid =
|
|
2766
|
+
selectionGrid[selectionGrid.length - 1][0].rowIndex;
|
|
2767
|
+
const { cellId } = selectionGrid[
|
|
2768
|
+
(rowIndex + lastIndexInGrid + 1) % selectionGrid.length
|
|
2769
|
+
].find(g => g.cellIndex === cellIndex);
|
|
2770
|
+
cellIdToCopy = cellId;
|
|
2771
|
+
}
|
|
2772
|
+
|
|
2773
|
+
const [rowIdToCopy, cellPathToCopy] = cellIdToCopy.split(":");
|
|
2774
|
+
newVal = getCellVal(
|
|
2775
|
+
entityMap[rowIdToCopy].e,
|
|
2776
|
+
cellPathToCopy,
|
|
2777
|
+
pathToField[cellPathToCopy]
|
|
2778
|
+
);
|
|
2779
|
+
} else {
|
|
2780
|
+
newVal = selectedCellVal;
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
const { error } = editCellHelper({
|
|
2784
|
+
entity: entityToUpdate,
|
|
2785
|
+
path: cellPath,
|
|
2786
|
+
schema,
|
|
2787
|
+
newVal
|
|
2788
|
+
});
|
|
2789
|
+
newCellValidate[cellId] = error;
|
|
2790
|
+
}
|
|
2791
|
+
});
|
|
2792
|
+
});
|
|
2793
|
+
|
|
2794
|
+
// select the new cells
|
|
2795
|
+
this.updateValidation(entities, newCellValidate);
|
|
2796
|
+
change("reduxFormSelectedCells", newReduxFormSelectedCells);
|
|
2797
|
+
});
|
|
2798
|
+
};
|
|
2799
|
+
getCopyTextForCell = (val, row = {}, column = {}) => {
|
|
2800
|
+
const { cellRenderer } = computePresets(this.props);
|
|
2801
|
+
// TODOCOPY we need a way to potentially omit certain columns from being added as a \t element (talk to taoh about this)
|
|
2802
|
+
let text = typeof val !== "string" ? row.value : val;
|
|
2803
|
+
|
|
2804
|
+
const record = row.original;
|
|
2805
|
+
if (column.getClipboardData) {
|
|
2806
|
+
text = column.getClipboardData(row.value, record, row, this.props);
|
|
2807
|
+
} else if (column.getValueToFilterOn) {
|
|
2808
|
+
text = column.getValueToFilterOn(record, this.props);
|
|
2809
|
+
} else if (column.render) {
|
|
2810
|
+
text = column.render(row.value, record, row, this.props);
|
|
2811
|
+
} else if (cellRenderer && cellRenderer[column.path]) {
|
|
2812
|
+
text = cellRenderer[column.path](
|
|
2813
|
+
row.value,
|
|
2814
|
+
row.original,
|
|
2815
|
+
row,
|
|
2816
|
+
this.props
|
|
2817
|
+
);
|
|
2818
|
+
} else if (text) {
|
|
2819
|
+
text = React.isValidElement(text) ? text : String(text);
|
|
2820
|
+
}
|
|
2821
|
+
const getTextFromElementOrLink = text => {
|
|
2822
|
+
if (React.isValidElement(text)) {
|
|
2823
|
+
if (text.props?.to) {
|
|
2824
|
+
// this will convert Link elements to url strings
|
|
2825
|
+
return joinUrl(
|
|
2826
|
+
window.location.origin,
|
|
2827
|
+
window.frontEndConfig?.clientBasePath || "",
|
|
2828
|
+
text.props.to
|
|
2829
|
+
);
|
|
2830
|
+
} else {
|
|
2831
|
+
return getTextFromEl(text);
|
|
2832
|
+
}
|
|
2833
|
+
} else {
|
|
2834
|
+
return text;
|
|
2835
|
+
}
|
|
2836
|
+
};
|
|
2837
|
+
text = getTextFromElementOrLink(text);
|
|
2838
|
+
|
|
2839
|
+
if (Array.isArray(text)) {
|
|
2840
|
+
let arrText = text.map(getTextFromElementOrLink).join(", ");
|
|
2841
|
+
// because we sometimes insert commas after links when mapping over an array of elements we will have double ,'s
|
|
2842
|
+
arrText = arrText.replace(/, ,/g, ",");
|
|
2843
|
+
text = arrText;
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
const stringText = toString(text);
|
|
2847
|
+
if (stringText === "[object Object]") return "";
|
|
2848
|
+
return stringText;
|
|
2849
|
+
};
|
|
2850
|
+
|
|
2851
|
+
insertRows = ({ above, numRows = 1, appendToBottom } = {}) => {
|
|
2852
|
+
const { entities = [], reduxFormCellValidation } = computePresets(
|
|
2853
|
+
this.props
|
|
2854
|
+
);
|
|
2855
|
+
|
|
2856
|
+
const primaryCellId = this.getPrimarySelectedCellId();
|
|
2857
|
+
const [rowId] = primaryCellId?.split(":") || [];
|
|
2858
|
+
this.updateEntitiesHelper(entities, entities => {
|
|
2859
|
+
const newEntities = times(numRows).map(() => ({ id: nanoid() }));
|
|
2860
|
+
|
|
2861
|
+
const indexToInsert = entities.findIndex((e, i) => {
|
|
2862
|
+
return getIdOrCodeOrIndex(e, i) === rowId;
|
|
2863
|
+
});
|
|
2864
|
+
const insertIndex = above ? indexToInsert : indexToInsert + 1;
|
|
2865
|
+
let { newEnts, validationErrors } = this.formatAndValidateEntities(
|
|
2866
|
+
newEntities
|
|
2867
|
+
);
|
|
2868
|
+
newEnts = newEnts.map(e => ({
|
|
2869
|
+
...e,
|
|
2870
|
+
_isClean: true
|
|
2871
|
+
}));
|
|
2872
|
+
this.updateValidation(entities, {
|
|
2873
|
+
...reduxFormCellValidation,
|
|
2874
|
+
...validationErrors
|
|
2875
|
+
});
|
|
2876
|
+
|
|
2877
|
+
entities.splice(
|
|
2878
|
+
appendToBottom ? entities.length : insertIndex,
|
|
2879
|
+
0,
|
|
2880
|
+
...newEnts
|
|
2881
|
+
);
|
|
2882
|
+
});
|
|
2883
|
+
this.refocusTable();
|
|
2884
|
+
};
|
|
2885
|
+
|
|
2886
|
+
showContextMenu = (e, idMap) => {
|
|
2887
|
+
const {
|
|
2888
|
+
history,
|
|
2889
|
+
contextMenu,
|
|
2890
|
+
isCopyable,
|
|
2891
|
+
isCellEditable,
|
|
2892
|
+
entities = [],
|
|
2893
|
+
reduxFormSelectedCells = {}
|
|
2894
|
+
} = computePresets(this.props);
|
|
2895
|
+
let selectedRecords;
|
|
2896
|
+
if (isCellEditable) {
|
|
2897
|
+
const rowIds = {};
|
|
2898
|
+
Object.keys(reduxFormSelectedCells).forEach(cellKey => {
|
|
2899
|
+
const [rowId] = cellKey.split(":");
|
|
2900
|
+
rowIds[rowId] = true;
|
|
2901
|
+
});
|
|
2902
|
+
selectedRecords = entities.filter(e => rowIds[getIdOrCodeOrIndex(e)]);
|
|
2903
|
+
} else {
|
|
2904
|
+
selectedRecords = getRecordsFromIdMap(idMap);
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2907
|
+
const itemsToRender = contextMenu({
|
|
2908
|
+
selectedRecords,
|
|
2909
|
+
history
|
|
2910
|
+
});
|
|
2911
|
+
if (!itemsToRender && !isCopyable) return null;
|
|
2912
|
+
const copyMenuItems = [];
|
|
2913
|
+
|
|
2914
|
+
e.persist();
|
|
2915
|
+
if (isCopyable) {
|
|
2916
|
+
//compute the cellWrapper here so we don't lose access to it
|
|
2917
|
+
const cellWrapper =
|
|
2918
|
+
e.target.querySelector(".tg-cell-wrapper") ||
|
|
2919
|
+
e.target.closest(".tg-cell-wrapper");
|
|
2920
|
+
if (cellWrapper) {
|
|
2921
|
+
copyMenuItems.push(
|
|
2922
|
+
<MenuItem
|
|
2923
|
+
key="copyCell"
|
|
2924
|
+
onClick={() => {
|
|
2925
|
+
//TODOCOPY: we need to make sure that the cell copy is being used by the row copy.. right now we have 2 different things going on
|
|
2926
|
+
//do we need to be able to copy hidden cells? It seems like it should just copy what's on the page..?
|
|
2927
|
+
const text = this.getCellCopyText(cellWrapper);
|
|
2928
|
+
this.handleCopyHelper(text, "Cell copied");
|
|
2929
|
+
}}
|
|
2930
|
+
text="Cell"
|
|
2931
|
+
/>
|
|
2932
|
+
);
|
|
2933
|
+
copyMenuItems.push(
|
|
2934
|
+
<MenuItem
|
|
2935
|
+
key="copyColumn"
|
|
2936
|
+
onClick={() => {
|
|
2937
|
+
this.handleCopyColumn(e, cellWrapper);
|
|
2938
|
+
}}
|
|
2939
|
+
text="Column"
|
|
2940
|
+
/>
|
|
2941
|
+
);
|
|
2942
|
+
}
|
|
2943
|
+
if (selectedRecords.length === 0 || selectedRecords.length === 1) {
|
|
2944
|
+
//compute the row here so we don't lose access to it
|
|
2945
|
+
const cell =
|
|
2946
|
+
e.target.querySelector(".tg-cell-wrapper") ||
|
|
2947
|
+
e.target.closest(".tg-cell-wrapper") ||
|
|
2948
|
+
e.target.closest(".rt-td");
|
|
2949
|
+
const row = cell.closest(".rt-tr");
|
|
2950
|
+
copyMenuItems.push(
|
|
2951
|
+
<MenuItem
|
|
2952
|
+
key="copySelectedRows"
|
|
2953
|
+
onClick={() => {
|
|
2954
|
+
this.handleCopyRow(row);
|
|
2955
|
+
// loop through each cell in the row
|
|
2956
|
+
}}
|
|
2957
|
+
text="Row"
|
|
2958
|
+
/>
|
|
2959
|
+
);
|
|
2960
|
+
} else if (selectedRecords.length > 1) {
|
|
2961
|
+
copyMenuItems.push(
|
|
2962
|
+
<MenuItem
|
|
2963
|
+
key="copySelectedRows"
|
|
2964
|
+
onClick={() => {
|
|
2965
|
+
this.handleCopySelectedRows(selectedRecords, e);
|
|
2966
|
+
// loop through each cell in the row
|
|
2967
|
+
}}
|
|
2968
|
+
text="Rows"
|
|
2969
|
+
/>
|
|
2970
|
+
);
|
|
2971
|
+
}
|
|
2972
|
+
copyMenuItems.push(
|
|
2973
|
+
<MenuItem
|
|
2974
|
+
key="copyFullTableRows"
|
|
2975
|
+
onClick={() => {
|
|
2976
|
+
this.handleCopyTable(e);
|
|
2977
|
+
// loop through each cell in the row
|
|
2978
|
+
}}
|
|
2979
|
+
text="Table"
|
|
2980
|
+
/>
|
|
2981
|
+
);
|
|
2982
|
+
}
|
|
2983
|
+
const selectedRowIds = Object.keys(reduxFormSelectedCells).map(cellId => {
|
|
2984
|
+
const [rowId] = cellId.split(":");
|
|
2985
|
+
return rowId;
|
|
2986
|
+
});
|
|
2987
|
+
|
|
2988
|
+
const menu = (
|
|
2989
|
+
<Menu>
|
|
2990
|
+
{itemsToRender}
|
|
2991
|
+
{copyMenuItems.length && (
|
|
2992
|
+
<MenuItem icon="clipboard" key="copyOpts" text="Copy">
|
|
2993
|
+
{copyMenuItems}
|
|
2994
|
+
</MenuItem>
|
|
2995
|
+
)}
|
|
2996
|
+
{isCellEditable && (
|
|
2997
|
+
<>
|
|
2998
|
+
<MenuItem
|
|
2999
|
+
icon="add-row-top"
|
|
3000
|
+
text="Add Row Above"
|
|
3001
|
+
key="addRowAbove"
|
|
3002
|
+
onClick={() => {
|
|
3003
|
+
this.insertRows({ above: true });
|
|
3004
|
+
}}
|
|
3005
|
+
></MenuItem>
|
|
3006
|
+
<MenuItem
|
|
3007
|
+
icon="add-row-top"
|
|
3008
|
+
text="Add Row Below"
|
|
3009
|
+
key="addRowBelow"
|
|
3010
|
+
onClick={() => {
|
|
3011
|
+
this.insertRows({});
|
|
3012
|
+
}}
|
|
3013
|
+
></MenuItem>
|
|
3014
|
+
<MenuItem
|
|
3015
|
+
icon="remove"
|
|
3016
|
+
text={`Remove Row${selectedRowIds.length > 1 ? "s" : ""}`}
|
|
3017
|
+
key="removeRow"
|
|
3018
|
+
onClick={() => {
|
|
3019
|
+
const {
|
|
3020
|
+
entities = [],
|
|
3021
|
+
reduxFormCellValidation,
|
|
3022
|
+
reduxFormSelectedCells = {}
|
|
3023
|
+
} = computePresets(this.props);
|
|
3024
|
+
const selectedRowIds = Object.keys(reduxFormSelectedCells).map(
|
|
3025
|
+
cellId => {
|
|
3026
|
+
const [rowId] = cellId.split(":");
|
|
3027
|
+
return rowId;
|
|
3028
|
+
}
|
|
3029
|
+
);
|
|
3030
|
+
this.updateEntitiesHelper(entities, entities => {
|
|
3031
|
+
const ents = entities.filter(
|
|
3032
|
+
(e, i) => !selectedRowIds.includes(getIdOrCodeOrIndex(e, i))
|
|
3033
|
+
);
|
|
3034
|
+
this.updateValidation(
|
|
3035
|
+
ents,
|
|
3036
|
+
omitBy(reduxFormCellValidation, (v, cellId) =>
|
|
3037
|
+
selectedRowIds.includes(cellId.split(":")[0])
|
|
3038
|
+
)
|
|
3039
|
+
);
|
|
3040
|
+
return ents;
|
|
3041
|
+
});
|
|
3042
|
+
this.refocusTable();
|
|
3043
|
+
}}
|
|
3044
|
+
></MenuItem>
|
|
3045
|
+
</>
|
|
3046
|
+
)}
|
|
3047
|
+
</Menu>
|
|
3048
|
+
);
|
|
3049
|
+
ContextMenu.show(menu, { left: e.clientX, top: e.clientY });
|
|
3050
|
+
};
|
|
3051
|
+
|
|
3052
|
+
renderColumnHeader = column => {
|
|
3053
|
+
const {
|
|
3054
|
+
addFilters,
|
|
3055
|
+
setOrder,
|
|
3056
|
+
order,
|
|
3057
|
+
withFilter,
|
|
3058
|
+
withSort,
|
|
3059
|
+
filters,
|
|
3060
|
+
removeSingleFilter,
|
|
3061
|
+
currentParams,
|
|
3062
|
+
isLocalCall,
|
|
3063
|
+
setNewParams,
|
|
3064
|
+
compact,
|
|
3065
|
+
isCellEditable,
|
|
3066
|
+
extraCompact,
|
|
3067
|
+
entities
|
|
3068
|
+
} = computePresets(this.props);
|
|
3069
|
+
const {
|
|
3070
|
+
displayName,
|
|
3071
|
+
description,
|
|
3072
|
+
isUnique,
|
|
3073
|
+
sortDisabled,
|
|
3074
|
+
filterDisabled,
|
|
3075
|
+
columnFilterDisabled,
|
|
3076
|
+
renderTitleInner,
|
|
3077
|
+
filterIsActive = noop,
|
|
3078
|
+
noTitle,
|
|
3079
|
+
isNotEditable,
|
|
3080
|
+
type,
|
|
3081
|
+
path
|
|
3082
|
+
} = column;
|
|
3083
|
+
const columnDataType = column.type;
|
|
3084
|
+
const isActionColumn = columnDataType === "action";
|
|
3085
|
+
const disableSorting =
|
|
3086
|
+
sortDisabled ||
|
|
3087
|
+
isActionColumn ||
|
|
3088
|
+
(!isLocalCall && typeof path === "string" && path.includes(".")) ||
|
|
3089
|
+
columnDataType === "color";
|
|
3090
|
+
const disableFiltering =
|
|
3091
|
+
filterDisabled ||
|
|
3092
|
+
columnDataType === "color" ||
|
|
3093
|
+
isActionColumn ||
|
|
3094
|
+
columnFilterDisabled;
|
|
3095
|
+
const ccDisplayName = camelCase(displayName || path);
|
|
3096
|
+
let columnTitle = displayName || startCase(camelCase(path));
|
|
3097
|
+
if (isActionColumn) columnTitle = "";
|
|
3098
|
+
|
|
3099
|
+
const currentFilter =
|
|
3100
|
+
filters &&
|
|
3101
|
+
!!filters.length &&
|
|
3102
|
+
filters.filter(({ filterOn }) => {
|
|
3103
|
+
return filterOn === ccDisplayName;
|
|
3104
|
+
})[0];
|
|
3105
|
+
const filterActiveForColumn =
|
|
3106
|
+
!!currentFilter || filterIsActive(currentParams);
|
|
3107
|
+
let ordering;
|
|
3108
|
+
if (order && order.length) {
|
|
3109
|
+
order.forEach(order => {
|
|
3110
|
+
const orderField = order.replace("-", "");
|
|
3111
|
+
if (orderField === ccDisplayName) {
|
|
3112
|
+
if (orderField === order) {
|
|
3113
|
+
ordering = "asc";
|
|
3114
|
+
} else {
|
|
3115
|
+
ordering = "desc";
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
});
|
|
3119
|
+
}
|
|
3120
|
+
|
|
3121
|
+
const sortDown = ordering && ordering === "asc";
|
|
3122
|
+
const sortUp = ordering && !sortDown;
|
|
3123
|
+
const sortComponent =
|
|
3124
|
+
withSort && !disableSorting ? (
|
|
3125
|
+
<div className="tg-sort-arrow-container">
|
|
3126
|
+
<Icon
|
|
3127
|
+
data-tip="Sort Z-A (Hold shift to sort multiple columns)"
|
|
3128
|
+
icon="chevron-up"
|
|
3129
|
+
className={classNames({
|
|
3130
|
+
active: sortUp
|
|
3131
|
+
})}
|
|
3132
|
+
color={sortUp ? "#106ba3" : undefined}
|
|
3133
|
+
iconSize={extraCompact ? 10 : 12}
|
|
3134
|
+
onClick={e => {
|
|
3135
|
+
setOrder("-" + ccDisplayName, sortUp, e.shiftKey);
|
|
3136
|
+
}}
|
|
3137
|
+
/>
|
|
3138
|
+
<Icon
|
|
3139
|
+
data-tip="Sort A-Z (Hold shift to sort multiple columns)"
|
|
3140
|
+
icon="chevron-down"
|
|
3141
|
+
className={classNames({
|
|
3142
|
+
active: sortDown
|
|
3143
|
+
})}
|
|
3144
|
+
color={sortDown ? "#106ba3" : undefined}
|
|
3145
|
+
iconSize={extraCompact ? 10 : 12}
|
|
3146
|
+
onClick={e => {
|
|
3147
|
+
setOrder(ccDisplayName, sortDown, e.shiftKey);
|
|
3148
|
+
}}
|
|
3149
|
+
/>
|
|
3150
|
+
</div>
|
|
3151
|
+
) : null;
|
|
3152
|
+
const FilterMenu = column.FilterMenu || FilterAndSortMenu;
|
|
3153
|
+
|
|
3154
|
+
const filterMenu =
|
|
3155
|
+
withFilter && !disableFiltering ? (
|
|
3156
|
+
<ColumnFilterMenu
|
|
3157
|
+
FilterMenu={FilterMenu}
|
|
3158
|
+
filterActiveForColumn={filterActiveForColumn}
|
|
3159
|
+
addFilters={addFilters}
|
|
3160
|
+
removeSingleFilter={removeSingleFilter}
|
|
3161
|
+
currentFilter={currentFilter}
|
|
3162
|
+
filterOn={ccDisplayName}
|
|
3163
|
+
dataType={columnDataType}
|
|
3164
|
+
schemaForField={column}
|
|
3165
|
+
currentParams={currentParams}
|
|
3166
|
+
setNewParams={setNewParams}
|
|
3167
|
+
compact={compact}
|
|
3168
|
+
extraCompact={extraCompact}
|
|
3169
|
+
/>
|
|
3170
|
+
) : null;
|
|
3171
|
+
let maybeCheckbox;
|
|
3172
|
+
if (isCellEditable && !isNotEditable && type === "boolean") {
|
|
3173
|
+
let isIndeterminate = false;
|
|
3174
|
+
let isChecked = !!entities.length;
|
|
3175
|
+
let hasFalse;
|
|
3176
|
+
let hasTrue;
|
|
3177
|
+
entities.some(e => {
|
|
3178
|
+
if (!get(e, path)) {
|
|
3179
|
+
isChecked = false;
|
|
3180
|
+
hasFalse = true;
|
|
3181
|
+
} else {
|
|
3182
|
+
hasTrue = true;
|
|
3183
|
+
}
|
|
3184
|
+
if (hasFalse && hasTrue) {
|
|
3185
|
+
isIndeterminate = true;
|
|
3186
|
+
return true;
|
|
3187
|
+
}
|
|
3188
|
+
return false;
|
|
3189
|
+
});
|
|
3190
|
+
maybeCheckbox = (
|
|
3191
|
+
<Checkbox
|
|
3192
|
+
style={{ marginBottom: 0, marginLeft: 3 }}
|
|
3193
|
+
onChange={() => {
|
|
3194
|
+
this.updateEntitiesHelper(entities, ents => {
|
|
3195
|
+
ents.forEach(e => {
|
|
3196
|
+
delete e._isClean;
|
|
3197
|
+
set(e, path, isIndeterminate ? true : !isChecked);
|
|
3198
|
+
});
|
|
3199
|
+
});
|
|
3200
|
+
}}
|
|
3201
|
+
indeterminate={isIndeterminate}
|
|
3202
|
+
checked={isChecked}
|
|
3203
|
+
></Checkbox>
|
|
3204
|
+
);
|
|
3205
|
+
}
|
|
3206
|
+
|
|
3207
|
+
const columnTitleTextified = getTextFromEl(columnTitle);
|
|
3208
|
+
|
|
3209
|
+
return (
|
|
3210
|
+
<div
|
|
3211
|
+
{...(description && {
|
|
3212
|
+
"data-tip": `<div>
|
|
3213
|
+
<strong>${columnTitle}:</strong> <br>
|
|
3214
|
+
${description} ${isUnique ? "<br>Must be unique" : ""}</div>`
|
|
3215
|
+
})}
|
|
3216
|
+
data-test={columnTitleTextified}
|
|
3217
|
+
data-copy-text={columnTitleTextified}
|
|
3218
|
+
className={classNames("tg-react-table-column-header", {
|
|
3219
|
+
"sort-active": sortUp || sortDown
|
|
3220
|
+
})}
|
|
3221
|
+
>
|
|
3222
|
+
{columnTitleTextified && !noTitle && (
|
|
3223
|
+
<React.Fragment>
|
|
3224
|
+
{maybeCheckbox}
|
|
3225
|
+
<span
|
|
3226
|
+
title={columnTitleTextified}
|
|
3227
|
+
className={classNames({
|
|
3228
|
+
"tg-react-table-name": true,
|
|
3229
|
+
"no-data-tip": !!description
|
|
3230
|
+
})}
|
|
3231
|
+
style={{
|
|
3232
|
+
...(description && { fontStyle: "italic" }),
|
|
3233
|
+
display: "inline-block"
|
|
3234
|
+
}}
|
|
3235
|
+
>
|
|
3236
|
+
{renderTitleInner ? renderTitleInner : columnTitle}{" "}
|
|
3237
|
+
</span>
|
|
3238
|
+
</React.Fragment>
|
|
3239
|
+
)}
|
|
3240
|
+
<div
|
|
3241
|
+
style={{ display: "flex", marginLeft: "auto", alignItems: "center" }}
|
|
3242
|
+
>
|
|
3243
|
+
{sortComponent}
|
|
3244
|
+
{filterMenu}
|
|
3245
|
+
</div>
|
|
3246
|
+
</div>
|
|
3247
|
+
);
|
|
3248
|
+
};
|
|
3249
|
+
}
|
|
3250
|
+
|
|
3251
|
+
// const CompToExport = dataTableEnhancer(HotkeysTarget(DataTable));
|
|
3252
|
+
// // CompToExport.selectRecords = (form, value) => {
|
|
3253
|
+
// // return change(form, "reduxFormSelectedEntityIdMap", value)
|
|
3254
|
+
// // }
|
|
3255
|
+
// export default CompToExport
|
|
3256
|
+
const WrappedDT = dataTableEnhancer(DataTable);
|
|
3257
|
+
export default WrappedDT;
|
|
3258
|
+
const ConnectedPagingTool = dataTableEnhancer(PagingTool);
|
|
3259
|
+
export { ConnectedPagingTool };
|
|
3260
|
+
|
|
3261
|
+
const itemSizeEstimators = {
|
|
3262
|
+
compact: () => 25.34,
|
|
3263
|
+
normal: () => 33.34,
|
|
3264
|
+
comfortable: () => 41.34
|
|
3265
|
+
};
|
|
3266
|
+
|
|
3267
|
+
function getCellInfo({
|
|
3268
|
+
columnIndex,
|
|
3269
|
+
columnPath,
|
|
3270
|
+
rowId,
|
|
3271
|
+
schema,
|
|
3272
|
+
entities,
|
|
3273
|
+
rowIndex,
|
|
3274
|
+
isEntityDisabled,
|
|
3275
|
+
entity
|
|
3276
|
+
}) {
|
|
3277
|
+
const leftpath = schema.fields[columnIndex - 1]?.path;
|
|
3278
|
+
const rightpath = schema.fields[columnIndex + 1]?.path;
|
|
3279
|
+
const cellIdToLeft = leftpath && `${rowId}:${leftpath}`;
|
|
3280
|
+
const cellIdToRight = rightpath && `${rowId}:${rightpath}`;
|
|
3281
|
+
const rowAboveId =
|
|
3282
|
+
entities[rowIndex - 1] &&
|
|
3283
|
+
getIdOrCodeOrIndex(entities[rowIndex - 1], rowIndex - 1);
|
|
3284
|
+
const rowBelowId =
|
|
3285
|
+
entities[rowIndex + 1] &&
|
|
3286
|
+
getIdOrCodeOrIndex(entities[rowIndex + 1], rowIndex + 1);
|
|
3287
|
+
const cellIdAbove = rowAboveId && `${rowAboveId}:${columnPath}`;
|
|
3288
|
+
const cellIdBelow = rowBelowId && `${rowBelowId}:${columnPath}`;
|
|
3289
|
+
|
|
3290
|
+
const cellId = `${rowId}:${columnPath}`;
|
|
3291
|
+
const rowDisabled = isEntityDisabled(entity);
|
|
3292
|
+
return {
|
|
3293
|
+
cellId,
|
|
3294
|
+
cellIdAbove,
|
|
3295
|
+
cellIdToRight,
|
|
3296
|
+
cellIdBelow,
|
|
3297
|
+
cellIdToLeft,
|
|
3298
|
+
rowDisabled
|
|
3299
|
+
};
|
|
3300
|
+
}
|
|
3301
|
+
|
|
3302
|
+
function ColumnFilterMenu({
|
|
3303
|
+
FilterMenu,
|
|
3304
|
+
filterActiveForColumn,
|
|
3305
|
+
compact,
|
|
3306
|
+
extraCompact,
|
|
3307
|
+
...rest
|
|
3308
|
+
}) {
|
|
3309
|
+
const [columnFilterMenuOpen, setColumnFilterMenuOpen] = useState(false);
|
|
3310
|
+
return (
|
|
3311
|
+
<Popover
|
|
3312
|
+
position="bottom"
|
|
3313
|
+
onClose={() => {
|
|
3314
|
+
setColumnFilterMenuOpen(false);
|
|
3315
|
+
}}
|
|
3316
|
+
isOpen={columnFilterMenuOpen}
|
|
3317
|
+
modifiers={{
|
|
3318
|
+
preventOverflow: { enabled: true },
|
|
3319
|
+
hide: { enabled: false },
|
|
3320
|
+
flip: { enabled: false }
|
|
3321
|
+
}}
|
|
3322
|
+
>
|
|
3323
|
+
<Icon
|
|
3324
|
+
style={{ marginLeft: 5 }}
|
|
3325
|
+
icon="filter"
|
|
3326
|
+
iconSize={extraCompact ? 14 : undefined}
|
|
3327
|
+
onClick={() => {
|
|
3328
|
+
setColumnFilterMenuOpen(!columnFilterMenuOpen);
|
|
3329
|
+
}}
|
|
3330
|
+
className={classNames("tg-filter-menu-button", {
|
|
3331
|
+
"tg-active-filter": !!filterActiveForColumn
|
|
3332
|
+
})}
|
|
3333
|
+
/>
|
|
3334
|
+
<FilterMenu
|
|
3335
|
+
togglePopover={() => {
|
|
3336
|
+
setColumnFilterMenuOpen(false);
|
|
3337
|
+
}}
|
|
3338
|
+
{...rest}
|
|
3339
|
+
/>
|
|
3340
|
+
</Popover>
|
|
3341
|
+
);
|
|
3342
|
+
}
|
|
3343
|
+
|
|
3344
|
+
function getLastSelectedEntity(idMap) {
|
|
3345
|
+
let lastSelectedEnt;
|
|
3346
|
+
let latestTime;
|
|
3347
|
+
forEach(idMap, ({ time, entity }) => {
|
|
3348
|
+
if (!latestTime || time > latestTime) {
|
|
3349
|
+
lastSelectedEnt = entity;
|
|
3350
|
+
latestTime = time;
|
|
3351
|
+
}
|
|
3352
|
+
});
|
|
3353
|
+
return lastSelectedEnt;
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
function getNewEntToSelect({
|
|
3357
|
+
type,
|
|
3358
|
+
lastSelectedIndex,
|
|
3359
|
+
entities,
|
|
3360
|
+
isEntityDisabled
|
|
3361
|
+
}) {
|
|
3362
|
+
let newIndexToSelect;
|
|
3363
|
+
if (type === "up") {
|
|
3364
|
+
newIndexToSelect = lastSelectedIndex - 1;
|
|
3365
|
+
} else {
|
|
3366
|
+
newIndexToSelect = lastSelectedIndex + 1;
|
|
3367
|
+
}
|
|
3368
|
+
const newEntToSelect = entities[newIndexToSelect];
|
|
3369
|
+
if (!newEntToSelect) return;
|
|
3370
|
+
if (isEntityDisabled && isEntityDisabled(newEntToSelect)) {
|
|
3371
|
+
return getNewEntToSelect({
|
|
3372
|
+
type,
|
|
3373
|
+
lastSelectedIndex: newIndexToSelect,
|
|
3374
|
+
entities,
|
|
3375
|
+
isEntityDisabled
|
|
3376
|
+
});
|
|
3377
|
+
} else {
|
|
3378
|
+
return newEntToSelect;
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
function getAllRows(e) {
|
|
3383
|
+
const el = e.target.querySelector(".data-table-container")
|
|
3384
|
+
? e.target.querySelector(".data-table-container")
|
|
3385
|
+
: e.target.closest(".data-table-container");
|
|
3386
|
+
|
|
3387
|
+
const allRowEls = el.querySelectorAll(".rt-tr");
|
|
3388
|
+
if (!allRowEls || !allRowEls.length) {
|
|
3389
|
+
return;
|
|
3390
|
+
}
|
|
3391
|
+
return allRowEls;
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3394
|
+
function EditableCell({
|
|
3395
|
+
shouldSelectAll,
|
|
3396
|
+
stopSelectAll,
|
|
3397
|
+
initialValue,
|
|
3398
|
+
finishEdit,
|
|
3399
|
+
cancelEdit,
|
|
3400
|
+
isNumeric
|
|
3401
|
+
}) {
|
|
3402
|
+
const [v, setV] = useState(initialValue);
|
|
3403
|
+
return (
|
|
3404
|
+
<input
|
|
3405
|
+
style={{
|
|
3406
|
+
border: 0,
|
|
3407
|
+
width: "95%",
|
|
3408
|
+
fontSize: 12,
|
|
3409
|
+
background: "none"
|
|
3410
|
+
}}
|
|
3411
|
+
ref={r => {
|
|
3412
|
+
if (shouldSelectAll && r) {
|
|
3413
|
+
r?.select();
|
|
3414
|
+
stopSelectAll();
|
|
3415
|
+
}
|
|
3416
|
+
}}
|
|
3417
|
+
type={isNumeric ? "number" : undefined}
|
|
3418
|
+
value={v}
|
|
3419
|
+
autoFocus
|
|
3420
|
+
onKeyDown={e => {
|
|
3421
|
+
if (e.key === "Enter") {
|
|
3422
|
+
finishEdit(v);
|
|
3423
|
+
e.stopPropagation();
|
|
3424
|
+
} else if (e.key === "Escape") {
|
|
3425
|
+
e.stopPropagation();
|
|
3426
|
+
cancelEdit();
|
|
3427
|
+
}
|
|
3428
|
+
}}
|
|
3429
|
+
onBlur={() => {
|
|
3430
|
+
finishEdit(v);
|
|
3431
|
+
}}
|
|
3432
|
+
onChange={e => {
|
|
3433
|
+
setV(e.target.value);
|
|
3434
|
+
}}
|
|
3435
|
+
></input>
|
|
3436
|
+
);
|
|
3437
|
+
}
|
|
3438
|
+
|
|
3439
|
+
function DropdownCell({
|
|
3440
|
+
options,
|
|
3441
|
+
isMulti,
|
|
3442
|
+
initialValue,
|
|
3443
|
+
finishEdit,
|
|
3444
|
+
cancelEdit
|
|
3445
|
+
}) {
|
|
3446
|
+
const [v, setV] = useState(
|
|
3447
|
+
isMulti
|
|
3448
|
+
? initialValue.split(",").map(v => ({ value: v, label: v }))
|
|
3449
|
+
: initialValue
|
|
3450
|
+
);
|
|
3451
|
+
return (
|
|
3452
|
+
<div
|
|
3453
|
+
className={classNames("tg-dropdown-cell-edit-container", {
|
|
3454
|
+
"tg-dropdown-cell-edit-container-multi": isMulti
|
|
3455
|
+
})}
|
|
3456
|
+
>
|
|
3457
|
+
<TgSelect
|
|
3458
|
+
small
|
|
3459
|
+
multi={isMulti}
|
|
3460
|
+
autoOpen
|
|
3461
|
+
value={v}
|
|
3462
|
+
onChange={val => {
|
|
3463
|
+
if (isMulti) {
|
|
3464
|
+
setV(val);
|
|
3465
|
+
return;
|
|
3466
|
+
}
|
|
3467
|
+
finishEdit(val ? val.value : null);
|
|
3468
|
+
}}
|
|
3469
|
+
popoverProps={{
|
|
3470
|
+
onClose: e => {
|
|
3471
|
+
if (isMulti) {
|
|
3472
|
+
if (e && e.key === "Escape") {
|
|
3473
|
+
cancelEdit();
|
|
3474
|
+
} else {
|
|
3475
|
+
finishEdit(
|
|
3476
|
+
v && v.map
|
|
3477
|
+
? v
|
|
3478
|
+
.map(v => v.value)
|
|
3479
|
+
.filter(v => v)
|
|
3480
|
+
.join(",")
|
|
3481
|
+
: v
|
|
3482
|
+
);
|
|
3483
|
+
}
|
|
3484
|
+
} else {
|
|
3485
|
+
cancelEdit();
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
}}
|
|
3489
|
+
options={options.map(value => ({ label: value, value }))}
|
|
3490
|
+
></TgSelect>
|
|
3491
|
+
</div>
|
|
3492
|
+
);
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
function getFieldPathToIndex(schema) {
|
|
3496
|
+
const fieldToIndex = {};
|
|
3497
|
+
schema.fields.forEach((f, i) => {
|
|
3498
|
+
fieldToIndex[f.path] = i;
|
|
3499
|
+
});
|
|
3500
|
+
return fieldToIndex;
|
|
3501
|
+
}
|
|
3502
|
+
|
|
3503
|
+
function getFieldPathToField(schema) {
|
|
3504
|
+
const fieldPathToField = {};
|
|
3505
|
+
schema.fields.forEach(f => {
|
|
3506
|
+
fieldPathToField[f.path] = f;
|
|
3507
|
+
});
|
|
3508
|
+
return fieldPathToField;
|
|
3509
|
+
}
|
|
3510
|
+
|
|
3511
|
+
const defaultParsePaste = str => {
|
|
3512
|
+
return str.split(/\r\n|\n|\r/).map(row => row.split("\t"));
|
|
3513
|
+
};
|
|
3514
|
+
|
|
3515
|
+
function getEntityIdToEntity(entities) {
|
|
3516
|
+
const entityIdToEntity = {};
|
|
3517
|
+
entities.forEach((e, i) => {
|
|
3518
|
+
entityIdToEntity[getIdOrCodeOrIndex(e, i)] = { e, i };
|
|
3519
|
+
});
|
|
3520
|
+
return entityIdToEntity;
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3523
|
+
function endsWithNumber(str) {
|
|
3524
|
+
return /[0-9]+$/.test(str);
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
function getNumberStrAtEnd(str) {
|
|
3528
|
+
if (endsWithNumber(str)) {
|
|
3529
|
+
return str.match(/[0-9]+$/)[0];
|
|
3530
|
+
}
|
|
3531
|
+
|
|
3532
|
+
return null;
|
|
3533
|
+
}
|
|
3534
|
+
|
|
3535
|
+
function stripNumberAtEnd(str) {
|
|
3536
|
+
return str.replace(getNumberStrAtEnd(str), "");
|
|
3537
|
+
}
|