@teselagen/ui 0.5.19 → 0.5.20

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.
@@ -1,10 +1,15 @@
1
- import React, { createRef } from "react";
1
+ /* eslint react/jsx-no-bind: 0 */
2
+ import React, { useState } from "react";
3
+ import ReactDOM from "react-dom";
4
+ import copy from "copy-to-clipboard";
5
+ import download from "downloadjs";
2
6
  import {
3
7
  invert,
4
8
  toNumber,
5
9
  isEmpty,
6
10
  min,
7
11
  max,
12
+ flatMap,
8
13
  set,
9
14
  map,
10
15
  toString,
@@ -26,6 +31,7 @@ import {
26
31
  every
27
32
  } from "lodash-es";
28
33
  import joinUrl from "url-join";
34
+
29
35
  import {
30
36
  Button,
31
37
  Menu,
@@ -34,6 +40,7 @@ import {
34
40
  ContextMenu,
35
41
  Checkbox,
36
42
  Icon,
43
+ Popover,
37
44
  Intent,
38
45
  Callout,
39
46
  Tooltip
@@ -51,50 +58,30 @@ import immer, { produceWithPatches, enablePatches, applyPatches } from "immer";
51
58
  import papaparse from "papaparse";
52
59
  import remarkGfm from "remark-gfm";
53
60
 
54
- import {
55
- computePresets,
56
- defaultParsePaste,
57
- formatPasteData,
58
- getAllRows,
59
- getCellCopyText,
60
- getCellInfo,
61
- getEntityIdToEntity,
62
- getFieldPathToIndex,
63
- getFieldPathToField,
64
- getIdOrCodeOrIndex,
65
- getLastSelectedEntity,
66
- getNewEntToSelect,
67
- getNumberStrAtEnd,
68
- getRecordsFromIdMap,
69
- getRowCopyText,
70
- getSelectedRowsFromEntities,
71
- handleCopyColumn,
72
- handleCopyHelper,
73
- handleCopyRows,
74
- isBottomRightCornerOfRectangle,
75
- isEntityClean,
76
- removeCleanRows,
77
- stripNumberAtEnd
78
- } from "./utils";
79
- import InfoHelper from "../InfoHelper";
61
+ import TgSelect from "../TgSelect";
80
62
  import { withHotkeys } from "../utils/hotkeyUtils";
63
+ import InfoHelper from "../InfoHelper";
81
64
  import getTextFromEl from "../utils/getTextFromEl";
65
+ import { getSelectedRowsFromEntities } from "./utils/selection";
82
66
  import rowClick, {
83
67
  changeSelectedEntities,
84
68
  finalizeSelection
85
69
  } from "./utils/rowClick";
86
70
  import PagingTool from "./PagingTool";
87
71
  import FilterAndSortMenu from "./FilterAndSortMenu";
72
+ import getIdOrCodeOrIndex from "./utils/getIdOrCodeOrIndex";
88
73
  import SearchBar from "./SearchBar";
89
74
  import DisplayOptions from "./DisplayOptions";
90
75
  import DisabledLoadingComponent from "./DisabledLoadingComponent";
91
76
  import SortableColumns from "./SortableColumns";
77
+ import computePresets from "./utils/computePresets";
92
78
  import dataTableEnhancer from "./dataTableEnhancer";
93
79
  import defaultProps from "./defaultProps";
94
80
 
95
81
  import "../toastr";
96
82
  import "@teselagen/react-table/react-table.css";
97
83
  import "./style.css";
84
+ import { getRecordsFromIdMap } from "./utils/withSelectedEntities";
98
85
  import { CellDragHandle } from "./CellDragHandle";
99
86
  import { nanoid } from "nanoid";
100
87
  import { SwitchField } from "../FormComponents";
@@ -103,27 +90,15 @@ import { editCellHelper } from "./editCellHelper";
103
90
  import { getCellVal } from "./getCellVal";
104
91
  import { getVals } from "./getVals";
105
92
  import { throwFormError } from "../throwFormError";
106
- import { DropdownCell } from "./DropdownCell";
107
- import { EditableCell } from "./EditabelCell";
108
- import { ColumnFilterMenu } from "./ColumnFilterMenu";
109
93
  enablePatches();
110
94
 
111
95
  const PRIMARY_SELECTED_VAL = "main_cell";
112
96
 
113
97
  dayjs.extend(localizedFormat);
114
98
  const IS_LINUX = window.navigator.platform.toLowerCase().search("linux") > -1;
115
-
116
- const itemSizeEstimators = {
117
- compact: () => 25.34,
118
- normal: () => 33.34,
119
- comfortable: () => 41.34
120
- };
121
-
122
99
  class DataTable extends React.Component {
123
100
  constructor(props) {
124
101
  super(props);
125
-
126
- this.tableRef = createRef();
127
102
  if (this.props.helperProp) {
128
103
  this.props.helperProp.updateValidationHelper =
129
104
  this.updateValidationHelper;
@@ -204,52 +179,25 @@ class DataTable extends React.Component {
204
179
  }
205
180
  });
206
181
  }
207
-
208
182
  state = {
209
183
  columns: [],
210
- fullscreen: false,
211
- // This state prevents the first letter from not being written,
212
- // when the user starts typing in a cell. It is quite hacky, we should
213
- // refactor this in the future.
214
- editableCellInitialValue: ""
184
+ fullscreen: false
215
185
  };
216
- static defaultProps = defaultProps;
217
186
 
218
- getPrimarySelectedCellId = () => {
219
- const { reduxFormSelectedCells = {} } = this.props;
220
- for (const k of Object.keys(reduxFormSelectedCells)) {
221
- if (reduxFormSelectedCells[k] === PRIMARY_SELECTED_VAL) {
222
- return k;
223
- }
224
- }
225
- };
187
+ static defaultProps = defaultProps;
226
188
 
227
- startCellEdit = (cellId, { shouldSelectAll } = {}) => {
228
- const {
229
- change,
230
- reduxFormSelectedCells = {},
231
- reduxFormEditingCell
232
- } = computePresets(this.props);
233
- const newSelectedCells = { ...reduxFormSelectedCells };
234
- newSelectedCells[cellId] = PRIMARY_SELECTED_VAL;
235
- //check if the cell is already selected and editing and if so, don't change it
236
- if (reduxFormEditingCell === cellId) return;
237
- change("reduxFormSelectedCells", newSelectedCells);
238
- change("reduxFormEditingCell", cellId);
239
- if (shouldSelectAll) {
240
- //we should select the text
241
- change("reduxFormEditingCellSelectAll", true);
242
- }
189
+ toggleFullscreen = () => {
190
+ this.setState({
191
+ fullscreen: !this.state.fullscreen
192
+ });
243
193
  };
244
-
245
194
  handleEnterStartCellEdit = e => {
246
195
  e.stopPropagation();
247
196
  this.startCellEdit(this.getPrimarySelectedCellId());
248
197
  };
249
-
250
198
  flashTableBorder = () => {
251
199
  try {
252
- const table = this.tableRef.current.tableRef;
200
+ const table = ReactDOM.findDOMNode(this.table);
253
201
  table.classList.add("tgBorderBlue");
254
202
  setTimeout(() => {
255
203
  table.classList.remove("tgBorderBlue");
@@ -258,59 +206,6 @@ class DataTable extends React.Component {
258
206
  console.error(`err when flashing table border:`, e);
259
207
  }
260
208
  };
261
-
262
- formatAndValidateEntities = (
263
- entities,
264
- { useDefaultValues, indexToStartAt } = {}
265
- ) => {
266
- const { schema } = this.props;
267
- const editableFields = schema.fields.filter(f => !f.isNotEditable);
268
- const validationErrors = {};
269
-
270
- const newEnts = immer(entities, entities => {
271
- entities.forEach((e, index) => {
272
- editableFields.forEach(columnSchema => {
273
- if (useDefaultValues) {
274
- if (e[columnSchema.path] === undefined) {
275
- if (isFunction(columnSchema.defaultValue)) {
276
- e[columnSchema.path] = columnSchema.defaultValue(
277
- index + indexToStartAt,
278
- e
279
- );
280
- } else e[columnSchema.path] = columnSchema.defaultValue;
281
- }
282
- }
283
- //mutative
284
- const { error } = editCellHelper({
285
- entity: e,
286
- columnSchema,
287
- newVal: e[columnSchema.path]
288
- });
289
- if (error) {
290
- const rowId = getIdOrCodeOrIndex(e, index);
291
- validationErrors[`${rowId}:${columnSchema.path}`] = error;
292
- }
293
- });
294
- });
295
- });
296
- return {
297
- newEnts,
298
- validationErrors
299
- };
300
- };
301
-
302
- updateValidation = (entities, newCellValidate) => {
303
- const { change, schema } = computePresets(this.props);
304
- const tableWideErr = validateTableWideErrors({
305
- entities,
306
- schema,
307
- newCellValidate,
308
- props: this.props
309
- });
310
- change("reduxFormCellValidation", tableWideErr);
311
- this.forceUpdate();
312
- };
313
-
314
209
  handleUndo = () => {
315
210
  const {
316
211
  change,
@@ -336,7 +231,6 @@ class DataTable extends React.Component {
336
231
  });
337
232
  }
338
233
  };
339
-
340
234
  handleRedo = () => {
341
235
  const {
342
236
  change,
@@ -361,7 +255,6 @@ class DataTable extends React.Component {
361
255
  });
362
256
  }
363
257
  };
364
-
365
258
  updateFromProps = (oldProps, newProps) => {
366
259
  const {
367
260
  selectedIds,
@@ -373,6 +266,7 @@ class DataTable extends React.Component {
373
266
  reduxFormExpandedEntityIdMap,
374
267
  change
375
268
  } = newProps;
269
+ const table = ReactDOM.findDOMNode(this.table);
376
270
 
377
271
  const idMap = reduxFormSelectedEntityIdMap;
378
272
 
@@ -438,8 +332,7 @@ class DataTable extends React.Component {
438
332
  // if not changing selectedIds then we just want to make sure selected entities
439
333
  // stored in redux are in proper format
440
334
  // if selected ids have changed then it will handle redux selection
441
- const tableScrollElement =
442
- this.tableRef.current.tableRef.getElementsByClassName("rt-table")[0];
335
+ const tableScrollElement = table.getElementsByClassName("rt-table")[0];
443
336
  const {
444
337
  entities: oldEntities = [],
445
338
  reduxFormSelectedEntityIdMap: oldIdMap
@@ -488,9 +381,8 @@ class DataTable extends React.Component {
488
381
  const entityIndexToScrollTo = entities.findIndex(
489
382
  e => e.id === idToScrollTo || e.code === idToScrollTo
490
383
  );
491
- if (entityIndexToScrollTo === -1 || !this.tableRef.current) return;
492
- const tableBody =
493
- this.tableRef.current.tableRef.querySelector(".rt-tbody");
384
+ if (entityIndexToScrollTo === -1 || !table) return;
385
+ const tableBody = table.querySelector(".rt-tbody");
494
386
  if (!tableBody) return;
495
387
  const rowEl =
496
388
  tableBody.getElementsByClassName("rt-tr-group")[entityIndexToScrollTo];
@@ -505,7 +397,45 @@ class DataTable extends React.Component {
505
397
  }, 0);
506
398
  }
507
399
  };
400
+ formatAndValidateEntities = (
401
+ entities,
402
+ { useDefaultValues, indexToStartAt } = {}
403
+ ) => {
404
+ const { schema } = this.props;
405
+ const editableFields = schema.fields.filter(f => !f.isNotEditable);
406
+ const validationErrors = {};
508
407
 
408
+ const newEnts = immer(entities, entities => {
409
+ entities.forEach((e, index) => {
410
+ editableFields.forEach(columnSchema => {
411
+ if (useDefaultValues) {
412
+ if (e[columnSchema.path] === undefined) {
413
+ if (isFunction(columnSchema.defaultValue)) {
414
+ e[columnSchema.path] = columnSchema.defaultValue(
415
+ index + indexToStartAt,
416
+ e
417
+ );
418
+ } else e[columnSchema.path] = columnSchema.defaultValue;
419
+ }
420
+ }
421
+ //mutative
422
+ const { error } = editCellHelper({
423
+ entity: e,
424
+ columnSchema,
425
+ newVal: e[columnSchema.path]
426
+ });
427
+ if (error) {
428
+ const rowId = getIdOrCodeOrIndex(e, index);
429
+ validationErrors[`${rowId}:${columnSchema.path}`] = error;
430
+ }
431
+ });
432
+ });
433
+ });
434
+ return {
435
+ newEnts,
436
+ validationErrors
437
+ };
438
+ };
509
439
  formatAndValidateTableInitial = () => {
510
440
  const {
511
441
  _origEntities,
@@ -532,25 +462,151 @@ class DataTable extends React.Component {
532
462
  });
533
463
  };
534
464
 
535
- updateEntitiesHelper = (ents, fn) => {
536
- const { change, reduxFormEntitiesUndoRedoStack = { currentVersion: 0 } } =
537
- this.props;
538
- const [nextState, patches, inversePatches] = produceWithPatches(ents, fn);
539
- if (!inversePatches.length) return;
540
- const thatNewNew = [...nextState];
541
- thatNewNew.isDirty = true;
542
- change("reduxFormEntities", thatNewNew);
543
- change("reduxFormEntitiesUndoRedoStack", {
544
- ...omitBy(reduxFormEntitiesUndoRedoStack, (v, k) => {
545
- return toNumber(k) > reduxFormEntitiesUndoRedoStack.currentVersion + 1;
546
- }),
547
- currentVersion: reduxFormEntitiesUndoRedoStack.currentVersion + 1,
548
- [reduxFormEntitiesUndoRedoStack.currentVersion + 1]: {
549
- inversePatches,
550
- patches
465
+ componentDidMount() {
466
+ const {
467
+ isCellEditable,
468
+ entities = [],
469
+ isLoading,
470
+ showForcedHiddenColumns,
471
+ setShowForcedHidden
472
+ } = this.props;
473
+ isCellEditable && this.formatAndValidateTableInitial();
474
+ this.updateFromProps({}, computePresets(this.props));
475
+ document.addEventListener("paste", this.handlePaste);
476
+
477
+ if (!entities.length && !isLoading && !showForcedHiddenColumns) {
478
+ setShowForcedHidden(true);
479
+ }
480
+ // const table = ReactDOM.findDOMNode(this.table);
481
+ // let theads = table.getElementsByClassName("rt-thead");
482
+ // let tbody = table.getElementsByClassName("rt-tbody")[0];
483
+
484
+ // tbody.addEventListener("scroll", () => {
485
+ // for (let i = 0; i < theads.length; i++) {
486
+ // theads.item(i).scrollLeft = tbody.scrollLeft;
487
+ // }
488
+ // });
489
+ }
490
+
491
+ componentDidUpdate(oldProps) {
492
+ // const tableBody = table.querySelector(".rt-tbody");
493
+ // const headerNode = table.querySelector(".rt-thead.-header");
494
+ // if (headerNode) headerNode.style.overflowY = "inherit";
495
+ // if (tableBody && tableBody.scrollHeight > tableBody.clientHeight) {
496
+ // if (headerNode) {
497
+ // headerNode.style.overflowY = "scroll";
498
+ // headerNode.style.overflowX = "hidden";
499
+ // }
500
+ // }
501
+
502
+ this.updateFromProps(computePresets(oldProps), computePresets(this.props));
503
+
504
+ // comment in to test what is causing re-render
505
+ // Object.entries(this.props).forEach(
506
+ // ([key, val]) =>
507
+ // oldProps[key] !== val && console.info(`Prop '${key}' changed`)
508
+ // );
509
+ }
510
+
511
+ componentWillUnmount() {
512
+ document.removeEventListener("paste", this.handlePaste);
513
+ }
514
+
515
+ handleRowMove = (type, shiftHeld) => e => {
516
+ e.preventDefault();
517
+ e.stopPropagation();
518
+ const props = computePresets(this.props);
519
+ const {
520
+ noSelect,
521
+ entities,
522
+ reduxFormSelectedEntityIdMap: idMap,
523
+ isEntityDisabled,
524
+ isSingleSelect
525
+ } = props;
526
+ let newIdMap = {};
527
+ const lastSelectedEnt = getLastSelectedEntity(idMap);
528
+
529
+ if (noSelect) return;
530
+ if (lastSelectedEnt) {
531
+ let lastSelectedIndex = entities.findIndex(
532
+ ent => ent === lastSelectedEnt
533
+ );
534
+ if (lastSelectedIndex === -1) {
535
+ if (lastSelectedEnt.id !== undefined) {
536
+ lastSelectedIndex = entities.findIndex(
537
+ ent => ent.id === lastSelectedEnt.id
538
+ );
539
+ } else if (lastSelectedEnt.code !== undefined) {
540
+ lastSelectedIndex = entities.findIndex(
541
+ ent => ent.code === lastSelectedEnt.code
542
+ );
543
+ }
544
+ }
545
+ if (lastSelectedIndex === -1) {
546
+ return;
547
+ }
548
+ const newEntToSelect = getNewEntToSelect({
549
+ type,
550
+ lastSelectedIndex,
551
+ entities,
552
+ isEntityDisabled
553
+ });
554
+
555
+ if (!newEntToSelect) return;
556
+ if (shiftHeld && !isSingleSelect) {
557
+ if (idMap[newEntToSelect.id || newEntToSelect.code]) {
558
+ //the entity being moved to has already been selected
559
+ newIdMap = omit(idMap, [lastSelectedEnt.id || lastSelectedEnt.code]);
560
+ newIdMap[newEntToSelect.id || newEntToSelect.code].time =
561
+ Date.now() + 1;
562
+ } else {
563
+ //the entity being moved to has NOT been selected yet
564
+ newIdMap = {
565
+ ...idMap,
566
+ [newEntToSelect.id || newEntToSelect.code]: {
567
+ entity: newEntToSelect,
568
+ time: Date.now()
569
+ }
570
+ };
571
+ }
572
+ } else {
573
+ //no shiftHeld
574
+ newIdMap[newEntToSelect.id || newEntToSelect.code] = {
575
+ entity: newEntToSelect,
576
+ time: Date.now()
577
+ };
551
578
  }
579
+ }
580
+
581
+ finalizeSelection({
582
+ idMap: newIdMap,
583
+ entities,
584
+ props
552
585
  });
553
586
  };
587
+ handleCopyHotkey = e => {
588
+ const { isCellEditable, reduxFormSelectedEntityIdMap } = computePresets(
589
+ this.props
590
+ );
591
+
592
+ if (isCellEditable) {
593
+ this.handleCopySelectedCells(e);
594
+ } else {
595
+ this.handleCopySelectedRows(
596
+ getRecordsFromIdMap(reduxFormSelectedEntityIdMap),
597
+ e
598
+ );
599
+ }
600
+ };
601
+
602
+ getPrimarySelectedCellId = () => {
603
+ const { reduxFormSelectedCells = {} } = this.props;
604
+ for (const k of Object.keys(reduxFormSelectedCells)) {
605
+ if (reduxFormSelectedCells[k] === PRIMARY_SELECTED_VAL) {
606
+ return k;
607
+ }
608
+ }
609
+ };
554
610
 
555
611
  handlePaste = e => {
556
612
  const {
@@ -663,188 +719,49 @@ class DataTable extends React.Component {
663
719
  const pathToIndex = getFieldPathToIndex(schema);
664
720
  const indexToPath = invert(pathToIndex);
665
721
  const startCellIndex = pathToIndex[primaryCellPath];
666
- pasteData.forEach((row, i) => {
667
- row.forEach((newVal, j) => {
668
- if (newVal) {
669
- const cellIndexToChange = startCellIndex + j;
670
- const entity = entitiesToManipulate[i];
671
- if (entity) {
672
- delete entity._isClean;
673
- const path = indexToPath[cellIndexToChange];
674
- if (path) {
675
- const { error } = editCellHelper({
676
- entity,
677
- path,
678
- schema,
679
- newVal: formatPasteData({
680
- newVal,
681
- path,
682
- schema
683
- })
684
- });
685
- const cellId = `${getIdOrCodeOrIndex(entity)}:${path}`;
686
- if (!newSelectedCells[cellId]) {
687
- newSelectedCells[cellId] = true;
688
- }
689
- if (error) {
690
- newCellValidate[cellId] = error;
691
- } else {
692
- delete newCellValidate[cellId];
693
- }
694
- }
695
- }
696
- }
697
- });
698
- });
699
- this.updateValidation(entities, newCellValidate);
700
- });
701
- change("reduxFormSelectedCells", newSelectedCells);
702
- }
703
- }
704
- } catch (error) {
705
- console.error(`error:`, error);
706
- }
707
- }
708
- };
709
-
710
- componentDidMount() {
711
- const {
712
- isCellEditable,
713
- entities = [],
714
- isLoading,
715
- showForcedHiddenColumns,
716
- setShowForcedHidden
717
- } = this.props;
718
- isCellEditable && this.formatAndValidateTableInitial();
719
- this.updateFromProps({}, computePresets(this.props));
720
- document.addEventListener("paste", this.handlePaste);
721
-
722
- if (!entities.length && !isLoading && !showForcedHiddenColumns) {
723
- setShowForcedHidden(true);
724
- }
725
- // const table = this.tableRef.current.tableRef;
726
- // let theads = table.getElementsByClassName("rt-thead");
727
- // let tbody = table.getElementsByClassName("rt-tbody")[0];
728
-
729
- // tbody.addEventListener("scroll", () => {
730
- // for (let i = 0; i < theads.length; i++) {
731
- // theads.item(i).scrollLeft = tbody.scrollLeft;
732
- // }
733
- // });
734
- }
735
-
736
- componentDidUpdate(oldProps) {
737
- // const tableBody = table.querySelector(".rt-tbody");
738
- // const headerNode = table.querySelector(".rt-thead.-header");
739
- // if (headerNode) headerNode.style.overflowY = "inherit";
740
- // if (tableBody && tableBody.scrollHeight > tableBody.clientHeight) {
741
- // if (headerNode) {
742
- // headerNode.style.overflowY = "scroll";
743
- // headerNode.style.overflowX = "hidden";
744
- // }
745
- // }
746
-
747
- this.updateFromProps(computePresets(oldProps), computePresets(this.props));
748
-
749
- // comment in to test what is causing re-render
750
- // Object.entries(this.props).forEach(
751
- // ([key, val]) =>
752
- // oldProps[key] !== val && console.info(`Prop '${key}' changed`)
753
- // );
754
- }
755
-
756
- componentWillUnmount() {
757
- document.removeEventListener("paste", this.handlePaste);
758
- }
759
-
760
- handleRowMove = (type, shiftHeld) => e => {
761
- e.preventDefault();
762
- e.stopPropagation();
763
- const props = computePresets(this.props);
764
- const {
765
- noSelect,
766
- entities,
767
- reduxFormSelectedEntityIdMap: idMap,
768
- isEntityDisabled,
769
- isSingleSelect
770
- } = props;
771
- let newIdMap = {};
772
- const lastSelectedEnt = getLastSelectedEntity(idMap);
773
-
774
- if (noSelect) return;
775
- if (lastSelectedEnt) {
776
- let lastSelectedIndex = entities.findIndex(
777
- ent => ent === lastSelectedEnt
778
- );
779
- if (lastSelectedIndex === -1) {
780
- if (lastSelectedEnt.id !== undefined) {
781
- lastSelectedIndex = entities.findIndex(
782
- ent => ent.id === lastSelectedEnt.id
783
- );
784
- } else if (lastSelectedEnt.code !== undefined) {
785
- lastSelectedIndex = entities.findIndex(
786
- ent => ent.code === lastSelectedEnt.code
787
- );
788
- }
789
- }
790
- if (lastSelectedIndex === -1) {
791
- return;
792
- }
793
- const newEntToSelect = getNewEntToSelect({
794
- type,
795
- lastSelectedIndex,
796
- entities,
797
- isEntityDisabled
798
- });
799
-
800
- if (!newEntToSelect) return;
801
- if (shiftHeld && !isSingleSelect) {
802
- if (idMap[newEntToSelect.id || newEntToSelect.code]) {
803
- //the entity being moved to has already been selected
804
- newIdMap = omit(idMap, [lastSelectedEnt.id || lastSelectedEnt.code]);
805
- newIdMap[newEntToSelect.id || newEntToSelect.code].time =
806
- Date.now() + 1;
807
- } else {
808
- //the entity being moved to has NOT been selected yet
809
- newIdMap = {
810
- ...idMap,
811
- [newEntToSelect.id || newEntToSelect.code]: {
812
- entity: newEntToSelect,
813
- time: Date.now()
814
- }
815
- };
722
+ pasteData.forEach((row, i) => {
723
+ row.forEach((newVal, j) => {
724
+ if (newVal) {
725
+ const cellIndexToChange = startCellIndex + j;
726
+ const entity = entitiesToManipulate[i];
727
+ if (entity) {
728
+ delete entity._isClean;
729
+ const path = indexToPath[cellIndexToChange];
730
+ if (path) {
731
+ const { error } = editCellHelper({
732
+ entity,
733
+ path,
734
+ schema,
735
+ newVal: formatPasteData({
736
+ newVal,
737
+ path,
738
+ schema
739
+ })
740
+ });
741
+ const cellId = `${getIdOrCodeOrIndex(entity)}:${path}`;
742
+ if (!newSelectedCells[cellId]) {
743
+ newSelectedCells[cellId] = true;
744
+ }
745
+ if (error) {
746
+ newCellValidate[cellId] = error;
747
+ } else {
748
+ delete newCellValidate[cellId];
749
+ }
750
+ }
751
+ }
752
+ }
753
+ });
754
+ });
755
+ this.updateValidation(entities, newCellValidate);
756
+ });
757
+ change("reduxFormSelectedCells", newSelectedCells);
758
+ }
816
759
  }
817
- } else {
818
- //no shiftHeld
819
- newIdMap[newEntToSelect.id || newEntToSelect.code] = {
820
- entity: newEntToSelect,
821
- time: Date.now()
822
- };
760
+ } catch (error) {
761
+ console.error(`error:`, error);
823
762
  }
824
763
  }
825
-
826
- finalizeSelection({
827
- idMap: newIdMap,
828
- entities,
829
- props
830
- });
831
- };
832
-
833
- handleCopyHotkey = e => {
834
- const { isCellEditable, reduxFormSelectedEntityIdMap } = computePresets(
835
- this.props
836
- );
837
-
838
- if (isCellEditable) {
839
- this.handleCopySelectedCells(e);
840
- } else {
841
- this.handleCopySelectedRows(
842
- getRecordsFromIdMap(reduxFormSelectedEntityIdMap),
843
- e
844
- );
845
- }
846
764
  };
847
-
848
765
  handleSelectAllRows = e => {
849
766
  const {
850
767
  change,
@@ -883,12 +800,22 @@ class DataTable extends React.Component {
883
800
  });
884
801
  }
885
802
  };
886
-
887
803
  updateValidationHelper = () => {
888
804
  const { entities, reduxFormCellValidation } = computePresets(this.props);
889
805
  this.updateValidation(entities, reduxFormCellValidation);
890
806
  };
891
807
 
808
+ updateValidation = (entities, newCellValidate) => {
809
+ const { change, schema } = computePresets(this.props);
810
+ const tableWideErr = validateTableWideErrors({
811
+ entities,
812
+ schema,
813
+ newCellValidate,
814
+ props: this.props
815
+ });
816
+ change("reduxFormCellValidation", tableWideErr);
817
+ this.forceUpdate();
818
+ };
892
819
  handleDeleteCell = () => {
893
820
  const {
894
821
  reduxFormSelectedCells,
@@ -929,11 +856,119 @@ class DataTable extends React.Component {
929
856
  this.handleCopyHotkey(e);
930
857
  };
931
858
 
859
+ getCellCopyText = cellWrapper => {
860
+ const text = cellWrapper && cellWrapper.getAttribute("data-copy-text");
861
+ const jsonText = cellWrapper && cellWrapper.getAttribute("data-copy-json");
862
+
863
+ const textContent = text || cellWrapper.textContent || "";
864
+ return [textContent, jsonText];
865
+ };
866
+
867
+ handleCopyColumn = (e, cellWrapper, selectedRecords) => {
868
+ const specificColumn = cellWrapper.getAttribute("data-test");
869
+ let rowElsToCopy = getAllRows(e);
870
+ if (!rowElsToCopy) return;
871
+ if (selectedRecords) {
872
+ const ids = selectedRecords.map(e => getIdOrCodeOrIndex(e)?.toString());
873
+ rowElsToCopy = Array.from(rowElsToCopy).filter(rowEl => {
874
+ const id = rowEl.closest(".rt-tr-group")?.getAttribute("data-test-id");
875
+ return id !== undefined && ids.includes(id);
876
+ });
877
+ }
878
+ if (!rowElsToCopy) return;
879
+ this.handleCopyRows(rowElsToCopy, {
880
+ specificColumn,
881
+ onFinishMsg: "Column Copied"
882
+ });
883
+ };
884
+ handleCopyRows = (
885
+ rowElsToCopy,
886
+ { specificColumn, onFinishMsg, isDownload } = {}
887
+ ) => {
888
+ let textToCopy = [];
889
+ const jsonToCopy = [];
890
+ forEach(rowElsToCopy, rowEl => {
891
+ const [t, j] = this.getRowCopyText(rowEl, { specificColumn });
892
+ textToCopy.push(t);
893
+ jsonToCopy.push(j);
894
+ });
895
+ textToCopy = textToCopy.filter(text => text).join("\n");
896
+ if (!textToCopy) return window.toastr.warning("No text to copy");
897
+ if (isDownload) {
898
+ download(textToCopy.replaceAll("\t", ","), "tableData.csv", "text/csv");
899
+ } else {
900
+ this.handleCopyHelper(
901
+ textToCopy,
902
+ jsonToCopy,
903
+ onFinishMsg || "Row Copied"
904
+ );
905
+ }
906
+ };
907
+ updateEntitiesHelper = (ents, fn) => {
908
+ const { change, reduxFormEntitiesUndoRedoStack = { currentVersion: 0 } } =
909
+ this.props;
910
+ const [nextState, patches, inversePatches] = produceWithPatches(ents, fn);
911
+ if (!inversePatches.length) return;
912
+ const thatNewNew = [...nextState];
913
+ thatNewNew.isDirty = true;
914
+ change("reduxFormEntities", thatNewNew);
915
+ change("reduxFormEntitiesUndoRedoStack", {
916
+ ...omitBy(reduxFormEntitiesUndoRedoStack, (v, k) => {
917
+ return toNumber(k) > reduxFormEntitiesUndoRedoStack.currentVersion + 1;
918
+ }),
919
+ currentVersion: reduxFormEntitiesUndoRedoStack.currentVersion + 1,
920
+ [reduxFormEntitiesUndoRedoStack.currentVersion + 1]: {
921
+ inversePatches,
922
+ patches
923
+ }
924
+ });
925
+ };
926
+
927
+ getRowCopyText = (rowEl, { specificColumn } = {}) => {
928
+ //takes in a row element
929
+ if (!rowEl) return [];
930
+ const textContent = [];
931
+ const jsonText = [];
932
+
933
+ forEach(rowEl.children, cellEl => {
934
+ const cellChild = cellEl.querySelector(`[data-copy-text]`);
935
+ if (!cellChild) {
936
+ if (specificColumn) return []; //strip it
937
+ return; //just leave it blank
938
+ }
939
+ if (
940
+ specificColumn &&
941
+ cellChild.getAttribute("data-test") !== specificColumn
942
+ ) {
943
+ return [];
944
+ }
945
+ const [t, j] = this.getCellCopyText(cellChild);
946
+ textContent.push(t);
947
+ jsonText.push(j);
948
+ });
949
+
950
+ return [flatMap(textContent).join("\t"), jsonText];
951
+ };
952
+
953
+ handleCopyHelper = (stringToCopy, jsonToCopy, message) => {
954
+ !window.Cypress &&
955
+ copy(stringToCopy, {
956
+ onCopy: clipboardData => {
957
+ clipboardData.setData("application/json", JSON.stringify(jsonToCopy));
958
+ },
959
+ // keep this so that pasting into spreadsheets works.
960
+ format: "text/plain"
961
+ });
962
+ if (message) {
963
+ window.toastr.success(message);
964
+ }
965
+ };
966
+
932
967
  handleCopyTable = (e, opts) => {
933
968
  try {
934
969
  const allRowEls = getAllRows(e);
935
970
  if (!allRowEls) return;
936
- handleCopyRows(allRowEls, {
971
+ this.handleCopyRows(allRowEls, {
937
972
  ...opts,
938
973
  onFinishMsg: "Table Copied"
939
974
  });
@@ -942,7 +977,6 @@ class DataTable extends React.Component {
942
977
  window.toastr.error("Error copying rows.");
943
978
  }
944
979
  };
945
-
946
980
  handleCopySelectedCells = e => {
947
981
  const {
948
982
  entities = [],
@@ -988,7 +1022,7 @@ class DataTable extends React.Component {
988
1022
  } else {
989
1023
  const jsonRow = [];
990
1024
  // ignore header
991
- let [rowCopyText, json] = getRowCopyText(allRows[i + 1]);
1025
+ let [rowCopyText, json] = this.getRowCopyText(allRows[i + 1]);
992
1026
  rowCopyText = rowCopyText.split("\t");
993
1027
  times(row.length, i => {
994
1028
  const cell = row[i];
@@ -1003,7 +1037,7 @@ class DataTable extends React.Component {
1003
1037
  });
1004
1038
  if (!fullCellText) return window.toastr.warning("No text to copy");
1005
1039
 
1006
- handleCopyHelper(fullCellText, fullJson, "Selected cells copied");
1040
+ this.handleCopyHelper(fullCellText, fullJson, "Selected cells copied");
1007
1041
  };
1008
1042
 
1009
1043
  handleCopySelectedRows = (selectedRecords, e) => {
@@ -1026,7 +1060,7 @@ class DataTable extends React.Component {
1026
1060
  if (!allRowEls) return;
1027
1061
  const rowEls = rowNumbersToCopy.map(i => allRowEls[i]);
1028
1062
 
1029
- handleCopyRows(rowEls, {
1063
+ this.handleCopyRows(rowEls, {
1030
1064
  onFinishMsg: "Selected rows copied"
1031
1065
  });
1032
1066
  } catch (error) {
@@ -1083,7 +1117,6 @@ class DataTable extends React.Component {
1083
1117
  />
1084
1118
  );
1085
1119
  };
1086
-
1087
1120
  getThComponent = compose(
1088
1121
  withProps(props => {
1089
1122
  const { columnindex } = props;
@@ -1302,11 +1335,7 @@ class DataTable extends React.Component {
1302
1335
  icon="fullscreen"
1303
1336
  active={fullscreen}
1304
1337
  minimal
1305
- onClick={() => {
1306
- this.setState({
1307
- fullscreen: !this.state.fullscreen
1308
- });
1309
- }}
1338
+ onClick={this.toggleFullscreen}
1310
1339
  />
1311
1340
  );
1312
1341
 
@@ -1461,17 +1490,24 @@ class DataTable extends React.Component {
1461
1490
  {...(isCellEditable && {
1462
1491
  tabIndex: -1,
1463
1492
  onKeyDown: e => {
1464
- const isTabKey = e.key === "Tab";
1465
- const isArrowKey = e.key.startsWith("Arrow");
1493
+ // const isArrowKey =
1494
+ // (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode === 9;
1495
+ // if (isArrowKey && e.target?.tagName !== "INPUT") {
1496
+ const isTabKey = e.keyCode === 9;
1497
+ // const isEnter = e.keyCode === 13;
1498
+ // console.log(`onKeydown datatable inner`);
1499
+ // console.log(`isEnter:`, isEnter)
1500
+ const isArrowKey = e.keyCode >= 37 && e.keyCode <= 40;
1501
+ // console.log(`e.target?.tagName:`,e.target?.tagName)
1466
1502
  if (
1467
1503
  (isArrowKey && e.target?.tagName !== "INPUT") ||
1468
1504
  isTabKey
1469
1505
  // || (isEnter && e.target?.tagName === "INPUT")
1470
1506
  ) {
1471
1507
  const { schema, entities } = computePresets(this.props);
1472
- const left = e.key === "ArrowLeft";
1473
- const up = e.key === "ArrowUp";
1474
- const down = e.key === "ArrowDown" || e.key === "Enter";
1508
+ const left = e.keyCode === 37;
1509
+ const up = e.keyCode === 38;
1510
+ const down = e.keyCode === 40 || e.keyCode === 13;
1475
1511
  let cellIdToUse = this.getPrimarySelectedCellId();
1476
1512
  const pathToIndex = getFieldPathToIndex(schema);
1477
1513
  const entityMap = getEntityIdToEntity(entities);
@@ -1564,23 +1600,13 @@ class DataTable extends React.Component {
1564
1600
  const entity = entityIdToEntity[rowId].e;
1565
1601
  if (!entity) return;
1566
1602
  const rowDisabled = isEntityDisabled(entity);
1567
- const isNum = e.code?.startsWith("Digit");
1568
- const isLetter = e.code?.startsWith("Key");
1569
- if (!isNum && !isLetter) {
1570
- this.setState(prev => ({
1571
- ...prev,
1572
- editableCellInitialValue: ""
1573
- }));
1574
- return;
1575
- } else {
1576
- this.setState(prev => ({
1577
- ...prev,
1578
- editableCellInitialValue: e.key
1579
- }));
1580
- }
1603
+ const isNum = e.keyCode >= 48 && e.keyCode <= 57;
1604
+ const isLetter = e.keyCode >= 65 && e.keyCode <= 90;
1605
+ if (!isNum && !isLetter) return;
1581
1606
  if (rowDisabled) return;
1582
1607
  this.startCellEdit(cellId, { shouldSelectAll: true });
1583
1608
  e.stopPropagation();
1609
+ // e.preventDefault();
1584
1610
  }
1585
1611
  })}
1586
1612
  >
@@ -1720,7 +1746,10 @@ class DataTable extends React.Component {
1720
1746
  )}
1721
1747
  <ReactTable
1722
1748
  data={filteredEnts}
1723
- ref={this.tableRef}
1749
+ ref={n => {
1750
+ if (n) this.table = n;
1751
+ }}
1752
+ // additionalBodyEl={}
1724
1753
  className={classNames({
1725
1754
  isCellEditable,
1726
1755
  "tg-table-loading": isLoading,
@@ -1807,7 +1836,7 @@ class DataTable extends React.Component {
1807
1836
  data-tip="Download Table as CSV"
1808
1837
  minimal
1809
1838
  icon="download"
1810
- />
1839
+ ></Button>
1811
1840
  </div>
1812
1841
  )}
1813
1842
  {!noFooter && (
@@ -1952,6 +1981,24 @@ class DataTable extends React.Component {
1952
1981
  };
1953
1982
  };
1954
1983
 
1984
+ startCellEdit = (cellId, { shouldSelectAll } = {}) => {
1985
+ const {
1986
+ change,
1987
+ reduxFormSelectedCells = {},
1988
+ reduxFormEditingCell
1989
+ } = computePresets(this.props);
1990
+ const newSelectedCells = { ...reduxFormSelectedCells };
1991
+ newSelectedCells[cellId] = PRIMARY_SELECTED_VAL;
1992
+ //check if the cell is already selected and editing and if so, don't change it
1993
+ if (reduxFormEditingCell === cellId) return;
1994
+ change("reduxFormSelectedCells", newSelectedCells);
1995
+ change("reduxFormEditingCell", cellId);
1996
+ if (shouldSelectAll) {
1997
+ //we should select the text
1998
+ change("reduxFormEditingCellSelectAll", true);
1999
+ }
2000
+ };
2001
+
1955
2002
  getTableCellProps = (state, rowInfo, column) => {
1956
2003
  const {
1957
2004
  entities,
@@ -2147,7 +2194,6 @@ class DataTable extends React.Component {
2147
2194
 
2148
2195
  change("reduxFormSelectedCells", newSelectedCells);
2149
2196
  };
2150
-
2151
2197
  renderCheckboxHeader = () => {
2152
2198
  const {
2153
2199
  reduxFormSelectedEntityIdMap,
@@ -2270,10 +2316,9 @@ class DataTable extends React.Component {
2270
2316
  change("reduxFormEditingCell", null);
2271
2317
  this.refocusTable();
2272
2318
  };
2273
-
2274
2319
  refocusTable = () => {
2275
2320
  setTimeout(() => {
2276
- const table = this.tableRef.current?.tableRef?.closest(
2321
+ const table = ReactDOM.findDOMNode(this.table)?.closest(
2277
2322
  ".data-table-container>div"
2278
2323
  );
2279
2324
  table?.focus();
@@ -2550,6 +2595,7 @@ class DataTable extends React.Component {
2550
2595
  <Checkbox
2551
2596
  disabled={isEntityDisabled(row.original)}
2552
2597
  className="tg-cell-edit-boolean-checkbox"
2598
+ // {...dataTest}
2553
2599
  checked={oldVal === "True"}
2554
2600
  onChange={e => {
2555
2601
  const checked = e.target.checked;
@@ -2559,6 +2605,9 @@ class DataTable extends React.Component {
2559
2605
  );
2560
2606
  noEllipsis = true;
2561
2607
  } else {
2608
+ // if (column.type === "genericSelect") {
2609
+ // val =
2610
+ // }
2562
2611
  if (reduxFormEditingCell === cellId) {
2563
2612
  if (column.type === "genericSelect") {
2564
2613
  const GenericSelectComp = column.GenericSelectComp;
@@ -2589,7 +2638,7 @@ class DataTable extends React.Component {
2589
2638
  }}
2590
2639
  dataTest={dataTest}
2591
2640
  cancelEdit={this.cancelCellEdit}
2592
- />
2641
+ ></DropdownCell>
2593
2642
  );
2594
2643
  } else {
2595
2644
  return (
@@ -2601,18 +2650,11 @@ class DataTable extends React.Component {
2601
2650
  shouldSelectAll={reduxFormEditingCellSelectAll}
2602
2651
  cancelEdit={this.cancelCellEdit}
2603
2652
  isNumeric={column.type === "number"}
2604
- initialValue={
2605
- this.state.editableCellInitialValue.length
2606
- ? this.state.editableCellInitialValue
2607
- : text
2608
- }
2609
- isEditableCellInitialValue={
2610
- !!this.state.editableCellInitialValue.length
2611
- }
2653
+ initialValue={text}
2612
2654
  finishEdit={newVal => {
2613
2655
  this.finishCellEdit(cellId, newVal);
2614
2656
  }}
2615
- />
2657
+ ></EditableCell>
2616
2658
  );
2617
2659
  }
2618
2660
  }
@@ -2684,7 +2726,7 @@ class DataTable extends React.Component {
2684
2726
 
2685
2727
  {isSelectedCell &&
2686
2728
  (isRect
2687
- ? isBottomRightCornerOfRectangle({
2729
+ ? this.isBottomRightCornerOfRectangle({
2688
2730
  cellId,
2689
2731
  selectionGrid,
2690
2732
  lastRowIndex,
@@ -2695,11 +2737,11 @@ class DataTable extends React.Component {
2695
2737
  : isSelectedCell === PRIMARY_SELECTED_VAL) && (
2696
2738
  <CellDragHandle
2697
2739
  key={cellId}
2698
- thisTable={this.tableRef.current.tableRef}
2740
+ thisTable={this.table}
2699
2741
  cellId={cellId}
2700
2742
  isSelectionARectangle={this.isSelectionARectangle}
2701
2743
  onDragEnd={this.onDragEnd}
2702
- />
2744
+ ></CellDragHandle>
2703
2745
  )}
2704
2746
  </>
2705
2747
  );
@@ -2709,6 +2751,26 @@ class DataTable extends React.Component {
2709
2751
  });
2710
2752
  return columnsToRender;
2711
2753
  };
2754
+ isBottomRightCornerOfRectangle = ({
2755
+ cellId,
2756
+ selectionGrid,
2757
+ lastRowIndex,
2758
+ lastCellIndex,
2759
+ entityMap,
2760
+ pathToIndex
2761
+ }) => {
2762
+ selectionGrid.forEach(row => {
2763
+ // remove undefineds from start of row
2764
+ while (row[0] === undefined && row.length) row.shift();
2765
+ });
2766
+ const [rowId, cellPath] = cellId.split(":");
2767
+ const ent = entityMap[rowId];
2768
+ if (!ent) return;
2769
+ const { i } = ent;
2770
+ const cellIndex = pathToIndex[cellPath];
2771
+ const isBottomRight = i === lastRowIndex && cellIndex === lastCellIndex;
2772
+ return isBottomRight;
2773
+ };
2712
2774
 
2713
2775
  onDragEnd = cellsToSelect => {
2714
2776
  const {
@@ -2913,7 +2975,6 @@ class DataTable extends React.Component {
2913
2975
  change("reduxFormSelectedCells", newReduxFormSelectedCells);
2914
2976
  });
2915
2977
  };
2916
-
2917
2978
  getCopyTextForCell = (val, row = {}, column = {}) => {
2918
2979
  const { cellRenderer } = computePresets(this.props);
2919
2980
  // TODOCOPY we need a way to potentially omit certain columns from being added as a \t element (talk to taoh about this)
@@ -2999,7 +3060,6 @@ class DataTable extends React.Component {
2999
3060
  });
3000
3061
  });
3001
3062
  };
3002
-
3003
3063
  getEditableTableInfoAndThrowFormError = () => {
3004
3064
  const { schema, reduxFormEntities, reduxFormCellValidation } =
3005
3065
  computePresets(this.props);
@@ -3109,12 +3169,12 @@ class DataTable extends React.Component {
3109
3169
  //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
3110
3170
  //do we need to be able to copy hidden cells? It seems like it should just copy what's on the page..?
3111
3171
  const specificColumn = cellWrapper.getAttribute("data-test");
3112
- handleCopyRows([cellWrapper.closest(".rt-tr")], {
3172
+ this.handleCopyRows([cellWrapper.closest(".rt-tr")], {
3113
3173
  specificColumn,
3114
3174
  onFinishMsg: "Cell copied"
3115
3175
  });
3116
- const [text, jsonText] = getCellCopyText(cellWrapper);
3117
- handleCopyHelper(text, jsonText);
3176
+ const [text, jsonText] = this.getCellCopyText(cellWrapper);
3177
+ this.handleCopyHelper(text, jsonText);
3118
3178
  }}
3119
3179
  text="Cell"
3120
3180
  />
@@ -3124,7 +3184,7 @@ class DataTable extends React.Component {
3124
3184
  <MenuItem
3125
3185
  key="copyColumn"
3126
3186
  onClick={() => {
3127
- handleCopyColumn(e, cellWrapper);
3187
+ this.handleCopyColumn(e, cellWrapper);
3128
3188
  }}
3129
3189
  text="Column"
3130
3190
  />
@@ -3134,7 +3194,7 @@ class DataTable extends React.Component {
3134
3194
  <MenuItem
3135
3195
  key="copyColumnSelected"
3136
3196
  onClick={() => {
3137
- handleCopyColumn(e, cellWrapper, selectedRecords);
3197
+ this.handleCopyColumn(e, cellWrapper, selectedRecords);
3138
3198
  }}
3139
3199
  text="Column (Selected)"
3140
3200
  />
@@ -3152,7 +3212,7 @@ class DataTable extends React.Component {
3152
3212
  <MenuItem
3153
3213
  key="copySelectedRows"
3154
3214
  onClick={() => {
3155
- handleCopyRows([row]);
3215
+ this.handleCopyRows([row]);
3156
3216
  // loop through each cell in the row
3157
3217
  }}
3158
3218
  text="Row"
@@ -3203,7 +3263,7 @@ class DataTable extends React.Component {
3203
3263
  onClick={() => {
3204
3264
  this.insertRows({ above: true });
3205
3265
  }}
3206
- />
3266
+ ></MenuItem>
3207
3267
  <MenuItem
3208
3268
  icon="add-row-top"
3209
3269
  text="Add Row Below"
@@ -3211,7 +3271,7 @@ class DataTable extends React.Component {
3211
3271
  onClick={() => {
3212
3272
  this.insertRows({});
3213
3273
  }}
3214
- />
3274
+ ></MenuItem>
3215
3275
  <MenuItem
3216
3276
  icon="remove"
3217
3277
  text={`Remove Row${selectedRowIds.length > 1 ? "s" : ""}`}
@@ -3401,7 +3461,7 @@ class DataTable extends React.Component {
3401
3461
  }}
3402
3462
  indeterminate={isIndeterminate}
3403
3463
  checked={isChecked}
3404
- />
3464
+ ></Checkbox>
3405
3465
  );
3406
3466
  }
3407
3467
 
@@ -3426,7 +3486,7 @@ class DataTable extends React.Component {
3426
3486
  })}
3427
3487
  >
3428
3488
  {columnTitleTextified && !noTitle && (
3429
- <>
3489
+ <React.Fragment>
3430
3490
  {maybeCheckbox}
3431
3491
  <span
3432
3492
  title={columnTitleTextified}
@@ -3441,7 +3501,7 @@ class DataTable extends React.Component {
3441
3501
  >
3442
3502
  {renderTitleInner ? renderTitleInner : columnTitle}{" "}
3443
3503
  </span>
3444
- </>
3504
+ </React.Fragment>
3445
3505
  )}
3446
3506
  <div
3447
3507
  style={{ display: "flex", marginLeft: "auto", alignItems: "center" }}
@@ -3463,3 +3523,333 @@ const WrappedDT = dataTableEnhancer(DataTable);
3463
3523
  export default WrappedDT;
3464
3524
  const ConnectedPagingTool = dataTableEnhancer(PagingTool);
3465
3525
  export { ConnectedPagingTool };
3526
+
3527
+ const itemSizeEstimators = {
3528
+ compact: () => 25.34,
3529
+ normal: () => 33.34,
3530
+ comfortable: () => 41.34
3531
+ };
3532
+
3533
+ function getCellInfo({
3534
+ columnIndex,
3535
+ columnPath,
3536
+ rowId,
3537
+ schema,
3538
+ entities,
3539
+ rowIndex,
3540
+ isEntityDisabled,
3541
+ entity
3542
+ }) {
3543
+ const leftpath = schema.fields[columnIndex - 1]?.path;
3544
+ const rightpath = schema.fields[columnIndex + 1]?.path;
3545
+ const cellIdToLeft = leftpath && `${rowId}:${leftpath}`;
3546
+ const cellIdToRight = rightpath && `${rowId}:${rightpath}`;
3547
+ const rowAboveId =
3548
+ entities[rowIndex - 1] &&
3549
+ getIdOrCodeOrIndex(entities[rowIndex - 1], rowIndex - 1);
3550
+ const rowBelowId =
3551
+ entities[rowIndex + 1] &&
3552
+ getIdOrCodeOrIndex(entities[rowIndex + 1], rowIndex + 1);
3553
+ const cellIdAbove = rowAboveId && `${rowAboveId}:${columnPath}`;
3554
+ const cellIdBelow = rowBelowId && `${rowBelowId}:${columnPath}`;
3555
+
3556
+ const cellId = `${rowId}:${columnPath}`;
3557
+ const rowDisabled = isEntityDisabled(entity);
3558
+ return {
3559
+ cellId,
3560
+ cellIdAbove,
3561
+ cellIdToRight,
3562
+ cellIdBelow,
3563
+ cellIdToLeft,
3564
+ rowDisabled
3565
+ };
3566
+ }
3567
+
3568
+ function ColumnFilterMenu({
3569
+ FilterMenu,
3570
+ filterActiveForColumn,
3571
+ compact,
3572
+ extraCompact,
3573
+ ...rest
3574
+ }) {
3575
+ const [columnFilterMenuOpen, setColumnFilterMenuOpen] = useState(false);
3576
+ return (
3577
+ <Popover
3578
+ position="bottom"
3579
+ onClose={() => {
3580
+ setColumnFilterMenuOpen(false);
3581
+ }}
3582
+ isOpen={columnFilterMenuOpen}
3583
+ modifiers={{
3584
+ preventOverflow: { enabled: true },
3585
+ hide: { enabled: false },
3586
+ flip: { enabled: false }
3587
+ }}
3588
+ >
3589
+ <Icon
3590
+ style={{ marginLeft: 5 }}
3591
+ icon="filter"
3592
+ iconSize={extraCompact ? 14 : undefined}
3593
+ onClick={() => {
3594
+ setColumnFilterMenuOpen(!columnFilterMenuOpen);
3595
+ }}
3596
+ className={classNames("tg-filter-menu-button", {
3597
+ "tg-active-filter": !!filterActiveForColumn
3598
+ })}
3599
+ />
3600
+ <FilterMenu
3601
+ togglePopover={() => {
3602
+ setColumnFilterMenuOpen(false);
3603
+ }}
3604
+ {...rest}
3605
+ />
3606
+ </Popover>
3607
+ );
3608
+ }
3609
+
3610
+ function getLastSelectedEntity(idMap) {
3611
+ let lastSelectedEnt;
3612
+ let latestTime;
3613
+ forEach(idMap, ({ time, entity }) => {
3614
+ if (!latestTime || time > latestTime) {
3615
+ lastSelectedEnt = entity;
3616
+ latestTime = time;
3617
+ }
3618
+ });
3619
+ return lastSelectedEnt;
3620
+ }
3621
+
3622
+ function getNewEntToSelect({
3623
+ type,
3624
+ lastSelectedIndex,
3625
+ entities,
3626
+ isEntityDisabled
3627
+ }) {
3628
+ let newIndexToSelect;
3629
+ if (type === "up") {
3630
+ newIndexToSelect = lastSelectedIndex - 1;
3631
+ } else {
3632
+ newIndexToSelect = lastSelectedIndex + 1;
3633
+ }
3634
+ const newEntToSelect = entities[newIndexToSelect];
3635
+ if (!newEntToSelect) return;
3636
+ if (isEntityDisabled && isEntityDisabled(newEntToSelect)) {
3637
+ return getNewEntToSelect({
3638
+ type,
3639
+ lastSelectedIndex: newIndexToSelect,
3640
+ entities,
3641
+ isEntityDisabled
3642
+ });
3643
+ } else {
3644
+ return newEntToSelect;
3645
+ }
3646
+ }
3647
+
3648
+ function getAllRows(e) {
3649
+ const el = e.target.querySelector(".data-table-container")
3650
+ ? e.target.querySelector(".data-table-container")
3651
+ : e.target.closest(".data-table-container");
3652
+
3653
+ const allRowEls = el.querySelectorAll(".rt-tr");
3654
+ if (!allRowEls || !allRowEls.length) {
3655
+ return;
3656
+ }
3657
+ return allRowEls;
3658
+ }
3659
+
3660
+ function EditableCell({
3661
+ shouldSelectAll,
3662
+ stopSelectAll,
3663
+ initialValue,
3664
+ finishEdit,
3665
+ cancelEdit,
3666
+ isNumeric,
3667
+ dataTest
3668
+ }) {
3669
+ const [v, setV] = useState(initialValue);
3670
+ return (
3671
+ <input
3672
+ style={{
3673
+ border: 0,
3674
+ width: "95%",
3675
+ fontSize: 12,
3676
+ background: "none"
3677
+ }}
3678
+ ref={r => {
3679
+ if (shouldSelectAll && r) {
3680
+ r?.select();
3681
+ stopSelectAll();
3682
+ }
3683
+ }}
3684
+ {...dataTest}
3685
+ type={isNumeric ? "number" : undefined}
3686
+ value={v}
3687
+ autoFocus
3688
+ onKeyDown={e => {
3689
+ if (e.key === "Enter") {
3690
+ finishEdit(v);
3691
+ e.stopPropagation();
3692
+ } else if (e.key === "Escape") {
3693
+ e.stopPropagation();
3694
+ cancelEdit();
3695
+ }
3696
+ }}
3697
+ onBlur={() => {
3698
+ finishEdit(v);
3699
+ }}
3700
+ onChange={e => {
3701
+ setV(e.target.value);
3702
+ }}
3703
+ ></input>
3704
+ );
3705
+ }
3706
+
3707
+ function DropdownCell({
3708
+ options,
3709
+ isMulti,
3710
+ initialValue,
3711
+ finishEdit,
3712
+ cancelEdit,
3713
+ dataTest
3714
+ }) {
3715
+ const [v, setV] = useState(
3716
+ isMulti
3717
+ ? initialValue.split(",").map(v => ({ value: v, label: v }))
3718
+ : initialValue
3719
+ );
3720
+ return (
3721
+ <div
3722
+ className={classNames("tg-dropdown-cell-edit-container", {
3723
+ "tg-dropdown-cell-edit-container-multi": isMulti
3724
+ })}
3725
+ >
3726
+ <TgSelect
3727
+ small
3728
+ multi={isMulti}
3729
+ autoOpen
3730
+ value={v}
3731
+ onChange={val => {
3732
+ if (isMulti) {
3733
+ setV(val);
3734
+ return;
3735
+ }
3736
+ finishEdit(val ? val.value : null);
3737
+ }}
3738
+ {...dataTest}
3739
+ popoverProps={{
3740
+ onClose: e => {
3741
+ if (isMulti) {
3742
+ if (e && e.key === "Escape") {
3743
+ cancelEdit();
3744
+ } else {
3745
+ finishEdit(
3746
+ v && v.map
3747
+ ? v
3748
+ .map(v => v.value)
3749
+ .filter(v => v)
3750
+ .join(",")
3751
+ : v
3752
+ );
3753
+ }
3754
+ } else {
3755
+ cancelEdit();
3756
+ }
3757
+ }
3758
+ }}
3759
+ options={options.map(value => ({ label: value, value }))}
3760
+ ></TgSelect>
3761
+ </div>
3762
+ );
3763
+ }
3764
+
3765
+ function getFieldPathToIndex(schema) {
3766
+ const fieldToIndex = {};
3767
+ schema.fields.forEach((f, i) => {
3768
+ fieldToIndex[f.path] = i;
3769
+ });
3770
+ return fieldToIndex;
3771
+ }
3772
+
3773
+ function getFieldPathToField(schema) {
3774
+ const fieldPathToField = {};
3775
+ schema.fields.forEach(f => {
3776
+ fieldPathToField[f.path] = f;
3777
+ });
3778
+ return fieldPathToField;
3779
+ }
3780
+
3781
+ const defaultParsePaste = str => {
3782
+ return str.split(/\r\n|\n|\r/).map(row => row.split("\t"));
3783
+ };
3784
+
3785
+ function getEntityIdToEntity(entities) {
3786
+ const entityIdToEntity = {};
3787
+ entities.forEach((e, i) => {
3788
+ entityIdToEntity[getIdOrCodeOrIndex(e, i)] = { e, i };
3789
+ });
3790
+ return entityIdToEntity;
3791
+ }
3792
+
3793
+ function endsWithNumber(str) {
3794
+ return /[0-9]+$/.test(str);
3795
+ }
3796
+
3797
+ function getNumberStrAtEnd(str) {
3798
+ if (endsWithNumber(str)) {
3799
+ return str.match(/[0-9]+$/)[0];
3800
+ }
3801
+
3802
+ return null;
3803
+ }
3804
+
3805
+ function stripNumberAtEnd(str) {
3806
+ return str?.replace?.(getNumberStrAtEnd(str), "");
3807
+ }
3808
+
3809
+ export function isEntityClean(e) {
3810
+ let isClean = true;
3811
+ some(e, (val, key) => {
3812
+ if (key === "id") return;
3813
+ if (key === "_isClean") return;
3814
+ if (val) {
3815
+ isClean = false;
3816
+ return true;
3817
+ }
3818
+ });
3819
+ return isClean;
3820
+ }
3821
+
3822
+ const formatPasteData = ({ schema, newVal, path }) => {
3823
+ const pathToField = getFieldPathToField(schema);
3824
+ const column = pathToField[path];
3825
+ if (column.type === "genericSelect") {
3826
+ if (newVal?.__genSelCol === path) {
3827
+ newVal = newVal.__strVal;
3828
+ } else {
3829
+ newVal = undefined;
3830
+ }
3831
+ } else {
3832
+ newVal = Object.hasOwn(newVal, "__strVal") ? newVal.__strVal : newVal;
3833
+ }
3834
+ return newVal;
3835
+ };
3836
+
3837
+ export function removeCleanRows(reduxFormEntities, reduxFormCellValidation) {
3838
+ const toFilterOut = {};
3839
+ const entsToUse = (reduxFormEntities || []).filter(e => {
3840
+ if (!(e._isClean || isEntityClean(e))) return true;
3841
+ else {
3842
+ toFilterOut[getIdOrCodeOrIndex(e)] = true;
3843
+ return false;
3844
+ }
3845
+ });
3846
+
3847
+ const validationToUse = {};
3848
+ forEach(reduxFormCellValidation, (v, k) => {
3849
+ const [rowId] = k.split(":");
3850
+ if (!toFilterOut[rowId]) {
3851
+ validationToUse[k] = v;
3852
+ }
3853
+ });
3854
+ return { entsToUse, validationToUse };
3855
+ }