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