@teselagen/ui 0.6.6 → 0.7.1

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