@vuu-ui/vuu-table-extras 2.2.1 → 3.0.0-beta.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/cjs/packages/vuu-table-extras/src/cell-renderers/dropdown-cell/DropdownCell.js +3 -0
- package/cjs/packages/vuu-table-extras/src/cell-renderers/dropdown-cell/DropdownCell.js.map +1 -1
- package/cjs/packages/vuu-table-extras/src/column-picker/ColumnModel.js +1 -1
- package/cjs/packages/vuu-table-extras/src/column-picker/ColumnModel.js.map +1 -1
- package/cjs/packages/vuu-table-extras/src/csv-upload/CsvUpload.js +62 -48
- package/cjs/packages/vuu-table-extras/src/csv-upload/CsvUpload.js.map +1 -1
- package/cjs/packages/vuu-table-extras/src/csv-upload/data-upload-preview/DataUploadPreview.css.js +6 -0
- package/cjs/packages/vuu-table-extras/src/csv-upload/data-upload-preview/DataUploadPreview.css.js.map +1 -0
- package/cjs/packages/vuu-table-extras/src/csv-upload/data-upload-preview/DataUploadPreview.js +118 -0
- package/cjs/packages/vuu-table-extras/src/csv-upload/data-upload-preview/DataUploadPreview.js.map +1 -0
- package/cjs/packages/vuu-table-extras/src/csv-upload/data-upload-preview/useDataUploadPreview.js +76 -0
- package/cjs/packages/vuu-table-extras/src/csv-upload/data-upload-preview/useDataUploadPreview.js.map +1 -0
- package/cjs/packages/vuu-table-extras/src/csv-upload/parse/csv-upload-utils.js +1 -44
- package/cjs/packages/vuu-table-extras/src/csv-upload/parse/csv-upload-utils.js.map +1 -1
- package/cjs/packages/vuu-table-extras/src/csv-upload/useCsvUpload.js +154 -66
- package/cjs/packages/vuu-table-extras/src/csv-upload/useCsvUpload.js.map +1 -1
- package/cjs/packages/vuu-table-extras/src/index.js +6 -2
- package/cjs/packages/vuu-table-extras/src/index.js.map +1 -1
- package/cjs/packages/vuu-table-extras/src/inline-add-row/InlineAddRow.css.js +6 -0
- package/cjs/packages/vuu-table-extras/src/inline-add-row/InlineAddRow.css.js.map +1 -0
- package/cjs/packages/vuu-table-extras/src/inline-add-row/InlineAddRow.js +44 -0
- package/cjs/packages/vuu-table-extras/src/inline-add-row/InlineAddRow.js.map +1 -0
- package/cjs/packages/vuu-table-extras/src/inline-add-row/useInlineAddRow.js +137 -0
- package/cjs/packages/vuu-table-extras/src/inline-add-row/useInlineAddRow.js.map +1 -0
- package/cjs/packages/vuu-table-extras/src/table-footer/TableFooter.css.js +1 -1
- package/cjs/packages/vuu-table-extras/src/table-footer/TableFooter.js +5 -2
- package/cjs/packages/vuu-table-extras/src/table-footer/TableFooter.js.map +1 -1
- package/esm/packages/vuu-table-extras/src/cell-renderers/dropdown-cell/DropdownCell.js +3 -0
- package/esm/packages/vuu-table-extras/src/cell-renderers/dropdown-cell/DropdownCell.js.map +1 -1
- package/esm/packages/vuu-table-extras/src/column-picker/ColumnModel.js +2 -2
- package/esm/packages/vuu-table-extras/src/column-picker/ColumnModel.js.map +1 -1
- package/esm/packages/vuu-table-extras/src/csv-upload/CsvUpload.js +63 -49
- package/esm/packages/vuu-table-extras/src/csv-upload/CsvUpload.js.map +1 -1
- package/esm/packages/vuu-table-extras/src/csv-upload/data-upload-preview/DataUploadPreview.css.js +4 -0
- package/esm/packages/vuu-table-extras/src/csv-upload/data-upload-preview/DataUploadPreview.css.js.map +1 -0
- package/esm/packages/vuu-table-extras/src/csv-upload/data-upload-preview/DataUploadPreview.js +116 -0
- package/esm/packages/vuu-table-extras/src/csv-upload/data-upload-preview/DataUploadPreview.js.map +1 -0
- package/esm/packages/vuu-table-extras/src/csv-upload/data-upload-preview/useDataUploadPreview.js +73 -0
- package/esm/packages/vuu-table-extras/src/csv-upload/data-upload-preview/useDataUploadPreview.js.map +1 -0
- package/esm/packages/vuu-table-extras/src/csv-upload/parse/csv-upload-utils.js +2 -43
- package/esm/packages/vuu-table-extras/src/csv-upload/parse/csv-upload-utils.js.map +1 -1
- package/esm/packages/vuu-table-extras/src/csv-upload/useCsvUpload.js +157 -69
- package/esm/packages/vuu-table-extras/src/csv-upload/useCsvUpload.js.map +1 -1
- package/esm/packages/vuu-table-extras/src/index.js +3 -1
- package/esm/packages/vuu-table-extras/src/index.js.map +1 -1
- package/esm/packages/vuu-table-extras/src/inline-add-row/InlineAddRow.css.js +4 -0
- package/esm/packages/vuu-table-extras/src/inline-add-row/InlineAddRow.css.js.map +1 -0
- package/esm/packages/vuu-table-extras/src/inline-add-row/InlineAddRow.js +42 -0
- package/esm/packages/vuu-table-extras/src/inline-add-row/InlineAddRow.js.map +1 -0
- package/esm/packages/vuu-table-extras/src/inline-add-row/useInlineAddRow.js +135 -0
- package/esm/packages/vuu-table-extras/src/inline-add-row/useInlineAddRow.js.map +1 -0
- package/esm/packages/vuu-table-extras/src/table-footer/TableFooter.css.js +1 -1
- package/esm/packages/vuu-table-extras/src/table-footer/TableFooter.js +5 -2
- package/esm/packages/vuu-table-extras/src/table-footer/TableFooter.js.map +1 -1
- package/package.json +12 -11
- package/types/csv-upload/CsvUpload.d.ts +9 -1
- package/types/csv-upload/data-upload-preview/DataUploadPreview.d.ts +10 -0
- package/types/csv-upload/data-upload-preview/useDataUploadPreview.d.ts +17 -0
- package/types/csv-upload/index.d.ts +2 -2
- package/types/csv-upload/useCsvUpload.d.ts +6 -3
- package/types/index.d.ts +1 -0
- package/types/inline-add-row/InlineAddRow.d.ts +3 -0
- package/types/inline-add-row/index.d.ts +2 -0
- package/types/inline-add-row/useInlineAddRow.d.ts +57 -0
- package/types/table-footer/TableFooter.d.ts +3 -2
- package/cjs/packages/vuu-table-extras/src/csv-upload/useCsvUploadSessionPreview.js +0 -73
- package/cjs/packages/vuu-table-extras/src/csv-upload/useCsvUploadSessionPreview.js.map +0 -1
- package/esm/packages/vuu-table-extras/src/csv-upload/useCsvUploadSessionPreview.js +0 -71
- package/esm/packages/vuu-table-extras/src/csv-upload/useCsvUploadSessionPreview.js.map +0 -1
- package/types/csv-upload/useCsvUploadSessionPreview.d.ts +0 -10
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useComponentCssInjection } from '@salt-ds/styles';
|
|
3
|
+
import { useWindow } from '@salt-ds/window';
|
|
4
|
+
import { DataEditingProvider, EditButtons, UNDO_CELL_RENDERER, EDIT_ACTION_ROW_CLASS_NAME_GENERATOR } from '@vuu-ui/vuu-data-editing';
|
|
5
|
+
import { Table } from '@vuu-ui/vuu-table';
|
|
6
|
+
import cx from 'clsx';
|
|
7
|
+
import { useMemo, useCallback } from 'react';
|
|
8
|
+
import { TableFooter, TableFooterTray } from '../../table-footer/TableFooter.js';
|
|
9
|
+
import css from './DataUploadPreview.css.js';
|
|
10
|
+
import { getSessionDataSource, useDataUploadPreview } from './useDataUploadPreview.js';
|
|
11
|
+
|
|
12
|
+
const classBase = "vuuDataUploadPreview";
|
|
13
|
+
const undoColumn = {
|
|
14
|
+
name: "undo",
|
|
15
|
+
source: "client",
|
|
16
|
+
width: 80,
|
|
17
|
+
type: {
|
|
18
|
+
name: "string",
|
|
19
|
+
renderer: {
|
|
20
|
+
name: UNDO_CELL_RENDERER
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const rowClassNameGenerators = [EDIT_ACTION_ROW_CLASS_NAME_GENERATOR];
|
|
25
|
+
const DataUploadPreview = ({
|
|
26
|
+
className,
|
|
27
|
+
editSession,
|
|
28
|
+
onClose,
|
|
29
|
+
saveLabel = "Submit",
|
|
30
|
+
tableSchema,
|
|
31
|
+
...htmlAttributes
|
|
32
|
+
}) => {
|
|
33
|
+
const dataSource = useMemo(
|
|
34
|
+
() => getSessionDataSource(editSession),
|
|
35
|
+
[editSession]
|
|
36
|
+
);
|
|
37
|
+
const targetWindow = useWindow();
|
|
38
|
+
useComponentCssInjection({
|
|
39
|
+
testId: "vuu-data-upload-preview",
|
|
40
|
+
css,
|
|
41
|
+
window: targetWindow
|
|
42
|
+
});
|
|
43
|
+
const {
|
|
44
|
+
canCancel,
|
|
45
|
+
canSave,
|
|
46
|
+
hasSelection,
|
|
47
|
+
onCancel,
|
|
48
|
+
onDelete,
|
|
49
|
+
onSave,
|
|
50
|
+
sessionError
|
|
51
|
+
} = useDataUploadPreview({ dataSource, editSession, onClose });
|
|
52
|
+
const isRowSelectable = useCallback(
|
|
53
|
+
(dataRow) => dataRow.vuu_action !== "deleteRow",
|
|
54
|
+
[]
|
|
55
|
+
);
|
|
56
|
+
const config = useMemo(() => {
|
|
57
|
+
const columns = tableSchema.columns.map(
|
|
58
|
+
(column) => column.name === "vuuMsg" ? {
|
|
59
|
+
...column,
|
|
60
|
+
editable: false,
|
|
61
|
+
hidden: false,
|
|
62
|
+
label: "Error",
|
|
63
|
+
width: 300
|
|
64
|
+
} : {
|
|
65
|
+
...column,
|
|
66
|
+
editable: true
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
if (!columns.some(({ name }) => name === "vuuMsg")) {
|
|
70
|
+
columns.push({
|
|
71
|
+
editable: false,
|
|
72
|
+
label: "Error",
|
|
73
|
+
name: "vuuMsg",
|
|
74
|
+
serverDataType: "string",
|
|
75
|
+
width: 300
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
columns: columns.concat({ hidden: true, name: "vuu_action" }, undoColumn),
|
|
80
|
+
columnDefaultWidth: 150,
|
|
81
|
+
rowClassNameGenerators,
|
|
82
|
+
rowSeparators: true,
|
|
83
|
+
zebraStripes: true
|
|
84
|
+
};
|
|
85
|
+
}, [tableSchema.columns]);
|
|
86
|
+
return /* @__PURE__ */ jsxs("div", { ...htmlAttributes, className: cx(classBase, className), children: [
|
|
87
|
+
sessionError ? /* @__PURE__ */ jsx("div", { role: "alert", children: sessionError }) : null,
|
|
88
|
+
/* @__PURE__ */ jsx("div", { className: `${classBase}-content`, children: /* @__PURE__ */ jsx(DataEditingProvider, { editSession, children: /* @__PURE__ */ jsx(
|
|
89
|
+
Table,
|
|
90
|
+
{
|
|
91
|
+
config,
|
|
92
|
+
"data-viewport": dataSource.viewport,
|
|
93
|
+
dataSource,
|
|
94
|
+
isRowSelectable,
|
|
95
|
+
renderBufferSize: 10,
|
|
96
|
+
selectionModel: "checkbox"
|
|
97
|
+
}
|
|
98
|
+
) }) }),
|
|
99
|
+
/* @__PURE__ */ jsx(TableFooter, { children: /* @__PURE__ */ jsx(TableFooterTray, { position: "center", children: /* @__PURE__ */ jsx(
|
|
100
|
+
EditButtons,
|
|
101
|
+
{
|
|
102
|
+
canCancel,
|
|
103
|
+
canSave,
|
|
104
|
+
editSession,
|
|
105
|
+
hasSelection,
|
|
106
|
+
onCancel,
|
|
107
|
+
onDelete,
|
|
108
|
+
onSave,
|
|
109
|
+
saveLabel
|
|
110
|
+
}
|
|
111
|
+
) }) })
|
|
112
|
+
] });
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export { DataUploadPreview };
|
|
116
|
+
//# sourceMappingURL=DataUploadPreview.js.map
|
package/esm/packages/vuu-table-extras/src/csv-upload/data-upload-preview/DataUploadPreview.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DataUploadPreview.js","sources":["../../../../../../../../packages/vuu-table-extras/src/csv-upload/data-upload-preview/DataUploadPreview.tsx"],"sourcesContent":["import { useComponentCssInjection } from \"@salt-ds/styles\";\nimport { useWindow } from \"@salt-ds/window\";\nimport type { TableSchema } from \"@vuu-ui/vuu-data-types\";\nimport {\n DataEditingProvider,\n EDIT_ACTION_ROW_CLASS_NAME_GENERATOR,\n EditButtons,\n type EditSession,\n UNDO_CELL_RENDERER,\n} from \"@vuu-ui/vuu-data-editing\";\nimport { Table } from \"@vuu-ui/vuu-table\";\nimport type {\n ColumnDescriptor,\n DataRow,\n TableConfig,\n} from \"@vuu-ui/vuu-table-types\";\nimport cx from \"clsx\";\nimport type { HTMLAttributes } from \"react\";\nimport { useCallback, useMemo } from \"react\";\nimport { TableFooter, TableFooterTray } from \"../../table-footer/TableFooter\";\nimport css from \"./DataUploadPreview.css\";\nimport {\n getSessionDataSource,\n useDataUploadPreview,\n} from \"./useDataUploadPreview\";\n\nconst classBase = \"vuuDataUploadPreview\";\n\nconst undoColumn: ColumnDescriptor = {\n name: \"undo\",\n source: \"client\",\n width: 80,\n type: {\n name: \"string\",\n renderer: {\n name: UNDO_CELL_RENDERER,\n },\n },\n};\n\nconst rowClassNameGenerators = [EDIT_ACTION_ROW_CLASS_NAME_GENERATOR];\n\nexport interface DataUploadPreviewProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"onSelect\"> {\n editSession: EditSession;\n onClose: () => void;\n saveLabel?: string;\n tableSchema: TableSchema;\n}\n\nexport const DataUploadPreview = ({\n className,\n editSession,\n onClose,\n saveLabel = \"Submit\",\n tableSchema,\n ...htmlAttributes\n}: DataUploadPreviewProps) => {\n const dataSource = useMemo(\n () => getSessionDataSource(editSession),\n [editSession],\n );\n const targetWindow = useWindow();\n useComponentCssInjection({\n testId: \"vuu-data-upload-preview\",\n css,\n window: targetWindow,\n });\n\n const {\n canCancel,\n canSave,\n hasSelection,\n onCancel,\n onDelete,\n onSave,\n sessionError,\n } = useDataUploadPreview({ dataSource, editSession, onClose });\n\n const isRowSelectable = useCallback(\n (dataRow: DataRow) => dataRow.vuu_action !== \"deleteRow\",\n [],\n );\n const config = useMemo<TableConfig>(() => {\n const columns = tableSchema.columns.map<ColumnDescriptor>((column) =>\n column.name === \"vuuMsg\"\n ? {\n ...column,\n editable: false,\n hidden: false,\n label: \"Error\",\n width: 300,\n }\n : {\n ...column,\n editable: true,\n },\n );\n if (!columns.some(({ name }) => name === \"vuuMsg\")) {\n columns.push({\n editable: false,\n label: \"Error\",\n name: \"vuuMsg\",\n serverDataType: \"string\",\n width: 300,\n });\n }\n\n return {\n columns: columns.concat({ hidden: true, name: \"vuu_action\" }, undoColumn),\n columnDefaultWidth: 150,\n rowClassNameGenerators,\n rowSeparators: true,\n zebraStripes: true,\n };\n }, [tableSchema.columns]);\n\n return (\n <div {...htmlAttributes} className={cx(classBase, className)}>\n {sessionError ? <div role=\"alert\">{sessionError}</div> : null}\n <div className={`${classBase}-content`}>\n <DataEditingProvider editSession={editSession}>\n <Table\n config={config}\n data-viewport={dataSource.viewport}\n dataSource={dataSource}\n isRowSelectable={isRowSelectable}\n renderBufferSize={10}\n selectionModel=\"checkbox\"\n />\n </DataEditingProvider>\n </div>\n <TableFooter>\n <TableFooterTray position=\"center\">\n <EditButtons\n canCancel={canCancel}\n canSave={canSave}\n editSession={editSession}\n hasSelection={hasSelection}\n onCancel={onCancel}\n onDelete={onDelete}\n onSave={onSave}\n saveLabel={saveLabel}\n />\n </TableFooterTray>\n </TableFooter>\n </div>\n );\n};\n"],"names":[],"mappings":"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"}
|
package/esm/packages/vuu-table-extras/src/csv-upload/data-upload-preview/useDataUploadPreview.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useState, useCallback, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
const getSessionDataSource = (editSession) => {
|
|
4
|
+
const dataSource = editSession.sessionDataSource;
|
|
5
|
+
if (!dataSource) {
|
|
6
|
+
throw new Error("[DataUploadPreview] requires an active edit session");
|
|
7
|
+
}
|
|
8
|
+
return dataSource;
|
|
9
|
+
};
|
|
10
|
+
const useDataUploadPreview = ({
|
|
11
|
+
dataSource,
|
|
12
|
+
editSession,
|
|
13
|
+
onClose
|
|
14
|
+
}) => {
|
|
15
|
+
const [editState, setEditState] = useState(editSession.editState);
|
|
16
|
+
const [lifecycle, setLifecycle] = useState(
|
|
17
|
+
editSession.lifecycle
|
|
18
|
+
);
|
|
19
|
+
const [selectionCount, setSelectionCount] = useState(0);
|
|
20
|
+
const [sessionError, setSessionError] = useState();
|
|
21
|
+
const endSession = useCallback(
|
|
22
|
+
async (save, force = false) => {
|
|
23
|
+
try {
|
|
24
|
+
await editSession.end(save, force);
|
|
25
|
+
onClose();
|
|
26
|
+
} catch (error) {
|
|
27
|
+
setSessionError(
|
|
28
|
+
error instanceof Error ? error.message : "Unable to end edit session"
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
[editSession, onClose]
|
|
33
|
+
);
|
|
34
|
+
const onCancel = useCallback(() => endSession(false), [endSession]);
|
|
35
|
+
const onDelete = useCallback(
|
|
36
|
+
() => editSession.deleteSelectedRows(),
|
|
37
|
+
[editSession]
|
|
38
|
+
);
|
|
39
|
+
const onSave = useCallback(
|
|
40
|
+
(force = false) => endSession(true, force),
|
|
41
|
+
[endSession]
|
|
42
|
+
);
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
const handleEditState = (nextEditState) => {
|
|
45
|
+
setEditState(nextEditState);
|
|
46
|
+
};
|
|
47
|
+
const handleLifecycle = (nextLifecycle) => {
|
|
48
|
+
setLifecycle(nextLifecycle);
|
|
49
|
+
};
|
|
50
|
+
editSession.on("editState", handleEditState);
|
|
51
|
+
editSession.on("lifecycle", handleLifecycle);
|
|
52
|
+
dataSource.on("row-selection", setSelectionCount);
|
|
53
|
+
return () => {
|
|
54
|
+
editSession.removeListener("editState", handleEditState);
|
|
55
|
+
editSession.removeListener("lifecycle", handleLifecycle);
|
|
56
|
+
dataSource.removeListener("row-selection", setSelectionCount);
|
|
57
|
+
};
|
|
58
|
+
}, [dataSource, editSession]);
|
|
59
|
+
const canCancel = lifecycle.status === "active" || lifecycle.status === "error" && lifecycle.operation === "end";
|
|
60
|
+
const canSave = canCancel && (editState === "dirty" || editState === "stale") && editSession.invalidCount === 0;
|
|
61
|
+
return {
|
|
62
|
+
canCancel,
|
|
63
|
+
canSave,
|
|
64
|
+
hasSelection: selectionCount > 0,
|
|
65
|
+
onCancel,
|
|
66
|
+
onDelete,
|
|
67
|
+
onSave,
|
|
68
|
+
sessionError
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export { getSessionDataSource, useDataUploadPreview };
|
|
73
|
+
//# sourceMappingURL=useDataUploadPreview.js.map
|
package/esm/packages/vuu-table-extras/src/csv-upload/data-upload-preview/useDataUploadPreview.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDataUploadPreview.js","sources":["../../../../../../../../packages/vuu-table-extras/src/csv-upload/data-upload-preview/useDataUploadPreview.ts"],"sourcesContent":["import type { DataSource } from \"@vuu-ui/vuu-data-types\";\nimport type {\n EditLifecycle,\n EditSession,\n EditState,\n} from \"@vuu-ui/vuu-data-editing\";\nimport { useCallback, useEffect, useState } from \"react\";\n\nexport interface UseDataUploadPreviewProps {\n dataSource: DataSource;\n editSession: EditSession;\n onClose: () => void;\n}\n\nexport const getSessionDataSource = (editSession: EditSession): DataSource => {\n const dataSource = editSession.sessionDataSource;\n if (!dataSource) {\n throw new Error(\"[DataUploadPreview] requires an active edit session\");\n }\n return dataSource;\n};\n\nexport const useDataUploadPreview = ({\n dataSource,\n editSession,\n onClose,\n}: UseDataUploadPreviewProps) => {\n const [editState, setEditState] = useState<EditState>(editSession.editState);\n const [lifecycle, setLifecycle] = useState<EditLifecycle>(\n editSession.lifecycle,\n );\n const [selectionCount, setSelectionCount] = useState(0);\n const [sessionError, setSessionError] = useState<string>();\n\n const endSession = useCallback(\n async (save: boolean, force = false) => {\n try {\n await editSession.end(save, force);\n onClose();\n } catch (error) {\n setSessionError(\n error instanceof Error ? error.message : \"Unable to end edit session\",\n );\n }\n },\n [editSession, onClose],\n );\n\n const onCancel = useCallback(() => endSession(false), [endSession]);\n const onDelete = useCallback(\n () => editSession.deleteSelectedRows(),\n [editSession],\n );\n const onSave = useCallback(\n (force = false) => endSession(true, force),\n [endSession],\n );\n\n useEffect(() => {\n const handleEditState = (nextEditState: EditState) => {\n setEditState(nextEditState);\n };\n const handleLifecycle = (nextLifecycle: EditLifecycle) => {\n setLifecycle(nextLifecycle);\n };\n editSession.on(\"editState\", handleEditState);\n editSession.on(\"lifecycle\", handleLifecycle);\n dataSource.on(\"row-selection\", setSelectionCount);\n return () => {\n editSession.removeListener(\"editState\", handleEditState);\n editSession.removeListener(\"lifecycle\", handleLifecycle);\n dataSource.removeListener(\"row-selection\", setSelectionCount);\n };\n }, [dataSource, editSession]);\n\n const canCancel =\n lifecycle.status === \"active\" ||\n (lifecycle.status === \"error\" && lifecycle.operation === \"end\");\n const canSave =\n canCancel &&\n (editState === \"dirty\" || editState === \"stale\") &&\n editSession.invalidCount === 0;\n\n return {\n canCancel,\n canSave,\n hasSelection: selectionCount > 0,\n onCancel,\n onDelete,\n onSave,\n sessionError,\n };\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import '@vuu-ui/vuu-utils';
|
|
2
2
|
import { CSV_FIRST_DATA_ROW_NUMBER } from './csv-constants.js';
|
|
3
3
|
|
|
4
4
|
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
@@ -30,47 +30,6 @@ const toValidationErrorsFromParseRowErrors = (parseError) => {
|
|
|
30
30
|
(error) => error.rowNum >= CSV_FIRST_DATA_ROW_NUMBER
|
|
31
31
|
);
|
|
32
32
|
};
|
|
33
|
-
const normalizeTableData = (value, fallback) => {
|
|
34
|
-
const tableData = isRecord(value) && "data" in value ? value.data : value;
|
|
35
|
-
if (!isRecord(tableData)) {
|
|
36
|
-
return fallback;
|
|
37
|
-
}
|
|
38
|
-
const columns = tableData.columns;
|
|
39
|
-
const rows = tableData.rows;
|
|
40
|
-
if (!Array.isArray(columns) || columns.some((column) => typeof column !== "string") || !Array.isArray(rows)) {
|
|
41
|
-
return fallback;
|
|
42
|
-
}
|
|
43
|
-
return {
|
|
44
|
-
columns,
|
|
45
|
-
rows: rows.map((row) => {
|
|
46
|
-
if (Array.isArray(row)) {
|
|
47
|
-
return row;
|
|
48
|
-
}
|
|
49
|
-
if (!isRecord(row)) {
|
|
50
|
-
return columns.map(() => "");
|
|
51
|
-
}
|
|
52
|
-
return columns.map((column) => row[column] ?? "");
|
|
53
|
-
})
|
|
54
|
-
};
|
|
55
|
-
};
|
|
56
|
-
const executeBatchRpcCalls = async (items, executor, errorFormatter) => {
|
|
57
|
-
const errors = [];
|
|
58
|
-
const results = [];
|
|
59
|
-
for (const item of items) {
|
|
60
|
-
try {
|
|
61
|
-
const result = await executor(item);
|
|
62
|
-
const rpcResult = result;
|
|
63
|
-
if (isRpcError(rpcResult)) {
|
|
64
|
-
errors.push(errorFormatter(item, rpcResult.errorMessage));
|
|
65
|
-
} else {
|
|
66
|
-
results.push(result);
|
|
67
|
-
}
|
|
68
|
-
} catch (err) {
|
|
69
|
-
errors.push(errorFormatter(item, toErrorMessage(err)));
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return { errors, results };
|
|
73
|
-
};
|
|
74
33
|
|
|
75
|
-
export { buildRowErrorMessage, createUploadError,
|
|
34
|
+
export { buildRowErrorMessage, createUploadError, hasFileParseErrors, isCsvParseError, mergeValidationWithParseErrors, toErrorMessage, toValidationErrorsFromParseRowErrors };
|
|
76
35
|
//# sourceMappingURL=csv-upload-utils.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"csv-upload-utils.js","sources":["../../../../../../../../packages/vuu-table-extras/src/csv-upload/parse/csv-upload-utils.ts"],"sourcesContent":["import type { RpcResult, VuuRowDataItemType } from \"@vuu-ui/vuu-protocol-types\";\nimport { isRpcError } from \"@vuu-ui/vuu-utils\";\nimport type { CsvUploadError } from \"../CsvUpload\";\nimport type { CsvParseError } from \"./csv-parse\";\nimport { CSV_FIRST_DATA_ROW_NUMBER } from \"./csv-constants\";\nimport type {\n CsvValidationError,\n CsvValidationResult,\n} from \"./csv-schema-validation\";\n\nexport type CsvUploadTableData = {\n columns: string[];\n rows: VuuRowDataItemType[][];\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null;\n\nexport const toErrorMessage = (error: unknown) =>\n error instanceof Error ? error.message : String(error);\n\nexport const isCsvParseError = (value: unknown): value is CsvParseError =>\n isRecord(value) &&\n \"message\" in value &&\n typeof value.message === \"string\" &&\n \"errors\" in value &&\n Array.isArray(value.errors) &&\n \"errorMap\" in value &&\n isRecord(value.errorMap);\n\nexport const hasFileParseErrors = (parseError: CsvParseError) =>\n Object.keys(parseError.errorMap.fileErrors).length > 0;\n\nexport const createUploadError = (\n source: CsvUploadError[\"source\"],\n message: string,\n parseError?: CsvParseError,\n validationError?: CsvUploadError[\"validationError\"],\n): CsvUploadError => ({\n message,\n parseError,\n validationError,\n source,\n});\n\nexport const mergeValidationWithParseErrors = (\n schemaValidation: CsvValidationResult,\n parseError: CsvParseError | undefined,\n): CsvValidationResult =>\n parseError\n ? {\n ...schemaValidation,\n errors: [\n ...schemaValidation.errors,\n ...toValidationErrorsFromParseRowErrors(parseError),\n ],\n parseRowError: parseError,\n }\n : schemaValidation;\n\nexport const buildRowErrorMessage = (prefix: string, rowErrors: string[]) => {\n const details = rowErrors.slice(0, 8).join(\"; \");\n const remaining = rowErrors.length - 8;\n const suffix = remaining > 0 ? `; and ${remaining} more` : \"\";\n return `${prefix} for ${rowErrors.length} row(s): ${details}${suffix}`;\n};\n\nexport const toValidationErrorsFromParseRowErrors = (\n parseError: CsvParseError,\n): CsvValidationError[] => {\n return parseError.errors.filter(\n (error) => error.rowNum >= CSV_FIRST_DATA_ROW_NUMBER,\n );\n};\n\nexport const normalizeTableData = (\n value: unknown,\n fallback: CsvUploadTableData,\n): CsvUploadTableData => {\n const tableData =\n isRecord(value) && \"data\" in value ? (value.data as unknown) : value;\n\n if (!isRecord(tableData)) {\n return fallback;\n }\n\n const columns = tableData.columns;\n const rows = tableData.rows;\n\n if (\n !Array.isArray(columns) ||\n columns.some((column) => typeof column !== \"string\") ||\n !Array.isArray(rows)\n ) {\n return fallback;\n }\n\n return {\n columns,\n rows: rows.map((row) => {\n if (Array.isArray(row)) {\n return row as VuuRowDataItemType[];\n }\n if (!isRecord(row)) {\n return columns.map(() => \"\");\n }\n return columns.map((column) => (row[column] as VuuRowDataItemType) ?? \"\");\n }),\n };\n};\n\nexport type RpcBatchResult = {\n errors: string[];\n results: unknown[];\n};\n\nexport const executeBatchRpcCalls = async <T>(\n items: T[],\n executor: (item: T) => Promise<unknown>,\n errorFormatter: (item: T, error: string) => string,\n): Promise<RpcBatchResult> => {\n const errors: string[] = [];\n const results: unknown[] = [];\n\n for (const item of items) {\n try {\n const result = await executor(item);\n const rpcResult = result as RpcResult | undefined;\n if (isRpcError(rpcResult)) {\n errors.push(errorFormatter(item, rpcResult.errorMessage));\n } else {\n results.push(result);\n }\n } catch (err) {\n errors.push(errorFormatter(item, toErrorMessage(err)));\n }\n }\n\n return { errors, results };\n};\n"],"names":[],"mappings":";;;AAeA,MAAM,WAAW,CAAC,KAAA,KAChB,OAAO,KAAA,KAAU,YAAY,KAAU,KAAA,IAAA;AAE5B,MAAA,cAAA,GAAiB,CAAC,KAC7B,KAAA,KAAA,YAAiB,QAAQ,KAAM,CAAA,OAAA,GAAU,OAAO,KAAK;AAE1C,MAAA,eAAA,GAAkB,CAAC,KAC9B,KAAA,QAAA,CAAS,KAAK,CACd,IAAA,SAAA,IAAa,KACb,IAAA,OAAO,KAAM,CAAA,OAAA,KAAY,YACzB,QAAY,IAAA,KAAA,IACZ,KAAM,CAAA,OAAA,CAAQ,KAAM,CAAA,MAAM,KAC1B,UAAc,IAAA,KAAA,IACd,QAAS,CAAA,KAAA,CAAM,QAAQ;AAEZ,MAAA,kBAAA,GAAqB,CAAC,UACjC,KAAA,MAAA,CAAO,KAAK,UAAW,CAAA,QAAA,CAAS,UAAU,CAAA,CAAE,MAAS,GAAA;AAEhD,MAAM,iBAAoB,GAAA,CAC/B,MACA,EAAA,OAAA,EACA,YACA,eACoB,MAAA;AAAA,EACpB,OAAA;AAAA,EACA,UAAA;AAAA,EACA,eAAA;AAAA,EACA;AACF,CAAA;AAEO,MAAM,8BAAiC,GAAA,CAC5C,gBACA,EAAA,UAAA,KAEA,UACI,GAAA;AAAA,EACE,GAAG,gBAAA;AAAA,EACH,MAAQ,EAAA;AAAA,IACN,GAAG,gBAAiB,CAAA,MAAA;AAAA,IACpB,GAAG,qCAAqC,UAAU;AAAA,GACpD;AAAA,EACA,aAAe,EAAA;AACjB,CACA,GAAA;AAEO,MAAA,oBAAA,GAAuB,CAAC,MAAA,EAAgB,SAAwB,KAAA;AAC3E,EAAA,MAAM,UAAU,SAAU,CAAA,KAAA,CAAM,GAAG,CAAC,CAAA,CAAE,KAAK,IAAI,CAAA;AAC/C,EAAM,MAAA,SAAA,GAAY,UAAU,MAAS,GAAA,CAAA;AACrC,EAAA,MAAM,MAAS,GAAA,SAAA,GAAY,CAAI,GAAA,CAAA,MAAA,EAAS,SAAS,CAAU,KAAA,CAAA,GAAA,EAAA;AAC3D,EAAO,OAAA,CAAA,EAAG,MAAM,CAAQ,KAAA,EAAA,SAAA,CAAU,MAAM,CAAY,SAAA,EAAA,OAAO,GAAG,MAAM,CAAA,CAAA;AACtE;AAEa,MAAA,oCAAA,GAAuC,CAClD,UACyB,KAAA;AACzB,EAAA,OAAO,WAAW,MAAO,CAAA,MAAA;AAAA,IACvB,CAAC,KAAU,KAAA,KAAA,CAAM,MAAU,IAAA;AAAA,GAC7B;AACF
|
|
1
|
+
{"version":3,"file":"csv-upload-utils.js","sources":["../../../../../../../../packages/vuu-table-extras/src/csv-upload/parse/csv-upload-utils.ts"],"sourcesContent":["import type { RpcResult, VuuRowDataItemType } from \"@vuu-ui/vuu-protocol-types\";\nimport { isRpcError } from \"@vuu-ui/vuu-utils\";\nimport type { CsvUploadError } from \"../CsvUpload\";\nimport type { CsvParseError } from \"./csv-parse\";\nimport { CSV_FIRST_DATA_ROW_NUMBER } from \"./csv-constants\";\nimport type {\n CsvValidationError,\n CsvValidationResult,\n} from \"./csv-schema-validation\";\n\nexport type CsvUploadTableData = {\n columns: string[];\n rows: VuuRowDataItemType[][];\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null;\n\nexport const toErrorMessage = (error: unknown) =>\n error instanceof Error ? error.message : String(error);\n\nexport const isCsvParseError = (value: unknown): value is CsvParseError =>\n isRecord(value) &&\n \"message\" in value &&\n typeof value.message === \"string\" &&\n \"errors\" in value &&\n Array.isArray(value.errors) &&\n \"errorMap\" in value &&\n isRecord(value.errorMap);\n\nexport const hasFileParseErrors = (parseError: CsvParseError) =>\n Object.keys(parseError.errorMap.fileErrors).length > 0;\n\nexport const createUploadError = (\n source: CsvUploadError[\"source\"],\n message: string,\n parseError?: CsvParseError,\n validationError?: CsvUploadError[\"validationError\"],\n): CsvUploadError => ({\n message,\n parseError,\n validationError,\n source,\n});\n\nexport const mergeValidationWithParseErrors = (\n schemaValidation: CsvValidationResult,\n parseError: CsvParseError | undefined,\n): CsvValidationResult =>\n parseError\n ? {\n ...schemaValidation,\n errors: [\n ...schemaValidation.errors,\n ...toValidationErrorsFromParseRowErrors(parseError),\n ],\n parseRowError: parseError,\n }\n : schemaValidation;\n\nexport const buildRowErrorMessage = (prefix: string, rowErrors: string[]) => {\n const details = rowErrors.slice(0, 8).join(\"; \");\n const remaining = rowErrors.length - 8;\n const suffix = remaining > 0 ? `; and ${remaining} more` : \"\";\n return `${prefix} for ${rowErrors.length} row(s): ${details}${suffix}`;\n};\n\nexport const toValidationErrorsFromParseRowErrors = (\n parseError: CsvParseError,\n): CsvValidationError[] => {\n return parseError.errors.filter(\n (error) => error.rowNum >= CSV_FIRST_DATA_ROW_NUMBER,\n );\n};\n\nexport const normalizeTableData = (\n value: unknown,\n fallback: CsvUploadTableData,\n): CsvUploadTableData => {\n const tableData =\n isRecord(value) && \"data\" in value ? (value.data as unknown) : value;\n\n if (!isRecord(tableData)) {\n return fallback;\n }\n\n const columns = tableData.columns;\n const rows = tableData.rows;\n\n if (\n !Array.isArray(columns) ||\n columns.some((column) => typeof column !== \"string\") ||\n !Array.isArray(rows)\n ) {\n return fallback;\n }\n\n return {\n columns,\n rows: rows.map((row) => {\n if (Array.isArray(row)) {\n return row as VuuRowDataItemType[];\n }\n if (!isRecord(row)) {\n return columns.map(() => \"\");\n }\n return columns.map((column) => (row[column] as VuuRowDataItemType) ?? \"\");\n }),\n };\n};\n\nexport type RpcBatchResult = {\n errors: string[];\n results: unknown[];\n};\n\nexport const executeBatchRpcCalls = async <T>(\n items: T[],\n executor: (item: T) => Promise<unknown>,\n errorFormatter: (item: T, error: string) => string,\n): Promise<RpcBatchResult> => {\n const errors: string[] = [];\n const results: unknown[] = [];\n\n for (const item of items) {\n try {\n const result = await executor(item);\n const rpcResult = result as RpcResult | undefined;\n if (isRpcError(rpcResult)) {\n errors.push(errorFormatter(item, rpcResult.errorMessage));\n } else {\n results.push(result);\n }\n } catch (err) {\n errors.push(errorFormatter(item, toErrorMessage(err)));\n }\n }\n\n return { errors, results };\n};\n"],"names":[],"mappings":";;;AAeA,MAAM,WAAW,CAAC,KAAA,KAChB,OAAO,KAAA,KAAU,YAAY,KAAU,KAAA,IAAA;AAE5B,MAAA,cAAA,GAAiB,CAAC,KAC7B,KAAA,KAAA,YAAiB,QAAQ,KAAM,CAAA,OAAA,GAAU,OAAO,KAAK;AAE1C,MAAA,eAAA,GAAkB,CAAC,KAC9B,KAAA,QAAA,CAAS,KAAK,CACd,IAAA,SAAA,IAAa,KACb,IAAA,OAAO,KAAM,CAAA,OAAA,KAAY,YACzB,QAAY,IAAA,KAAA,IACZ,KAAM,CAAA,OAAA,CAAQ,KAAM,CAAA,MAAM,KAC1B,UAAc,IAAA,KAAA,IACd,QAAS,CAAA,KAAA,CAAM,QAAQ;AAEZ,MAAA,kBAAA,GAAqB,CAAC,UACjC,KAAA,MAAA,CAAO,KAAK,UAAW,CAAA,QAAA,CAAS,UAAU,CAAA,CAAE,MAAS,GAAA;AAEhD,MAAM,iBAAoB,GAAA,CAC/B,MACA,EAAA,OAAA,EACA,YACA,eACoB,MAAA;AAAA,EACpB,OAAA;AAAA,EACA,UAAA;AAAA,EACA,eAAA;AAAA,EACA;AACF,CAAA;AAEO,MAAM,8BAAiC,GAAA,CAC5C,gBACA,EAAA,UAAA,KAEA,UACI,GAAA;AAAA,EACE,GAAG,gBAAA;AAAA,EACH,MAAQ,EAAA;AAAA,IACN,GAAG,gBAAiB,CAAA,MAAA;AAAA,IACpB,GAAG,qCAAqC,UAAU;AAAA,GACpD;AAAA,EACA,aAAe,EAAA;AACjB,CACA,GAAA;AAEO,MAAA,oBAAA,GAAuB,CAAC,MAAA,EAAgB,SAAwB,KAAA;AAC3E,EAAA,MAAM,UAAU,SAAU,CAAA,KAAA,CAAM,GAAG,CAAC,CAAA,CAAE,KAAK,IAAI,CAAA;AAC/C,EAAM,MAAA,SAAA,GAAY,UAAU,MAAS,GAAA,CAAA;AACrC,EAAA,MAAM,MAAS,GAAA,SAAA,GAAY,CAAI,GAAA,CAAA,MAAA,EAAS,SAAS,CAAU,KAAA,CAAA,GAAA,EAAA;AAC3D,EAAO,OAAA,CAAA,EAAG,MAAM,CAAQ,KAAA,EAAA,SAAA,CAAU,MAAM,CAAY,SAAA,EAAA,OAAO,GAAG,MAAM,CAAA,CAAA;AACtE;AAEa,MAAA,oCAAA,GAAuC,CAClD,UACyB,KAAA;AACzB,EAAA,OAAO,WAAW,MAAO,CAAA,MAAA;AAAA,IACvB,CAAC,KAAU,KAAA,KAAA,CAAM,MAAU,IAAA;AAAA,GAC7B;AACF;;;;"}
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { EditSession } from '@vuu-ui/vuu-data-editing';
|
|
2
|
+
import { isSessionTable, isRpcError } from '@vuu-ui/vuu-utils';
|
|
3
|
+
import { useState, useMemo, useRef, useEffect, useCallback } from 'react';
|
|
3
4
|
import { parseCsv } from './parse/csv-parse.js';
|
|
4
5
|
import { validateCsvAgainstSchema } from './parse/csv-schema-validation.js';
|
|
5
|
-
import {
|
|
6
|
+
import { toErrorMessage, buildRowErrorMessage, hasFileParseErrors, createUploadError, mergeValidationWithParseErrors, isCsvParseError } from './parse/csv-upload-utils.js';
|
|
6
7
|
import { CSV_FIRST_DATA_ROW_NUMBER } from './parse/csv-constants.js';
|
|
7
8
|
|
|
8
9
|
const useCsvUpload = ({
|
|
9
10
|
dataSource,
|
|
11
|
+
importMode = "direct",
|
|
10
12
|
onImportSessionEnded,
|
|
11
13
|
onImportSessionStarted,
|
|
12
14
|
onError,
|
|
13
15
|
onImported,
|
|
16
|
+
onPreview,
|
|
14
17
|
onProcessingStarted,
|
|
15
18
|
maxRows,
|
|
16
19
|
parseOptions
|
|
@@ -19,37 +22,70 @@ const useCsvUpload = ({
|
|
|
19
22
|
const [sessionTable, setSessionTable] = useState();
|
|
20
23
|
const [isProcessingFile, setIsProcessingFile] = useState(false);
|
|
21
24
|
const [isImporting, setIsImporting] = useState(false);
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
const editSession = useMemo(() => new EditSession(dataSource), [dataSource]);
|
|
26
|
+
const ownsEditSessionRef = useRef(true);
|
|
27
|
+
const operationIdRef = useRef(0);
|
|
28
|
+
const processingPromiseRef = useRef(void 0);
|
|
29
|
+
const sessionDataSourceRef = useRef(void 0);
|
|
30
|
+
useEffect(
|
|
31
|
+
() => () => {
|
|
32
|
+
operationIdRef.current++;
|
|
33
|
+
const processingPromise = processingPromiseRef.current;
|
|
34
|
+
void (async () => {
|
|
35
|
+
try {
|
|
36
|
+
await processingPromise;
|
|
37
|
+
} finally {
|
|
38
|
+
if (ownsEditSessionRef.current) {
|
|
39
|
+
try {
|
|
40
|
+
await editSession.end(false);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error(
|
|
43
|
+
"[useCsvUpload] failed to discard edit session during cleanup",
|
|
44
|
+
error
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
})();
|
|
50
|
+
},
|
|
51
|
+
[editSession]
|
|
52
|
+
);
|
|
53
|
+
const setActiveSessionDataSource = useCallback(
|
|
54
|
+
(sessionDataSource) => {
|
|
55
|
+
sessionDataSourceRef.current = sessionDataSource;
|
|
56
|
+
const table2 = sessionDataSource?.table;
|
|
57
|
+
setSessionTable(
|
|
58
|
+
table2 && isSessionTable(table2) ? table2 : void 0
|
|
59
|
+
);
|
|
27
60
|
},
|
|
28
61
|
[]
|
|
29
62
|
);
|
|
30
63
|
const endEditSessionAndNotify = useCallback(
|
|
31
64
|
async (save, reason) => {
|
|
32
|
-
|
|
33
|
-
|
|
65
|
+
const sessionDataSource = sessionDataSourceRef.current;
|
|
66
|
+
if (!sessionDataSource) {
|
|
67
|
+
throw Error("CsvUpload has no active edit session.");
|
|
68
|
+
}
|
|
69
|
+
if (!sessionDataSource.endEditSession) {
|
|
70
|
+
throw Error(
|
|
71
|
+
"CsvUpload requires the session datasource to support endEditSession."
|
|
72
|
+
);
|
|
34
73
|
}
|
|
35
|
-
const currentSessionTable =
|
|
36
|
-
|
|
37
|
-
|
|
74
|
+
const currentSessionTable = sessionDataSource.table;
|
|
75
|
+
await editSession.end(save);
|
|
76
|
+
setActiveSessionDataSource(void 0);
|
|
38
77
|
onImportSessionEnded?.({
|
|
39
78
|
reason,
|
|
40
|
-
sessionTable: currentSessionTable
|
|
79
|
+
sessionTable: currentSessionTable && isSessionTable(currentSessionTable) ? currentSessionTable : void 0
|
|
41
80
|
});
|
|
42
|
-
return
|
|
81
|
+
return void 0;
|
|
43
82
|
},
|
|
44
|
-
[
|
|
83
|
+
[editSession, onImportSessionEnded, setActiveSessionDataSource]
|
|
45
84
|
);
|
|
46
85
|
const table = dataSource.table;
|
|
47
86
|
const schema = dataSource.tableSchema;
|
|
48
87
|
const addAllRows = useCallback(
|
|
49
|
-
async (mergedValidation) => {
|
|
50
|
-
if (table === void 0) {
|
|
51
|
-
throw Error("CsvUpload requires dataSource.table to be defined.");
|
|
52
|
-
}
|
|
88
|
+
async (mergedValidation, operationId) => {
|
|
53
89
|
const vuuMsgByRow = /* @__PURE__ */ new Map();
|
|
54
90
|
for (const { rowNum, column, message } of mergedValidation.errors) {
|
|
55
91
|
if (rowNum < CSV_FIRST_DATA_ROW_NUMBER) continue;
|
|
@@ -61,7 +97,9 @@ const useCsvUpload = ({
|
|
|
61
97
|
);
|
|
62
98
|
}
|
|
63
99
|
const parsedRowCount = mergedValidation.rows.length;
|
|
64
|
-
const unparsedErrorRows = [...vuuMsgByRow.keys()].filter(
|
|
100
|
+
const unparsedErrorRows = [...vuuMsgByRow.keys()].filter(
|
|
101
|
+
(rowNum) => rowNum - CSV_FIRST_DATA_ROW_NUMBER >= parsedRowCount
|
|
102
|
+
).map((rowNum) => ({
|
|
65
103
|
rowNum,
|
|
66
104
|
rowData: {},
|
|
67
105
|
vuuMsg: vuuMsgByRow.get(rowNum) ?? ""
|
|
@@ -74,70 +112,82 @@ const useCsvUpload = ({
|
|
|
74
112
|
})),
|
|
75
113
|
...unparsedErrorRows
|
|
76
114
|
];
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
115
|
+
const rpcErrors = [];
|
|
116
|
+
for (const { rowNum, rowData, vuuMsg } of allRows) {
|
|
117
|
+
if (operationId !== operationIdRef.current) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
80
121
|
const payload = { ...rowData, rowNum, vuuMsg };
|
|
81
|
-
const result = await
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
params: {
|
|
85
|
-
key: String(rowNum),
|
|
86
|
-
data: payload
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
if (!isRpcSuccess(result)) {
|
|
90
|
-
const msg = result && "errorMessage" in result ? String(result.errorMessage) : "addRow RPC failed";
|
|
91
|
-
throw Error(msg);
|
|
122
|
+
const result = await editSession.addRow(payload);
|
|
123
|
+
if (isRpcError(result)) {
|
|
124
|
+
throw Error(result.errorMessage);
|
|
92
125
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
126
|
+
if (typeof result === "string") {
|
|
127
|
+
throw Error(result);
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
rpcErrors.push(`Row ${rowNum}: ${toErrorMessage(error)}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
97
133
|
if (rpcErrors.length > 0) {
|
|
98
|
-
|
|
99
|
-
const errors = {
|
|
100
|
-
importError: createUploadError("import", errorMessage)
|
|
101
|
-
};
|
|
102
|
-
onError?.({ errors });
|
|
134
|
+
throw Error(buildRowErrorMessage("Import failed", rpcErrors));
|
|
103
135
|
}
|
|
136
|
+
return true;
|
|
104
137
|
},
|
|
105
|
-
[
|
|
138
|
+
[editSession]
|
|
106
139
|
);
|
|
107
140
|
const beginEditSession = useCallback(async () => {
|
|
108
|
-
|
|
109
|
-
throw Error("CsvUpload requires datasource createSessionDataSource support.");
|
|
110
|
-
}
|
|
111
|
-
const sessionDataSource = await dataSource.createSessionDataSource("Empty");
|
|
141
|
+
const sessionDataSource = await editSession.begin("Empty");
|
|
112
142
|
const sessionVuuTable = sessionDataSource?.table;
|
|
113
|
-
if (sessionVuuTable
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
143
|
+
if (sessionDataSource === void 0 || sessionVuuTable === void 0 || !isSessionTable(sessionVuuTable)) {
|
|
144
|
+
throw Error(
|
|
145
|
+
"CsvUpload createSessionDataSource returned no session datasource."
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
if (!sessionDataSource.columns.includes("vuuMsg")) {
|
|
149
|
+
sessionDataSource.columns = sessionDataSource.columns.concat("vuuMsg");
|
|
118
150
|
}
|
|
119
|
-
|
|
151
|
+
setActiveSessionDataSource(sessionDataSource);
|
|
152
|
+
onImportSessionStarted?.(sessionDataSource);
|
|
153
|
+
return sessionDataSource;
|
|
154
|
+
}, [editSession, onImportSessionStarted, setActiveSessionDataSource]);
|
|
120
155
|
const closePendingEditSession = useCallback(
|
|
121
156
|
async (save) => {
|
|
122
|
-
if (
|
|
157
|
+
if (sessionDataSourceRef.current === void 0) {
|
|
123
158
|
return;
|
|
124
159
|
}
|
|
125
160
|
await endEditSessionAndNotify(save, save ? "saved" : "discarded");
|
|
126
161
|
},
|
|
127
162
|
[endEditSessionAndNotify]
|
|
128
163
|
);
|
|
164
|
+
const cancelImport = useCallback(async () => {
|
|
165
|
+
operationIdRef.current++;
|
|
166
|
+
try {
|
|
167
|
+
await processingPromiseRef.current;
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
await closePendingEditSession(false);
|
|
171
|
+
}, [closePendingEditSession]);
|
|
129
172
|
const processFile = useCallback(
|
|
130
|
-
async (file) => {
|
|
173
|
+
async (file, operationId) => {
|
|
131
174
|
setValidation(void 0);
|
|
132
175
|
onError?.(void 0);
|
|
133
176
|
await closePendingEditSession(false);
|
|
177
|
+
if (operationId !== operationIdRef.current) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
134
180
|
if (schema === void 0) {
|
|
135
181
|
throw Error("Table schema is not yet available.");
|
|
136
182
|
}
|
|
137
183
|
if (table === void 0) {
|
|
138
184
|
throw Error("CsvUpload requires dataSource.table to be defined.");
|
|
139
185
|
}
|
|
140
|
-
const
|
|
186
|
+
const fileContents = await file.text();
|
|
187
|
+
if (operationId !== operationIdRef.current) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
const parsedCsv = parseCsv(fileContents, parseOptions);
|
|
141
191
|
if (parsedCsv.error && hasFileParseErrors(parsedCsv.error)) {
|
|
142
192
|
setValidation(void 0);
|
|
143
193
|
onError?.({
|
|
@@ -179,8 +229,14 @@ const useCsvUpload = ({
|
|
|
179
229
|
setValidation(mergedValidation);
|
|
180
230
|
if (mergedValidation.rows.length > 0) {
|
|
181
231
|
await beginEditSession();
|
|
232
|
+
if (operationId !== operationIdRef.current) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
182
235
|
try {
|
|
183
|
-
await addAllRows(mergedValidation);
|
|
236
|
+
const rowsAdded = await addAllRows(mergedValidation, operationId);
|
|
237
|
+
if (!rowsAdded) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
184
240
|
} catch (error) {
|
|
185
241
|
await endEditSessionAndNotify(false, "failed");
|
|
186
242
|
setValidation(void 0);
|
|
@@ -215,11 +271,14 @@ const useCsvUpload = ({
|
|
|
215
271
|
}
|
|
216
272
|
setIsProcessingFile(true);
|
|
217
273
|
onProcessingStarted?.();
|
|
274
|
+
const operationId = ++operationIdRef.current;
|
|
275
|
+
const processingPromise = processFile(file, operationId);
|
|
276
|
+
processingPromiseRef.current = processingPromise;
|
|
218
277
|
try {
|
|
219
|
-
await
|
|
278
|
+
await processingPromise;
|
|
220
279
|
} catch (err) {
|
|
221
280
|
setValidation(void 0);
|
|
222
|
-
|
|
281
|
+
setActiveSessionDataSource(void 0);
|
|
223
282
|
const parseError = isCsvParseError(err) ? err : void 0;
|
|
224
283
|
const errorMessage = parseError ? `Validation failed: ${parseError.message}` : `Validation failed: ${toErrorMessage(err)}`;
|
|
225
284
|
const errors = {
|
|
@@ -233,10 +292,13 @@ const useCsvUpload = ({
|
|
|
233
292
|
};
|
|
234
293
|
onError?.(errors);
|
|
235
294
|
} finally {
|
|
295
|
+
if (processingPromiseRef.current === processingPromise) {
|
|
296
|
+
processingPromiseRef.current = void 0;
|
|
297
|
+
}
|
|
236
298
|
setIsProcessingFile(false);
|
|
237
299
|
}
|
|
238
300
|
},
|
|
239
|
-
[onError, onProcessingStarted, processFile,
|
|
301
|
+
[onError, onProcessingStarted, processFile, setActiveSessionDataSource]
|
|
240
302
|
);
|
|
241
303
|
const onDrop = useCallback(
|
|
242
304
|
(_event, files) => {
|
|
@@ -256,22 +318,37 @@ const useCsvUpload = ({
|
|
|
256
318
|
);
|
|
257
319
|
const importData = useCallback(async () => {
|
|
258
320
|
if (!canImport || validation === void 0) {
|
|
259
|
-
return;
|
|
321
|
+
return false;
|
|
260
322
|
}
|
|
261
323
|
setIsImporting(true);
|
|
262
324
|
onError?.(void 0);
|
|
263
325
|
try {
|
|
264
|
-
const rpcResult = await endEditSessionAndNotify(true, "saved");
|
|
265
326
|
const fallbackTableData = {
|
|
266
327
|
columns: validation.columns,
|
|
267
328
|
rows: validation.rows.map(
|
|
268
329
|
(row) => validation.columns.map((column) => row[column] ?? "")
|
|
269
330
|
)
|
|
270
331
|
};
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
332
|
+
const tableData = fallbackTableData;
|
|
333
|
+
if (importMode === "preview") {
|
|
334
|
+
const sessionDataSource = sessionDataSourceRef.current;
|
|
335
|
+
if (!sessionDataSource) {
|
|
336
|
+
throw Error("CsvUpload has no session datasource to preview.");
|
|
337
|
+
}
|
|
338
|
+
if (!onPreview) {
|
|
339
|
+
throw Error("CsvUpload preview mode requires an onPreview callback.");
|
|
340
|
+
}
|
|
341
|
+
onPreview({
|
|
342
|
+
dataSource: sessionDataSource,
|
|
343
|
+
editSession,
|
|
344
|
+
tableData
|
|
345
|
+
});
|
|
346
|
+
ownsEditSessionRef.current = false;
|
|
347
|
+
} else {
|
|
348
|
+
await endEditSessionAndNotify(true, "saved");
|
|
349
|
+
onImported?.({ tableData });
|
|
350
|
+
}
|
|
351
|
+
return true;
|
|
275
352
|
} catch (err) {
|
|
276
353
|
const errorMessage = `RPC import failed: ${String(err)}`;
|
|
277
354
|
const errors = {
|
|
@@ -280,12 +357,23 @@ const useCsvUpload = ({
|
|
|
280
357
|
}
|
|
281
358
|
};
|
|
282
359
|
onError?.(errors);
|
|
360
|
+
return false;
|
|
283
361
|
} finally {
|
|
284
362
|
setIsImporting(false);
|
|
285
363
|
}
|
|
286
|
-
}, [
|
|
364
|
+
}, [
|
|
365
|
+
canImport,
|
|
366
|
+
editSession,
|
|
367
|
+
endEditSessionAndNotify,
|
|
368
|
+
onError,
|
|
369
|
+
onImported,
|
|
370
|
+
onPreview,
|
|
371
|
+
importMode,
|
|
372
|
+
validation
|
|
373
|
+
]);
|
|
287
374
|
return {
|
|
288
375
|
canImport,
|
|
376
|
+
cancelImport,
|
|
289
377
|
importData,
|
|
290
378
|
isImporting,
|
|
291
379
|
isProcessingFile,
|