@teselagen/ui 0.6.6 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/DataTable/ColumnFilterMenu.d.ts +2 -1
  2. package/DataTable/Columns.d.ts +51 -0
  3. package/DataTable/DisplayOptions.d.ts +14 -14
  4. package/DataTable/EditabelCell.d.ts +2 -5
  5. package/DataTable/EditableCell.d.ts +7 -0
  6. package/DataTable/FilterAndSortMenu.d.ts +9 -9
  7. package/DataTable/PagingTool.d.ts +25 -2
  8. package/DataTable/RenderCell.d.ts +18 -0
  9. package/DataTable/SearchBar.d.ts +4 -3
  10. package/DataTable/SortableColumns.d.ts +6 -9
  11. package/DataTable/ThComponent.d.ts +9 -0
  12. package/DataTable/index.d.ts +0 -5
  13. package/DataTable/utils/getIdOrCodeOrIndex.d.ts +1 -2
  14. package/DataTable/utils/handleCopyTable.d.ts +1 -0
  15. package/DataTable/utils/index.d.ts +4 -2
  16. package/DataTable/utils/primarySelectedValue.d.ts +1 -0
  17. package/DataTable/utils/queryParams.d.ts +13 -8
  18. package/DataTable/utils/removeCleanRows.d.ts +1 -1
  19. package/DataTable/utils/rowClick.d.ts +24 -3
  20. package/DataTable/utils/useDeepEqualMemo.d.ts +1 -0
  21. package/DataTable/utils/useTableEntities.d.ts +5 -0
  22. package/DataTable/utils/useTableParams.d.ts +49 -0
  23. package/DataTable/utils/withTableParams.d.ts +3 -16
  24. package/DataTable/viewColumn.d.ts +11 -4
  25. package/FormComponents/AbstractField.d.ts +1 -0
  26. package/FormComponents/Uploader.d.ts +34 -1
  27. package/FormComponents/index.d.ts +111 -60
  28. package/MatchHeaders.d.ts +9 -10
  29. package/SimpleStepViz.d.ts +2 -1
  30. package/TgSuggest/index.d.ts +1 -21
  31. package/UploadCsvWizard.d.ts +1 -1
  32. package/index.cjs.js +47861 -49125
  33. package/index.d.ts +6 -3
  34. package/index.es.js +47959 -49223
  35. package/package.json +6 -5
  36. package/src/DataTable/CellDragHandle.js +70 -69
  37. package/src/DataTable/ColumnFilterMenu.js +23 -21
  38. package/src/DataTable/Columns.js +948 -0
  39. package/src/DataTable/Columns.jsx +945 -0
  40. package/src/DataTable/DisplayOptions.js +173 -192
  41. package/src/DataTable/EditabelCell.js +7 -18
  42. package/src/DataTable/EditabelCell.jsx +44 -0
  43. package/src/DataTable/EditableCell.js +44 -0
  44. package/src/DataTable/FilterAndSortMenu.js +215 -234
  45. package/src/DataTable/PagingTool.js +47 -56
  46. package/src/DataTable/RenderCell.js +191 -0
  47. package/src/DataTable/RenderCell.jsx +191 -0
  48. package/src/DataTable/SearchBar.js +12 -5
  49. package/src/DataTable/SortableColumns.js +44 -39
  50. package/src/DataTable/ThComponent.js +44 -0
  51. package/src/DataTable/dataTableEnhancer.js +32 -295
  52. package/src/DataTable/index.js +2945 -3596
  53. package/src/DataTable/utils/getIdOrCodeOrIndex.js +1 -1
  54. package/src/DataTable/utils/handleCopyTable.js +16 -0
  55. package/src/DataTable/utils/index.js +7 -3
  56. package/src/DataTable/utils/primarySelectedValue.js +1 -0
  57. package/src/DataTable/utils/queryParams.js +110 -85
  58. package/src/DataTable/utils/removeCleanRows.js +3 -3
  59. package/src/DataTable/utils/rowClick.js +34 -9
  60. package/src/DataTable/utils/selection.js +1 -1
  61. package/src/DataTable/utils/useDeepEqualMemo.js +10 -0
  62. package/src/DataTable/utils/useTableEntities.js +38 -0
  63. package/src/DataTable/utils/useTableParams.js +362 -0
  64. package/src/DataTable/utils/withTableParams.js +244 -274
  65. package/src/DataTable/validateTableWideErrors.js +1 -1
  66. package/src/DataTable/viewColumn.js +5 -9
  67. package/src/DialogFooter/index.js +3 -3
  68. package/src/FillWindow.js +2 -3
  69. package/src/FormComponents/AbstractField.js +388 -0
  70. package/src/FormComponents/Uploader.js +674 -649
  71. package/src/FormComponents/index.js +505 -654
  72. package/src/FormComponents/tryToMatchSchemas.js +1 -6
  73. package/src/MatchHeaders.js +27 -22
  74. package/src/SimpleStepViz.js +19 -23
  75. package/src/TgSelect/index.js +1 -1
  76. package/src/TgSuggest/index.js +94 -106
  77. package/src/UploadCsvWizard.js +571 -577
  78. package/src/enhancers/withDialog/tg_modalState.js +1 -0
  79. package/src/index.js +10 -4
  80. package/src/showDialogOnDocBody.js +5 -9
  81. package/src/useDialog.js +25 -26
  82. package/src/utils/commandControls.js +2 -2
  83. package/src/utils/handlerHelpers.js +19 -25
  84. package/src/utils/hooks/index.js +1 -0
  85. package/src/utils/hooks/useDeepEqualMemo.js +10 -0
  86. package/src/utils/hooks/useStableReference.js +9 -0
  87. package/src/utils/renderOnDoc.js +8 -5
  88. package/src/utils/tagUtils.js +3 -3
  89. package/src/utils/useTraceUpdate.js +19 -0
  90. package/src/wrapDialog.js +0 -2
  91. package/style.css +251 -251
  92. package/useDialog.d.ts +2 -6
  93. package/utils/hooks/index.d.ts +1 -0
  94. package/utils/hooks/useDeepEqualMemo.d.ts +1 -0
  95. package/utils/hooks/useStableReference.d.ts +1 -0
  96. package/utils/renderOnDoc.d.ts +1 -1
  97. package/utils/tagUtils.d.ts +5 -1
  98. package/utils/useTraceUpdate.d.ts +1 -0
