@teselagen/ui 0.0.11 → 0.0.12

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