@teselagen/ui 0.8.6-beta.23 → 0.8.6-beta.24
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/DataTable/EditabelCell.d.ts +7 -0
- package/DataTable/defaultProps.d.ts +43 -0
- package/DataTable/utils/computePresets.d.ts +1 -0
- package/DataTable/utils/convertSchema.d.ts +22 -2
- package/DataTable/utils/getAllRows.d.ts +1 -1
- package/DataTable/utils/handleCopyColumn.d.ts +1 -1
- package/DataTable/utils/handleCopyTable.d.ts +1 -1
- package/DataTable/utils/isBottomRightCornerOfRectangle.d.ts +10 -8
- package/DataTable/utils/isEntityClean.d.ts +3 -1
- package/DataTable/utils/primarySelectedValue.d.ts +1 -1
- package/DataTable/utils/removeCleanRows.d.ts +11 -3
- package/DataTable/utils/selection.d.ts +3 -1
- package/DataTable/utils/useDeepEqualMemo.d.ts +1 -0
- package/DataTable/utils/useTableEntities.d.ts +17 -4
- package/DataTable/utils/useTableParams.d.ts +49 -0
- package/index.cjs.js +124 -108
- package/index.es.js +124 -108
- package/package.json +2 -2
- package/src/DataTable/Columns.jsx +945 -0
- package/src/DataTable/EditabelCell.js +44 -0
- package/src/DataTable/EditabelCell.jsx +44 -0
- package/src/DataTable/RenderCell.jsx +191 -0
- package/src/DataTable/defaultProps.js +45 -0
- package/src/DataTable/index.js +96 -68
- package/src/DataTable/utils/computePresets.js +42 -0
- package/src/DataTable/utils/convertSchema.ts +79 -0
- package/src/DataTable/utils/getAllRows.js +2 -6
- package/src/DataTable/utils/handleCopyColumn.js +2 -2
- package/src/DataTable/utils/handleCopyTable.js +2 -2
- package/src/DataTable/utils/isBottomRightCornerOfRectangle.ts +27 -0
- package/src/DataTable/utils/isEntityClean.ts +15 -0
- package/src/DataTable/utils/primarySelectedValue.ts +1 -0
- package/src/DataTable/utils/removeCleanRows.ts +25 -0
- package/src/DataTable/utils/selection.ts +11 -0
- package/src/DataTable/utils/useDeepEqualMemo.js +10 -0
- package/src/DataTable/utils/useTableEntities.ts +60 -0
- package/src/DataTable/utils/useTableParams.js +361 -0
- package/style.css +10537 -0
|
@@ -0,0 +1,945 @@
|
|
|
1
|
+
import React, { isValidElement, useCallback } from "react";
|
|
2
|
+
import classNames from "classnames";
|
|
3
|
+
import { Button, Classes, Checkbox, Icon } from "@blueprintjs/core";
|
|
4
|
+
import {
|
|
5
|
+
set,
|
|
6
|
+
toString,
|
|
7
|
+
camelCase,
|
|
8
|
+
startCase,
|
|
9
|
+
noop,
|
|
10
|
+
cloneDeep,
|
|
11
|
+
get,
|
|
12
|
+
padStart
|
|
13
|
+
} from "lodash-es";
|
|
14
|
+
import dayjs from "dayjs";
|
|
15
|
+
import localizedFormat from "dayjs/plugin/localizedFormat";
|
|
16
|
+
import ReactMarkdown from "react-markdown";
|
|
17
|
+
import remarkGfm from "remark-gfm";
|
|
18
|
+
import joinUrl from "url-join";
|
|
19
|
+
import InfoHelper from "../InfoHelper";
|
|
20
|
+
import {
|
|
21
|
+
getEntityIdToEntity,
|
|
22
|
+
getFieldPathToIndex,
|
|
23
|
+
getFieldPathToField,
|
|
24
|
+
getIdOrCodeOrIndex,
|
|
25
|
+
getNumberStrAtEnd,
|
|
26
|
+
getSelectedRowsFromEntities,
|
|
27
|
+
PRIMARY_SELECTED_VAL,
|
|
28
|
+
stripNumberAtEnd
|
|
29
|
+
} from "./utils";
|
|
30
|
+
import FilterAndSortMenu from "./FilterAndSortMenu";
|
|
31
|
+
import { ColumnFilterMenu } from "./ColumnFilterMenu";
|
|
32
|
+
import getTextFromEl from "../utils/getTextFromEl";
|
|
33
|
+
import rowClick, { finalizeSelection } from "./utils/rowClick";
|
|
34
|
+
import { editCellHelper } from "./editCellHelper";
|
|
35
|
+
import { getCellVal } from "./getCellVal";
|
|
36
|
+
import { getCCDisplayName } from "./utils/queryParams";
|
|
37
|
+
import { useDispatch } from "react-redux";
|
|
38
|
+
import { change as _change } from "redux-form";
|
|
39
|
+
import { RenderCell } from "./RenderCell";
|
|
40
|
+
|
|
41
|
+
dayjs.extend(localizedFormat);
|
|
42
|
+
|
|
43
|
+
const RenderColumnHeader = ({
|
|
44
|
+
addFilters,
|
|
45
|
+
column,
|
|
46
|
+
compact,
|
|
47
|
+
currentParams,
|
|
48
|
+
entities,
|
|
49
|
+
extraCompact,
|
|
50
|
+
filters,
|
|
51
|
+
isCellEditable,
|
|
52
|
+
isLocalCall,
|
|
53
|
+
order,
|
|
54
|
+
removeSingleFilter,
|
|
55
|
+
setNewParams,
|
|
56
|
+
setOrder,
|
|
57
|
+
updateEntitiesHelper,
|
|
58
|
+
withFilter,
|
|
59
|
+
withSort
|
|
60
|
+
}) => {
|
|
61
|
+
const {
|
|
62
|
+
displayName,
|
|
63
|
+
description,
|
|
64
|
+
isUnique,
|
|
65
|
+
sortDisabled,
|
|
66
|
+
filterDisabled,
|
|
67
|
+
columnFilterDisabled,
|
|
68
|
+
renderTitleInner,
|
|
69
|
+
filterIsActive = noop,
|
|
70
|
+
noTitle,
|
|
71
|
+
isNotEditable,
|
|
72
|
+
type,
|
|
73
|
+
path
|
|
74
|
+
} = column;
|
|
75
|
+
const columnDataType = column.type;
|
|
76
|
+
const isActionColumn = columnDataType === "action";
|
|
77
|
+
const disableSorting =
|
|
78
|
+
sortDisabled ||
|
|
79
|
+
isActionColumn ||
|
|
80
|
+
(!isLocalCall && typeof path === "string" && path.includes(".")) ||
|
|
81
|
+
columnDataType === "color";
|
|
82
|
+
const disableFiltering =
|
|
83
|
+
filterDisabled ||
|
|
84
|
+
columnDataType === "color" ||
|
|
85
|
+
isActionColumn ||
|
|
86
|
+
columnFilterDisabled;
|
|
87
|
+
const ccDisplayName = getCCDisplayName(column);
|
|
88
|
+
let columnTitle = displayName || startCase(camelCase(path));
|
|
89
|
+
if (isActionColumn) columnTitle = "";
|
|
90
|
+
|
|
91
|
+
const currentFilter =
|
|
92
|
+
filters &&
|
|
93
|
+
!!filters.length &&
|
|
94
|
+
filters.filter(({ filterOn }) => {
|
|
95
|
+
return filterOn === ccDisplayName;
|
|
96
|
+
})[0];
|
|
97
|
+
const filterActiveForColumn =
|
|
98
|
+
!!currentFilter || filterIsActive(currentParams);
|
|
99
|
+
let ordering;
|
|
100
|
+
if (order && order.length) {
|
|
101
|
+
order.forEach(order => {
|
|
102
|
+
const orderField = order.replace("-", "");
|
|
103
|
+
if (orderField === ccDisplayName) {
|
|
104
|
+
if (orderField === order) {
|
|
105
|
+
ordering = "asc";
|
|
106
|
+
} else {
|
|
107
|
+
ordering = "desc";
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const sortDown = ordering && ordering === "asc";
|
|
114
|
+
const sortUp = ordering && !sortDown;
|
|
115
|
+
const FilterMenu = column.FilterMenu || FilterAndSortMenu;
|
|
116
|
+
|
|
117
|
+
let maybeCheckbox;
|
|
118
|
+
if (isCellEditable && !isNotEditable && type === "boolean") {
|
|
119
|
+
let isIndeterminate = false;
|
|
120
|
+
let isChecked = !!entities.length;
|
|
121
|
+
let hasFalse;
|
|
122
|
+
let hasTrue;
|
|
123
|
+
entities.some(e => {
|
|
124
|
+
if (!get(e, path)) {
|
|
125
|
+
isChecked = false;
|
|
126
|
+
hasFalse = true;
|
|
127
|
+
} else {
|
|
128
|
+
hasTrue = true;
|
|
129
|
+
}
|
|
130
|
+
if (hasFalse && hasTrue) {
|
|
131
|
+
isIndeterminate = true;
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
});
|
|
136
|
+
maybeCheckbox = (
|
|
137
|
+
<Checkbox
|
|
138
|
+
style={{ marginBottom: 0, marginLeft: 3 }}
|
|
139
|
+
onChange={() => {
|
|
140
|
+
updateEntitiesHelper(entities, ents => {
|
|
141
|
+
ents.forEach(e => {
|
|
142
|
+
delete e._isClean;
|
|
143
|
+
set(e, path, isIndeterminate ? true : !isChecked);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}}
|
|
147
|
+
indeterminate={isIndeterminate}
|
|
148
|
+
checked={isChecked}
|
|
149
|
+
/>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const columnTitleTextified = getTextFromEl(columnTitle);
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<div
|
|
157
|
+
{...(description && {
|
|
158
|
+
"data-tip": `<div>
|
|
159
|
+
<strong>${columnTitle}:</strong> <br>
|
|
160
|
+
${description} ${isUnique ? "<br>Must be unique" : ""}</div>`
|
|
161
|
+
})}
|
|
162
|
+
data-test={columnTitleTextified}
|
|
163
|
+
data-path={path}
|
|
164
|
+
data-copy-text={columnTitleTextified}
|
|
165
|
+
data-copy-json={JSON.stringify({
|
|
166
|
+
__strVal: columnTitleTextified,
|
|
167
|
+
__isHeaderCell: true
|
|
168
|
+
})}
|
|
169
|
+
className={classNames("tg-react-table-column-header", {
|
|
170
|
+
"sort-active": sortUp || sortDown
|
|
171
|
+
})}
|
|
172
|
+
>
|
|
173
|
+
{columnTitleTextified && !noTitle && (
|
|
174
|
+
<>
|
|
175
|
+
{maybeCheckbox}
|
|
176
|
+
<span
|
|
177
|
+
title={columnTitleTextified}
|
|
178
|
+
className={classNames({
|
|
179
|
+
"tg-react-table-name": true,
|
|
180
|
+
"no-data-tip": !!description
|
|
181
|
+
})}
|
|
182
|
+
style={{
|
|
183
|
+
...(description && { fontStyle: "italic" }),
|
|
184
|
+
display: "inline-block"
|
|
185
|
+
}}
|
|
186
|
+
>
|
|
187
|
+
{renderTitleInner ? renderTitleInner : columnTitle}{" "}
|
|
188
|
+
</span>
|
|
189
|
+
</>
|
|
190
|
+
)}
|
|
191
|
+
<div
|
|
192
|
+
style={{ display: "flex", marginLeft: "auto", alignItems: "center" }}
|
|
193
|
+
>
|
|
194
|
+
{withSort && !disableSorting && (
|
|
195
|
+
<div className="tg-sort-arrow-container">
|
|
196
|
+
<Icon
|
|
197
|
+
data-tip="Sort Z-A (Hold shift to sort multiple columns)"
|
|
198
|
+
icon="chevron-up"
|
|
199
|
+
className={classNames({
|
|
200
|
+
active: sortUp
|
|
201
|
+
})}
|
|
202
|
+
color={sortUp ? "#106ba3" : undefined}
|
|
203
|
+
iconSize={extraCompact ? 10 : 12}
|
|
204
|
+
onClick={e => {
|
|
205
|
+
setOrder("-" + ccDisplayName, sortUp, e.shiftKey);
|
|
206
|
+
}}
|
|
207
|
+
/>
|
|
208
|
+
<Icon
|
|
209
|
+
data-tip="Sort A-Z (Hold shift to sort multiple columns)"
|
|
210
|
+
icon="chevron-down"
|
|
211
|
+
className={classNames({
|
|
212
|
+
active: sortDown
|
|
213
|
+
})}
|
|
214
|
+
color={sortDown ? "#106ba3" : undefined}
|
|
215
|
+
iconSize={extraCompact ? 10 : 12}
|
|
216
|
+
onClick={e => {
|
|
217
|
+
setOrder(ccDisplayName, sortDown, e.shiftKey);
|
|
218
|
+
}}
|
|
219
|
+
/>
|
|
220
|
+
</div>
|
|
221
|
+
)}
|
|
222
|
+
{withFilter && !disableFiltering && (
|
|
223
|
+
<ColumnFilterMenu
|
|
224
|
+
FilterMenu={FilterMenu}
|
|
225
|
+
filterActiveForColumn={filterActiveForColumn}
|
|
226
|
+
addFilters={addFilters}
|
|
227
|
+
removeSingleFilter={removeSingleFilter}
|
|
228
|
+
currentFilter={currentFilter}
|
|
229
|
+
filterOn={ccDisplayName}
|
|
230
|
+
dataType={columnDataType}
|
|
231
|
+
schemaForField={column}
|
|
232
|
+
currentParams={currentParams}
|
|
233
|
+
setNewParams={setNewParams}
|
|
234
|
+
compact={compact}
|
|
235
|
+
extraCompact={extraCompact}
|
|
236
|
+
/>
|
|
237
|
+
)}
|
|
238
|
+
</div>
|
|
239
|
+
</div>
|
|
240
|
+
);
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const renderCheckboxHeader = ({
|
|
244
|
+
change,
|
|
245
|
+
entities,
|
|
246
|
+
isEntityDisabled,
|
|
247
|
+
isSingleSelect,
|
|
248
|
+
noDeselectAll,
|
|
249
|
+
noSelect,
|
|
250
|
+
noUserSelect = false,
|
|
251
|
+
onDeselect,
|
|
252
|
+
onMultiRowSelect,
|
|
253
|
+
onRowSelect,
|
|
254
|
+
onSingleRowSelect,
|
|
255
|
+
reduxFormSelectedEntityIdMap
|
|
256
|
+
}) => {
|
|
257
|
+
const checkedRows = getSelectedRowsFromEntities(
|
|
258
|
+
entities,
|
|
259
|
+
reduxFormSelectedEntityIdMap
|
|
260
|
+
);
|
|
261
|
+
const checkboxProps = {
|
|
262
|
+
checked: false,
|
|
263
|
+
indeterminate: false
|
|
264
|
+
};
|
|
265
|
+
const notDisabledEntityCount = entities.reduce((acc, e) => {
|
|
266
|
+
return isEntityDisabled(e) ? acc : acc + 1;
|
|
267
|
+
}, 0);
|
|
268
|
+
if (checkedRows.length === notDisabledEntityCount) {
|
|
269
|
+
//tnr: maybe this will need to change if we want enable select all across pages
|
|
270
|
+
checkboxProps.checked = notDisabledEntityCount !== 0;
|
|
271
|
+
} else {
|
|
272
|
+
if (checkedRows.length) {
|
|
273
|
+
checkboxProps.indeterminate = true;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return !isSingleSelect ? (
|
|
278
|
+
<Checkbox
|
|
279
|
+
name="checkBoxHeader"
|
|
280
|
+
disabled={noSelect || noUserSelect}
|
|
281
|
+
onChange={() => {
|
|
282
|
+
const newIdMap = cloneDeep(reduxFormSelectedEntityIdMap) || {};
|
|
283
|
+
entities.forEach((entity, i) => {
|
|
284
|
+
if (isEntityDisabled(entity)) return;
|
|
285
|
+
const entityId = getIdOrCodeOrIndex(entity, i);
|
|
286
|
+
if (checkboxProps.checked) {
|
|
287
|
+
delete newIdMap[entityId];
|
|
288
|
+
} else {
|
|
289
|
+
newIdMap[entityId] = { entity };
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
finalizeSelection({
|
|
294
|
+
idMap: newIdMap,
|
|
295
|
+
entities,
|
|
296
|
+
props: {
|
|
297
|
+
onDeselect,
|
|
298
|
+
onSingleRowSelect,
|
|
299
|
+
onMultiRowSelect,
|
|
300
|
+
noDeselectAll,
|
|
301
|
+
onRowSelect,
|
|
302
|
+
noSelect,
|
|
303
|
+
change
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}}
|
|
307
|
+
{...checkboxProps}
|
|
308
|
+
/>
|
|
309
|
+
) : null;
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
export const useColumns = ({
|
|
313
|
+
addFilters,
|
|
314
|
+
cellRenderer,
|
|
315
|
+
columns,
|
|
316
|
+
currentParams,
|
|
317
|
+
compact,
|
|
318
|
+
editingCell,
|
|
319
|
+
editingCellSelectAll,
|
|
320
|
+
entities,
|
|
321
|
+
expandedEntityIdMap,
|
|
322
|
+
extraCompact,
|
|
323
|
+
filters,
|
|
324
|
+
formName,
|
|
325
|
+
getCellHoverText,
|
|
326
|
+
isCellEditable,
|
|
327
|
+
isEntityDisabled,
|
|
328
|
+
isLocalCall,
|
|
329
|
+
isSimple,
|
|
330
|
+
isSingleSelect,
|
|
331
|
+
isSelectionARectangle,
|
|
332
|
+
noDeselectAll,
|
|
333
|
+
noSelect,
|
|
334
|
+
noUserSelect,
|
|
335
|
+
onDeselect,
|
|
336
|
+
onMultiRowSelect,
|
|
337
|
+
onRowClick,
|
|
338
|
+
onRowSelect,
|
|
339
|
+
onSingleRowSelect,
|
|
340
|
+
order,
|
|
341
|
+
primarySelectedCellId,
|
|
342
|
+
reduxFormCellValidation,
|
|
343
|
+
reduxFormSelectedEntityIdMap,
|
|
344
|
+
refocusTable,
|
|
345
|
+
removeSingleFilter = noop,
|
|
346
|
+
schema,
|
|
347
|
+
selectedCells,
|
|
348
|
+
setExpandedEntityIdMap,
|
|
349
|
+
setNewParams,
|
|
350
|
+
setOrder = noop,
|
|
351
|
+
setSelectedCells,
|
|
352
|
+
shouldShowSubComponent,
|
|
353
|
+
startCellEdit,
|
|
354
|
+
SubComponent,
|
|
355
|
+
tableRef,
|
|
356
|
+
updateEntitiesHelper,
|
|
357
|
+
updateValidation,
|
|
358
|
+
withCheckboxes,
|
|
359
|
+
withExpandAndCollapseAllButton,
|
|
360
|
+
withFilter: _withFilter,
|
|
361
|
+
withSort = true
|
|
362
|
+
}) => {
|
|
363
|
+
const dispatch = useDispatch();
|
|
364
|
+
const change = useCallback(
|
|
365
|
+
(...args) => dispatch(_change(formName, ...args)),
|
|
366
|
+
[dispatch, formName]
|
|
367
|
+
);
|
|
368
|
+
const withFilter = _withFilter === undefined ? !isSimple : _withFilter;
|
|
369
|
+
|
|
370
|
+
const onDragEnd = useCallback(
|
|
371
|
+
cellsToSelect => {
|
|
372
|
+
const [primaryRowId, primaryCellPath] = primarySelectedCellId.split(":");
|
|
373
|
+
const pathToField = getFieldPathToField(schema);
|
|
374
|
+
const { selectedPaths, selectionGrid } = isSelectionARectangle();
|
|
375
|
+
let allSelectedPaths = selectedPaths;
|
|
376
|
+
if (!allSelectedPaths) {
|
|
377
|
+
allSelectedPaths = [primaryCellPath];
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
updateEntitiesHelper(entities, entities => {
|
|
381
|
+
let newSelectedCells;
|
|
382
|
+
if (selectedPaths) {
|
|
383
|
+
newSelectedCells = {
|
|
384
|
+
...selectedCells
|
|
385
|
+
};
|
|
386
|
+
} else {
|
|
387
|
+
newSelectedCells = {
|
|
388
|
+
[primarySelectedCellId]: PRIMARY_SELECTED_VAL
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const newCellValidate = {
|
|
393
|
+
...reduxFormCellValidation
|
|
394
|
+
};
|
|
395
|
+
const entityMap = getEntityIdToEntity(entities);
|
|
396
|
+
const { e: selectedEnt } = entityMap[primaryRowId];
|
|
397
|
+
const firstCellToSelectRowIndex =
|
|
398
|
+
entityMap[cellsToSelect[0]?.split(":")[0]]?.i;
|
|
399
|
+
const pathToIndex = getFieldPathToIndex(schema);
|
|
400
|
+
|
|
401
|
+
allSelectedPaths.forEach(selectedPath => {
|
|
402
|
+
const column = pathToField[selectedPath];
|
|
403
|
+
|
|
404
|
+
const selectedCellVal = getCellVal(selectedEnt, selectedPath, column);
|
|
405
|
+
const cellIndexOfSelectedPath = pathToIndex[selectedPath];
|
|
406
|
+
let incrementStart;
|
|
407
|
+
let incrementPrefix;
|
|
408
|
+
let incrementPad = 0;
|
|
409
|
+
if (column.type === "string" || column.type === "number") {
|
|
410
|
+
const cellNumStr = getNumberStrAtEnd(selectedCellVal);
|
|
411
|
+
const cellNum = Number(cellNumStr);
|
|
412
|
+
const entityAbovePrimaryCell =
|
|
413
|
+
entities[entityMap[primaryRowId].i - 1];
|
|
414
|
+
if (cellNumStr !== null && !isNaN(cellNum)) {
|
|
415
|
+
if (
|
|
416
|
+
entityAbovePrimaryCell &&
|
|
417
|
+
(!selectionGrid || selectionGrid.length <= 1)
|
|
418
|
+
) {
|
|
419
|
+
const cellAboveVal = get(
|
|
420
|
+
entityAbovePrimaryCell,
|
|
421
|
+
selectedPath,
|
|
422
|
+
""
|
|
423
|
+
);
|
|
424
|
+
const cellAboveNumStr = getNumberStrAtEnd(cellAboveVal);
|
|
425
|
+
const cellAboveNum = Number(cellAboveNumStr);
|
|
426
|
+
if (!isNaN(cellAboveNum)) {
|
|
427
|
+
const isIncremental = cellNum - cellAboveNum === 1;
|
|
428
|
+
if (isIncremental) {
|
|
429
|
+
const cellTextNoNum = stripNumberAtEnd(selectedCellVal);
|
|
430
|
+
const sameText =
|
|
431
|
+
stripNumberAtEnd(cellAboveVal) === cellTextNoNum;
|
|
432
|
+
if (sameText) {
|
|
433
|
+
incrementStart = cellNum + 1;
|
|
434
|
+
incrementPrefix = cellTextNoNum || "";
|
|
435
|
+
if (cellNumStr && cellNumStr.startsWith("0")) {
|
|
436
|
+
incrementPad = cellNumStr.length;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
if (incrementStart === undefined) {
|
|
443
|
+
const draggingDown =
|
|
444
|
+
firstCellToSelectRowIndex > selectionGrid?.[0][0].rowIndex;
|
|
445
|
+
if (selectedPaths && draggingDown) {
|
|
446
|
+
let checkIncrement;
|
|
447
|
+
let prefix;
|
|
448
|
+
let maybePad;
|
|
449
|
+
// determine if all the cells in this column of the selectionGrid are incrementing
|
|
450
|
+
const allAreIncrementing = selectionGrid.every(row => {
|
|
451
|
+
// see if cell is selected
|
|
452
|
+
const cellInfo = row[cellIndexOfSelectedPath];
|
|
453
|
+
if (!cellInfo) return false;
|
|
454
|
+
const { cellId } = cellInfo;
|
|
455
|
+
const [rowId] = cellId.split(":");
|
|
456
|
+
const cellVal = getCellVal(
|
|
457
|
+
entityMap[rowId].e,
|
|
458
|
+
selectedPath,
|
|
459
|
+
pathToField[selectedPath]
|
|
460
|
+
);
|
|
461
|
+
const cellNumStr = getNumberStrAtEnd(cellVal);
|
|
462
|
+
const cellNum = Number(cellNumStr);
|
|
463
|
+
const cellTextNoNum = stripNumberAtEnd(cellVal);
|
|
464
|
+
if (cellNumStr?.startsWith("0")) {
|
|
465
|
+
maybePad = cellNumStr.length;
|
|
466
|
+
}
|
|
467
|
+
if (cellTextNoNum && !prefix) {
|
|
468
|
+
prefix = cellTextNoNum;
|
|
469
|
+
}
|
|
470
|
+
if (cellTextNoNum && prefix !== cellTextNoNum) {
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
if (!isNaN(cellNum)) {
|
|
474
|
+
if (!checkIncrement) {
|
|
475
|
+
checkIncrement = cellNum;
|
|
476
|
+
return true;
|
|
477
|
+
} else {
|
|
478
|
+
return ++checkIncrement === cellNum;
|
|
479
|
+
}
|
|
480
|
+
} else {
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
if (allAreIncrementing) {
|
|
486
|
+
incrementStart = checkIncrement + 1;
|
|
487
|
+
incrementPrefix = prefix || "";
|
|
488
|
+
incrementPad = maybePad;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
let firstSelectedCellRowIndex;
|
|
496
|
+
if (selectionGrid) {
|
|
497
|
+
selectionGrid[0].some(cell => {
|
|
498
|
+
if (cell) {
|
|
499
|
+
firstSelectedCellRowIndex = cell.rowIndex;
|
|
500
|
+
return true;
|
|
501
|
+
}
|
|
502
|
+
return false;
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
cellsToSelect.forEach(cellId => {
|
|
507
|
+
const [rowId, cellPath] = cellId.split(":");
|
|
508
|
+
if (cellPath !== selectedPath) return;
|
|
509
|
+
newSelectedCells[cellId] = true;
|
|
510
|
+
const { e: entityToUpdate, i: rowIndex } = entityMap[rowId] || {};
|
|
511
|
+
if (entityToUpdate) {
|
|
512
|
+
delete entityToUpdate._isClean;
|
|
513
|
+
let newVal;
|
|
514
|
+
if (incrementStart !== undefined) {
|
|
515
|
+
const num = incrementStart++;
|
|
516
|
+
newVal = incrementPrefix + padStart(num, incrementPad, "0");
|
|
517
|
+
} else {
|
|
518
|
+
if (selectionGrid && selectionGrid.length > 1) {
|
|
519
|
+
// if there are multiple cells selected then we want to copy them repeating
|
|
520
|
+
// ex: if we have 1,2,3 selected and we drag for 5 more rows we want it to
|
|
521
|
+
// be 1,2,3,1,2 for the new row cells in this column
|
|
522
|
+
const draggingDown = rowIndex > firstSelectedCellRowIndex;
|
|
523
|
+
const cellIndex = pathToIndex[cellPath];
|
|
524
|
+
let cellIdToCopy;
|
|
525
|
+
if (draggingDown) {
|
|
526
|
+
const { cellId } = selectionGrid[
|
|
527
|
+
(rowIndex - firstSelectedCellRowIndex) %
|
|
528
|
+
selectionGrid.length
|
|
529
|
+
].find(g => g && g.cellIndex === cellIndex);
|
|
530
|
+
cellIdToCopy = cellId;
|
|
531
|
+
} else {
|
|
532
|
+
const lastIndexInGrid =
|
|
533
|
+
selectionGrid[selectionGrid.length - 1][0].rowIndex;
|
|
534
|
+
const { cellId } = selectionGrid[
|
|
535
|
+
(rowIndex + lastIndexInGrid + 1) % selectionGrid.length
|
|
536
|
+
].find(g => g.cellIndex === cellIndex);
|
|
537
|
+
cellIdToCopy = cellId;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const [rowIdToCopy, cellPathToCopy] = cellIdToCopy.split(":");
|
|
541
|
+
newVal = getCellVal(
|
|
542
|
+
entityMap[rowIdToCopy].e,
|
|
543
|
+
cellPathToCopy,
|
|
544
|
+
pathToField[cellPathToCopy]
|
|
545
|
+
);
|
|
546
|
+
} else {
|
|
547
|
+
newVal = selectedCellVal;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
const { error } = editCellHelper({
|
|
551
|
+
entity: entityToUpdate,
|
|
552
|
+
path: cellPath,
|
|
553
|
+
schema,
|
|
554
|
+
newVal
|
|
555
|
+
});
|
|
556
|
+
newCellValidate[cellId] = error;
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
// select the new cells
|
|
562
|
+
updateValidation(entities, newCellValidate);
|
|
563
|
+
setSelectedCells(newSelectedCells);
|
|
564
|
+
});
|
|
565
|
+
},
|
|
566
|
+
[
|
|
567
|
+
entities,
|
|
568
|
+
isSelectionARectangle,
|
|
569
|
+
primarySelectedCellId,
|
|
570
|
+
reduxFormCellValidation,
|
|
571
|
+
schema,
|
|
572
|
+
selectedCells,
|
|
573
|
+
setSelectedCells,
|
|
574
|
+
updateEntitiesHelper,
|
|
575
|
+
updateValidation
|
|
576
|
+
]
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
const getCopyTextForCell = useCallback(
|
|
580
|
+
(val, row = {}, column = {}) => {
|
|
581
|
+
// TODOCOPY we need a way to potentially omit certain columns from being added as a \t element (talk to taoh about this)
|
|
582
|
+
let text = typeof val !== "string" ? row.value : val;
|
|
583
|
+
|
|
584
|
+
// We should try to take out the props from here, it produces
|
|
585
|
+
// unnecessary rerenders
|
|
586
|
+
const record = row.original;
|
|
587
|
+
if (column.getClipboardData) {
|
|
588
|
+
text = column.getClipboardData(row.value, record, row);
|
|
589
|
+
} else if (column.getValueToFilterOn) {
|
|
590
|
+
text = column.getValueToFilterOn(record);
|
|
591
|
+
} else if (column.render) {
|
|
592
|
+
text = column.render(row.value, record, row, {
|
|
593
|
+
currentParams,
|
|
594
|
+
setNewParams
|
|
595
|
+
});
|
|
596
|
+
} else if (cellRenderer && cellRenderer[column.path]) {
|
|
597
|
+
text = cellRenderer[column.path](row.value, row.original, row, {
|
|
598
|
+
currentParams,
|
|
599
|
+
setNewParams
|
|
600
|
+
});
|
|
601
|
+
} else if (text) {
|
|
602
|
+
text = isValidElement(text) ? text : String(text);
|
|
603
|
+
}
|
|
604
|
+
const getTextFromElementOrLink = text => {
|
|
605
|
+
if (isValidElement(text)) {
|
|
606
|
+
if (text.props?.to) {
|
|
607
|
+
// this will convert Link elements to url strings
|
|
608
|
+
return joinUrl(
|
|
609
|
+
window.location.origin,
|
|
610
|
+
window.frontEndConfig?.clientBasePath || "",
|
|
611
|
+
text.props.to
|
|
612
|
+
);
|
|
613
|
+
} else {
|
|
614
|
+
return getTextFromEl(text);
|
|
615
|
+
}
|
|
616
|
+
} else {
|
|
617
|
+
return text;
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
text = getTextFromElementOrLink(text);
|
|
621
|
+
|
|
622
|
+
if (Array.isArray(text)) {
|
|
623
|
+
let arrText = text.map(getTextFromElementOrLink).join(", ");
|
|
624
|
+
// because we sometimes insert commas after links when mapping over an array of elements we will have double ,'s
|
|
625
|
+
arrText = arrText.replace(/, ,/g, ",");
|
|
626
|
+
text = arrText;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const stringText = toString(text);
|
|
630
|
+
if (stringText === "[object Object]") return "";
|
|
631
|
+
return stringText;
|
|
632
|
+
},
|
|
633
|
+
[cellRenderer, currentParams, setNewParams]
|
|
634
|
+
);
|
|
635
|
+
|
|
636
|
+
const renderCheckboxCell = useCallback(
|
|
637
|
+
row => {
|
|
638
|
+
const rowIndex = row.index;
|
|
639
|
+
const checkedRows = getSelectedRowsFromEntities(
|
|
640
|
+
entities,
|
|
641
|
+
reduxFormSelectedEntityIdMap
|
|
642
|
+
);
|
|
643
|
+
|
|
644
|
+
const isSelected = checkedRows.some(rowNum => {
|
|
645
|
+
return rowNum === rowIndex;
|
|
646
|
+
});
|
|
647
|
+
if (rowIndex >= entities.length) {
|
|
648
|
+
return <div />;
|
|
649
|
+
}
|
|
650
|
+
const entity = entities[rowIndex];
|
|
651
|
+
return (
|
|
652
|
+
<Checkbox
|
|
653
|
+
name={`${getIdOrCodeOrIndex(entity, rowIndex)}-checkbox`}
|
|
654
|
+
disabled={noSelect || noUserSelect || isEntityDisabled(entity)}
|
|
655
|
+
onClick={e => {
|
|
656
|
+
rowClick(e, row, entities, {
|
|
657
|
+
reduxFormSelectedEntityIdMap,
|
|
658
|
+
isSingleSelect,
|
|
659
|
+
noSelect,
|
|
660
|
+
onRowClick,
|
|
661
|
+
isEntityDisabled,
|
|
662
|
+
withCheckboxes,
|
|
663
|
+
onDeselect,
|
|
664
|
+
onSingleRowSelect,
|
|
665
|
+
onMultiRowSelect,
|
|
666
|
+
noDeselectAll,
|
|
667
|
+
onRowSelect,
|
|
668
|
+
change
|
|
669
|
+
});
|
|
670
|
+
}}
|
|
671
|
+
checked={isSelected}
|
|
672
|
+
/>
|
|
673
|
+
);
|
|
674
|
+
},
|
|
675
|
+
[
|
|
676
|
+
change,
|
|
677
|
+
entities,
|
|
678
|
+
isEntityDisabled,
|
|
679
|
+
isSingleSelect,
|
|
680
|
+
noDeselectAll,
|
|
681
|
+
noSelect,
|
|
682
|
+
noUserSelect,
|
|
683
|
+
onDeselect,
|
|
684
|
+
onMultiRowSelect,
|
|
685
|
+
onRowClick,
|
|
686
|
+
onRowSelect,
|
|
687
|
+
onSingleRowSelect,
|
|
688
|
+
reduxFormSelectedEntityIdMap,
|
|
689
|
+
withCheckboxes
|
|
690
|
+
]
|
|
691
|
+
);
|
|
692
|
+
|
|
693
|
+
const finishCellEdit = useCallback(
|
|
694
|
+
(cellId, newVal, doNotStopEditing) => {
|
|
695
|
+
const [rowId, path] = cellId.split(":");
|
|
696
|
+
!doNotStopEditing && change("reduxFormEditingCell", null);
|
|
697
|
+
updateEntitiesHelper(entities, entities => {
|
|
698
|
+
const entity = entities.find((e, i) => {
|
|
699
|
+
return getIdOrCodeOrIndex(e, i) === rowId;
|
|
700
|
+
});
|
|
701
|
+
delete entity._isClean;
|
|
702
|
+
const { error } = editCellHelper({
|
|
703
|
+
entity,
|
|
704
|
+
path,
|
|
705
|
+
schema,
|
|
706
|
+
newVal
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
updateValidation(entities, {
|
|
710
|
+
...reduxFormCellValidation,
|
|
711
|
+
[cellId]: error
|
|
712
|
+
});
|
|
713
|
+
});
|
|
714
|
+
!doNotStopEditing && refocusTable();
|
|
715
|
+
},
|
|
716
|
+
[
|
|
717
|
+
change,
|
|
718
|
+
entities,
|
|
719
|
+
reduxFormCellValidation,
|
|
720
|
+
refocusTable,
|
|
721
|
+
schema,
|
|
722
|
+
updateEntitiesHelper,
|
|
723
|
+
updateValidation
|
|
724
|
+
]
|
|
725
|
+
);
|
|
726
|
+
|
|
727
|
+
const cancelCellEdit = useCallback(() => {
|
|
728
|
+
change("reduxFormEditingCell", null);
|
|
729
|
+
refocusTable();
|
|
730
|
+
}, [change, refocusTable]);
|
|
731
|
+
|
|
732
|
+
if (!columns.length) {
|
|
733
|
+
return columns;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
const columnsToRender = [];
|
|
737
|
+
if (SubComponent) {
|
|
738
|
+
columnsToRender.push({
|
|
739
|
+
...(withExpandAndCollapseAllButton && {
|
|
740
|
+
Header: () => {
|
|
741
|
+
const showCollapseAll =
|
|
742
|
+
Object.values(expandedEntityIdMap).filter(i => i).length ===
|
|
743
|
+
entities.length;
|
|
744
|
+
return (
|
|
745
|
+
<InfoHelper
|
|
746
|
+
content={showCollapseAll ? "Collapse All" : "Expand All"}
|
|
747
|
+
isButton
|
|
748
|
+
minimal
|
|
749
|
+
small
|
|
750
|
+
style={{ padding: 2 }}
|
|
751
|
+
popoverProps={{
|
|
752
|
+
modifiers: {
|
|
753
|
+
preventOverflow: { enabled: false },
|
|
754
|
+
hide: { enabled: false }
|
|
755
|
+
}
|
|
756
|
+
}}
|
|
757
|
+
onClick={() => {
|
|
758
|
+
showCollapseAll
|
|
759
|
+
? setExpandedEntityIdMap({})
|
|
760
|
+
: setExpandedEntityIdMap(prev => {
|
|
761
|
+
const newMap = { ...prev };
|
|
762
|
+
entities.forEach(e => {
|
|
763
|
+
newMap[getIdOrCodeOrIndex(e)] = true;
|
|
764
|
+
});
|
|
765
|
+
return newMap;
|
|
766
|
+
});
|
|
767
|
+
}}
|
|
768
|
+
className={classNames("tg-expander-all")}
|
|
769
|
+
icon={showCollapseAll ? "chevron-down" : "chevron-right"}
|
|
770
|
+
/>
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
}),
|
|
774
|
+
expander: true,
|
|
775
|
+
Expander: ({ isExpanded, original: record }) => {
|
|
776
|
+
let shouldShow = true;
|
|
777
|
+
if (shouldShowSubComponent) {
|
|
778
|
+
shouldShow = shouldShowSubComponent(record);
|
|
779
|
+
}
|
|
780
|
+
if (!shouldShow) return null;
|
|
781
|
+
return (
|
|
782
|
+
<Button
|
|
783
|
+
className={classNames(
|
|
784
|
+
"tg-expander",
|
|
785
|
+
Classes.MINIMAL,
|
|
786
|
+
Classes.SMALL
|
|
787
|
+
)}
|
|
788
|
+
icon={isExpanded ? "chevron-down" : "chevron-right"}
|
|
789
|
+
/>
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (withCheckboxes) {
|
|
796
|
+
columnsToRender.push({
|
|
797
|
+
Header: renderCheckboxHeader({
|
|
798
|
+
change,
|
|
799
|
+
entities,
|
|
800
|
+
isEntityDisabled,
|
|
801
|
+
isSingleSelect,
|
|
802
|
+
noDeselectAll,
|
|
803
|
+
noSelect,
|
|
804
|
+
noUserSelect,
|
|
805
|
+
onDeselect,
|
|
806
|
+
onMultiRowSelect,
|
|
807
|
+
onRowSelect,
|
|
808
|
+
onSingleRowSelect,
|
|
809
|
+
reduxFormSelectedEntityIdMap
|
|
810
|
+
}),
|
|
811
|
+
Cell: renderCheckboxCell,
|
|
812
|
+
width: 35,
|
|
813
|
+
resizable: false,
|
|
814
|
+
getHeaderProps: () => {
|
|
815
|
+
return {
|
|
816
|
+
className: "tg-react-table-checkbox-header-container",
|
|
817
|
+
immovable: "true"
|
|
818
|
+
};
|
|
819
|
+
},
|
|
820
|
+
getProps: () => {
|
|
821
|
+
return {
|
|
822
|
+
className: "tg-react-table-checkbox-cell-container"
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
const tableColumns = columns.map(column => {
|
|
829
|
+
const tableColumn = {
|
|
830
|
+
...column,
|
|
831
|
+
Header: RenderColumnHeader({
|
|
832
|
+
column,
|
|
833
|
+
isLocalCall,
|
|
834
|
+
filters,
|
|
835
|
+
currentParams,
|
|
836
|
+
order,
|
|
837
|
+
setOrder,
|
|
838
|
+
withSort,
|
|
839
|
+
extraCompact,
|
|
840
|
+
withFilter,
|
|
841
|
+
addFilters,
|
|
842
|
+
removeSingleFilter,
|
|
843
|
+
setNewParams,
|
|
844
|
+
compact,
|
|
845
|
+
isCellEditable,
|
|
846
|
+
entities,
|
|
847
|
+
updateEntitiesHelper
|
|
848
|
+
}),
|
|
849
|
+
accessor: column.path,
|
|
850
|
+
getHeaderProps: () => ({
|
|
851
|
+
// needs to be a string because it is getting passed
|
|
852
|
+
// to the dom
|
|
853
|
+
immovable: column.immovable ? "true" : "false",
|
|
854
|
+
columnindex: column.columnIndex
|
|
855
|
+
})
|
|
856
|
+
};
|
|
857
|
+
const noEllipsis = column.noEllipsis;
|
|
858
|
+
if (column.width) {
|
|
859
|
+
tableColumn.width = column.width;
|
|
860
|
+
}
|
|
861
|
+
if (cellRenderer && cellRenderer[column.path]) {
|
|
862
|
+
tableColumn.Cell = row => {
|
|
863
|
+
const val = cellRenderer[column.path](row.value, row.original, row, {
|
|
864
|
+
currentParams,
|
|
865
|
+
setNewParams
|
|
866
|
+
});
|
|
867
|
+
return val;
|
|
868
|
+
};
|
|
869
|
+
} else if (column.render) {
|
|
870
|
+
tableColumn.Cell = row => {
|
|
871
|
+
const val = column.render(row.value, row.original, row, {
|
|
872
|
+
currentParams,
|
|
873
|
+
setNewParams
|
|
874
|
+
});
|
|
875
|
+
return val;
|
|
876
|
+
};
|
|
877
|
+
} else if (column.type === "timestamp") {
|
|
878
|
+
tableColumn.Cell = ({ value }) => {
|
|
879
|
+
return value ? dayjs(value).format("lll") : "";
|
|
880
|
+
};
|
|
881
|
+
} else if (column.type === "color") {
|
|
882
|
+
tableColumn.Cell = ({ value }) => {
|
|
883
|
+
return value ? (
|
|
884
|
+
<div
|
|
885
|
+
style={{
|
|
886
|
+
height: 20,
|
|
887
|
+
width: 40,
|
|
888
|
+
background: value,
|
|
889
|
+
border: "1px solid #182026",
|
|
890
|
+
borderRadius: 5
|
|
891
|
+
}}
|
|
892
|
+
/>
|
|
893
|
+
) : (
|
|
894
|
+
""
|
|
895
|
+
);
|
|
896
|
+
};
|
|
897
|
+
} else if (column.type === "boolean") {
|
|
898
|
+
if (isCellEditable) {
|
|
899
|
+
tableColumn.Cell = ({ value }) => (value ? "True" : "False");
|
|
900
|
+
} else {
|
|
901
|
+
tableColumn.Cell = ({ value }) => (
|
|
902
|
+
<Icon
|
|
903
|
+
className={classNames({
|
|
904
|
+
[Classes.TEXT_MUTED]: !value
|
|
905
|
+
})}
|
|
906
|
+
icon={value ? "tick" : "cross"}
|
|
907
|
+
/>
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
} else if (column.type === "markdown") {
|
|
911
|
+
tableColumn.Cell = ({ value }) => (
|
|
912
|
+
<ReactMarkdown remarkPlugins={[remarkGfm]}>{value}</ReactMarkdown>
|
|
913
|
+
);
|
|
914
|
+
} else {
|
|
915
|
+
tableColumn.Cell = ({ value }) => value;
|
|
916
|
+
}
|
|
917
|
+
const oldFunc = tableColumn.Cell;
|
|
918
|
+
|
|
919
|
+
tableColumn.Cell = (...args) => (
|
|
920
|
+
<RenderCell
|
|
921
|
+
oldFunc={oldFunc}
|
|
922
|
+
formName={formName}
|
|
923
|
+
getCopyTextForCell={getCopyTextForCell}
|
|
924
|
+
column={column}
|
|
925
|
+
isCellEditable={isCellEditable}
|
|
926
|
+
isEntityDisabled={isEntityDisabled}
|
|
927
|
+
finishCellEdit={finishCellEdit}
|
|
928
|
+
noEllipsis={noEllipsis}
|
|
929
|
+
editingCell={editingCell}
|
|
930
|
+
cancelCellEdit={cancelCellEdit}
|
|
931
|
+
editingCellSelectAll={editingCellSelectAll}
|
|
932
|
+
getCellHoverText={getCellHoverText}
|
|
933
|
+
selectedCells={selectedCells}
|
|
934
|
+
isSelectionARectangle={isSelectionARectangle}
|
|
935
|
+
startCellEdit={startCellEdit}
|
|
936
|
+
tableRef={tableRef}
|
|
937
|
+
onDragEnd={onDragEnd}
|
|
938
|
+
args={args}
|
|
939
|
+
/>
|
|
940
|
+
);
|
|
941
|
+
return tableColumn;
|
|
942
|
+
});
|
|
943
|
+
|
|
944
|
+
return columnsToRender.concat(tableColumns);
|
|
945
|
+
};
|