@@ -1,4 +1,10 @@
1
- import React, { useEffect, useRef, useState } from "react";
1
+ import React, {
2
+ useCallback,
3
+ useEffect,
4
+ useMemo,
5
+ useRef,
6
+ useState
7
+ } from "react";
2
8
  import {
3
9
  Button,
4
10
  Callout,
@@ -16,8 +22,6 @@ import classnames from "classnames";
16
22
  import { nanoid } from "nanoid";
17
23
  import papaparse, { unparse } from "papaparse";
18
24
  import downloadjs from "downloadjs";
19
- import { configure, makeObservable, observable } from "mobx";
20
- import { observer } from "mobx-react";
21
25
  import UploadCsvWizardDialog, {
22
26
  SimpleInsertDataDialog
23
27
  } from "../UploadCsvWizard";
@@ -30,7 +34,7 @@ import {
30
34
  removeExt
31
35
  } from "@teselagen/file-utils";
32
36
  import tryToMatchSchemas from "./tryToMatchSchemas";
33
- import { forEach, isArray, isFunction, isPlainObject, noop } from "lodash-es";
37
+ import { isArray, isFunction, isPlainObject, noop } from "lodash-es";
34
38
  import { flatMap } from "lodash-es";
35
39
  import urljoin from "url-join";
36
40
  import popoverOverflowModifiers from "../utils/popoverOverflowModifiers";
@@ -38,14 +42,17 @@ import writeXlsxFile from "write-excel-file";
38
42
  import { startCase } from "lodash-es";
39
43
  import { getNewName } from "./getNewName";
40
44
  import { isObject } from "lodash-es";
41
- import { connect } from "react-redux";
42
- import { initialize } from "redux-form";
45
+ import { change, touch, initialize } from "redux-form";
43
46
  import classNames from "classnames";
44
- import { compose } from "recompose";
45
47
  import convertSchema from "../DataTable/utils/convertSchema";
46
48
  import { LoadingDots } from "./LoadingDots";
49
+ import { useDispatch } from "react-redux";
50
+ import { flushSync } from "react-dom";
51
+ import { useStableReference } from "../utils/hooks/useStableReference";
52
+
53
+ const manualEnterMessage = "Build CSV File";
54
+ const manualEnterSubMessage = "Paste or type data to build a CSV file";
47
55
 
48
- configure({ isolateGlobalState: true });
49
56
  const helperText = [
50
57
  `How to Use This Template to Upload New Data`,
51
58
  `1. Go to the first tab and delete the example data.`,
@@ -64,99 +71,298 @@ const helperSchema = [
64
71
  }
65
72
  ];
66
73
 
67
- class ValidateAgainstSchema {
68
- fields = [];
69
-
70
- constructor() {
71
- makeObservable(this, {
72
- fields: observable.shallow
73
- });
74
+ const setValidateAgainstSchema = newValidateAgainstSchema => {
75
+ if (!newValidateAgainstSchema) return;
76
+ const schema = convertSchema(newValidateAgainstSchema);
77
+ if (
78
+ schema.fields.some(f => {
79
+ if (f.path === "id") {
80
+ return true;
81
+ }
82
+ return false;
83
+ })
84
+ ) {
85
+ throw new Error(
86
+ `Uploader was passed a validateAgainstSchema with a fields array that contains a field with a path of "id". This is not allowed.`
87
+ );
74
88
  }
89
+ return schema;
90
+ };
75
91
 
76
- setValidateAgainstSchema(newValidateAgainstSchema) {
77
- if (!newValidateAgainstSchema) {
78
- this.fields = [];
79
- return;
80
- }
81
- const schema = convertSchema(newValidateAgainstSchema);
82
- if (
83
- schema.fields.some(f => {
84
- if (f.path === "id") {
85
- return true;
86
- }
87
- return false;
88
- })
89
- ) {
90
- throw new Error(
91
- `Uploader was passed a validateAgainstSchema with a fields array that contains a field with a path of "id". This is not allowed.`
92
- );
92
+ const getFileDownloadAttr = exampleFile => {
93
+ const baseUrl = window?.frontEndConfig?.serverBasePath || "";
94
+ return isFunction(exampleFile)
95
+ ? { onClick: exampleFile }
96
+ : exampleFile && {
97
+ target: "_blank",
98
+ download: true,
99
+ href:
100
+ exampleFile.startsWith("https") || exampleFile.startsWith("www")
101
+ ? exampleFile
102
+ : baseUrl
103
+ ? urljoin(baseUrl, "exampleFiles", exampleFile)
104
+ : exampleFile
105
+ };
106
+ };
107
+
108
+ const stripId = (ents = []) =>
109
+ ents.map(ent => {
110
+ const { id, ...rest } = ent;
111
+ return rest;
112
+ });
113
+
114
+ const getNewCsvFile = (ents, fileName) => {
115
+ const strippedEnts = stripId(ents);
116
+ return {
117
+ newFile: new File([papaparse.unparse(strippedEnts)], fileName),
118
+ cleanedEntities: strippedEnts
119
+ };
120
+ };
121
+
122
+ const trimFiles = (incomingFiles, fileLimit) => {
123
+ if (fileLimit) {
124
+ if (incomingFiles.length > fileLimit) {
125
+ window.toastr &&
126
+ window.toastr.warning(
127
+ `Detected additional files in your upload that we are ignoring. You can only upload ${fileLimit} file${
128
+ fileLimit > 1 ? "s" : ""
129
+ } at a time.`
130
+ );
93
131
  }
94
- forEach(schema, (v, k) => {
95
- this[k] = v;
96
- });
132
+ return incomingFiles.slice(0, fileLimit);
97
133
  }
98
- }
99
-
100
- // autorun(() => {
101
- // console.log(
102
- // `validateAgainstSchemaStore?.fields:`,
103
- // JSON.stringify(validateAgainstSchemaStore?.fields, null, 4)
104
- // );
105
- // });
106
- // validateAgainstSchemaStore.fields = ["hahah"];
107
- // validateAgainstSchemaStore.fields.push("yaa");
134
+ return incomingFiles;
135
+ };
108
136
 
109
- // const validateAgainstSchema = observable.shallow({
110
- // fields: []
111
- // })
137
+ const InnerDropZone = ({
138
+ getRootProps,
139
+ getInputProps,
140
+ isDragAccept,
141
+ isDragReject,
142
+ isDragActive,
143
+ className,
144
+ minimal,
145
+ dropzoneDisabled,
146
+ contentOverride,
147
+ simpleAccept,
148
+ innerIcon,
149
+ innerText,
150
+ validateAgainstSchema,
151
+ handleManuallyEnterData,
152
+ noBuildCsvOption,
153
+ showFilesCount,
154
+ fileList
155
+ // isDragActive
156
+ // isDragReject
157
+ // isDragAccept
158
+ }) => (
159
+ <section>
160
+ <div
161
+ {...getRootProps()}
162
+ className={classnames("tg-dropzone", className, {
163
+ "tg-dropzone-minimal": minimal,
164
+ "tg-dropzone-active": isDragActive,
165
+ "tg-dropzone-reject": isDragReject, // tnr: the acceptClassName/rejectClassName doesn't work with file extensions (only mimetypes are supported when dragging). Thus we'll just always turn the drop area blue when dragging and let the filtering occur on drop. See https://github.com/react-dropzone/react-dropzone/issues/888#issuecomment-773938074
166
+ "tg-dropzone-accept": isDragAccept,
167
+ "tg-dropzone-disabled": dropzoneDisabled,
168
+ "bp3-disabled": dropzoneDisabled
169
+ })}
170
+ >
171
+ <input {...getInputProps()} />
172
+ {contentOverride || (
173
+ <div
174
+ title={
175
+ simpleAccept
176
+ ? "Accepts only the following file types: " + simpleAccept
177
+ : "Accepts any file input"
178
+ }
179
+ className="tg-upload-inner"
180
+ >
181
+ {innerIcon || <Icon icon="upload" iconSize={minimal ? 15 : 30} />}
182
+ {innerText || (minimal ? "Upload" : "Click or drag to upload")}
183
+ {validateAgainstSchema && !noBuildCsvOption && (
184
+ <div
185
+ style={{
186
+ textAlign: "center",
187
+ // fontSize: 18,
188
+ marginTop: 7,
189
+ marginBottom: 5
190
+ }}
191
+ onClick={handleManuallyEnterData}
192
+ className="link-button"
193
+ >
194
+ ...or {manualEnterMessage}
195
+ {/* <div
196
+ style={{
197
+ fontSize: 11,
198
+ color: Colors.GRAY3,
199
+ fontStyle: "italic"
200
+ }}
201
+ >
202
+ {manualEnterSubMessage}
203
+ </div> */}
204
+ </div>
205
+ )}
206
+ </div>
207
+ )}
208
+ </div>
112
209
 
113
- // validateAgainstSchema.fields = ["hahah"];
210
+ {showFilesCount ? (
211
+ <div className="tg-upload-file-list-counter">
212
+ Files: {fileList ? fileList.length : 0}
213
+ </div>
214
+ ) : null}
215
+ </section>
216
+ );
114
217
 
115
- // wink wink
116
- const emptyPromise = Promise.resolve.bind(Promise);
218
+ const onFileSuccessDefault = async () => {
219
+ return;
220
+ };
117
221
 
118
- function UploaderInner({
222
+ const Uploader = ({
119
223
  accept: __accept,
120
- contentOverride: maybeContentOverride,
121
- innerIcon,
122
- innerText,
123
224
  action,
124
- className = "",
125
- minimal,
126
- validateAgainstSchema: _validateAgainstSchema,
225
+ autoUnzip,
226
+ beforeUpload,
127
227
  callout: _callout,
228
+ className = "",
229
+ contentOverride: maybeContentOverride,
230
+ disabled,
231
+ dropzoneProps = {},
128
232
  fileLimit,
129
- readBeforeUpload, //read the file using the browser's FileReader before passing it to onChange and/or uploading it
130
- showUploadList = true,
131
- beforeUpload,
132
233
  fileList, //list of files with options: {name, loading, error, url, originalName, downloadName}
133
- onFileSuccess = emptyPromise, //called each time a file is finished and before the file.loading gets set to false, needs to return a promise!
234
+ innerIcon,
235
+ innerText,
236
+ meta: { form: formName } = {},
237
+ minimal,
238
+ name,
239
+ noBuildCsvOption,
240
+ noRedux = true,
241
+ onChange: _onChange = noop, //this is almost always getting passed by redux-form, no need to pass this handler manually
134
242
  onFieldSubmit = noop, //called when all files have successfully uploaded
135
- // fileFinished = noop,
136
- onRemove = noop, //called when a file has been selected to be removed
137
- onChange = noop, //this is almost always getting passed by redux-form, no need to pass this handler manually
138
243
  onFileClick, // called when a file link in the filelist is clicked
139
- dropzoneProps = {},
244
+ onFileSuccess = onFileSuccessDefault, //called each time a file is finished and before the file.loading gets set to false, needs to return a promise!
245
+ onPreviewClick,
246
+ onRemove = noop, //called when a file has been selected to be removed
140
247
  overflowList,
141
- autoUnzip,
142
- disabled: _disabled,
143
- noBuildCsvOption,
144
- initializeForm,
248
+ readBeforeUpload, //read the file using the browser's FileReader before passing it to onChange and/or uploading it
145
249
  showFilesCount,
250
+ showUploadList = true,
146
251
  threeDotMenuItems,
147
- onPreviewClick
148
- }) {
149
- let dropzoneDisabled = _disabled;
150
- let _accept = __accept;
151
- const validateAgainstSchemaStore = useRef(new ValidateAgainstSchema());
252
+ validateAgainstSchema: _validateAgainstSchema
253
+ }) => {
254
+ const dispatch = useDispatch();
152
255
  const [acceptLoading, setAcceptLoading] = useState();
153
256
  const [resolvedAccept, setResolvedAccept] = useState();
154
- if (resolvedAccept) {
155
- _accept = resolvedAccept;
156
- }
157
- const isAcceptPromise =
158
- __accept?.then ||
159
- (Array.isArray(__accept) ? __accept.some(a => a?.then) : false);
257
+ const [loading, setLoading] = useState(false);
258
+ const filesToClean = useRef([]);
259
+
260
+ // We do this because we don't want functions influencing on the dependencies
261
+ const stableOnChange = useStableReference(_onChange);
262
+ const stableBeforeUpload = useStableReference(beforeUpload);
263
+ // onChange received from redux-form is not working anymore,
264
+ // so we need to overwrite it for redux to works.
265
+ const onChange = useCallback(
266
+ val => {
267
+ flushSync(() => {
268
+ if (noRedux) {
269
+ return stableOnChange.current(val);
270
+ }
271
+ dispatch(touch(formName, name));
272
+ dispatch(change(formName, name, val));
273
+ });
274
+ },
275
+ [dispatch, formName, name, noRedux, stableOnChange]
276
+ );
277
+
278
+ const handleSecondHalfOfUpload = useCallback(
279
+ async ({ acceptedFiles, cleanedFileList }) => {
280
+ // This onChange is not changing things, we need to check whether the error is here or later
281
+ onChange(cleanedFileList); //tnw: this line is necessary, if you want to clear the file list in the beforeUpload, call onChange([])
282
+ // beforeUpload is called, otherwise beforeUpload will not be able to truly cancel the upload
283
+ const keepGoing = stableBeforeUpload.current
284
+ ? await stableBeforeUpload.current(cleanedFileList, onChange)
285
+ : true;
286
+ if (!keepGoing) return;
287
+
288
+ if (action) {
289
+ const responses = [];
290
+ await Promise.all(
291
+ acceptedFiles.map(async fileToUpload => {
292
+ const data = new FormData();
293
+ data.append("file", fileToUpload);
294
+ try {
295
+ const res = await (window.serverApi
296
+ ? window.serverApi.post(action, data)
297
+ : fetch(action, {
298
+ method: "POST",
299
+ body: data
300
+ }));
301
+ responses.push(res.data && res.data[0]);
302
+ onFileSuccess(res.data[0]).then(() => {
303
+ cleanedFileList = cleanedFileList.map(file => {
304
+ const fileToReturn = {
305
+ ...file,
306
+ ...res.data[0]
307
+ };
308
+ if (fileToReturn.id === fileToUpload.id) {
309
+ fileToReturn.loading = false;
310
+ }
311
+ return fileToReturn;
312
+ });
313
+ onChange(cleanedFileList);
314
+ });
315
+ } catch (err) {
316
+ console.error("Error uploading file:", err);
317
+ responses.push({
318
+ ...fileToUpload,
319
+ error: err && err.msg ? err.msg : err
320
+ });
321
+ cleanedFileList = cleanedFileList.map(file => {
322
+ const fileToReturn = { ...file };
323
+ if (fileToReturn.id === fileToUpload.id) {
324
+ fileToReturn.loading = false;
325
+ fileToReturn.error = true;
326
+ }
327
+ return fileToReturn;
328
+ });
329
+ onChange(cleanedFileList);
330
+ }
331
+ })
332
+ );
333
+ onFieldSubmit(responses);
334
+ } else {
335
+ onChange(
336
+ cleanedFileList.map(function (file) {
337
+ return {
338
+ ...file,
339
+ loading: false
340
+ };
341
+ })
342
+ );
343
+ }
344
+ setLoading(false);
345
+ },
346
+ [action, stableBeforeUpload, onChange, onFieldSubmit, onFileSuccess]
347
+ );
348
+
349
+ const isAcceptPromise = useMemo(
350
+ () =>
351
+ __accept?.then ||
352
+ (Array.isArray(__accept) ? __accept.some(acc => acc?.then) : false),
353
+ [__accept]
354
+ );
355
+
356
+ const _accept = useMemo(() => {
357
+ if (resolvedAccept) {
358
+ return resolvedAccept;
359
+ }
360
+ if (isAcceptPromise && !resolvedAccept) {
361
+ return [];
362
+ }
363
+ return __accept;
364
+ }, [__accept, isAcceptPromise, resolvedAccept]);
365
+
160
366
  useEffect(() => {
161
367
  if (isAcceptPromise) {
162
368
  setAcceptLoading(true);
@@ -169,35 +375,34 @@ function UploaderInner({
169
375
  );
170
376
  }
171
377
  }, [__accept, isAcceptPromise]);
172
- if (isAcceptPromise && !resolvedAccept) {
173
- _accept = [];
174
- }
378
+
379
+ let dropzoneDisabled = disabled;
175
380
  if (acceptLoading) dropzoneDisabled = true;
176
- const accept = !_accept
177
- ? undefined
178
- : isAcceptPromise && !resolvedAccept
179
- ? []
180
- : isPlainObject(_accept)
181
- ? [_accept]
182
- : isArray(_accept)
183
- ? _accept
184
- : _accept.split(",").map(a => ({ type: a }));
185
- const callout = _callout || accept?.find?.(a => a?.callout)?.callout;
186
381
 
187
- const validateAgainstSchemaToUse =
188
- _validateAgainstSchema ||
189
- accept?.find?.(a => a?.validateAgainstSchema)?.validateAgainstSchema;
382
+ const accept = useMemo(
383
+ () =>
384
+ !_accept
385
+ ? undefined
386
+ : isAcceptPromise && !resolvedAccept
387
+ ? []
388
+ : isPlainObject(_accept)
389
+ ? [_accept]
390
+ : isArray(_accept)
391
+ ? _accept
392
+ : _accept.split(",").map(acc => ({ type: acc })),
393
+ [_accept, isAcceptPromise, resolvedAccept]
394
+ );
190
395
 
191
- useEffect(() => {
192
- // validateAgainstSchema
193
- validateAgainstSchemaStore.current.setValidateAgainstSchema(
194
- validateAgainstSchemaToUse
195
- );
196
- }, [validateAgainstSchemaToUse]);
197
- let validateAgainstSchema;
198
- if (validateAgainstSchemaToUse) {
199
- validateAgainstSchema = validateAgainstSchemaStore.current;
200
- }
396
+ const callout = _callout || accept?.find?.(a => a?.callout)?.callout;
397
+
398
+ const validateAgainstSchema = useMemo(
399
+ () =>
400
+ setValidateAgainstSchema(
401
+ _validateAgainstSchema ||
402
+ accept?.find?.(a => a?.validateAgainstSchema)?.validateAgainstSchema
403
+ ),
404
+ [_validateAgainstSchema, accept]
405
+ );
201
406
 
202
407
  if (
203
408
  (validateAgainstSchema || autoUnzip) &&
@@ -209,13 +414,12 @@ function UploaderInner({
209
414
  description: "Any of the following types, just compressed"
210
415
  });
211
416
  }
212
- const [loading, setLoading] = useState(false);
213
- const filesToClean = useRef([]);
214
417
 
215
- const { showDialogPromise: showUploadCsvWizardDialog, comp } = useDialog({
418
+ const { showDialogPromise: showUploadCsvWizardDialog, Comp } = useDialog({
216
419
  ModalComponent: UploadCsvWizardDialog
217
420
  });
218
- const { showDialogPromise: showSimpleInsertDataDialog, comp: comp2 } =
421
+
422
+ const { showDialogPromise: showSimpleInsertDataDialog, Comp: Comp2 } =
219
423
  useDialog({
220
424
  ModalComponent: SimpleInsertDataDialog
221
425
  });
@@ -238,13 +442,13 @@ function UploaderInner({
238
442
  let advancedAccept;
239
443
 
240
444
  if (Array.isArray(accept)) {
241
- if (accept.some(a => isPlainObject(a))) {
445
+ if (accept.some(acc => isPlainObject(acc))) {
242
446
  //advanced accept
243
447
  advancedAccept = accept;
244
- simpleAccept = flatMap(accept, a => {
245
- if (a.validateAgainstSchema) {
246
- if (!a.type) {
247
- a.type = [".csv", ".xlsx"];
448
+ simpleAccept = flatMap(accept, acc => {
449
+ if (acc.validateAgainstSchema) {
450
+ if (!acc.type) {
451
+ acc.type = [".csv", ".xlsx"];
248
452
  }
249
453
  handleManuallyEnterData = async e => {
250
454
  e.stopPropagation();
@@ -254,41 +458,38 @@ function UploaderInner({
254
458
  validateAgainstSchema
255
459
  }
256
460
  );
257
- if (!newEntities) {
258
- return;
259
- } else {
260
- //check existing files to make sure the new file name gets incremented if necessary
261
- // fileList
262
- const newFileName = getNewName(fileListToUse, fileName);
263
- const { newFile, cleanedEntities } = getNewCsvFile(
264
- newEntities,
265
- newFileName
266
- );
461
+ if (!newEntities) return;
462
+ //check existing files to make sure the new file name gets incremented if necessary
463
+ // fileList
464
+ const newFileName = getNewName(fileListToUse, fileName);
465
+ const { newFile, cleanedEntities } = getNewCsvFile(
466
+ newEntities,
467
+ newFileName
468
+ );
267
469
 
268
- const file = {
269
- ...newFile,
270
- parsedData: cleanedEntities,
271
- meta: {
272
- fields: validateAgainstSchema.fields.map(({ path }) => path)
273
- },
274
- name: newFileName,
275
- originFileObj: newFile,
276
- originalFileObj: newFile,
277
- id: nanoid(),
278
- hasEditClick: true
279
- };
470
+ const file = {
471
+ ...newFile,
472
+ parsedData: cleanedEntities,
473
+ meta: {
474
+ fields: validateAgainstSchema.fields.map(({ path }) => path)
475
+ },
476
+ name: newFileName,
477
+ originFileObj: newFile,
478
+ originalFileObj: newFile,
479
+ id: nanoid(),
480
+ hasEditClick: true
481
+ };
280
482
 
281
- const cleanedFileList = [file, ...fileListToUse].slice(
282
- 0,
283
- fileLimit ? fileLimit : undefined
284
- );
285
- handleSecondHalfOfUpload({
286
- acceptedFiles: cleanedFileList,
287
- cleanedFileList
288
- });
483
+ const cleanedFileList = [file, ...fileListToUse].slice(
484
+ 0,
485
+ fileLimit ? fileLimit : undefined
486
+ );
487
+ handleSecondHalfOfUpload({
488
+ acceptedFiles: cleanedFileList,
489
+ cleanedFileList
490
+ });
289
491
 
290
- window.toastr.success(`File Added`);
291
- }
492
+ window.toastr.success(`File Added`);
292
493
  };
293
494
 
294
495
  const nameToUse =
@@ -338,7 +539,7 @@ function UploaderInner({
338
539
  }
339
540
  };
340
541
  });
341
- const b = await writeXlsxFile(
542
+ const blobFile = await writeXlsxFile(
342
543
  [[mainExampleData], fieldsToUse, helperText],
343
544
  {
344
545
  headerStyle: {
@@ -349,18 +550,18 @@ function UploaderInner({
349
550
  filePath: "file.xlsx"
350
551
  }
351
552
  );
352
- downloadjs(b, `${nameToUse}.xlsx`, "xlsx");
553
+ downloadjs(blobFile, `${nameToUse}.xlsx`, "xlsx");
353
554
  };
354
555
  // handleDownloadXlsxFile()
355
- a.exampleFiles = [
556
+ acc.exampleFiles = [
356
557
  // ...(a.exampleFile ? [a.exampleFile] : []),
357
558
  {
358
559
  description: "Download Example CSV File",
359
560
  exampleFile: () => {
360
561
  const rows = [];
361
562
  const schemaToUse = [
362
- ...a.validateAgainstSchema.fields,
363
- ...(a.validateAgainstSchema.exampleDownloadFields ?? [])
563
+ ...acc.validateAgainstSchema.fields,
564
+ ...(acc.validateAgainstSchema.exampleDownloadFields ?? [])
364
565
  ];
365
566
  rows.push(
366
567
  schemaToUse.map(f => {
@@ -394,10 +595,10 @@ function UploaderInner({
394
595
  }
395
596
  ])
396
597
  ];
397
- delete a.exampleFile;
598
+ delete acc.exampleFile;
398
599
  }
399
- if (a.type) return a.type;
400
- return a;
600
+ if (acc.type) return acc.type;
601
+ return acc;
401
602
  });
402
603
  simpleAccept = simpleAccept.join(", ");
403
604
  } else {
@@ -409,73 +610,6 @@ function UploaderInner({
409
610
 
410
611
  const fileListToUse = fileList ? fileList : [];
411
612
 
412
- async function handleSecondHalfOfUpload({ acceptedFiles, cleanedFileList }) {
413
- onChange(cleanedFileList); //tnw: this line is necessary, if you want to clear the file list in the beforeUpload, call onChange([])
414
- // beforeUpload is called, otherwise beforeUpload will not be able to truly cancel the upload
415
- const keepGoing = beforeUpload
416
- ? await beforeUpload(cleanedFileList, onChange)
417
- : true;
418
- if (!keepGoing) return;
419
-
420
- if (action) {
421
- const responses = [];
422
- await Promise.all(
423
- acceptedFiles.map(async fileToUpload => {
424
- const data = new FormData();
425
- data.append("file", fileToUpload);
426
- try {
427
- const res = await (window.serverApi
428
- ? window.serverApi.post(action, data)
429
- : fetch(action, {
430
- method: "POST",
431
- body: data
432
- }));
433
- responses.push(res.data && res.data[0]);
434
- onFileSuccess(res.data[0]).then(() => {
435
- cleanedFileList = cleanedFileList.map(file => {
436
- const fileToReturn = {
437
- ...file,
438
- ...res.data[0]
439
- };
440
- if (fileToReturn.id === fileToUpload.id) {
441
- fileToReturn.loading = false;
442
- }
443
- return fileToReturn;
444
- });
445
- onChange(cleanedFileList);
446
- });
447
- } catch (err) {
448
- console.error("Error uploading file:", err);
449
- responses.push({
450
- ...fileToUpload,
451
- error: err && err.msg ? err.msg : err
452
- });
453
- cleanedFileList = cleanedFileList.map(file => {
454
- const fileToReturn = { ...file };
455
- if (fileToReturn.id === fileToUpload.id) {
456
- fileToReturn.loading = false;
457
- fileToReturn.error = true;
458
- }
459
- return fileToReturn;
460
- });
461
- onChange(cleanedFileList);
462
- }
463
- })
464
- );
465
- onFieldSubmit(responses);
466
- } else {
467
- onChange(
468
- cleanedFileList.map(function (file) {
469
- return {
470
- ...file,
471
- loading: false
472
- };
473
- })
474
- );
475
- }
476
- setLoading(false);
477
- }
478
-
479
613
  return (
480
614
  <>
481
615
  {callout && (
@@ -491,8 +625,8 @@ function UploaderInner({
491
625
  height: "fit-content"
492
626
  }}
493
627
  >
494
- {comp}
495
- {comp2}
628
+ <Comp />
629
+ <Comp2 />
496
630
  <div
497
631
  className="tg-uploader-inner"
498
632
  style={{ width: "100%", height: "fit-content", minWidth: 0 }}
@@ -503,17 +637,17 @@ function UploaderInner({
503
637
  style={{ fontSize: 11, marginBottom: 5 }}
504
638
  >
505
639
  {advancedAccept && !acceptLoading ? (
506
- <div style={{}}>
640
+ <div>
507
641
  Accepts &nbsp;
508
- <span style={{}}>
509
- {advancedAccept.map((a, i) => {
642
+ <span>
643
+ {advancedAccept.map((acc, i) => {
510
644
  const disabled = !(
511
- a.description ||
512
- a.exampleFile ||
513
- a.exampleFiles
645
+ acc.description ||
646
+ acc.exampleFile ||
647
+ acc.exampleFiles
514
648
  );
515
- const PopOrTooltip = a.exampleFiles ? Popover : Tooltip;
516
- const hasDownload = a.exampleFile || a.exampleFiles;
649
+ const PopOrTooltip = acc.exampleFiles ? Popover : Tooltip;
650
+ const hasDownload = acc.exampleFile || acc.exampleFiles;
517
651
  const CustomTag = !hasDownload ? "span" : "a";
518
652
  return (
519
653
  <PopOrTooltip
@@ -522,40 +656,38 @@ function UploaderInner({
522
656
  disabled={disabled}
523
657
  modifiers={popoverOverflowModifiers}
524
658
  content={
525
- a.exampleFiles ? (
659
+ acc.exampleFiles ? (
526
660
  <Menu>
527
- {a.exampleFiles.map(
661
+ {acc.exampleFiles.map(
528
662
  (
529
663
  { description, subtext, exampleFile, icon },
530
664
  i
531
- ) => {
532
- return (
533
- <MenuItem
534
- icon={icon || "download"}
535
- intent="primary"
536
- text={
537
- subtext ? (
538
- <div>
539
- <div>{description}</div>
540
- <div
541
- style={{
542
- fontSize: 11,
543
- fontStyle: "italic",
544
- color: Colors.GRAY3
545
- }}
546
- >
547
- {subtext}
548
- </div>{" "}
549
- </div>
550
- ) : (
551
- description
552
- )
553
- }
554
- {...getFileDownloadAttr(exampleFile)}
555
- key={i}
556
- ></MenuItem>
557
- );
558
- }
665
+ ) => (
666
+ <MenuItem
667
+ icon={icon || "download"}
668
+ intent="primary"
669
+ text={
670
+ subtext ? (
671
+ <div>
672
+ <div>{description}</div>
673
+ <div
674
+ style={{
675
+ fontSize: 11,
676
+ fontStyle: "italic",
677
+ color: Colors.GRAY3
678
+ }}
679
+ >
680
+ {subtext}
681
+ </div>{" "}
682
+ </div>
683
+ ) : (
684
+ description
685
+ )
686
+ }
687
+ {...getFileDownloadAttr(exampleFile)}
688
+ key={i}
689
+ />
690
+ )
559
691
  )}
560
692
  </Menu>
561
693
  ) : (
@@ -565,20 +697,20 @@ function UploaderInner({
565
697
  wordBreak: "break-word"
566
698
  }}
567
699
  >
568
- {a.description ? (
700
+ {acc.description ? (
569
701
  <div
570
702
  style={{
571
703
  marginBottom: 4,
572
704
  fontStyle: "italic"
573
705
  }}
574
706
  >
575
- {a.description}
707
+ {acc.description}
576
708
  </div>
577
709
  ) : (
578
710
  ""
579
711
  )}
580
- {a.exampleFile &&
581
- (a.isTemplate
712
+ {acc.exampleFile &&
713
+ (acc.isTemplate
582
714
  ? "Download Example Template"
583
715
  : "Download Example File")}
584
716
  </div>
@@ -588,20 +720,20 @@ function UploaderInner({
588
720
  <CustomTag
589
721
  className="tgFileTypeDescriptor"
590
722
  style={{ marginRight: 10, cursor: "pointer" }}
591
- {...getFileDownloadAttr(a.exampleFile)}
723
+ {...getFileDownloadAttr(acc.exampleFile)}
592
724
  >
593
- {(a.type
594
- ? isArray(a.type)
595
- ? a.type
596
- : [a.type]
597
- : [a]
725
+ {(acc.type
726
+ ? isArray(acc.type)
727
+ ? acc.type
728
+ : [acc.type]
729
+ : [acc]
598
730
  )
599
731
  .map(t => {
600
732
  if (!t.startsWith) {
601
- console.error(`Missing type here:`, a);
733
+ console.error(`Missing type here:`, acc);
602
734
  throw new Error(
603
735
  `Missing "type" here: ${JSON.stringify(
604
- a,
736
+ acc,
605
737
  null,
606
738
  4
607
739
  )}`
@@ -619,7 +751,7 @@ function UploaderInner({
619
751
  }}
620
752
  size={10}
621
753
  icon="download"
622
- ></Icon>
754
+ />
623
755
  )}
624
756
  </CustomTag>
625
757
  </PopOrTooltip>
@@ -631,7 +763,8 @@ function UploaderInner({
631
763
  // make the dots below "load"
632
764
 
633
765
  <>
634
- Accept Loading<LoadingDots></LoadingDots>
766
+ Accept Loading
767
+ <LoadingDots />
635
768
  </>
636
769
  ) : (
637
770
  <>Accepts {simpleAccept}</>
@@ -646,139 +779,140 @@ function UploaderInner({
646
779
  simpleAccept
647
780
  ? simpleAccept
648
781
  .split(", ")
649
- .map(a => (a.startsWith(".") ? a : "." + a))
782
+ .map(acc => (acc.startsWith(".") ? acc : "." + acc))
650
783
  .join(", ")
651
784
  : undefined
652
785
  }
653
- {...{
654
- onDrop: async (_acceptedFiles, rejectedFiles) => {
655
- let acceptedFiles = [];
656
- for (const file of _acceptedFiles) {
657
- if ((validateAgainstSchema || autoUnzip) && isZipFile(file)) {
658
- const files = await filterFilesInZip(
659
- file,
660
- simpleAccept
661
- ?.split(", ")
662
- ?.map(a => (a.startsWith(".") ? a : "." + a)) || []
663
- );
664
- acceptedFiles.push(...files.map(f => f.originFileObj));
665
- } else {
666
- acceptedFiles.push(file);
667
- }
668
- }
669
- cleanupFiles();
670
- if (rejectedFiles.length) {
671
- let msg = "";
672
- rejectedFiles.forEach(file => {
673
- if (msg) msg += "\n";
674
- msg +=
675
- `${file.file.name}: ` +
676
- file.errors.map(err => err.message).join(", ");
677
- });
678
- window.toastr &&
679
- window.toastr.warning(
680
- <div className="preserve-newline">{msg}</div>
681
- );
786
+ onDrop={async (_acceptedFiles, rejectedFiles) => {
787
+ let acceptedFiles = [];
788
+ for (const file of _acceptedFiles) {
789
+ if ((validateAgainstSchema || autoUnzip) && isZipFile(file)) {
790
+ const files = await filterFilesInZip(
791
+ file,
792
+ simpleAccept
793
+ ?.split(", ")
794
+ ?.map(acc => (acc.startsWith(".") ? acc : "." + acc)) ||
795
+ []
796
+ );
797
+ acceptedFiles.push(...files.map(f => f.originFileObj));
798
+ } else {
799
+ acceptedFiles.push(file);
682
800
  }
683
- if (!acceptedFiles.length) return;
684
- setLoading(true);
685
- acceptedFiles = trimFiles(acceptedFiles, fileLimit);
686
-
687
- acceptedFiles.forEach(file => {
688
- file.preview = URL.createObjectURL(file);
689
- file.loading = true;
690
- if (!file.id) {
691
- file.id = nanoid();
692
- }
693
- filesToClean.current.push(file);
801
+ }
802
+ cleanupFiles();
803
+ if (rejectedFiles.length) {
804
+ let msg = "";
805
+ rejectedFiles.forEach(file => {
806
+ if (msg) msg += "\n";
807
+ msg +=
808
+ `${file.file.name}: ` +
809
+ file.errors.map(err => err.message).join(", ");
694
810
  });
695
-
696
- if (readBeforeUpload) {
697
- acceptedFiles = await Promise.all(
698
- acceptedFiles.map(file => {
699
- return new Promise((resolve, reject) => {
700
- const reader = new FileReader();
701
- reader.readAsText(file, "UTF-8");
702
- reader.onload = evt => {
703
- file.parsedString = evt.target.result;
704
- resolve(file);
705
- };
706
- reader.onerror = err => {
707
- console.error("err:", err);
708
- reject(err);
709
- };
710
- });
711
- })
811
+ window.toastr &&
812
+ window.toastr.warning(
813
+ <div className="preserve-newline">{msg}</div>
712
814
  );
815
+ }
816
+ if (!acceptedFiles.length) return;
817
+ setLoading(true);
818
+ acceptedFiles = trimFiles(acceptedFiles, fileLimit);
819
+
820
+ acceptedFiles.forEach(file => {
821
+ file.preview = URL.createObjectURL(file);
822
+ file.loading = true;
823
+ if (!file.id) {
824
+ file.id = nanoid();
713
825
  }
714
- const cleanedAccepted = acceptedFiles.map(file => {
715
- return {
716
- originFileObj: file,
717
- originalFileObj: file,
718
- id: file.id,
719
- lastModified: file.lastModified,
720
- lastModifiedDate: file.lastModifiedDate,
721
- loading: file.loading,
722
- name: file.name,
723
- preview: file.preview,
724
- size: file.size,
725
- type: file.type,
726
- ...(file.parsedString
727
- ? { parsedString: file.parsedString }
728
- : {})
729
- };
730
- });
826
+ filesToClean.current.push(file);
827
+ });
731
828
 
732
- const toKeep = [];
733
- if (validateAgainstSchema) {
734
- const filesWIssues = [];
735
- const filesWOIssues = [];
736
- for (const [i, file] of cleanedAccepted.entries()) {
737
- if (isCsvOrExcelFile(file)) {
738
- let parsedF;
739
- try {
740
- parsedF = await parseCsvOrExcelFile(file, {
741
- csvParserOptions: isFunction(
742
- validateAgainstSchema.csvParserOptions
743
- )
744
- ? validateAgainstSchema.csvParserOptions({
745
- validateAgainstSchema
746
- })
747
- : validateAgainstSchema.csvParserOptions
748
- });
749
- } catch (error) {
750
- console.error("error:", error);
751
- window.toastr &&
752
- window.toastr.error(
753
- `There was an error parsing your file. Please try again. ${
754
- error.message || error
755
- }`
756
- );
757
- return;
758
- }
829
+ if (readBeforeUpload) {
830
+ acceptedFiles = await Promise.all(
831
+ acceptedFiles.map(file => {
832
+ return new Promise((resolve, reject) => {
833
+ const reader = new FileReader();
834
+ reader.readAsText(file, "UTF-8");
835
+ reader.onload = evt => {
836
+ file.parsedString = evt.target.result;
837
+ resolve(file);
838
+ };
839
+ reader.onerror = err => {
840
+ console.error("err:", err);
841
+ reject(err);
842
+ };
843
+ });
844
+ })
845
+ );
846
+ }
847
+ const cleanedAccepted = acceptedFiles.map(file => {
848
+ return {
849
+ originFileObj: file,
850
+ originalFileObj: file,
851
+ id: file.id,
852
+ lastModified: file.lastModified,
853
+ lastModifiedDate: file.lastModifiedDate,
854
+ loading: file.loading,
855
+ name: file.name,
856
+ preview: file.preview,
857
+ size: file.size,
858
+ type: file.type,
859
+ ...(file.parsedString
860
+ ? { parsedString: file.parsedString }
861
+ : {})
862
+ };
863
+ });
759
864
 
760
- const {
761
- csvValidationIssue: _csvValidationIssue,
762
- matchedHeaders,
763
- userSchema,
764
- searchResults,
765
- ignoredHeadersMsg
766
- } = await tryToMatchSchemas({
767
- incomingData: parsedF.data,
768
- validateAgainstSchema
865
+ const toKeep = [];
866
+ if (validateAgainstSchema) {
867
+ const filesWIssues = [];
868
+ const filesWOIssues = [];
869
+ for (const [i, file] of cleanedAccepted.entries()) {
870
+ if (isCsvOrExcelFile(file)) {
871
+ let parsedF;
872
+ try {
873
+ parsedF = await parseCsvOrExcelFile(file, {
874
+ csvParserOptions: isFunction(
875
+ validateAgainstSchema.csvParserOptions
876
+ )
877
+ ? validateAgainstSchema.csvParserOptions({
878
+ validateAgainstSchema
879
+ })
880
+ : validateAgainstSchema.csvParserOptions
769
881
  });
770
- if (userSchema?.userData?.length === 0) {
771
- console.error(
772
- `userSchema, parsedF.data:`,
773
- userSchema,
774
- parsedF.data
882
+ } catch (error) {
883
+ console.error("error:", error);
884
+ window.toastr &&
885
+ window.toastr.error(
886
+ `There was an error parsing your file. Please try again. ${
887
+ error.message || error
888
+ }`
775
889
  );
776
- } else {
777
- toKeep.push(file);
778
- let csvValidationIssue = _csvValidationIssue;
779
- if (csvValidationIssue) {
780
- if (isObject(csvValidationIssue)) {
781
- initializeForm(
890
+ return;
891
+ }
892
+
893
+ const {
894
+ csvValidationIssue: _csvValidationIssue,
895
+ matchedHeaders,
896
+ userSchema,
897
+ searchResults,
898
+ ignoredHeadersMsg
899
+ } = await tryToMatchSchemas({
900
+ incomingData: parsedF.data,
901
+ validateAgainstSchema
902
+ });
903
+ if (userSchema?.userData?.length === 0) {
904
+ console.error(
905
+ `userSchema, parsedF.data:`,
906
+ userSchema,
907
+ parsedF.data
908
+ );
909
+ } else {
910
+ toKeep.push(file);
911
+ let csvValidationIssue = _csvValidationIssue;
912
+ if (csvValidationIssue) {
913
+ if (isObject(csvValidationIssue)) {
914
+ dispatch(
915
+ initialize(
782
916
  `editableCellTable${
783
917
  cleanedAccepted.length > 1 ? `-${i}` : ""
784
918
  }`,
@@ -790,149 +924,141 @@ function UploaderInner({
790
924
  keepValues: true,
791
925
  updateUnregisteredFields: true
792
926
  }
927
+ )
928
+ );
929
+ const err = Object.values(csvValidationIssue)[0];
930
+ // csvValidationIssue = `It looks like there was an error with your data - \n\n${
931
+ // err && err.message ? err.message : err
932
+ // }.\n\nPlease review your headers and then correct any errors on the next page.`; //pass just the first error as a string
933
+ const errMsg = err && err.message ? err.message : err;
934
+ if (isPlainObject(errMsg)) {
935
+ throw new Error(
936
+ `errMsg is an object ${JSON.stringify(
937
+ errMsg,
938
+ null,
939
+ 4
940
+ )}`
793
941
  );
794
- const err = Object.values(csvValidationIssue)[0];
795
- // csvValidationIssue = `It looks like there was an error with your data - \n\n${
796
- // err && err.message ? err.message : err
797
- // }.\n\nPlease review your headers and then correct any errors on the next page.`; //pass just the first error as a string
798
- const errMsg =
799
- err && err.message ? err.message : err;
800
- if (isPlainObject(errMsg)) {
801
- throw new Error(
802
- `errMsg is an object ${JSON.stringify(
803
- errMsg,
804
- null,
805
- 4
806
- )}`
807
- );
808
- }
809
- csvValidationIssue = (
942
+ }
943
+ csvValidationIssue = (
944
+ <div>
810
945
  <div>
811
- <div>
812
- It looks like there was an error with your
813
- data (Correct on the Review Data page):
814
- </div>
815
- <div style={{ color: "red" }}>{errMsg}</div>
816
- <div>
817
- Please review your headers and then correct
818
- any errors on the next page.
819
- </div>
946
+ It looks like there was an error with your data
947
+ (Correct on the Review Data page):
820
948
  </div>
821
- );
822
- }
823
- filesWIssues.push({
824
- file,
825
- csvValidationIssue,
826
- ignoredHeadersMsg,
827
- matchedHeaders,
828
- userSchema,
829
- searchResults
830
- });
831
- } else {
832
- filesWOIssues.push({
833
- file,
834
- csvValidationIssue,
835
- ignoredHeadersMsg,
836
- matchedHeaders,
837
- userSchema,
838
- searchResults
839
- });
840
- const newFileName = removeExt(file.name) + `.csv`;
841
-
842
- const { newFile, cleanedEntities } = getNewCsvFile(
843
- userSchema.userData,
844
- newFileName
949
+ <div style={{ color: "red" }}>{errMsg}</div>
950
+ <div>
951
+ Please review your headers and then correct any
952
+ errors on the next page.
953
+ </div>
954
+ </div>
845
955
  );
846
-
847
- file.meta = parsedF.meta;
848
- file.hasEditClick = true;
849
- file.parsedData = cleanedEntities;
850
- file.name = newFileName;
851
- file.originFileObj = newFile;
852
- file.originalFileObj = newFile;
853
956
  }
854
- }
855
- } else {
856
- toKeep.push(file);
857
- }
858
- }
859
- if (filesWIssues.length) {
860
- const { file } = filesWIssues[0];
861
- const allFiles = [...filesWIssues, ...filesWOIssues];
862
- const doAllFilesHaveSameHeaders = allFiles.every(f => {
863
- if (f.userSchema.fields && f.userSchema.fields.length) {
864
- return f.userSchema.fields.every((h, i) => {
865
- return (
866
- h.path === allFiles[0].userSchema.fields[i].path
867
- );
957
+ filesWIssues.push({
958
+ file,
959
+ csvValidationIssue,
960
+ ignoredHeadersMsg,
961
+ matchedHeaders,
962
+ userSchema,
963
+ searchResults
868
964
  });
869
- }
870
- return false;
871
- });
872
- const multipleFiles = allFiles.length > 1;
873
- const { res } = await showUploadCsvWizardDialog(
874
- "onUploadWizardFinish",
875
- {
876
- dialogProps: {
877
- title: `Fix Up File${multipleFiles ? "s" : ""} ${
878
- multipleFiles
879
- ? ""
880
- : file.name
881
- ? `"${file.name}"`
882
- : ""
883
- }`
884
- },
885
- doAllFilesHaveSameHeaders,
886
- filesWIssues: allFiles,
887
- validateAgainstSchema
888
- }
889
- );
965
+ } else {
966
+ filesWOIssues.push({
967
+ file,
968
+ csvValidationIssue,
969
+ ignoredHeadersMsg,
970
+ matchedHeaders,
971
+ userSchema,
972
+ searchResults
973
+ });
974
+ const newFileName = removeExt(file.name) + `.csv`;
890
975
 
891
- if (!res) {
892
- window.toastr.warning(`File Upload Aborted`);
893
- return;
894
- } else {
895
- allFiles.forEach(({ file }, i) => {
896
- const newEntities = res[i];
897
- // const newFileName = removeExt(file.name) + `_updated.csv`;
898
- //swap out file with a new csv file
899
976
  const { newFile, cleanedEntities } = getNewCsvFile(
900
- newEntities,
901
- file.name
977
+ userSchema.userData,
978
+ newFileName
902
979
  );
903
980
 
981
+ file.meta = parsedF.meta;
904
982
  file.hasEditClick = true;
905
983
  file.parsedData = cleanedEntities;
906
- // file.name = newFileName;
984
+ file.name = newFileName;
907
985
  file.originFileObj = newFile;
908
986
  file.originalFileObj = newFile;
909
- });
910
- setTimeout(() => {
911
- //inside a timeout for cypress purposes
912
- window.toastr.success(
913
- `Added Fixed Up File${
914
- allFiles.length > 1 ? "s" : ""
915
- } ${allFiles.map(({ file }) => file.name).join(", ")}`
916
- );
917
- }, 200);
987
+ }
918
988
  }
989
+ } else {
990
+ toKeep.push(file);
919
991
  }
920
- } else {
921
- toKeep.push(...cleanedAccepted);
922
992
  }
993
+ if (filesWIssues.length) {
994
+ const { file } = filesWIssues[0];
995
+ const allFiles = [...filesWIssues, ...filesWOIssues];
996
+ const doAllFilesHaveSameHeaders = allFiles.every(f => {
997
+ if (f.userSchema.fields && f.userSchema.fields.length) {
998
+ return f.userSchema.fields.every((h, i) => {
999
+ return h.path === allFiles[0].userSchema.fields[i].path;
1000
+ });
1001
+ }
1002
+ return false;
1003
+ });
1004
+ const multipleFiles = allFiles.length > 1;
1005
+ const { res } = await showUploadCsvWizardDialog(
1006
+ "onUploadWizardFinish",
1007
+ {
1008
+ dialogProps: {
1009
+ title: `Fix Up File${multipleFiles ? "s" : ""} ${
1010
+ multipleFiles ? "" : file.name ? `"${file.name}"` : ""
1011
+ }`
1012
+ },
1013
+ doAllFilesHaveSameHeaders,
1014
+ filesWIssues: allFiles,
1015
+ validateAgainstSchema
1016
+ }
1017
+ );
923
1018
 
924
- if (toKeep.length === 0) {
925
- window.toastr &&
926
- window.toastr.error(
927
- `It looks like there wasn't any data in your file. Please add some data and try again`
928
- );
1019
+ if (!res) {
1020
+ window.toastr.warning(`File Upload Aborted`);
1021
+ return;
1022
+ } else {
1023
+ allFiles.forEach(({ file }, i) => {
1024
+ const newEntities = res[i];
1025
+ // const newFileName = removeExt(file.name) + `_updated.csv`;
1026
+ //swap out file with a new csv file
1027
+ const { newFile, cleanedEntities } = getNewCsvFile(
1028
+ newEntities,
1029
+ file.name
1030
+ );
1031
+
1032
+ file.hasEditClick = true;
1033
+ file.parsedData = cleanedEntities;
1034
+ file.originFileObj = newFile;
1035
+ file.originalFileObj = newFile;
1036
+ });
1037
+ setTimeout(() => {
1038
+ //inside a timeout for cypress purposes
1039
+ window.toastr.success(
1040
+ `Added Fixed Up File${
1041
+ allFiles.length > 1 ? "s" : ""
1042
+ } ${allFiles.map(({ file }) => file.name).join(", ")}`
1043
+ );
1044
+ }, 200);
1045
+ }
929
1046
  }
930
- const cleanedFileList = trimFiles(
931
- [...toKeep, ...fileListToUse],
932
- fileLimit
933
- );
934
- handleSecondHalfOfUpload({ acceptedFiles, cleanedFileList });
1047
+ } else {
1048
+ toKeep.push(...cleanedAccepted);
1049
+ }
1050
+
1051
+ if (toKeep.length === 0) {
1052
+ window.toastr &&
1053
+ window.toastr.error(
1054
+ `It looks like there wasn't any data in your file. Please add some data and try again`
1055
+ );
935
1056
  }
1057
+ const cleanedFileList = trimFiles(
1058
+ [...toKeep, ...fileListToUse],
1059
+ fileLimit
1060
+ );
1061
+ handleSecondHalfOfUpload({ acceptedFiles, cleanedFileList });
936
1062
  }}
937
1063
  {...dropzoneProps}
938
1064
  >
@@ -942,71 +1068,26 @@ function UploaderInner({
942
1068
  isDragAccept,
943
1069
  isDragReject,
944
1070
  isDragActive
945
- // isDragActive
946
- // isDragReject
947
- // isDragAccept
948
1071
  }) => (
949
- <section>
950
- <div
951
- {...getRootProps()}
952
- className={classnames("tg-dropzone", className, {
953
- "tg-dropzone-minimal": minimal,
954
- "tg-dropzone-active": isDragActive,
955
- "tg-dropzone-reject": isDragReject, // tnr: the acceptClassName/rejectClassName doesn't work with file extensions (only mimetypes are supported when dragging). Thus we'll just always turn the drop area blue when dragging and let the filtering occur on drop. See https://github.com/react-dropzone/react-dropzone/issues/888#issuecomment-773938074
956
- "tg-dropzone-accept": isDragAccept,
957
- "tg-dropzone-disabled": dropzoneDisabled,
958
- "bp3-disabled": dropzoneDisabled
959
- })}
960
- >
961
- <input {...getInputProps()} />
962
- {contentOverride || (
963
- <div
964
- title={
965
- simpleAccept
966
- ? "Accepts only the following file types: " +
967
- simpleAccept
968
- : "Accepts any file input"
969
- }
970
- className="tg-upload-inner"
971
- >
972
- {innerIcon || (
973
- <Icon icon="upload" iconSize={minimal ? 15 : 30} />
974
- )}
975
- {innerText ||
976
- (minimal ? "Upload" : "Click or drag to upload")}
977
- {validateAgainstSchema && !noBuildCsvOption && (
978
- <div
979
- style={{
980
- textAlign: "center",
981
- // fontSize: 18,
982
- marginTop: 7,
983
- marginBottom: 5
984
- }}
985
- onClick={handleManuallyEnterData}
986
- className="link-button"
987
- >
988
- ...or {manualEnterMessage}
989
- {/* <div
990
- style={{
991
- fontSize: 11,
992
- color: Colors.GRAY3,
993
- fontStyle: "italic"
994
- }}
995
- >
996
- {manualEnterSubMessage}
997
- </div> */}
998
- </div>
999
- )}
1000
- </div>
1001
- )}
1002
- </div>
1003
-
1004
- {showFilesCount ? (
1005
- <div className="tg-upload-file-list-counter">
1006
- Files: {fileList ? fileList.length : 0}
1007
- </div>
1008
- ) : null}
1009
- </section>
1072
+ <InnerDropZone
1073
+ getRootProps={getRootProps}
1074
+ getInputProps={getInputProps}
1075
+ isDragAccept={isDragAccept}
1076
+ isDragReject={isDragReject}
1077
+ isDragActive={isDragActive}
1078
+ className={className}
1079
+ minimal={minimal}
1080
+ dropzoneDisabled={dropzoneDisabled}
1081
+ contentOverride={contentOverride}
1082
+ simpleAccept={simpleAccept}
1083
+ innerIcon={innerIcon}
1084
+ innerText={innerText}
1085
+ validateAgainstSchema={validateAgainstSchema}
1086
+ handleManuallyEnterData={handleManuallyEnterData}
1087
+ noBuildCsvOption={noBuildCsvOption}
1088
+ showFilesCount={showFilesCount}
1089
+ fileList={fileList}
1090
+ />
1010
1091
  )}
1011
1092
  </Dropzone>
1012
1093
  {/* {validateAgainstSchema && <CsvWizardHelper bindToggle={{}} validateAgainstSchema={validateAgainstSchema}></CsvWizardHelper>} */}
@@ -1101,24 +1182,23 @@ function UploaderInner({
1101
1182
  userSchema
1102
1183
  }
1103
1184
  );
1104
-
1105
1185
  if (!newEntities) {
1106
1186
  return;
1107
1187
  } else {
1108
1188
  const { newFile, cleanedEntities } =
1109
1189
  getNewCsvFile(newEntities, fileName);
1110
- const zoink = Object.assign({}, file, {
1190
+ const tmpFile = Object.assign({}, file, {
1111
1191
  ...newFile,
1112
1192
  originFileObj: newFile,
1113
1193
  originalFileObj: newFile,
1114
1194
  parsedData: cleanedEntities
1115
1195
  });
1116
- zoink.name = newFile.name;
1117
- fileList = [...fileList];
1118
- fileList[index] = zoink;
1196
+ tmpFile.name = newFile.name;
1197
+ const tmpFileList = [...fileList];
1198
+ tmpFileList[index] = tmpFile;
1119
1199
  handleSecondHalfOfUpload({
1120
- acceptedFiles: fileList,
1121
- cleanedFileList: fileList
1200
+ acceptedFiles: tmpFileList,
1201
+ cleanedFileList: tmpFileList
1122
1202
  });
1123
1203
  window.toastr.success(`File Updated`);
1124
1204
  }
@@ -1188,61 +1268,6 @@ function UploaderInner({
1188
1268
  </div>
1189
1269
  </>
1190
1270
  );
1191
- }
1192
-
1193
- const Uploader = compose(
1194
- connect(undefined, { initializeForm: initialize }),
1195
- observer
1196
- )(UploaderInner);
1271
+ };
1197
1272
 
1198
1273
  export default Uploader;
1199
-
1200
- function getFileDownloadAttr(exampleFile) {
1201
- const baseUrl = window?.frontEndConfig?.serverBasePath || "";
1202
- return isFunction(exampleFile)
1203
- ? { onClick: exampleFile }
1204
- : exampleFile && {
1205
- target: "_blank",
1206
- download: true,
1207
- href:
1208
- exampleFile.startsWith("https") || exampleFile.startsWith("www")
1209
- ? exampleFile
1210
- : baseUrl
1211
- ? urljoin(baseUrl, "exampleFiles", exampleFile)
1212
- : exampleFile
1213
- };
1214
- }
1215
-
1216
- function getNewCsvFile(ents, fileName) {
1217
- const strippedEnts = stripId(ents);
1218
-
1219
- return {
1220
- newFile: new File([papaparse.unparse(strippedEnts)], fileName),
1221
- cleanedEntities: strippedEnts
1222
- };
1223
- }
1224
-
1225
- function stripId(ents = []) {
1226
- return ents.map(ent => {
1227
- const { id, ...rest } = ent;
1228
- return rest;
1229
- });
1230
- }
1231
-
1232
- const manualEnterMessage = "Build CSV File";
1233
- const manualEnterSubMessage = "Paste or type data to build a CSV file";
1234
-
1235
- function trimFiles(incomingFiles, fileLimit) {
1236
- if (fileLimit) {
1237
- if (fileLimit && incomingFiles.length > fileLimit) {
1238
- window.toastr &&
1239
- window.toastr.warning(
1240
- `Detected additional files in your upload that we are ignoring. You can only upload ${fileLimit} file${
1241
- fileLimit > 1 ? "s" : ""
1242
- } at a time.`
1243
- );
1244
- }
1245
- return incomingFiles.slice(0, fileLimit);
1246
- }
1247
- return incomingFiles;
1248
- }