@teselagen/ui 0.7.33-beta.5 → 0.7.33

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 (119) 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 +18 -9
  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 +970 -826
  67. package/index.d.ts +0 -1
  68. package/index.es.js +970 -826
  69. package/index.js +196 -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/src/DataTable/index.js +1 -1
  92. package/src/DataTable/utils/filterLocalEntitiesToHasura.js +14 -0
  93. package/src/DataTable/utils/filterLocalEntitiesToHasura.test.js +49 -0
  94. package/src/DataTable/utils/queryParams.js +12 -9
  95. package/src/DataTable/utils/tableQueryParamsToHasuraClauses.js +146 -143
  96. package/style.css +29 -0
  97. package/tagUtils.js +45 -0
  98. package/tgFormValues.js +35 -0
  99. package/tg_modalState.js +47 -0
  100. package/throwFormError.js +16 -0
  101. package/toastr.js +148 -0
  102. package/tryToMatchSchemas.js +264 -0
  103. package/typeToCommonType.js +6 -0
  104. package/useDeepEqualMemo.js +15 -0
  105. package/useDialog.js +63 -0
  106. package/useStableReference.js +9 -0
  107. package/useTableEntities.js +38 -0
  108. package/useTraceUpdate.js +19 -0
  109. package/utils.js +37 -0
  110. package/validateTableWideErrors.js +160 -0
  111. package/viewColumn.js +97 -0
  112. package/withField.js +20 -0
  113. package/withFields.js +11 -0
  114. package/withLocalStorage.js +11 -0
  115. package/withSelectTableRecords.js +43 -0
  116. package/withSelectedEntities.js +65 -0
  117. package/withStore.js +10 -0
  118. package/withTableParams.js +301 -0
  119. package/wrapDialog.js +116 -0
