@vuu-ui/vuu-table-extras 4.0.0-alpha.2 → 4.0.0-alpha.20
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/package.json +15 -13
- package/src/calculated-column/CalculatedColumnPanel.js +2 -2
- package/src/calculated-column/useEditCalculatedColumn.js +1 -1
- package/src/column-expression-input/useColumnExpressionSuggestionProvider.js +2 -2
- package/src/column-formatting-settings/ColumnFormattingPanel.js +12 -9
- package/src/column-formatting-settings/DateTimeFormattingSettings.js +107 -3
- package/src/column-formatting-settings/LongTypeFormattingSettings.js +8 -3
- package/src/column-picker/ColumnModel.js +14 -25
- package/src/column-picker/ColumnPicker.css.js +0 -86
- package/src/column-picker/ColumnPicker.js +12 -180
- package/src/column-picker/useColumnPicker.js +28 -22
- package/src/csv-export/export-utils.js +66 -19
- package/src/csv-export/index.js +1 -1
- package/src/csv-export/useCsvExport.js +9 -1
- package/src/csv-upload/CsvUpload.css.js +34 -0
- package/src/csv-upload/CsvUpload.js +107 -63
- package/src/csv-upload/parse/csv-errors.js +1 -1
- package/src/csv-upload/parse/csv-parse.js +1 -1
- package/src/csv-upload/parse/csv-schema-validation.js +46 -7
- package/src/csv-upload/useCsvUpload.js +12 -14
- package/src/tabbed-table-config-panel/TabbedTableConfigPanel.js +17 -8
- package/src/tabbed-table-config-panel/useTabbedTableConfigPanel.js +1 -1
- package/src/table-footer/TableFooter.js +3 -3
- package/types/calculated-column/CalculatedColumnPanel.d.ts +2 -2
- package/types/column-formatting-settings/DateTimeFormattingSettings.d.ts +1 -1
- package/types/column-picker/ColumnModel.d.ts +4 -3
- package/types/column-picker/ColumnPicker.d.ts +4 -6
- package/types/column-picker/useColumnPicker.d.ts +7 -10
- package/types/column-settings-panel/useColumnSettings.d.ts +3 -3
- package/types/csv-export/export-utils.d.ts +22 -9
- package/types/csv-export/index.d.ts +1 -1
- package/types/csv-export/useCsvExport.d.ts +5 -2
- package/types/csv-upload/CsvUpload.d.ts +1 -5
- package/types/csv-upload/parse/csv-errors.d.ts +1 -1
- package/types/csv-upload/parse/csv-schema-validation.d.ts +8 -2
- package/types/csv-upload/useCsvUpload.d.ts +2 -2
- package/types/index.d.ts +0 -1
- package/types/inline-add-row/useInlineAddRow.d.ts +2 -1
- package/types/tabbed-table-config-panel/useTabbedTableConfigPanel.d.ts +4 -3
- package/types/table-footer/TableFooter.d.ts +2 -2
|
@@ -2,12 +2,12 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { Button, Dialog, DialogActions, DialogContent, DialogHeader, FileDropZone, FileDropZoneIcon, FileDropZoneTrigger, Text } from "@salt-ds/core";
|
|
3
3
|
import { useComponentCssInjection } from "@salt-ds/styles";
|
|
4
4
|
import { useWindow } from "@salt-ds/window";
|
|
5
|
-
import { useCallback, useState } from "react";
|
|
5
|
+
import { useCallback, useMemo, useState } from "react";
|
|
6
6
|
import { useCsvUpload } from "./useCsvUpload.js";
|
|
7
7
|
import CsvUpload from "./CsvUpload.css";
|
|
8
8
|
const classBase = "vuuCsvUpload";
|
|
9
9
|
const CsvUpload_CsvUpload = (props)=>{
|
|
10
|
-
const { children, dialogTitle = "Import CSV", embedded = false, onCancel, onClose, open
|
|
10
|
+
const { children, dialogTitle = "Import CSV", embedded = false, onCancel, onClose, open } = props;
|
|
11
11
|
const isControlledOpen = void 0 !== open;
|
|
12
12
|
const [internalOpen, setInternalOpen] = useState(open ?? true);
|
|
13
13
|
const dialogOpen = isControlledOpen ? open : internalOpen;
|
|
@@ -18,7 +18,23 @@ const CsvUpload_CsvUpload = (props)=>{
|
|
|
18
18
|
window: targetWindow
|
|
19
19
|
});
|
|
20
20
|
const { canImport, cancelImport, isProcessingFile, isImporting, importData, onDrop, onTriggerChange, schema, validation, error } = useCsvUpload(props);
|
|
21
|
-
const
|
|
21
|
+
const errorsToRender = useMemo(()=>{
|
|
22
|
+
if (validation?.errors && validation.errors.length > 0) return validation.errors;
|
|
23
|
+
const parseErrors = error?.errors.schemaError?.parseError?.errors || error?.errors.validationError?.parseError?.errors;
|
|
24
|
+
if (parseErrors && parseErrors.length > 0) return parseErrors;
|
|
25
|
+
return [];
|
|
26
|
+
}, [
|
|
27
|
+
validation,
|
|
28
|
+
error
|
|
29
|
+
]);
|
|
30
|
+
const parsedSchemaErrors = useMemo(()=>{
|
|
31
|
+
const schemaErrorMsg = error?.errors.schemaError?.message || error?.errors.validationError?.message;
|
|
32
|
+
if (!schemaErrorMsg) return [];
|
|
33
|
+
const cleanedMsg = schemaErrorMsg.replace(/^CSV validation failed:\s*/, "");
|
|
34
|
+
return cleanedMsg.split(";").map((item)=>item.trim()).filter((item)=>item.length > 0);
|
|
35
|
+
}, [
|
|
36
|
+
error
|
|
37
|
+
]);
|
|
22
38
|
const hasErrors = !!error || validation && validation.errors.length > 0;
|
|
23
39
|
const isUploaded = canImport || isImporting;
|
|
24
40
|
const status = hasErrors ? "error" : isUploaded ? "success" : void 0;
|
|
@@ -37,73 +53,101 @@ const CsvUpload_CsvUpload = (props)=>{
|
|
|
37
53
|
importData,
|
|
38
54
|
onClose
|
|
39
55
|
]);
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}),
|
|
53
|
-
/*#__PURE__*/ jsx("strong", {
|
|
54
|
-
children: "Upload completed"
|
|
55
|
-
})
|
|
56
|
-
]
|
|
57
|
-
}) : /*#__PURE__*/ jsxs(Fragment, {
|
|
58
|
-
children: [
|
|
59
|
-
/*#__PURE__*/ jsx(FileDropZoneIcon, {
|
|
60
|
-
status: hasErrors ? "error" : void 0
|
|
61
|
-
}),
|
|
62
|
-
validation && validation.errors.length > 0 ? /*#__PURE__*/ jsxs(Fragment, {
|
|
63
|
-
children: [
|
|
64
|
-
/*#__PURE__*/ jsx("div", {
|
|
65
|
-
children: "Your file contains errors"
|
|
66
|
-
}),
|
|
67
|
-
fileErrors.length > 0 && /*#__PURE__*/ jsx("ul", {
|
|
68
|
-
className: `${classBase}-errorList`,
|
|
69
|
-
children: fileErrors.map((error, i)=>/*#__PURE__*/ jsx("li", {
|
|
70
|
-
className: `${classBase}-errorItem`,
|
|
71
|
-
children: error.message
|
|
72
|
-
}, `${error.column}-${error.message}-${i}`))
|
|
73
|
-
}),
|
|
74
|
-
/*#__PURE__*/ jsx("div", {
|
|
75
|
-
children: "Please rectify and reupload"
|
|
76
|
-
})
|
|
77
|
-
]
|
|
78
|
-
}) : /*#__PURE__*/ jsx("div", {
|
|
79
|
-
children: "Drop a file here or"
|
|
80
|
-
}),
|
|
81
|
-
/*#__PURE__*/ jsx(FileDropZoneTrigger, {
|
|
82
|
-
accept: ".csv,text/csv",
|
|
83
|
-
onChange: onTriggerChange,
|
|
84
|
-
children: "BROWSE FILES"
|
|
85
|
-
}),
|
|
86
|
-
/*#__PURE__*/ jsx(Text, {
|
|
87
|
-
children: "Only .csv files"
|
|
88
|
-
}),
|
|
89
|
-
children
|
|
90
|
-
]
|
|
56
|
+
const renderErrorsContent = useMemo(()=>{
|
|
57
|
+
if (error?.errors.importError) return /*#__PURE__*/ jsxs(Fragment, {
|
|
58
|
+
children: [
|
|
59
|
+
/*#__PURE__*/ jsx("strong", {
|
|
60
|
+
children: "Import failed"
|
|
61
|
+
}),
|
|
62
|
+
/*#__PURE__*/ jsx("div", {
|
|
63
|
+
className: `${classBase}-errorContainer`,
|
|
64
|
+
children: /*#__PURE__*/ jsx("div", {
|
|
65
|
+
className: `${classBase}-errorItem ${classBase}-importErrorItem`,
|
|
66
|
+
children: error.errors.importError.message
|
|
67
|
+
})
|
|
91
68
|
})
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
69
|
+
]
|
|
70
|
+
});
|
|
71
|
+
if (hasErrors) {
|
|
72
|
+
const headingText = error ? "Validation failed" : "Your file contains errors";
|
|
73
|
+
return /*#__PURE__*/ jsxs(Fragment, {
|
|
95
74
|
children: [
|
|
75
|
+
/*#__PURE__*/ jsx("strong", {
|
|
76
|
+
children: headingText
|
|
77
|
+
}),
|
|
96
78
|
/*#__PURE__*/ jsx("div", {
|
|
97
|
-
|
|
98
|
-
|
|
79
|
+
className: `${classBase}-errorContainer`,
|
|
80
|
+
children: errorsToRender.length > 0 ? /*#__PURE__*/ jsx("ul", {
|
|
81
|
+
className: `${classBase}-errorList`,
|
|
82
|
+
children: errorsToRender.map((error, i)=>/*#__PURE__*/ jsxs("li", {
|
|
83
|
+
className: `${classBase}-errorItem`,
|
|
84
|
+
children: [
|
|
85
|
+
error.rowNum > 0 ? `Row ${error.rowNum}: ` : "",
|
|
86
|
+
"*" === error.column ? "" : `${error.column}: `,
|
|
87
|
+
error.message
|
|
88
|
+
]
|
|
89
|
+
}, `${error.column}-${error.message}-${i}`))
|
|
90
|
+
}) : parsedSchemaErrors.length > 0 ? /*#__PURE__*/ jsx("ul", {
|
|
91
|
+
className: `${classBase}-errorList`,
|
|
92
|
+
children: parsedSchemaErrors.map((errMsg)=>/*#__PURE__*/ jsx("li", {
|
|
93
|
+
className: `${classBase}-errorItem`,
|
|
94
|
+
children: errMsg
|
|
95
|
+
}, errMsg))
|
|
96
|
+
}) : /*#__PURE__*/ jsx("div", {
|
|
97
|
+
className: `${classBase}-errorItem ${classBase}-importErrorItem`,
|
|
98
|
+
children: error?.errors.schemaError?.message || error?.errors.validationError?.message || "An unexpected error occurred during import"
|
|
99
99
|
})
|
|
100
|
+
})
|
|
101
|
+
]
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return /*#__PURE__*/ jsx("div", {
|
|
105
|
+
children: "Drop a file here or"
|
|
106
|
+
});
|
|
107
|
+
}, [
|
|
108
|
+
error,
|
|
109
|
+
hasErrors,
|
|
110
|
+
errorsToRender,
|
|
111
|
+
parsedSchemaErrors
|
|
112
|
+
]);
|
|
113
|
+
const content = /*#__PURE__*/ jsx("div", {
|
|
114
|
+
className: classBase,
|
|
115
|
+
children: /*#__PURE__*/ jsx(FileDropZone, {
|
|
116
|
+
className: `${classBase}-dropZone`,
|
|
117
|
+
disabled: void 0 === schema || isProcessingFile || isImporting,
|
|
118
|
+
onDrop: onDrop,
|
|
119
|
+
status: status,
|
|
120
|
+
children: isUploaded ? /*#__PURE__*/ jsxs(Fragment, {
|
|
121
|
+
children: [
|
|
122
|
+
/*#__PURE__*/ jsx(FileDropZoneIcon, {
|
|
123
|
+
status: "success"
|
|
100
124
|
}),
|
|
101
|
-
/*#__PURE__*/ jsx("
|
|
102
|
-
children:
|
|
125
|
+
/*#__PURE__*/ jsx("strong", {
|
|
126
|
+
children: "Upload completed"
|
|
103
127
|
})
|
|
104
128
|
]
|
|
105
|
-
})
|
|
106
|
-
|
|
129
|
+
}) : /*#__PURE__*/ jsxs(Fragment, {
|
|
130
|
+
children: [
|
|
131
|
+
/*#__PURE__*/ jsx(FileDropZoneIcon, {
|
|
132
|
+
status: hasErrors ? "error" : void 0
|
|
133
|
+
}),
|
|
134
|
+
renderErrorsContent,
|
|
135
|
+
hasErrors && /*#__PURE__*/ jsx(Text, {
|
|
136
|
+
className: `${classBase}-rectifyPrompt`,
|
|
137
|
+
children: "Please rectify and reupload"
|
|
138
|
+
}),
|
|
139
|
+
/*#__PURE__*/ jsx(FileDropZoneTrigger, {
|
|
140
|
+
accept: ".csv,text/csv",
|
|
141
|
+
onChange: onTriggerChange,
|
|
142
|
+
children: "BROWSE FILES"
|
|
143
|
+
}),
|
|
144
|
+
/*#__PURE__*/ jsx(Text, {
|
|
145
|
+
children: "Only .csv files"
|
|
146
|
+
}),
|
|
147
|
+
children
|
|
148
|
+
]
|
|
149
|
+
})
|
|
150
|
+
})
|
|
107
151
|
});
|
|
108
152
|
const actions = /*#__PURE__*/ jsxs(DialogActions, {
|
|
109
153
|
children: [
|
|
@@ -13,7 +13,7 @@ var csv_errors_CsvValidationErrorEnum = /*#__PURE__*/ function(CsvValidationErro
|
|
|
13
13
|
CsvValidationErrorEnum["MISSING_KEY_COLUMN"] = "MISSING_KEY_COLUMN";
|
|
14
14
|
CsvValidationErrorEnum["UNKNOWN_COLUMN"] = "UNKNOWN_COLUMN";
|
|
15
15
|
CsvValidationErrorEnum["MAX_ROWS_EXCEEDED"] = "MAX_ROWS_EXCEEDED";
|
|
16
|
-
CsvValidationErrorEnum["
|
|
16
|
+
CsvValidationErrorEnum["REQUIRED_FIELD_MISSING"] = "REQUIRED_FIELD_MISSING";
|
|
17
17
|
CsvValidationErrorEnum["TYPE_MISMATCH"] = "TYPE_MISMATCH";
|
|
18
18
|
CsvValidationErrorEnum["CUSTOM_VALIDATION"] = "CUSTOM_VALIDATION";
|
|
19
19
|
return CsvValidationErrorEnum;
|
|
@@ -68,7 +68,7 @@ const parseCsv = (csvText, options)=>{
|
|
|
68
68
|
}
|
|
69
69
|
const rows = records.slice(1);
|
|
70
70
|
rows.forEach((row, rowIndex)=>{
|
|
71
|
-
if (row.length !== header.length) addCsvRowError(errorState, rowIndex + CSV_FIRST_DATA_ROW_NUMBER, "*", CsvParseErrorEnum.ROW_COLUMN_COUNT_MISMATCH, "
|
|
71
|
+
if (row.length !== header.length) addCsvRowError(errorState, rowIndex + CSV_FIRST_DATA_ROW_NUMBER, "*", CsvParseErrorEnum.ROW_COLUMN_COUNT_MISMATCH, "This row has too many or too few columns compared to the header line. Check for extra or missing commas.", row.join(","));
|
|
72
72
|
});
|
|
73
73
|
if (hasCsvErrors(errorState.errorMap)) return withError("CSV rows must have the same number of columns as the header.", errorState.errorMap, errorState.errors, header, rows);
|
|
74
74
|
return {
|
|
@@ -4,10 +4,44 @@ import { CSV_FIRST_DATA_ROW_NUMBER, MAX_ROWS_IN_CSV } from "./csv-constants.js";
|
|
|
4
4
|
const INTERNAL_KEY_COLUMNS = new Set([
|
|
5
5
|
"vuuRowNum"
|
|
6
6
|
]);
|
|
7
|
+
const getFriendlyTypeName = (type)=>{
|
|
8
|
+
switch(type){
|
|
9
|
+
case "int":
|
|
10
|
+
case "long":
|
|
11
|
+
return "whole number";
|
|
12
|
+
case "double":
|
|
13
|
+
case "number":
|
|
14
|
+
case "decimal":
|
|
15
|
+
case "scaleddecimal":
|
|
16
|
+
case "scaleddecimal2":
|
|
17
|
+
case "scaleddecimal4":
|
|
18
|
+
case "scaleddecimal6":
|
|
19
|
+
case "scaleddecimal8":
|
|
20
|
+
return "decimal or number";
|
|
21
|
+
case "time":
|
|
22
|
+
return "time in 'HH:MM:SS' format";
|
|
23
|
+
case "epochtimestamp":
|
|
24
|
+
case "epochtimestampnano":
|
|
25
|
+
case "date/time":
|
|
26
|
+
return "date/time";
|
|
27
|
+
case "char":
|
|
28
|
+
return "single character";
|
|
29
|
+
case "boolean":
|
|
30
|
+
return "boolean (true/false)";
|
|
31
|
+
case "json":
|
|
32
|
+
return "JSON value";
|
|
33
|
+
default:
|
|
34
|
+
return type;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const getFriendlyErrorMessage = (errMessage, rawValue, toType)=>{
|
|
38
|
+
if (errMessage.includes("is not a valid")) return `'${rawValue}' is not a valid ${getFriendlyTypeName(toType)}`;
|
|
39
|
+
return errMessage;
|
|
40
|
+
};
|
|
7
41
|
const validateCsvAgainstSchema = (parsed, tableSchema, options)=>{
|
|
8
42
|
const schemaColumns = new Map(tableSchema.columns.map((col)=>[
|
|
9
43
|
col.name,
|
|
10
|
-
col
|
|
44
|
+
col
|
|
11
45
|
]));
|
|
12
46
|
const maxRows = options?.maxRows ?? MAX_ROWS_IN_CSV;
|
|
13
47
|
const errorState = createCsvErrorState();
|
|
@@ -21,22 +55,27 @@ const validateCsvAgainstSchema = (parsed, tableSchema, options)=>{
|
|
|
21
55
|
const rowNum = rowIndex + CSV_FIRST_DATA_ROW_NUMBER;
|
|
22
56
|
const typedRow = {};
|
|
23
57
|
const rawRow = parsed.header.reduce((acc, headerCol, idx)=>{
|
|
24
|
-
acc[headerCol] = rowValues[idx];
|
|
58
|
+
acc[headerCol] = rowValues[idx] ?? "";
|
|
25
59
|
return acc;
|
|
26
60
|
}, {});
|
|
27
61
|
parsed.header.forEach((columnName, columnIndex)=>{
|
|
28
|
-
const rawValue = rowValues[columnIndex];
|
|
29
|
-
const
|
|
30
|
-
|
|
62
|
+
const rawValue = rowValues[columnIndex] ?? "";
|
|
63
|
+
const schemaColumn = schemaColumns.get(columnName);
|
|
64
|
+
const schemaType = schemaColumn?.serverDataType;
|
|
65
|
+
const isRequired = schemaColumn?.required === true;
|
|
66
|
+
if (0 === rawValue.length) {
|
|
67
|
+
if (isRequired) addCsvRowError(errorState, rowNum, columnName, CsvValidationErrorEnum.REQUIRED_FIELD_MISSING, "This field is required and cannot be left blank", rawValue);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
31
70
|
let value;
|
|
32
71
|
const toType = schemaType ?? "string";
|
|
33
72
|
try {
|
|
34
73
|
value = getTypedValue(rawValue, toType);
|
|
35
74
|
} catch (err) {
|
|
36
|
-
addCsvRowError(errorState, rowNum, columnName, CsvValidationErrorEnum.TYPE_MISMATCH, err instanceof Error ? err.message : String(err), rawValue);
|
|
75
|
+
addCsvRowError(errorState, rowNum, columnName, CsvValidationErrorEnum.TYPE_MISMATCH, getFriendlyErrorMessage(err instanceof Error ? err.message : String(err), rawValue, toType), rawValue);
|
|
37
76
|
return;
|
|
38
77
|
}
|
|
39
|
-
if (void 0 === value) return void addCsvRowError(errorState, rowNum, columnName, CsvValidationErrorEnum.TYPE_MISMATCH, `
|
|
78
|
+
if (void 0 === value) return void addCsvRowError(errorState, rowNum, columnName, CsvValidationErrorEnum.TYPE_MISMATCH, `'${rawValue}' is not a valid ${getFriendlyTypeName(toType)}`, rawValue);
|
|
40
79
|
const customValidator = options?.validators?.[columnName];
|
|
41
80
|
if (customValidator) {
|
|
42
81
|
const validationResult = customValidator(rawValue, columnName, rawRow);
|
|
@@ -5,7 +5,7 @@ import { parseCsv } from "./parse/csv-parse.js";
|
|
|
5
5
|
import { validateCsvAgainstSchema } from "./parse/csv-schema-validation.js";
|
|
6
6
|
import { buildRowErrorMessage, createUploadError, hasFileParseErrors, isCsvParseError, mergeValidationWithParseErrors, toErrorMessage } from "./parse/csv-upload-utils.js";
|
|
7
7
|
import { CSV_FIRST_DATA_ROW_NUMBER } from "./parse/csv-constants.js";
|
|
8
|
-
const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importTable, onImportSessionEnded,
|
|
8
|
+
const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importTable, onImportSessionEnded, onImportSessionReady, onError, onImported, onPreview, onProcessingStarted, maxRows, parseOptions, rowDefaults, validators })=>{
|
|
9
9
|
const { getServerAPI } = useData();
|
|
10
10
|
const [validation, setValidation] = useState();
|
|
11
11
|
const [sessionTable, setSessionTable] = useState();
|
|
@@ -120,7 +120,7 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
|
|
|
120
120
|
try {
|
|
121
121
|
await editSession.end(save);
|
|
122
122
|
} finally{
|
|
123
|
-
if ("unsubscribed" !== sessionDataSource.status) sessionDataSource.unsubscribe();
|
|
123
|
+
if ("unsubscribed" !== sessionDataSource.status && "function" == typeof sessionDataSource.unsubscribe) sessionDataSource.unsubscribe();
|
|
124
124
|
setActiveSessionDataSource(void 0);
|
|
125
125
|
}
|
|
126
126
|
onImportSessionEnded?.({
|
|
@@ -140,7 +140,7 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
|
|
|
140
140
|
if (rowNum < CSV_FIRST_DATA_ROW_NUMBER) continue;
|
|
141
141
|
const existing = vuuMsgByRow.get(rowNum);
|
|
142
142
|
const columnError = `${column}: ${message}`;
|
|
143
|
-
vuuMsgByRow.set(rowNum, existing ? `${existing}; ${columnError}` :
|
|
143
|
+
vuuMsgByRow.set(rowNum, existing ? `${existing}; ${columnError}` : columnError);
|
|
144
144
|
}
|
|
145
145
|
const parsedRowCount = mergedValidation.rows.length;
|
|
146
146
|
const unparsedErrorRows = [
|
|
@@ -187,11 +187,9 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
|
|
|
187
187
|
const sessionVuuTable = sessionDataSource?.table;
|
|
188
188
|
if (void 0 === sessionDataSource || void 0 === sessionVuuTable || !isSessionTable(sessionVuuTable)) throw Error("CsvUpload createSessionDataSource returned no session datasource.");
|
|
189
189
|
setActiveSessionDataSource(sessionDataSource);
|
|
190
|
-
onImportSessionStarted?.(sessionDataSource);
|
|
191
190
|
return sessionDataSource;
|
|
192
191
|
}, [
|
|
193
192
|
editSession,
|
|
194
|
-
onImportSessionStarted,
|
|
195
193
|
setActiveSessionDataSource
|
|
196
194
|
]);
|
|
197
195
|
const closePendingEditSession = useCallback(async (save)=>{
|
|
@@ -245,13 +243,10 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
|
|
|
245
243
|
});
|
|
246
244
|
if (Object.keys(schemaValidation.errorMap.fileErrors).length > 0) {
|
|
247
245
|
setValidation(schemaValidation);
|
|
246
|
+
const detailedMessage = `CSV validation failed: ${schemaValidation.errors.map((e)=>"*" === e.column ? e.message : `${e.column}: ${e.message}`).join("; ")}`;
|
|
248
247
|
handleError({
|
|
249
248
|
errors: {
|
|
250
|
-
schemaError: createUploadError("schema",
|
|
251
|
-
errorMap: schemaValidation.errorMap,
|
|
252
|
-
errors: schemaValidation.errors,
|
|
253
|
-
message: "CSV validation failed."
|
|
254
|
-
})
|
|
249
|
+
schemaError: createUploadError("schema", detailedMessage, parsedCsv.error, schemaValidation)
|
|
255
250
|
}
|
|
256
251
|
});
|
|
257
252
|
return;
|
|
@@ -259,10 +254,11 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
|
|
|
259
254
|
const mergedValidation = mergeValidationWithParseErrors(schemaValidation, parsedCsv.error);
|
|
260
255
|
setValidation(mergedValidation);
|
|
261
256
|
if (mergedValidation.rows.length > 0) try {
|
|
262
|
-
await beginEditSession();
|
|
257
|
+
const sessionDataSource = await beginEditSession();
|
|
263
258
|
if (operationId !== operationIdRef.current) return;
|
|
264
259
|
const rowsAdded = await addAllRows(mergedValidation, operationId);
|
|
265
260
|
if (!rowsAdded) return;
|
|
261
|
+
onImportSessionReady?.(sessionDataSource);
|
|
266
262
|
} catch (error) {
|
|
267
263
|
if (void 0 !== sessionDataSourceRef.current) await endEditSessionAndNotify(false, "failed");
|
|
268
264
|
else try {
|
|
@@ -290,7 +286,8 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
|
|
|
290
286
|
table,
|
|
291
287
|
endEditSessionAndNotify,
|
|
292
288
|
schema,
|
|
293
|
-
validators
|
|
289
|
+
validators,
|
|
290
|
+
onImportSessionReady
|
|
294
291
|
]);
|
|
295
292
|
const handleFiles = useCallback(async (files)=>{
|
|
296
293
|
const file = files[0];
|
|
@@ -333,10 +330,11 @@ const useCsvUpload = ({ dataSource, importMode = "direct", importSchema, importT
|
|
|
333
330
|
}, [
|
|
334
331
|
handleFiles
|
|
335
332
|
]);
|
|
336
|
-
const canImport = useMemo(()=>void 0 !== validation && 0 === validation.errors.length && validation.rows.length > 0 && !isProcessingFile && !isImporting, [
|
|
333
|
+
const canImport = useMemo(()=>void 0 !== validation && 0 === validation.errors.length && validation.rows.length > 0 && !isProcessingFile && !isImporting && void 0 === error, [
|
|
337
334
|
isImporting,
|
|
338
335
|
isProcessingFile,
|
|
339
|
-
validation
|
|
336
|
+
validation,
|
|
337
|
+
error
|
|
340
338
|
]);
|
|
341
339
|
const importData = useCallback(async ()=>{
|
|
342
340
|
if (!canImport || void 0 === validation) return false;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Tab, TabBar, TabList, TabPanel, TabTrigger, Tabs } from "@salt-ds/core";
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button, Tab, TabBar, TabList, TabPanel, TabTrigger, Tabs } from "@salt-ds/core";
|
|
3
3
|
import { useComponentCssInjection } from "@salt-ds/styles";
|
|
4
4
|
import { useWindow } from "@salt-ds/window";
|
|
5
5
|
import clsx from "clsx";
|
|
@@ -78,12 +78,21 @@ const TabbedTableConfigPanel_TabbedTableConfigPanel = ({ allowCreateCalculatedCo
|
|
|
78
78
|
}),
|
|
79
79
|
/*#__PURE__*/ jsx(TabPanel, {
|
|
80
80
|
value: "table-columns",
|
|
81
|
-
children: /*#__PURE__*/
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
81
|
+
children: /*#__PURE__*/ jsxs(Fragment, {
|
|
82
|
+
children: [
|
|
83
|
+
/*#__PURE__*/ jsx(ColumnPicker, {
|
|
84
|
+
columnModel: columnModel,
|
|
85
|
+
onSelectionChange: handleSelectionChange,
|
|
86
|
+
selected: columns
|
|
87
|
+
}),
|
|
88
|
+
allowCreateCalculatedColumn ? /*#__PURE__*/ jsx("div", {
|
|
89
|
+
className: `${classBase}-column-buttons`,
|
|
90
|
+
children: /*#__PURE__*/ jsx(Button, {
|
|
91
|
+
onClick: onCreateCalculatedColumn,
|
|
92
|
+
children: "Create calculated column"
|
|
93
|
+
})
|
|
94
|
+
}) : null
|
|
95
|
+
]
|
|
87
96
|
})
|
|
88
97
|
}),
|
|
89
98
|
/*#__PURE__*/ jsx(TabPanel, {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useRef } from "react";
|
|
3
2
|
import { useContextPanel } from "@vuu-ui/vuu-ui-controls";
|
|
3
|
+
import { useCallback, useRef } from "react";
|
|
4
4
|
import { TabbedTableConfigPanel } from "./TabbedTableConfigPanel.js";
|
|
5
5
|
const useTabbedTableConfigPanel = ({ allowCreateCalculatedColumn, columnModel, config, onDisplayAttributeChange, vuuTable })=>{
|
|
6
6
|
const showContextPanel = useContextPanel();
|
|
@@ -4,10 +4,10 @@ import { useComponentCssInjection } from "@salt-ds/styles";
|
|
|
4
4
|
import { useWindow } from "@salt-ds/window";
|
|
5
5
|
import TableFooter from "./TableFooter.css";
|
|
6
6
|
const classBase = "vuuTableFooter";
|
|
7
|
-
const TableFooterTray = ({ children, position =
|
|
7
|
+
const TableFooterTray = ({ children, position = "end" })=>/*#__PURE__*/ jsx("div", {
|
|
8
8
|
className: clsx(`${classBase}Tray`, {
|
|
9
|
-
[`${classBase}Tray-center`]:
|
|
10
|
-
[`${classBase}Tray-end`]:
|
|
9
|
+
[`${classBase}Tray-center`]: "center" === position,
|
|
10
|
+
[`${classBase}Tray-end`]: "end" === position
|
|
11
11
|
}),
|
|
12
12
|
children: children
|
|
13
13
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { HTMLAttributes } from "react";
|
|
2
|
-
import { ColumnDescriptor } from "@vuu-ui/vuu-table-types";
|
|
3
1
|
import { VuuColumnDataType, VuuTable } from "@vuu-ui/vuu-protocol-types";
|
|
2
|
+
import { ColumnDescriptor } from "@vuu-ui/vuu-table-types";
|
|
3
|
+
import { HTMLAttributes } from "react";
|
|
4
4
|
import { ColumnModel } from "../column-picker/ColumnModel";
|
|
5
5
|
export interface CalculatedColumnPanelProps extends HTMLAttributes<HTMLDivElement> {
|
|
6
6
|
column: ColumnDescriptor;
|
|
@@ -58,12 +58,13 @@ export declare class ColumnModel extends EventEmitter<ColumnEvents> {
|
|
|
58
58
|
* Columns already selected and rendered in Table.
|
|
59
59
|
*/
|
|
60
60
|
selectedColumns: readonly ColumnDescriptor[]);
|
|
61
|
-
get availableColumns(): ColumnDescriptor[];
|
|
62
|
-
set searchPattern(pattern: string);
|
|
63
|
-
get searchPattern(): string;
|
|
64
61
|
get selectedColumns(): readonly ColumnDescriptor[];
|
|
65
62
|
setSelectedColumns(selectedColumns: readonly ColumnDescriptor[], source: ColumnChangeSource, changeDescriptor?: SelectedColumnChangeDescriptor): void;
|
|
66
63
|
getColumn(name: string): ColumnDescriptor;
|
|
64
|
+
/**
|
|
65
|
+
* Introduced for use by ColumnPicker
|
|
66
|
+
*/
|
|
67
|
+
addRemoveOrReorderSelectedColumns(newSelectedColumns: readonly ColumnDescriptor[], source: ColumnChangeSource): void;
|
|
67
68
|
addItemToSelectedColumns(name: string, source: ColumnChangeSource): void;
|
|
68
69
|
removeItemFromSelectedColumns(name: string, source: ColumnChangeSource): void;
|
|
69
70
|
reorderSelectedColumns(orderedColumnNames: string[], source: ColumnChangeSource): void;
|
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { ColumnPickerHookProps } from "./useColumnPicker";
|
|
1
|
+
import type { ListBoxProps } from "@salt-ds/core";
|
|
2
|
+
import type { ColumnDescriptor } from "@vuu-ui/vuu-table-types";
|
|
3
|
+
import { type HTMLAttributes } from "react";
|
|
4
|
+
import { type ColumnPickerHookProps } from "./useColumnPicker";
|
|
5
5
|
export declare const classBaseListItem = "vuuColumnPickerListItem";
|
|
6
6
|
export interface ColumnPickerProps extends ColumnPickerHookProps, Pick<ListBoxProps<ColumnDescriptor>, "selected" | "onSelectionChange">, HTMLAttributes<HTMLDivElement> {
|
|
7
|
-
allowCreateCalculatedColumn?: boolean;
|
|
8
|
-
onClickCreateCalculatedColumn?: MouseEventHandler<HTMLButtonElement>;
|
|
9
7
|
}
|
|
10
8
|
export declare const ColumnPicker: import("react").ForwardRefExoticComponent<ColumnPickerProps & import("react").RefAttributes<HTMLDivElement>>;
|
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { ColumnModel
|
|
1
|
+
import { TableSelectionModel } from "@vuu-ui/vuu-table-types";
|
|
2
|
+
import { ItemDescriptor } from "@vuu-ui/vuu-ui-controls";
|
|
3
|
+
import { ColumnModel } from "./ColumnModel";
|
|
4
4
|
export type ColumnSelectionModel = Extract<TableSelectionModel, "none" | "single">;
|
|
5
|
-
export type SelectedColumnsChangeHandler = (columns: ColumnDescriptor[], changeType: SelectedColumnChangeType) => void;
|
|
6
5
|
export interface ColumnPickerHookProps {
|
|
7
6
|
columnModel: ColumnModel;
|
|
8
7
|
}
|
|
9
8
|
export declare const useColumnPicker: ({ columnModel: model, }: ColumnPickerHookProps) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
searchText: string;
|
|
15
|
-
selectedColumns: readonly ColumnDescriptor[];
|
|
9
|
+
allItems: ItemDescriptor[];
|
|
10
|
+
selectedItems: ItemDescriptor[];
|
|
11
|
+
oneOrMoreColumnsIsCalculated: boolean;
|
|
12
|
+
handleSelectedItemsChange: (newSelectedItems: readonly ItemDescriptor[]) => void;
|
|
16
13
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { ColumnDescriptor,
|
|
2
|
-
import {
|
|
1
|
+
import { ColumnDescriptor, ColumnTypeFormatting, TableConfig } from "@vuu-ui/vuu-table-types";
|
|
2
|
+
import { DataValueTypeSimple } from "@vuu-ui/vuu-data-types";
|
|
3
3
|
import { VuuColumnDataType, VuuTable } from "@vuu-ui/vuu-protocol-types";
|
|
4
|
+
import { CellRendererDescriptor, ColumnRenderPropsChangeHandler } from "@vuu-ui/vuu-utils";
|
|
4
5
|
import { FormEventHandler } from "react";
|
|
5
|
-
import { DataValueTypeSimple } from "@vuu-ui/vuu-data-types";
|
|
6
6
|
import { ColumnModel } from "../column-picker/ColumnModel";
|
|
7
7
|
/**
|
|
8
8
|
* Describes the props for a Column Configuration Editor, for which
|
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
import type { CopyOption, DataSource, SessionDataSourceOverrides } from "@vuu-ui/vuu-data-types";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
import type { ColumnDescriptor, RowAccessor } from "@vuu-ui/vuu-table-types";
|
|
3
|
+
export type { RowAccessor };
|
|
4
|
+
export type ExportServerColumnDescriptor = Omit<ColumnDescriptor, "source" | "exportFormatter"> & {
|
|
5
|
+
name: string;
|
|
6
|
+
source?: "server";
|
|
5
7
|
label?: string;
|
|
6
|
-
|
|
8
|
+
exportable?: boolean;
|
|
9
|
+
exportFormatter?: (value: unknown, row: RowAccessor) => string;
|
|
7
10
|
};
|
|
8
|
-
export
|
|
11
|
+
export type ExportClientColumnDescriptor = Omit<ColumnDescriptor, "source" | "exportFormatter"> & {
|
|
12
|
+
name: string;
|
|
13
|
+
source: "client";
|
|
14
|
+
label?: string;
|
|
15
|
+
exportable?: boolean;
|
|
16
|
+
exportFormatter: (value: unknown, row: RowAccessor) => string;
|
|
17
|
+
};
|
|
18
|
+
export type ExportColumnDescriptor = ExportServerColumnDescriptor | ExportClientColumnDescriptor;
|
|
19
|
+
export interface ExportToCsvOptions {
|
|
9
20
|
filename?: string;
|
|
10
21
|
copyOption?: CopyOption;
|
|
11
22
|
excludeColumns?: string[];
|
|
12
23
|
maxRows?: number;
|
|
13
|
-
columnDescriptors?: ExportColumnDescriptor
|
|
24
|
+
columnDescriptors?: readonly (ExportColumnDescriptor | ColumnDescriptor)[];
|
|
25
|
+
columns?: readonly (string | ExportColumnDescriptor | ColumnDescriptor)[];
|
|
14
26
|
overrides?: SessionDataSourceOverrides;
|
|
15
27
|
/** Timeout in milliseconds before the export times out. Default: 30_000ms (0 to disable). */
|
|
16
28
|
timeout?: number;
|
|
@@ -20,13 +32,14 @@ export interface ExportToCsvOptions<TName extends string = string> {
|
|
|
20
32
|
export interface ExportCsvTemplateOptions {
|
|
21
33
|
filename?: string;
|
|
22
34
|
excludeColumns?: string[];
|
|
23
|
-
columns?: string[];
|
|
35
|
+
columns?: readonly (string | ColumnDescriptor)[];
|
|
24
36
|
overrides?: SessionDataSourceOverrides;
|
|
25
37
|
/** Timeout in milliseconds before template generation times out. Default: 10_000ms (0 to disable). */
|
|
26
38
|
timeout?: number;
|
|
27
39
|
onError?: (error: Error) => void;
|
|
28
40
|
onSuccess?: () => void;
|
|
29
41
|
}
|
|
42
|
+
export declare const isExportableColumn: (descriptor?: ExportColumnDescriptor | ColumnDescriptor) => boolean;
|
|
30
43
|
/** Downloads a single-row CSV containing only the column headers, for use as an import template. */
|
|
31
44
|
export declare const exportCsvTemplate: (dataSource: DataSource, options?: ExportCsvTemplateOptions) => Promise<void>;
|
|
32
45
|
/**
|
|
@@ -35,8 +48,8 @@ export declare const exportCsvTemplate: (dataSource: DataSource, options?: Expor
|
|
|
35
48
|
* then triggers a browser download. The session data source is unsubscribed automatically once the download
|
|
36
49
|
* is initiated.
|
|
37
50
|
*/
|
|
38
|
-
export declare const exportSessionTableToCsv:
|
|
51
|
+
export declare const exportSessionTableToCsv: (dataSource: DataSource, options?: ExportToCsvOptions) => Promise<void>;
|
|
39
52
|
/**
|
|
40
53
|
* Creates an export session table from `dataSource`, collects all rows in memory, then triggers a CSV download.
|
|
41
54
|
*/
|
|
42
|
-
export declare const exportToCsv:
|
|
55
|
+
export declare const exportToCsv: (dataSource: DataSource, options?: ExportToCsvOptions) => Promise<void>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { exportCsvTemplate, exportSessionTableToCsv, exportToCsv, type ExportColumnDescriptor, type ExportCsvTemplateOptions, type ExportToCsvOptions, } from "./export-utils";
|
|
1
|
+
export { exportCsvTemplate, exportSessionTableToCsv, exportToCsv, isExportableColumn, type ExportClientColumnDescriptor, type ExportColumnDescriptor, type ExportCsvTemplateOptions, type ExportServerColumnDescriptor, type ExportToCsvOptions, type RowAccessor, } from "./export-utils";
|
|
2
2
|
export { useCsvExport, type UseCsvExportProps, type UseCsvExportResult, } from "./useCsvExport";
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import type { DataSource } from "@vuu-ui/vuu-data-types";
|
|
2
|
-
import {
|
|
2
|
+
import type { ColumnDescriptor, TableConfig } from "@vuu-ui/vuu-table-types";
|
|
3
|
+
import { type ExportColumnDescriptor, type ExportCsvTemplateOptions, type ExportToCsvOptions } from "./export-utils";
|
|
3
4
|
export interface UseCsvExportProps {
|
|
4
5
|
dataSource?: DataSource;
|
|
6
|
+
tableConfig?: TableConfig;
|
|
7
|
+
columns?: readonly (string | ExportColumnDescriptor | ColumnDescriptor)[];
|
|
5
8
|
onError?: (error: Error) => void;
|
|
6
9
|
onSuccess?: () => void;
|
|
7
10
|
}
|
|
8
11
|
export interface UseCsvExportResult {
|
|
9
12
|
isExporting: boolean;
|
|
10
13
|
error: Error | null;
|
|
11
|
-
exportCsv:
|
|
14
|
+
exportCsv: (options?: ExportToCsvOptions, overrideDataSource?: DataSource) => Promise<void>;
|
|
12
15
|
exportTemplate: (options?: ExportCsvTemplateOptions, overrideDataSource?: DataSource) => Promise<void>;
|
|
13
16
|
}
|
|
14
17
|
export declare function useCsvExport(propsOrDataSource?: DataSource | UseCsvExportProps): UseCsvExportResult;
|