@teselagen/ui 0.7.33-beta.6 → 0.7.34

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 (114) hide show
  1. package/AdvancedOptions.js +33 -0
  2. package/AssignDefaultsModeContext.js +22 -0
  3. package/CellDragHandle.js +132 -0
  4. package/ColumnFilterMenu.js +62 -0
  5. package/Columns.js +979 -0
  6. package/DataTable/utils/queryParams.d.ts +14 -7
  7. package/DisabledLoadingComponent.js +15 -0
  8. package/DisplayOptions.js +199 -0
  9. package/DropdownButton.js +36 -0
  10. package/DropdownCell.js +61 -0
  11. package/EditableCell.js +44 -0
  12. package/FillWindow.css +6 -0
  13. package/FillWindow.js +69 -0
  14. package/FilterAndSortMenu.js +391 -0
  15. package/FormSeparator.js +9 -0
  16. package/LoadingDots.js +14 -0
  17. package/MatchHeaders.js +234 -0
  18. package/PagingTool.js +225 -0
  19. package/RenderCell.js +191 -0
  20. package/SearchBar.js +69 -0
  21. package/SimpleStepViz.js +22 -0
  22. package/SortableColumns.js +100 -0
  23. package/TableFormTrackerContext.js +10 -0
  24. package/Tag.js +112 -0
  25. package/ThComponent.js +44 -0
  26. package/TimelineEvent.js +31 -0
  27. package/UploadCsvWizard.css +4 -0
  28. package/UploadCsvWizard.js +719 -0
  29. package/Uploader.js +1278 -0
  30. package/adHoc.js +10 -0
  31. package/autoTooltip.js +201 -0
  32. package/basicHandleActionsWithFullState.js +14 -0
  33. package/browserUtils.js +3 -0
  34. package/combineReducersWithFullState.js +14 -0
  35. package/commandControls.js +82 -0
  36. package/commandUtils.js +112 -0
  37. package/constants.js +1 -0
  38. package/convertSchema.js +69 -0
  39. package/customIcons.js +361 -0
  40. package/dataTableEnhancer.js +41 -0
  41. package/defaultFormatters.js +32 -0
  42. package/defaultValidators.js +40 -0
  43. package/determineBlackOrWhiteTextColor.js +4 -0
  44. package/editCellHelper.js +44 -0
  45. package/formatPasteData.js +16 -0
  46. package/getAllRows.js +11 -0
  47. package/getCellCopyText.js +7 -0
  48. package/getCellInfo.js +36 -0
  49. package/getCellVal.js +20 -0
  50. package/getDayjsFormatter.js +35 -0
  51. package/getFieldPathToField.js +7 -0
  52. package/getIdOrCodeOrIndex.js +9 -0
  53. package/getLastSelectedEntity.js +11 -0
  54. package/getNewEntToSelect.js +25 -0
  55. package/getNewName.js +31 -0
  56. package/getRowCopyText.js +28 -0
  57. package/getTableConfigFromStorage.js +5 -0
  58. package/getTextFromEl.js +28 -0
  59. package/getVals.js +8 -0
  60. package/handleCopyColumn.js +21 -0
  61. package/handleCopyHelper.js +15 -0
  62. package/handleCopyRows.js +23 -0
  63. package/handleCopyTable.js +16 -0
  64. package/handlerHelpers.js +24 -0
  65. package/hotkeyUtils.js +131 -0
  66. package/index.cjs.js +972 -837
  67. package/index.d.ts +0 -1
  68. package/index.es.js +972 -837
  69. package/index.js +1 -0
  70. package/isBeingCalledExcessively.js +31 -0
  71. package/isBottomRightCornerOfRectangle.js +20 -0
  72. package/isEntityClean.js +15 -0
  73. package/isTruthy.js +12 -0
  74. package/isValueEmpty.js +3 -0
  75. package/itemUpload.js +84 -0
  76. package/menuUtils.js +433 -0
  77. package/package.json +1 -2
  78. package/popoverOverflowModifiers.js +11 -0
  79. package/primarySelectedValue.js +1 -0
  80. package/pureNoFunc.js +31 -0
  81. package/queryParams.js +1058 -0
  82. package/removeCleanRows.js +22 -0
  83. package/renderOnDoc.js +32 -0
  84. package/rerenderOnWindowResize.js +26 -0
  85. package/rowClick.js +181 -0
  86. package/selection.js +8 -0
  87. package/showAppSpinner.js +12 -0
  88. package/showDialogOnDocBody.js +33 -0
  89. package/showProgressToast.js +22 -0
  90. package/sortify.js +73 -0
  91. package/style.css +13 -0
  92. package/tagUtils.js +45 -0
  93. package/tgFormValues.js +35 -0
  94. package/tg_modalState.js +47 -0
  95. package/throwFormError.js +16 -0
  96. package/toastr.js +148 -0
  97. package/tryToMatchSchemas.js +264 -0
  98. package/typeToCommonType.js +6 -0
  99. package/useDeepEqualMemo.js +15 -0
  100. package/useDialog.js +63 -0
  101. package/useStableReference.js +9 -0
  102. package/useTableEntities.js +38 -0
  103. package/useTraceUpdate.js +19 -0
  104. package/utils.js +37 -0
  105. package/validateTableWideErrors.js +160 -0
  106. package/viewColumn.js +97 -0
  107. package/withField.js +20 -0
  108. package/withFields.js +11 -0
  109. package/withLocalStorage.js +11 -0
  110. package/withSelectTableRecords.js +43 -0
  111. package/withSelectedEntities.js +65 -0
  112. package/withStore.js +10 -0
  113. package/withTableParams.js +301 -0
  114. package/wrapDialog.js +116 -0