@@ -0,0 +1,719 @@
1
+ import React, { useState, useEffect, useMemo, useCallback } from "react";
2
+ import { reduxForm, change, formValueSelector, destroy } from "redux-form";
3
+ import { Callout, Icon, Intent, Tab, Tabs } from "@blueprintjs/core";
4
+ import immer from "immer";
5
+ import "./UploadCsvWizard.css";
6
+ import { isFunction } from "lodash-es";
7
+ import { compose } from "recompose";
8
+ import SimpleStepViz from "./SimpleStepViz";
9
+ import { nanoid } from "nanoid";
10
+ import { some } from "lodash-es";
11
+ import { times } from "lodash-es";
12
+ import DialogFooter from "./DialogFooter";
13
+ import DataTable from "./DataTable";
14
+ import { useDeepEqualMemo } from "./utils/hooks";
15
+ import { removeCleanRows } from "./DataTable/utils";
16
+ import wrapDialog from "./wrapDialog";
17
+ import { omit } from "lodash-es";
18
+ import { useDispatch, useSelector } from "react-redux";
19
+ import { MatchHeaders } from "./MatchHeaders";
20
+ import { isEmpty } from "lodash-es";
21
+ import { addSpecialPropToAsyncErrs } from "./FormComponents/tryToMatchSchemas";
22
+ import { cloneDeep } from "lodash-es";
23
+ import { InputField } from "./FormComponents";
24
+
25
+ const asyncValidateHelper = async (
26
+ validateAgainstSchema,
27
+ currentEnts,
28
+ changeForm,
29
+ tableName
30
+ ) => {
31
+ if (!validateAgainstSchema.tableWideAsyncValidation) return;
32
+ const res = await validateAgainstSchema.tableWideAsyncValidation({
33
+ entities: currentEnts
34
+ });
35
+ if (!isEmpty(res)) {
36
+ changeForm(tableName, "reduxFormCellValidation", {
37
+ ...addSpecialPropToAsyncErrs(res)
38
+ });
39
+ return true;
40
+ }
41
+ };
42
+
43
+ const maybeStripIdFromEntities = (ents, validateAgainstSchema) => {
44
+ let toRet;
45
+ if (validateAgainstSchema?.fields?.some(({ path }) => path === "id")) {
46
+ toRet = ents;
47
+ } else {
48
+ // if the schema we're validating against itself didn't have an id field,
49
+ // we don't want to include it in the returned entities
50
+ toRet = ents?.map(e => omit(e, ["id"]));
51
+ }
52
+ return toRet?.map(e => omit(e, ["_isClean"]));
53
+ };
54
+
55
+ const exampleData = { userData: times(5).map(() => ({ _isClean: true })) };
56
+
57
+ const getInitialSteps = csvValidationIssue => [
58
+ { text: "Review Headers", active: csvValidationIssue },
59
+ { text: "Review Data", active: !csvValidationIssue }
60
+ ];
61
+
62
+ export const PreviewCsvData = props => {
63
+ const {
64
+ matchedHeaders,
65
+ isEditingExistingFile,
66
+ showDoesDataLookCorrectMsg,
67
+ headerMessage,
68
+ datatableFormName = "editableCellTable",
69
+ validateAgainstSchema,
70
+ userSchema = exampleData,
71
+ entities
72
+ } = props;
73
+
74
+ const data = useMemo(() => {
75
+ return (
76
+ userSchema.userData &&
77
+ userSchema.userData.length &&
78
+ userSchema.userData.map((row, i) => {
79
+ const toRet = {
80
+ _isClean: row._isClean
81
+ };
82
+ validateAgainstSchema.fields.forEach(({ path, defaultValue }) => {
83
+ const matchingKey = matchedHeaders?.[path];
84
+ if (!matchingKey) {
85
+ toRet[path] = defaultValue === undefined ? defaultValue : "";
86
+ } else {
87
+ toRet[path] = row[matchingKey];
88
+ }
89
+ if (toRet[path] === undefined || toRet[path] === "") {
90
+ if (defaultValue) {
91
+ if (isFunction(defaultValue)) {
92
+ toRet[path] = defaultValue(i, row);
93
+ } else toRet[path] = defaultValue;
94
+ } else {
95
+ // const exampleToUse = isArray(example) //this means that the row was not added by a user
96
+ // ? example[i1]
97
+ // : i1 === 0 && example;
98
+ toRet[path] = "";
99
+ // if (useExampleData && exampleToUse) {
100
+ // toRet[path] = exampleToUse;
101
+ // delete toRet._isClean;
102
+ // } else {
103
+ // }
104
+ }
105
+ }
106
+ });
107
+
108
+ if (row.id === undefined) {
109
+ toRet.id = nanoid();
110
+ } else {
111
+ toRet.id = row.id;
112
+ }
113
+ return toRet;
114
+ })
115
+ );
116
+ }, [matchedHeaders, userSchema, validateAgainstSchema.fields]);
117
+
118
+ return (
119
+ <div style={{ minWidth: 400 }}>
120
+ <Callout style={{ marginBottom: 5 }} intent="primary">
121
+ {headerMessage ||
122
+ (showDoesDataLookCorrectMsg
123
+ ? "Does this data look correct? Edit it as needed."
124
+ : `${
125
+ isEditingExistingFile ? "Edit" : "Input"
126
+ } your data here. Hover table headers for additional instructions.`)}
127
+ </Callout>
128
+ {validateAgainstSchema.description && (
129
+ <Callout>{validateAgainstSchema.description}</Callout>
130
+ )}
131
+ <div
132
+ style={{
133
+ display: "flex",
134
+ justifyContent: "space-between",
135
+ alignItems: "flex-start"
136
+ }}
137
+ >
138
+ {validateAgainstSchema.HeaderComp && (
139
+ <validateAgainstSchema.HeaderComp {...props} />
140
+ )}
141
+ </div>
142
+ <DataTable
143
+ maxWidth={800}
144
+ maxHeight={500}
145
+ destroyOnUnmount={false}
146
+ doNotValidateUntouchedRows
147
+ formName={datatableFormName}
148
+ isSimple
149
+ isCellEditable
150
+ entities={(entities ? entities : data) || []}
151
+ schema={validateAgainstSchema}
152
+ />
153
+ </div>
154
+ );
155
+ };
156
+
157
+ export const SimpleInsertDataDialog = compose(
158
+ wrapDialog({
159
+ canEscapeKeyClose: false,
160
+ title: "Build CSV File",
161
+ style: { width: "fit-content" }
162
+ }),
163
+ reduxForm({ form: "SimpleInsertDataDialog" })
164
+ )(({
165
+ dataTableForm = "simpleInsertEditableTable",
166
+ entities,
167
+ handleSubmit,
168
+ headerMessage,
169
+ isEditingExistingFile,
170
+ matchedHeaders,
171
+ onSimpleInsertDialogFinish,
172
+ showDoesDataLookCorrectMsg,
173
+ submitting,
174
+ userSchema,
175
+ validateAgainstSchema,
176
+ initialValues
177
+ }) => {
178
+ const dispatch = useDispatch();
179
+ const _reduxFormEntities = useSelector(
180
+ state => state.form?.[dataTableForm]?.values.reduxFormEntities
181
+ );
182
+ const reduxFormEntities = useDeepEqualMemo(_reduxFormEntities);
183
+ useEffect(() => {
184
+ return () => dispatch(destroy(dataTableForm));
185
+ }, [dataTableForm, dispatch]);
186
+
187
+ const _reduxFormCellValidation = useSelector(
188
+ state => state.form?.[dataTableForm]?.values.reduxFormCellValidation
189
+ );
190
+ const reduxFormCellValidation = useDeepEqualMemo(_reduxFormCellValidation);
191
+
192
+ const { entsToUse, validationToUse } = useMemo(
193
+ () => removeCleanRows(reduxFormEntities, reduxFormCellValidation),
194
+ [reduxFormEntities, reduxFormCellValidation]
195
+ );
196
+ return (
197
+ <>
198
+ <div className="bp3-dialog-body">
199
+ <InputField
200
+ isRequired
201
+ rightElement={
202
+ <div style={{ paddingTop: 6, paddingRight: 5 }}>.csv</div>
203
+ }
204
+ inlineLabel
205
+ label="File Name:"
206
+ defaultValue={initialValues?.fileName ?? "manual_data_entry"}
207
+ name="fileName"
208
+ />
209
+ <PreviewCsvData
210
+ datatableFormName={dataTableForm}
211
+ entities={entities}
212
+ headerMessage={headerMessage}
213
+ isEditingExistingFile={isEditingExistingFile}
214
+ matchedHeaders={matchedHeaders}
215
+ showDoesDataLookCorrectMsg={showDoesDataLookCorrectMsg}
216
+ userSchema={userSchema}
217
+ validateAgainstSchema={validateAgainstSchema}
218
+ />
219
+ </div>
220
+ <DialogFooter
221
+ submitting={submitting}
222
+ onClick={handleSubmit(async ({ fileName }) => {
223
+ if (some(validationToUse, e => e)) return;
224
+ //do async validation here if needed
225
+ if (
226
+ await asyncValidateHelper(
227
+ validateAgainstSchema,
228
+ entsToUse,
229
+ (...args) => dispatch(change(...args)),
230
+ "simpleInsertEditableTable"
231
+ )
232
+ )
233
+ return;
234
+ onSimpleInsertDialogFinish({
235
+ fileName: fileName + ".csv",
236
+ newEntities: maybeStripIdFromEntities(
237
+ entsToUse,
238
+ validateAgainstSchema
239
+ )
240
+ });
241
+ })}
242
+ disabled={!entsToUse?.length || some(validationToUse, e => e)}
243
+ text={isEditingExistingFile ? "Edit Data" : "Add File"}
244
+ />
245
+ </>
246
+ );
247
+ });
248
+
249
+ const UploadCsvWizardDialogInner = reduxForm()(({
250
+ validateAgainstSchema,
251
+ userSchema,
252
+ searchResults,
253
+ onUploadWizardFinish,
254
+ csvValidationIssue,
255
+ ignoredHeadersMsg,
256
+ matchedHeaders,
257
+ handleSubmit,
258
+ fileIndex,
259
+ onBackClick,
260
+ changeForm,
261
+ setFilesWIssues,
262
+ doAllFilesHaveSameHeaders,
263
+ filesWIssues,
264
+ datatableFormName = "editableCellTable",
265
+ onMultiFileUploadSubmit,
266
+ isThisTheLastBadFile,
267
+ submitting
268
+ }) => {
269
+ const [hasSubmitted, setSubmitted] = useState(!csvValidationIssue);
270
+ const [steps, setSteps] = useState(getInitialSteps(csvValidationIssue));
271
+
272
+ const {
273
+ reduxFormEntities: _reduxFormEntities,
274
+ reduxFormCellValidation: _reduxFormCellValidation
275
+ } = useSelector(state =>
276
+ formValueSelector(datatableFormName)(
277
+ state,
278
+ "reduxFormEntities",
279
+ "reduxFormCellValidation"
280
+ )
281
+ );
282
+
283
+ const reduxFormEntities = useDeepEqualMemo(_reduxFormEntities);
284
+ const reduxFormCellValidation = useDeepEqualMemo(_reduxFormCellValidation);
285
+
286
+ let inner;
287
+ if (hasSubmitted) {
288
+ inner = (
289
+ <PreviewCsvData
290
+ datatableFormName={datatableFormName}
291
+ showDoesDataLookCorrectMsg
292
+ entities={reduxFormEntities || null}
293
+ matchedHeaders={matchedHeaders}
294
+ validateAgainstSchema={validateAgainstSchema}
295
+ userSchema={userSchema}
296
+ />
297
+ );
298
+ } else {
299
+ inner = (
300
+ <MatchHeaders
301
+ onMultiFileUploadSubmit={onMultiFileUploadSubmit}
302
+ csvValidationIssue={csvValidationIssue}
303
+ ignoredHeadersMsg={ignoredHeadersMsg}
304
+ searchResults={searchResults}
305
+ matchedHeaders={matchedHeaders}
306
+ userSchema={userSchema}
307
+ reduxFormEntitiesArray={reduxFormEntities ? [reduxFormEntities] : []}
308
+ datatableFormName={datatableFormName}
309
+ setFilesWIssues={setFilesWIssues}
310
+ filesWIssues={filesWIssues}
311
+ fileIndex={fileIndex}
312
+ />
313
+ );
314
+ }
315
+ const { entsToUse, validationToUse } = removeCleanRows(
316
+ reduxFormEntities,
317
+ reduxFormCellValidation
318
+ );
319
+
320
+ return (
321
+ <div>
322
+ {!doAllFilesHaveSameHeaders && (
323
+ <SimpleStepViz style={{ marginTop: 8 }} steps={steps} />
324
+ )}
325
+ <div className="bp3-dialog-body">{inner}</div>
326
+ <DialogFooter
327
+ text={
328
+ !hasSubmitted
329
+ ? "Review and Edit Data"
330
+ : onMultiFileUploadSubmit
331
+ ? isThisTheLastBadFile
332
+ ? "Finalize Files"
333
+ : "Next File"
334
+ : "Add File"
335
+ }
336
+ submitting={submitting}
337
+ disabled={
338
+ hasSubmitted && (!entsToUse?.length || some(validationToUse, v => v))
339
+ }
340
+ intent={
341
+ hasSubmitted && onMultiFileUploadSubmit && isThisTheLastBadFile
342
+ ? Intent.SUCCESS
343
+ : Intent.PRIMARY
344
+ }
345
+ noCancel={onMultiFileUploadSubmit}
346
+ {...(hasSubmitted && {
347
+ onBackClick:
348
+ onBackClick ||
349
+ (() => {
350
+ setSteps(
351
+ immer(steps, draft => {
352
+ draft[0].active = true;
353
+ draft[0].completed = false;
354
+ draft[1].active = false;
355
+ })
356
+ );
357
+ setSubmitted(false);
358
+ })
359
+ })}
360
+ onClick={handleSubmit(async () => {
361
+ if (!hasSubmitted) {
362
+ //step 1 submit
363
+ setSteps(
364
+ immer(steps, draft => {
365
+ draft[0].active = false;
366
+ draft[0].completed = true;
367
+ draft[1].active = true;
368
+ })
369
+ );
370
+ setSubmitted(true);
371
+ return;
372
+ }
373
+ if (!onMultiFileUploadSubmit) {
374
+ //do async validation here if needed
375
+ if (
376
+ await asyncValidateHelper(
377
+ validateAgainstSchema,
378
+ entsToUse,
379
+ changeForm,
380
+ datatableFormName
381
+ )
382
+ )
383
+ return;
384
+ }
385
+ //step 2 submit
386
+ const payload = maybeStripIdFromEntities(
387
+ entsToUse,
388
+ validateAgainstSchema
389
+ );
390
+ return onMultiFileUploadSubmit
391
+ ? await onMultiFileUploadSubmit()
392
+ : onUploadWizardFinish({ res: [payload] });
393
+ })}
394
+ style={{ alignSelf: "end" }}
395
+ />
396
+ </div>
397
+ );
398
+ });
399
+
400
+ const MultipleFileDialog = ({
401
+ focusedTab,
402
+ setFocusedTab,
403
+ filesWIssues,
404
+ finishedFiles,
405
+ doAllFilesHaveSameHeaders,
406
+ setSubmittedOuter,
407
+ setSteps,
408
+ reduxFormEntitiesArray,
409
+ validateAgainstSchema,
410
+ changeForm,
411
+ onUploadWizardFinish,
412
+ setFilesWIssues,
413
+ csvValidationIssue,
414
+ ignoredHeadersMsg,
415
+ searchResults,
416
+ matchedHeaders,
417
+ userSchema,
418
+ flippedMatchedHeaders,
419
+ steps,
420
+ hasSubmittedOuter
421
+ }) => {
422
+ const tabs = (
423
+ <>
424
+ <Callout style={{ marginBottom: 10, flexGrow: 0 }} intent="warning">
425
+ <div>
426
+ Please look over each of the following files and correct any issues.
427
+ </div>
428
+ </Callout>
429
+ <Tabs
430
+ // renderActiveTabPanelOnly
431
+ selectedTabId={focusedTab}
432
+ onChange={i => {
433
+ setFocusedTab(i);
434
+ }}
435
+ vertical
436
+ >
437
+ {filesWIssues.map((f, i) => {
438
+ const isGood = finishedFiles[i];
439
+ const isThisTheLastBadFile = finishedFiles.every((ff, j) => {
440
+ if (i === j) {
441
+ return true;
442
+ } else {
443
+ return !!ff;
444
+ }
445
+ });
446
+ return (
447
+ <Tab
448
+ key={i}
449
+ id={i}
450
+ title={
451
+ <div>
452
+ <Icon
453
+ intent={isGood ? "success" : "warning"}
454
+ icon={isGood ? "tick-circle" : "warning-sign"}
455
+ />{" "}
456
+ {f.file.name}
457
+ </div>
458
+ }
459
+ panel={
460
+ <UploadCsvWizardDialogInner
461
+ isThisTheLastBadFile={isThisTheLastBadFile}
462
+ onBackClick={
463
+ doAllFilesHaveSameHeaders &&
464
+ (() => {
465
+ setSubmittedOuter(false);
466
+ setSteps(getInitialSteps(true));
467
+ })
468
+ }
469
+ onMultiFileUploadSubmit={async () => {
470
+ let nextUnfinishedFile;
471
+ //find the next unfinished file
472
+ for (
473
+ let j = (i + 1) % finishedFiles.length;
474
+ j < finishedFiles.length;
475
+ j++
476
+ ) {
477
+ if (j === i) {
478
+ break;
479
+ } else if (!finishedFiles[j]) {
480
+ nextUnfinishedFile = j;
481
+ break;
482
+ } else if (j === finishedFiles.length - 1) {
483
+ j = -1;
484
+ }
485
+ }
486
+ if (nextUnfinishedFile !== undefined) {
487
+ //do async validation here if needed
488
+ const currentEnts = reduxFormEntitiesArray[focusedTab];
489
+ if (
490
+ await asyncValidateHelper(
491
+ validateAgainstSchema,
492
+ currentEnts,
493
+ changeForm,
494
+ `editableCellTable-${focusedTab}`
495
+ )
496
+ )
497
+ return;
498
+ setFocusedTab(nextUnfinishedFile);
499
+ } else {
500
+ //do async validation here if needed
501
+ for (const [i, ents] of finishedFiles.entries()) {
502
+ if (
503
+ await asyncValidateHelper(
504
+ validateAgainstSchema,
505
+ ents,
506
+ changeForm,
507
+ `editableCellTable-${i}`
508
+ )
509
+ )
510
+ return;
511
+ }
512
+ //we are done
513
+ onUploadWizardFinish({
514
+ res: finishedFiles.map(ents => {
515
+ return maybeStripIdFromEntities(
516
+ ents,
517
+ f.validateAgainstSchema
518
+ );
519
+ })
520
+ });
521
+ }
522
+ }}
523
+ validateAgainstSchema={validateAgainstSchema}
524
+ reduxFormEntitiesArray={reduxFormEntitiesArray}
525
+ filesWIssues={filesWIssues}
526
+ finishedFiles={finishedFiles}
527
+ onUploadWizardFinish={onUploadWizardFinish}
528
+ doAllFilesHaveSameHeaders={doAllFilesHaveSameHeaders}
529
+ setFilesWIssues={setFilesWIssues}
530
+ csvValidationIssue={csvValidationIssue}
531
+ ignoredHeadersMsg={ignoredHeadersMsg}
532
+ searchResults={searchResults}
533
+ matchedHeader={matchedHeaders}
534
+ userSchema={userSchema}
535
+ flippedMatchedHeaders={flippedMatchedHeaders}
536
+ changeForm={changeForm}
537
+ fileIndex={i}
538
+ form={`correctCSVHeadersForm-${i}`}
539
+ datatableFormName={`editableCellTable-${i}`}
540
+ {...f}
541
+ {...(doAllFilesHaveSameHeaders && {
542
+ csvValidationIssue: false
543
+ })}
544
+ />
545
+ }
546
+ />
547
+ );
548
+ })}
549
+ </Tabs>
550
+ </>
551
+ );
552
+ const comp = doAllFilesHaveSameHeaders ? (
553
+ <>
554
+ {doAllFilesHaveSameHeaders && (
555
+ <SimpleStepViz style={{ marginTop: 8 }} steps={steps} />
556
+ )}
557
+
558
+ {!hasSubmittedOuter && (
559
+ <MatchHeaders
560
+ doAllFilesHaveSameHeaders={doAllFilesHaveSameHeaders}
561
+ datatableFormNames={filesWIssues.map((f, i) => {
562
+ return `editableCellTable-${i}`;
563
+ })}
564
+ reduxFormEntitiesArray={reduxFormEntitiesArray}
565
+ csvValidationIssue={csvValidationIssue}
566
+ ignoredHeadersMsg={ignoredHeadersMsg}
567
+ searchResults={searchResults}
568
+ matchedHeaders={matchedHeaders}
569
+ userSchema={userSchema}
570
+ flippedMatchedHeaders={flippedMatchedHeaders}
571
+ setFilesWIssues={setFilesWIssues}
572
+ filesWIssues={filesWIssues}
573
+ fileIndex={0}
574
+ {...filesWIssues[0]}
575
+ />
576
+ )}
577
+ {hasSubmittedOuter && tabs}
578
+ {!hasSubmittedOuter && (
579
+ <DialogFooter
580
+ style={{ marginTop: 20 }}
581
+ onClick={() => {
582
+ setSubmittedOuter(true);
583
+ setSteps(getInitialSteps(false));
584
+ }}
585
+ text="Review and Edit Data"
586
+ />
587
+ )}
588
+ </>
589
+ ) : (
590
+ tabs
591
+ );
592
+
593
+ return <div style={{ padding: 10 }}>{comp}</div>;
594
+ };
595
+
596
+ const UploadCsvWizardDialog = compose(
597
+ wrapDialog({
598
+ canEscapeKeyClose: false,
599
+ style: { width: "fit-content" }
600
+ }),
601
+ reduxForm({
602
+ form: "UploadCsvWizardDialog"
603
+ })
604
+ )(({
605
+ csvValidationIssue,
606
+ doAllFilesHaveSameHeaders,
607
+ filesWIssues: _filesWIssues,
608
+ flippedMatchedHeaders,
609
+ ignoredHeadersMsg,
610
+ matchedHeaders,
611
+ onUploadWizardFinish,
612
+ searchResults,
613
+ userSchema,
614
+ validateAgainstSchema
615
+ }) => {
616
+ const dispatch = useDispatch();
617
+ const changeForm = useCallback(
618
+ (...args) => dispatch(change(...args)),
619
+ [dispatch]
620
+ );
621
+ // will unmount state hook
622
+ useEffect(() => {
623
+ return () => {
624
+ dispatch(
625
+ destroy(
626
+ "editableCellTable",
627
+ ...times(_filesWIssues.length, i => `editableCellTable-${i}`)
628
+ )
629
+ );
630
+ };
631
+ }, [_filesWIssues.length, dispatch]);
632
+
633
+ const { _reduxFormEntitiesArray, _finishedFiles } = useSelector(state => {
634
+ if (_filesWIssues.length > 0) {
635
+ const reduxFormEntitiesArray = [];
636
+ const finishedFiles = _filesWIssues.map((f, i) => {
637
+ const { reduxFormEntities, reduxFormCellValidation } =
638
+ formValueSelector(`editableCellTable-${i}`)(
639
+ state,
640
+ "reduxFormEntities",
641
+ "reduxFormCellValidation"
642
+ );
643
+ reduxFormEntitiesArray.push(reduxFormEntities);
644
+ const { entsToUse, validationToUse } = removeCleanRows(
645
+ reduxFormEntities,
646
+ reduxFormCellValidation
647
+ );
648
+ return (
649
+ entsToUse &&
650
+ entsToUse.length &&
651
+ !some(validationToUse, v => v) &&
652
+ entsToUse
653
+ );
654
+ });
655
+ return {
656
+ _reduxFormEntitiesArray: reduxFormEntitiesArray,
657
+ _finishedFiles: finishedFiles
658
+ };
659
+ }
660
+ });
661
+ const reduxFormEntitiesArray = useDeepEqualMemo(_reduxFormEntitiesArray);
662
+ const finishedFiles = useDeepEqualMemo(_finishedFiles);
663
+
664
+ const [hasSubmittedOuter, setSubmittedOuter] = useState();
665
+ const [steps, setSteps] = useState(getInitialSteps(true));
666
+
667
+ const [focusedTab, setFocusedTab] = useState(0);
668
+ const [filesWIssues, setFilesWIssues] = useState(
669
+ _filesWIssues.map(cloneDeep) //do this little trick to stop immer from preventing the file from being modified
670
+ );
671
+
672
+ if (filesWIssues.length > 1) {
673
+ return (
674
+ <MultipleFileDialog
675
+ focusedTab={focusedTab}
676
+ setFocusedTab={setFocusedTab}
677
+ filesWIssues={filesWIssues}
678
+ finishedFiles={finishedFiles}
679
+ doAllFilesHaveSameHeaders={doAllFilesHaveSameHeaders}
680
+ setSubmittedOuter={setSubmittedOuter}
681
+ setSteps={setSteps}
682
+ reduxFormEntitiesArray={reduxFormEntitiesArray}
683
+ validateAgainstSchema={validateAgainstSchema}
684
+ changeForm={changeForm}
685
+ onUploadWizardFinish={onUploadWizardFinish}
686
+ setFilesWIssues={setFilesWIssues}
687
+ csvValidationIssue={csvValidationIssue}
688
+ ignoredHeadersMsg={ignoredHeadersMsg}
689
+ searchResults={searchResults}
690
+ matchedHeaders={matchedHeaders}
691
+ userSchema={userSchema}
692
+ flippedMatchedHeaders={flippedMatchedHeaders}
693
+ steps={steps}
694
+ hasSubmittedOuter={hasSubmittedOuter}
695
+ />
696
+ );
697
+ } else {
698
+ return (
699
+ <UploadCsvWizardDialogInner
700
+ form="correctCSVHeadersForm"
701
+ validateAgainstSchema={validateAgainstSchema}
702
+ userSchema={userSchema}
703
+ searchResults={searchResults}
704
+ onUploadWizardFinish={onUploadWizardFinish}
705
+ csvValidationIssue={csvValidationIssue}
706
+ ignoredHeadersMsg={ignoredHeadersMsg}
707
+ matchedHeaders={matchedHeaders}
708
+ changeForm={changeForm}
709
+ setFilesWIssues={setFilesWIssues}
710
+ filesWIssues={filesWIssues}
711
+ flippedMatchedHeaders={flippedMatchedHeaders}
712
+ fileIndex={0}
713
+ {...filesWIssues[0]}
714
+ />
715
+ );
716
+ }
717
+ });
718
+
719
+ export default UploadCsvWizardDialog;