@@ -0,0 +1,391 @@
1
+ import React, { useState } from "react";
2
+ import { DateInput, DateRangeInput } from "@blueprintjs/datetime";
3
+ import { camelCase } from "lodash-es";
4
+ import classNames from "classnames";
5
+ import {
6
+ Menu,
7
+ Intent,
8
+ MenuDivider,
9
+ InputGroup,
10
+ Classes,
11
+ NumericInput,
12
+ MenuItem
13
+ } from "@blueprintjs/core";
14
+ import dayjs from "dayjs";
15
+
16
+ import getDayjsFormatter from "../utils/getDayjsFormatter";
17
+ import { onEnterHelper } from "../utils/handlerHelpers";
18
+ import DialogFooter from "../DialogFooter";
19
+ import TgSelect from "../TgSelect";
20
+ import "@teselagen/react-table/react-table.css";
21
+ import "./style.css";
22
+ import "../toastr";
23
+
24
+ const filterTypesDictionary = {
25
+ none: "",
26
+ startsWith: "text",
27
+ endsWith: "text",
28
+ contains: "text",
29
+ notContains: "text",
30
+ isExactly: "text",
31
+ isEmpty: "text",
32
+ notEmpty: "text",
33
+ inList: "list",
34
+ notInList: "list",
35
+ true: "boolean",
36
+ false: "boolean",
37
+ dateIs: "date",
38
+ notBetween: "dateRange",
39
+ isBetween: "dateRange",
40
+ isBefore: "date",
41
+ isAfter: "date",
42
+ greaterThan: "number",
43
+ lessThan: "number",
44
+ inRange: "numberRange",
45
+ outsideRange: "numberRange",
46
+ equalTo: "number",
47
+ regex: "text"
48
+ };
49
+
50
+ const isInvalidFilterValue = value => {
51
+ if (Array.isArray(value) && value.length) {
52
+ return value.some(item => isInvalidFilterValue(item));
53
+ }
54
+ return value === "" || value === undefined || value.length === 0;
55
+ };
56
+
57
+ const FilterAndSortMenu = ({
58
+ dataType,
59
+ togglePopover,
60
+ filterOn,
61
+ addFilters,
62
+ removeSingleFilter,
63
+ currentFilter
64
+ }) => {
65
+ const [selectedFilter, setSelectedFilter] = useState(
66
+ currentFilter?.selectedFilter ?? camelCase(getFilterMenuItems(dataType)[0])
67
+ );
68
+ const [filterValue, setFilterValue] = useState(
69
+ currentFilter?.filterValue ?? ""
70
+ );
71
+
72
+ const handleFilterChange = selectedFilter => {
73
+ if (
74
+ filterValue &&
75
+ !Array.isArray(filterValue) &&
76
+ filterTypesDictionary[selectedFilter] === "list"
77
+ ) {
78
+ setFilterValue(filterValue?.split(" ") || []);
79
+ } else if (
80
+ filterTypesDictionary[selectedFilter] === "text" &&
81
+ Array.isArray(filterValue)
82
+ ) {
83
+ setFilterValue(filterValue.join(" "));
84
+ }
85
+ setSelectedFilter(camelCase(selectedFilter));
86
+ };
87
+
88
+ const handleFilterValueChange = filterValue => setFilterValue(filterValue);
89
+
90
+ const handleFilterSubmit = () => {
91
+ const ccSelectedFilter = camelCase(selectedFilter);
92
+ let filterValToUse = filterValue;
93
+ if (ccSelectedFilter === "true" || ccSelectedFilter === "false") {
94
+ //manually set the filterValue because none is set when type=boolean
95
+ filterValToUse = ccSelectedFilter;
96
+ } else if (ccSelectedFilter === "notEmpty") {
97
+ // manually set filter value (nothing is selected by user)
98
+ filterValToUse = true;
99
+ } else if (ccSelectedFilter === "isEmpty") {
100
+ // manually set filter value (nothing is selected by user)
101
+ filterValToUse = false;
102
+ } else if (
103
+ ccSelectedFilter === "inList" ||
104
+ ccSelectedFilter === "notInList"
105
+ ) {
106
+ if (dataType === "number") {
107
+ filterValToUse =
108
+ filterValue &&
109
+ filterValue.map(val => parseFloat(val.replaceAll(",", "")));
110
+ }
111
+ }
112
+
113
+ if (isInvalidFilterValue(filterValToUse)) {
114
+ togglePopover();
115
+ return removeSingleFilter(filterOn);
116
+ }
117
+ addFilters([
118
+ {
119
+ filterOn,
120
+ selectedFilter: ccSelectedFilter,
121
+ filterValue: filterValToUse
122
+ }
123
+ ]);
124
+ togglePopover();
125
+ };
126
+
127
+ const filterMenuItems = getFilterMenuItems(dataType);
128
+ const ccSelectedFilter = camelCase(selectedFilter);
129
+ const requiresValue = ccSelectedFilter && ccSelectedFilter !== "none";
130
+
131
+ return (
132
+ <Menu className="data-table-header-menu">
133
+ <div className="custom-menu-item">
134
+ <div className={classNames(Classes.SELECT, Classes.FILL)}>
135
+ <select
136
+ onChange={function (e) {
137
+ const ccSelectedFilter = camelCase(e.target.value);
138
+ handleFilterChange(ccSelectedFilter);
139
+ }}
140
+ value={ccSelectedFilter}
141
+ >
142
+ {filterMenuItems.map(function (menuItem, index) {
143
+ return (
144
+ <option key={index} value={camelCase(menuItem)}>
145
+ {menuItem}
146
+ </option>
147
+ );
148
+ })}
149
+ </select>
150
+ </div>
151
+ </div>
152
+ <div className="custom-menu-item">
153
+ <FilterInput
154
+ dataType={dataType}
155
+ requiresValue={requiresValue}
156
+ handleFilterSubmit={handleFilterSubmit}
157
+ filterValue={filterValue}
158
+ handleFilterValueChange={handleFilterValueChange}
159
+ filterSubType={camelCase(selectedFilter)}
160
+ filterType={filterTypesDictionary[camelCase(selectedFilter)]}
161
+ />
162
+ </div>
163
+ <MenuDivider />
164
+ <DialogFooter
165
+ secondaryClassName={Classes.POPOVER_DISMISS}
166
+ onClick={() => {
167
+ handleFilterSubmit();
168
+ }}
169
+ intent={Intent.SUCCESS}
170
+ text="Filter"
171
+ secondaryText="Clear"
172
+ secondaryIntent={Intent.DANGER}
173
+ secondaryAction={() => {
174
+ if (currentFilter) removeSingleFilter(currentFilter.filterOn);
175
+ }}
176
+ />
177
+ </Menu>
178
+ );
179
+ };
180
+
181
+ export default FilterAndSortMenu;
182
+
183
+ const dateMinMaxHelpers = {
184
+ minDate: dayjs().subtract(25, "years").toDate(),
185
+ maxDate: dayjs().add(25, "years").toDate()
186
+ };
187
+
188
+ const renderCreateNewOption = (query, active, handleClick) => (
189
+ <MenuItem
190
+ icon="add"
191
+ text={query}
192
+ active={active}
193
+ onClick={handleClick}
194
+ shouldDismissPopover={false}
195
+ />
196
+ );
197
+
198
+ const FilterInput = ({
199
+ handleFilterValueChange,
200
+ handleFilterSubmit,
201
+ filterValue,
202
+ filterSubType,
203
+ filterType
204
+ }) => {
205
+ //Options: Text, Single number (before, after, equals), 2 numbers (range),
206
+ //Single Date (before, after, on), 2 dates (range)
207
+ let inputGroup = <div />;
208
+ switch (filterType) {
209
+ case "text":
210
+ inputGroup =
211
+ filterSubType === "notEmpty" || filterSubType === "isEmpty" ? (
212
+ <div />
213
+ ) : (
214
+ <div className="custom-menu-item">
215
+ <InputGroup
216
+ placeholder="Value"
217
+ onChange={function (e) {
218
+ handleFilterValueChange(e.target.value);
219
+ }}
220
+ autoFocus
221
+ {...onEnterHelper(handleFilterSubmit)}
222
+ value={filterValue}
223
+ />
224
+ </div>
225
+ );
226
+ break;
227
+ case "list":
228
+ inputGroup = (
229
+ <div className="custom-menu-item">
230
+ <TgSelect
231
+ placeholder="Add item"
232
+ renderCreateNewOption={renderCreateNewOption}
233
+ noResults={null}
234
+ multi={true}
235
+ creatable={true}
236
+ value={(filterValue || []).map(val => ({
237
+ label: val,
238
+ value: val
239
+ }))}
240
+ onChange={selectedOptions => {
241
+ selectedOptions.some(opt => opt.value === "")
242
+ ? handleFilterSubmit()
243
+ : handleFilterValueChange(
244
+ selectedOptions.map(opt => opt.value)
245
+ );
246
+ }}
247
+ options={[]}
248
+ />
249
+ </div>
250
+ );
251
+ break;
252
+ case "number":
253
+ inputGroup = (
254
+ <div className="custom-menu-item">
255
+ <NumericInput
256
+ placeholder="Value"
257
+ onValueChange={function (numVal) {
258
+ handleFilterValueChange(isNaN(numVal) ? 0 : numVal);
259
+ }}
260
+ autoFocus
261
+ {...onEnterHelper(handleFilterSubmit)}
262
+ value={filterValue}
263
+ />
264
+ </div>
265
+ );
266
+ break;
267
+ case "numberRange":
268
+ inputGroup = (
269
+ <div className="custom-menu-item">
270
+ <NumericInput
271
+ placeholder="Low"
272
+ onValueChange={function (numVal) {
273
+ handleFilterValueChange([
274
+ isNaN(numVal) ? 0 : numVal,
275
+ filterValue[1]
276
+ ]);
277
+ }}
278
+ {...onEnterHelper(handleFilterSubmit)}
279
+ value={filterValue && filterValue[0]}
280
+ />
281
+ <NumericInput
282
+ placeholder="High"
283
+ onValueChange={function (numVal) {
284
+ handleFilterValueChange([
285
+ filterValue[0],
286
+ isNaN(numVal) ? 0 : numVal
287
+ ]);
288
+ }}
289
+ {...onEnterHelper(handleFilterSubmit)}
290
+ value={filterValue && filterValue[1]}
291
+ />
292
+ </div>
293
+ );
294
+ break;
295
+ case "date":
296
+ inputGroup = (
297
+ <div className="custom-menu-item">
298
+ <DateInput
299
+ value={filterValue ? dayjs(filterValue).toDate() : undefined}
300
+ {...getDayjsFormatter("L")}
301
+ {...dateMinMaxHelpers}
302
+ onChange={selectedDates => {
303
+ handleFilterValueChange(selectedDates);
304
+ }}
305
+ />
306
+ </div>
307
+ );
308
+ break;
309
+ case "dateRange":
310
+ // eslint-disable-next-line no-case-declarations
311
+ let filterValueToUse;
312
+ if (Array.isArray(filterValue)) {
313
+ filterValueToUse = filterValue;
314
+ } else {
315
+ filterValueToUse =
316
+ filterValue && filterValue.split && filterValue.split(";");
317
+ }
318
+ inputGroup = (
319
+ <div className="custom-menu-item">
320
+ <DateRangeInput
321
+ value={
322
+ filterValueToUse && filterValueToUse[0] && filterValueToUse[1]
323
+ ? [new Date(filterValueToUse[0]), new Date(filterValueToUse[1])]
324
+ : undefined
325
+ }
326
+ popoverProps={{
327
+ captureDismiss: true
328
+ }}
329
+ formatDate={date => (date == null ? "" : date.toLocaleDateString())}
330
+ parseDate={str => new Date(Date.parse(str))}
331
+ placeholder="JS Date"
332
+ {...dateMinMaxHelpers}
333
+ onChange={selectedDates => {
334
+ if (selectedDates[0] && selectedDates[1]) {
335
+ handleFilterValueChange(selectedDates);
336
+ }
337
+ }}
338
+ />
339
+ </div>
340
+ );
341
+ break;
342
+ default:
343
+ // to do
344
+ }
345
+ return inputGroup;
346
+ };
347
+
348
+ function getFilterMenuItems(dataType) {
349
+ let filterMenuItems = [];
350
+ if (dataType === "string") {
351
+ filterMenuItems = [
352
+ "Contains",
353
+ "Not Contains",
354
+ "Starts With",
355
+ "Ends With",
356
+ "Is Exactly",
357
+ "Regex",
358
+ "In List",
359
+ "Not In List",
360
+ "Is Empty",
361
+ "Not Empty"
362
+ ];
363
+ } else if (dataType === "lookup") {
364
+ filterMenuItems = [
365
+ "Contains",
366
+ "Not Contains",
367
+ "Starts With",
368
+ "Ends With",
369
+ "Is Exactly",
370
+ "Regex"
371
+ ];
372
+ } else if (dataType === "boolean") {
373
+ filterMenuItems = ["True", "False"];
374
+ } else if (dataType === "number" || dataType === "integer") {
375
+ // else if (dataType === "lookup") {
376
+ // filterMenuItems = ["None"];
377
+ // }
378
+ filterMenuItems = [
379
+ "Greater Than",
380
+ "Less Than",
381
+ "In Range",
382
+ "Outside Range",
383
+ "Equal To",
384
+ "In List",
385
+ "Not In List"
386
+ ];
387
+ } else if (dataType === "timestamp") {
388
+ filterMenuItems = ["Is Between", "Not Between", "Is Before", "Is After"];
389
+ }
390
+ return filterMenuItems;
391
+ }
@@ -0,0 +1,9 @@
1
+ import React from "react";
2
+ import "./style.css";
3
+ export const FormSeparator = ({ label = "or" } = {}) => {
4
+ return (
5
+ <div className="form-separator">
6
+ <p style={{ opacity: 0.8 }}>{label}</p>
7
+ </div>
8
+ );
9
+ };
package/LoadingDots.js ADDED
@@ -0,0 +1,14 @@
1
+ import React, { useEffect, useState } from "react";
2
+
3
+ export const LoadingDots = () => {
4
+ const [dots, setDots] = useState("");
5
+ useEffect(() => {
6
+ const interval = setInterval(() => {
7
+ setDots(dots => {
8
+ return dots.length === 3 ? "" : dots + ".";
9
+ });
10
+ }, 500);
11
+ return () => clearInterval(interval);
12
+ }, []);
13
+ return <span>{dots}</span>;
14
+ };
@@ -0,0 +1,234 @@
1
+ import React, { useMemo } from "react";
2
+ import { Callout, Card, Intent } from "@blueprintjs/core";
3
+ import immer, { setAutoFreeze } from "immer";
4
+ import { flatMap, forEach } from "lodash-es";
5
+ import { ReactSelectField } from "./FormComponents";
6
+ import showConfirmationDialog from "./showConfirmationDialog";
7
+ import { startCase } from "lodash-es";
8
+ import { typeToCommonType } from "./typeToCommonType";
9
+ import { camelCase } from "lodash-es";
10
+ import { change } from "redux-form";
11
+ import { useDispatch } from "react-redux";
12
+
13
+ setAutoFreeze(false);
14
+ export const MatchHeaders = ({
15
+ csvValidationIssue,
16
+ datatableFormName,
17
+ datatableFormNames: _datatableFormNames,
18
+ doAllFilesHaveSameHeaders,
19
+ fileIndex,
20
+ filesWIssues,
21
+ ignoredHeadersMsg,
22
+ matchedHeaders,
23
+ onMultiFileUploadSubmit,
24
+ reduxFormEntitiesArray,
25
+ searchResults,
26
+ setFilesWIssues,
27
+ userSchema
28
+ }) => {
29
+ const datatableFormNames = _datatableFormNames || [datatableFormName];
30
+ const dispatch = useDispatch();
31
+ const flippedMatchedHeaders = useMemo(() => {
32
+ const _flippedMatchedHeaders = {};
33
+ forEach(matchedHeaders, (v, k) => {
34
+ if (v) _flippedMatchedHeaders[v] = k;
35
+ });
36
+ return _flippedMatchedHeaders;
37
+ }, [matchedHeaders]);
38
+
39
+ return (
40
+ <div style={{ maxWidth: 500 }}>
41
+ {!onMultiFileUploadSubmit && (
42
+ <Callout style={{ width: "fit-content" }} intent="warning">
43
+ {csvValidationIssue}
44
+ </Callout>
45
+ )}
46
+ {!onMultiFileUploadSubmit && ignoredHeadersMsg && (
47
+ <Callout style={{ width: "fit-content" }} intent="warning">
48
+ {ignoredHeadersMsg}
49
+ </Callout>
50
+ )}
51
+ <br />
52
+ <tr
53
+ style={{
54
+ display: "flex",
55
+ minHeight: 50,
56
+ alignItems: "center",
57
+ justifyContent: "space-between"
58
+ }}
59
+ >
60
+ <td
61
+ style={{
62
+ width: 200,
63
+ marginLeft: 20,
64
+ display: "flex",
65
+ fontWeight: "bold"
66
+ }}
67
+ >
68
+ Accepted Headers
69
+ </td>
70
+ <td
71
+ style={{
72
+ width: 200,
73
+ marginLeft: 20,
74
+ display: "flex",
75
+ fontWeight: "bold"
76
+ }}
77
+ >
78
+ Your Headers
79
+ </td>
80
+ <td
81
+ style={{
82
+ fontWeight: "bold",
83
+ marginLeft: 30
84
+ }}
85
+ >
86
+ Data Preview
87
+ </td>
88
+ </tr>
89
+ {searchResults.map(({ path, displayName, type }, i) => {
90
+ const userMatchedHeader = matchedHeaders[path];
91
+ const opts = flatMap(userSchema.fields, ({ path: pathInner }) => {
92
+ if (
93
+ pathInner !== userMatchedHeader &&
94
+ flippedMatchedHeaders[pathInner]
95
+ ) {
96
+ return [];
97
+ }
98
+ return {
99
+ value: pathInner,
100
+ label: pathInner
101
+ };
102
+ }).sort((a, b) => {
103
+ const ra = searchResults[i].matches
104
+ .map(m => m.item.path)
105
+ .indexOf(a.value);
106
+ const rb = searchResults[i].matches
107
+ .map(m => m.item.path)
108
+ .indexOf(b.value);
109
+ if (!ra) return -1;
110
+ if (!rb) return 1;
111
+ return rb - ra;
112
+ });
113
+ return (
114
+ <Card style={{ padding: 2 }} key={`field-${i}`}>
115
+ <table>
116
+ <tbody>
117
+ <tr
118
+ style={{
119
+ display: "flex",
120
+ minHeight: 50,
121
+ alignItems: "center",
122
+ justifyContent: "space-between"
123
+ }}
124
+ >
125
+ <td
126
+ style={{
127
+ width: 200,
128
+ display: "flex"
129
+ }}
130
+ >
131
+ <div
132
+ style={{
133
+ paddingTop: 2,
134
+ marginLeft: 15,
135
+ fontSize: 15
136
+ }}
137
+ >
138
+ <span
139
+ data-tip={`Column Type: ${
140
+ typeToCommonType[type || "string"] || type
141
+ }`}
142
+ >
143
+ {displayName || startCase(camelCase(path))}
144
+ </span>
145
+ </div>
146
+ </td>
147
+ <td style={{ width: 200 }}>
148
+ <ReactSelectField
149
+ noMarginBottom
150
+ tooltipError
151
+ beforeOnChange={async () => {
152
+ const clearEntities = () => {
153
+ datatableFormNames.forEach(name => {
154
+ dispatch(change(name, "reduxFormEntities", null));
155
+ });
156
+ };
157
+ if (reduxFormEntitiesArray.some(r => r?.isDirty)) {
158
+ //when the column mapping changes, update the column in reduxFormEntities (if reduxFormEntities exists)
159
+ const doAction = await showConfirmationDialog({
160
+ text: "Are you sure you want to edit the columm mapping? This will clear any changes you've already made to the table data",
161
+ intent: Intent.DANGER,
162
+ confirmButtonText: "Yes",
163
+ cancelButtonText: "No"
164
+ // canEscapeKeyCancel: true //this is false by default
165
+ });
166
+ if (doAction) {
167
+ clearEntities();
168
+ } else {
169
+ return { stopEarly: true };
170
+ }
171
+ } else {
172
+ clearEntities();
173
+ return { stopEarly: false };
174
+ }
175
+ }}
176
+ onChange={val => {
177
+ setFilesWIssues(
178
+ immer(filesWIssues, files => {
179
+ files.forEach((f, i) => {
180
+ const isCurrentFile = fileIndex === i;
181
+ if (isCurrentFile || doAllFilesHaveSameHeaders) {
182
+ f.matchedHeaders[path] = val;
183
+ }
184
+ });
185
+ })
186
+ );
187
+ }}
188
+ name={path}
189
+ // isRequired={!allowEmpty && defaultValue === undefined}
190
+ defaultValue={userMatchedHeader}
191
+ options={opts}
192
+ />
193
+ </td>
194
+ <td
195
+ style={{
196
+ marginTop: 10,
197
+ marginBottom: 10,
198
+ marginLeft: 20,
199
+ fontSize: 10 /* color: Colors.RED1 */
200
+ }}
201
+ >
202
+ {userMatchedHeader &&
203
+ [
204
+ // { [userMatchedHeader]: "Preview:" },
205
+ ...(userSchema.userData?.slice(0, 3) || [])
206
+ // { [userMatchedHeader]: "..." }
207
+ ].map((row, i) => {
208
+ return (
209
+ <div
210
+ style={{
211
+ maxWidth: 70,
212
+ overflow: "hidden",
213
+ textOverflow: "ellipsis",
214
+ whiteSpace: "nowrap"
215
+ }}
216
+ key={`userMatchedHeader-${i}`}
217
+ >
218
+ {row?.[userMatchedHeader] || ""}
219
+ </div>
220
+ );
221
+ })}
222
+ {/* {!allowEmpty &&
223
+ defaultValue === undefined &&
224
+ "(Required)"} */}
225
+ </td>
226
+ </tr>
227
+ </tbody>
228
+ </table>
229
+ </Card>
230
+ );
231
+ })}
232
+ </div>
233
+ );
234
+ };