@vuu-ui/vuu-table-extras 2.1.19-beta.1 → 2.1.19-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/package.json +13 -14
  2. package/src/calculated-column/CalculatedColumnPanel.js +95 -0
  3. package/src/calculated-column/useCalculatedColumnPanel.js +60 -0
  4. package/src/calculated-column/useEditCalculatedColumn.js +104 -0
  5. package/src/cell-edit-validators/CaseValidator.js +21 -0
  6. package/src/cell-edit-validators/PatternValidator.js +23 -0
  7. package/src/cell-edit-validators/index.js +2 -0
  8. package/src/cell-renderers/background-cell/BackgroundCell.css.js +103 -0
  9. package/src/cell-renderers/background-cell/BackgroundCell.js +76 -0
  10. package/src/cell-renderers/background-cell/BackgroundCellConfigurationEditor.css.js +9 -0
  11. package/src/cell-renderers/background-cell/BackgroundCellConfigurationEditor.js +71 -0
  12. package/src/cell-renderers/background-cell/BackgroundKeyframes.css.js +187 -0
  13. package/src/cell-renderers/background-cell/index.js +2 -0
  14. package/src/cell-renderers/background-cell/useDirection.js +25 -0
  15. package/src/cell-renderers/button-cell/IconButtonCell.css.js +23 -0
  16. package/src/cell-renderers/button-cell/IconButtonCell.js +33 -0
  17. package/src/cell-renderers/dropdown-cell/DropdownCell.css.js +36 -0
  18. package/src/cell-renderers/dropdown-cell/DropdownCell.js +84 -0
  19. package/src/cell-renderers/dropdown-cell/index.js +1 -0
  20. package/src/cell-renderers/index.js +6 -0
  21. package/src/cell-renderers/lookup-cell/LookupCell.js +15 -0
  22. package/src/cell-renderers/lookup-cell/index.js +1 -0
  23. package/src/cell-renderers/pct-progress-cell/PctProgressCell.css.js +31 -0
  24. package/src/cell-renderers/pct-progress-cell/PctProgressCell.js +49 -0
  25. package/src/cell-renderers/pct-progress-cell/index.js +1 -0
  26. package/src/cell-renderers/progress-cell/ProgressCell.css.js +43 -0
  27. package/src/cell-renderers/progress-cell/ProgressCell.js +74 -0
  28. package/src/cell-renderers/progress-cell/index.js +1 -0
  29. package/src/column-expression-input/ColumnExpressionInput.css.js +94 -0
  30. package/src/column-expression-input/ColumnExpressionInput.js +28 -0
  31. package/src/column-expression-input/column-function-descriptors.js +281 -0
  32. package/src/column-expression-input/column-language-parser/ColumnExpressionLanguage.js +18 -0
  33. package/src/column-expression-input/column-language-parser/ColumnExpressionTreeWalker.js +381 -0
  34. package/src/column-expression-input/column-language-parser/column-expression-parse-utils.js +56 -0
  35. package/src/column-expression-input/column-language-parser/index.js +3 -0
  36. package/src/column-expression-input/functionDocInfo.js +24 -0
  37. package/src/column-expression-input/highlighting.js +18 -0
  38. package/src/column-expression-input/index.js +4 -0
  39. package/src/column-expression-input/theme.js +57 -0
  40. package/src/column-expression-input/useColumnAutoComplete.js +307 -0
  41. package/src/column-expression-input/useColumnExpressionEditor.js +133 -0
  42. package/src/column-expression-input/useColumnExpressionSuggestionProvider.js +211 -0
  43. package/src/column-formatting-settings/BaseNumericFormattingSettings.js +96 -0
  44. package/src/column-formatting-settings/ColumnFormattingPanel.js +112 -0
  45. package/src/column-formatting-settings/DateTimeFormattingSettings.js +116 -0
  46. package/src/column-formatting-settings/LongTypeFormattingSettings.css.js +11 -0
  47. package/src/column-formatting-settings/LongTypeFormattingSettings.js +54 -0
  48. package/src/column-formatting-settings/index.js +3 -0
  49. package/src/column-menu/ColumnMenu.css.js +25 -0
  50. package/src/column-menu/ColumnMenu.js +69 -0
  51. package/src/column-menu/column-action-types.js +0 -0
  52. package/src/column-menu/column-menu-utils.js +318 -0
  53. package/src/column-menu/useColumnActions.js +77 -0
  54. package/src/column-picker/ColumnModel.js +113 -0
  55. package/src/column-picker/ColumnPicker.css.js +96 -0
  56. package/src/column-picker/ColumnPicker.js +204 -0
  57. package/src/column-picker/ColumnPickerAction.js +24 -0
  58. package/src/column-picker/useColumnPicker.js +42 -0
  59. package/src/column-picker/useTableColumnPicker.js +27 -0
  60. package/src/column-picker/useTableColumnPickerDeprecated.js +87 -0
  61. package/src/column-settings-panel/ColumnNameLabel.css.js +18 -0
  62. package/src/column-settings-panel/ColumnNameLabel.js +54 -0
  63. package/src/column-settings-panel/ColumnSettingsPanel.css.js +70 -0
  64. package/src/column-settings-panel/ColumnSettingsPanel.js +173 -0
  65. package/src/column-settings-panel/useColumnSettings.js +205 -0
  66. package/src/csv-upload/CsvUpload.css.js +28 -0
  67. package/src/csv-upload/CsvUpload.js +95 -0
  68. package/src/csv-upload/index.js +7 -0
  69. package/src/csv-upload/parse/csv-constants.js +3 -0
  70. package/src/csv-upload/parse/csv-errors.js +54 -0
  71. package/src/csv-upload/parse/csv-parse.js +79 -0
  72. package/src/csv-upload/parse/csv-schema-validation.js +45 -0
  73. package/src/csv-upload/parse/csv-upload-utils.js +59 -0
  74. package/src/csv-upload/parse/index.js +5 -0
  75. package/src/csv-upload/useCsvUpload.js +260 -0
  76. package/src/csv-upload/useCsvUploadSessionPreview.js +70 -0
  77. package/src/datasource-stats/DatasourceStats.css.js +56 -0
  78. package/src/datasource-stats/DatasourceStats.js +89 -0
  79. package/src/datasource-stats/useDatasourceStats.js +48 -0
  80. package/src/freeze-control/FreezeControl.css.js +108 -0
  81. package/src/freeze-control/FreezeControl.js +96 -0
  82. package/src/freeze-control/FrozenBanner.css.js +16 -0
  83. package/src/freeze-control/FrozenBanner.js +48 -0
  84. package/src/freeze-control/freezeControlBadge.js +9 -0
  85. package/src/freeze-control/useFreezeControl.js +68 -0
  86. package/src/index.js +22 -0
  87. package/src/tabbed-table-config-panel/TabbedTableConfigPanel.css.js +23 -0
  88. package/src/tabbed-table-config-panel/TabbedTableConfigPanel.js +99 -0
  89. package/src/tabbed-table-config-panel/TabbedTableSettingsAction.js +28 -0
  90. package/src/tabbed-table-config-panel/useTabbedTableConfigPanel.js +29 -0
  91. package/src/table-footer/TableFooter.css.js +22 -0
  92. package/src/table-footer/TableFooter.js +30 -0
  93. package/src/table-provider/TableProvider.js +33 -0
  94. package/src/table-settings-panel/TableSettingsPanel.css.js +70 -0
  95. package/src/table-settings-panel/TableSettingsPanel.js +128 -0
  96. package/src/table-settings-panel/useTableSettings.js +50 -0
@@ -0,0 +1,45 @@
1
+ import { getTypedValue } from "@vuu-ui/vuu-utils";
2
+ import { CsvValidationErrorEnum, addCsvFileError, addCsvRowError, createCsvErrorState } from "./csv-errors.js";
3
+ import { CSV_FIRST_DATA_ROW_NUMBER } from "./csv-constants.js";
4
+ const MAX_ROWS_IN_CSV = 25000;
5
+ const validateCsvAgainstSchema = (parsed, tableSchema, options)=>{
6
+ const schemaColumns = new Map(tableSchema.columns.map((col)=>[
7
+ col.name,
8
+ col.serverDataType
9
+ ]));
10
+ const maxRows = options?.maxRows ?? MAX_ROWS_IN_CSV;
11
+ const errorState = createCsvErrorState();
12
+ if (!parsed.header.includes(tableSchema.key)) addCsvFileError(errorState, tableSchema.key, CsvValidationErrorEnum.MISSING_KEY_COLUMN, `CSV must include key column '${tableSchema.key}'.`);
13
+ parsed.header.forEach((column)=>{
14
+ if (!schemaColumns.has(column)) addCsvFileError(errorState, column, CsvValidationErrorEnum.UNKNOWN_COLUMN, "Column is not present in table schema.", column);
15
+ });
16
+ if (parsed.rows.length > maxRows) addCsvFileError(errorState, "*", CsvValidationErrorEnum.MAX_ROWS_EXCEEDED, `CSV row count ${parsed.rows.length} exceeds maxRows ${maxRows}.`, String(parsed.rows.length));
17
+ const typedRows = [];
18
+ parsed.rows.forEach((rowValues, rowIndex)=>{
19
+ const rowNum = rowIndex + CSV_FIRST_DATA_ROW_NUMBER;
20
+ const typedRow = {};
21
+ parsed.header.forEach((columnName, columnIndex)=>{
22
+ const rawValue = rowValues[columnIndex];
23
+ const schemaType = schemaColumns.get(columnName);
24
+ if (0 === rawValue.length && "string" !== schemaType) return void addCsvRowError(errorState, rowNum, columnName, CsvValidationErrorEnum.EMPTY_NON_STRING_VALUE, "Empty value is not allowed for non-string columns.", rawValue);
25
+ let value;
26
+ const toType = schemaType ?? "string";
27
+ try {
28
+ value = getTypedValue(rawValue, toType);
29
+ } catch (err) {
30
+ addCsvRowError(errorState, rowNum, columnName, CsvValidationErrorEnum.TYPE_MISMATCH, err instanceof Error ? err.message : String(err), rawValue);
31
+ return;
32
+ }
33
+ if (void 0 === value) return void addCsvRowError(errorState, rowNum, columnName, CsvValidationErrorEnum.TYPE_MISMATCH, `Value '${rawValue}' is not a valid ${toType}`, rawValue);
34
+ typedRow[columnName] = value;
35
+ });
36
+ typedRows.push(typedRow);
37
+ });
38
+ return {
39
+ columns: parsed.header,
40
+ errorMap: errorState.errorMap,
41
+ errors: errorState.errors,
42
+ rows: typedRows
43
+ };
44
+ };
45
+ export { CsvValidationErrorEnum, validateCsvAgainstSchema };
@@ -0,0 +1,59 @@
1
+ import { isRpcError } from "@vuu-ui/vuu-utils";
2
+ import { CSV_FIRST_DATA_ROW_NUMBER } from "./csv-constants.js";
3
+ const isRecord = (value)=>"object" == typeof value && null !== value;
4
+ const toErrorMessage = (error)=>error instanceof Error ? error.message : String(error);
5
+ const isCsvParseError = (value)=>isRecord(value) && "message" in value && "string" == typeof value.message && "errors" in value && Array.isArray(value.errors) && "errorMap" in value && isRecord(value.errorMap);
6
+ const hasFileParseErrors = (parseError)=>Object.keys(parseError.errorMap.fileErrors).length > 0;
7
+ const createUploadError = (source, message, parseError, validationError)=>({
8
+ message,
9
+ parseError,
10
+ validationError,
11
+ source
12
+ });
13
+ const mergeValidationWithParseErrors = (schemaValidation, parseError)=>parseError ? {
14
+ ...schemaValidation,
15
+ errors: [
16
+ ...schemaValidation.errors,
17
+ ...toValidationErrorsFromParseRowErrors(parseError)
18
+ ],
19
+ parseRowError: parseError
20
+ } : schemaValidation;
21
+ const buildRowErrorMessage = (prefix, rowErrors)=>{
22
+ const details = rowErrors.slice(0, 8).join("; ");
23
+ const remaining = rowErrors.length - 8;
24
+ const suffix = remaining > 0 ? `; and ${remaining} more` : "";
25
+ return `${prefix} for ${rowErrors.length} row(s): ${details}${suffix}`;
26
+ };
27
+ const toValidationErrorsFromParseRowErrors = (parseError)=>parseError.errors.filter((error)=>error.rowNum >= CSV_FIRST_DATA_ROW_NUMBER);
28
+ const normalizeTableData = (value, fallback)=>{
29
+ const tableData = isRecord(value) && "data" in value ? value.data : value;
30
+ if (!isRecord(tableData)) return fallback;
31
+ const columns = tableData.columns;
32
+ const rows = tableData.rows;
33
+ if (!Array.isArray(columns) || columns.some((column)=>"string" != typeof column) || !Array.isArray(rows)) return fallback;
34
+ return {
35
+ columns,
36
+ rows: rows.map((row)=>{
37
+ if (Array.isArray(row)) return row;
38
+ if (!isRecord(row)) return columns.map(()=>"");
39
+ return columns.map((column)=>row[column] ?? "");
40
+ })
41
+ };
42
+ };
43
+ const executeBatchRpcCalls = async (items, executor, errorFormatter)=>{
44
+ const errors = [];
45
+ const results = [];
46
+ for (const item of items)try {
47
+ const result = await executor(item);
48
+ const rpcResult = result;
49
+ if (isRpcError(rpcResult)) errors.push(errorFormatter(item, rpcResult.errorMessage));
50
+ else results.push(result);
51
+ } catch (err) {
52
+ errors.push(errorFormatter(item, toErrorMessage(err)));
53
+ }
54
+ return {
55
+ errors,
56
+ results
57
+ };
58
+ };
59
+ export { buildRowErrorMessage, createUploadError, executeBatchRpcCalls, hasFileParseErrors, isCsvParseError, mergeValidationWithParseErrors, normalizeTableData, toErrorMessage, toValidationErrorsFromParseRowErrors };
@@ -0,0 +1,5 @@
1
+ export { CSV_FIRST_DATA_ROW_NUMBER, CSV_HEADER_ROW_NUMBER } from "./csv-constants.js";
2
+ export { CsvParseErrorEnum, CsvValidationErrorEnum, addCsvFileError, addCsvRowError, createCsvErrorState, hasCsvErrors } from "./csv-errors.js";
3
+ export { parseCsv } from "./csv-parse.js";
4
+ export { validateCsvAgainstSchema } from "./csv-schema-validation.js";
5
+ export { buildRowErrorMessage, executeBatchRpcCalls, hasFileParseErrors, isCsvParseError, mergeValidationWithParseErrors, normalizeTableData, toErrorMessage, toValidationErrorsFromParseRowErrors } from "./csv-upload-utils.js";
@@ -0,0 +1,260 @@
1
+ import { isRpcSuccess, isSessionTable } from "@vuu-ui/vuu-utils";
2
+ import { useCallback, useMemo, useRef, useState } from "react";
3
+ import { parseCsv } from "./parse/csv-parse.js";
4
+ import { validateCsvAgainstSchema } from "./parse/csv-schema-validation.js";
5
+ import { buildRowErrorMessage, createUploadError, executeBatchRpcCalls, hasFileParseErrors, isCsvParseError, mergeValidationWithParseErrors, normalizeTableData, toErrorMessage } from "./parse/csv-upload-utils.js";
6
+ import { CSV_FIRST_DATA_ROW_NUMBER } from "./parse/csv-constants.js";
7
+ const useCsvUpload = ({ dataSource, onEditSessionEnded, onEditSessionStarted, onError, onImported, onProcessingStarted, maxRows, parseOptions })=>{
8
+ const [validation, setValidation] = useState();
9
+ const [sessionTable, setSessionTable] = useState();
10
+ const [isProcessingFile, setIsProcessingFile] = useState(false);
11
+ const [isImporting, setIsImporting] = useState(false);
12
+ const sessionTableRef = useRef(void 0);
13
+ const setActiveSessionTable = useCallback((table)=>{
14
+ sessionTableRef.current = table;
15
+ if (table) {
16
+ const sessionDs = dataSource.createSessionDataSource?.(table);
17
+ if (sessionDs) onEditSessionStarted?.(sessionDs);
18
+ }
19
+ setSessionTable(table);
20
+ }, [
21
+ dataSource,
22
+ onEditSessionStarted
23
+ ]);
24
+ const endEditSessionAndNotify = useCallback(async (save, reason)=>{
25
+ if (!dataSource.endEditSession) throw Error("CsvUpload requires datasource endEditSession support.");
26
+ const currentSessionTable = sessionTableRef.current;
27
+ const result = await dataSource.endEditSession(save);
28
+ setActiveSessionTable(void 0);
29
+ onEditSessionEnded?.({
30
+ reason,
31
+ sessionTable: currentSessionTable
32
+ });
33
+ return result;
34
+ }, [
35
+ dataSource,
36
+ onEditSessionEnded,
37
+ setActiveSessionTable
38
+ ]);
39
+ const table = dataSource.table;
40
+ const schema = dataSource.tableSchema;
41
+ const addAllRows = useCallback(async (mergedValidation)=>{
42
+ if (void 0 === table) throw Error("CsvUpload requires dataSource.table to be defined.");
43
+ const vuuMsgByRow = new Map();
44
+ for (const { rowNum, column, message } of mergedValidation.errors){
45
+ if (rowNum < CSV_FIRST_DATA_ROW_NUMBER) continue;
46
+ const existing = vuuMsgByRow.get(rowNum);
47
+ const columnError = `${column}: ${message}`;
48
+ vuuMsgByRow.set(rowNum, existing ? `${existing}; ${columnError}` : `Row ${rowNum}: ${columnError}`);
49
+ }
50
+ const parsedRowCount = mergedValidation.rows.length;
51
+ const unparsedErrorRows = [
52
+ ...vuuMsgByRow.keys()
53
+ ].filter((rowNum)=>rowNum - CSV_FIRST_DATA_ROW_NUMBER >= parsedRowCount).map((rowNum)=>({
54
+ rowNum,
55
+ rowData: {},
56
+ vuuMsg: vuuMsgByRow.get(rowNum) ?? ""
57
+ }));
58
+ const allRows = [
59
+ ...mergedValidation.rows.map((rowData, idx)=>({
60
+ rowNum: idx + CSV_FIRST_DATA_ROW_NUMBER,
61
+ rowData: rowData ?? {},
62
+ vuuMsg: vuuMsgByRow.get(idx + CSV_FIRST_DATA_ROW_NUMBER) ?? ""
63
+ })),
64
+ ...unparsedErrorRows
65
+ ];
66
+ const { errors: rpcErrors } = await executeBatchRpcCalls(allRows, async ({ rowNum, rowData, vuuMsg })=>{
67
+ const payload = {
68
+ ...rowData,
69
+ rowNum,
70
+ vuuMsg
71
+ };
72
+ const result = await dataSource.rpcRequest?.({
73
+ type: "RPC_REQUEST",
74
+ rpcName: "addRow",
75
+ params: {
76
+ key: String(rowNum),
77
+ data: payload
78
+ }
79
+ });
80
+ if (!isRpcSuccess(result)) {
81
+ const msg = result && "errorMessage" in result ? String(result.errorMessage) : "addRow RPC failed";
82
+ throw Error(msg);
83
+ }
84
+ return result;
85
+ }, ({ rowNum }, error)=>`Row ${rowNum}: ${error}`);
86
+ if (rpcErrors.length > 0) {
87
+ const errorMessage = buildRowErrorMessage("Import failed", rpcErrors);
88
+ const errors = {
89
+ importError: createUploadError("import", errorMessage)
90
+ };
91
+ onError?.({
92
+ errors
93
+ });
94
+ }
95
+ }, [
96
+ dataSource,
97
+ onError,
98
+ table
99
+ ]);
100
+ const beginEditSession = useCallback(async ()=>{
101
+ if (!dataSource.beginEditSession) throw Error("CsvUpload requires datasource beginEditSession support.");
102
+ const sessionDataSource = await dataSource.beginEditSession("empty-session-table");
103
+ const sessionVuuTable = sessionDataSource?.table;
104
+ if (sessionVuuTable && isSessionTable(sessionVuuTable)) setActiveSessionTable(sessionVuuTable);
105
+ }, [
106
+ dataSource,
107
+ setActiveSessionTable
108
+ ]);
109
+ const closePendingEditSession = useCallback(async (save)=>{
110
+ if (void 0 === sessionTableRef.current) return;
111
+ await endEditSessionAndNotify(save, save ? "saved" : "discarded");
112
+ }, [
113
+ endEditSessionAndNotify
114
+ ]);
115
+ const processFile = useCallback(async (file)=>{
116
+ setValidation(void 0);
117
+ onError?.(void 0);
118
+ await closePendingEditSession(false);
119
+ if (void 0 === schema) throw Error("Table schema is not yet available.");
120
+ if (void 0 === table) throw Error("CsvUpload requires dataSource.table to be defined.");
121
+ const parsedCsv = parseCsv(await file.text(), parseOptions);
122
+ if (parsedCsv.error && hasFileParseErrors(parsedCsv.error)) {
123
+ setValidation(void 0);
124
+ onError?.({
125
+ errors: {
126
+ validationError: createUploadError("validation", `Validation failed: ${parsedCsv.error.message}`, parsedCsv.error)
127
+ }
128
+ });
129
+ return;
130
+ }
131
+ const schemaValidation = validateCsvAgainstSchema(parsedCsv, schema, {
132
+ maxRows
133
+ });
134
+ if (Object.keys(schemaValidation.errorMap.fileErrors).length > 0) {
135
+ setValidation(schemaValidation);
136
+ onError?.({
137
+ errors: {
138
+ schemaError: createUploadError("schema", "CSV validation failed.", parsedCsv.error, {
139
+ errorMap: schemaValidation.errorMap,
140
+ errors: schemaValidation.errors,
141
+ message: "CSV validation failed."
142
+ })
143
+ }
144
+ });
145
+ return;
146
+ }
147
+ const mergedValidation = mergeValidationWithParseErrors(schemaValidation, parsedCsv.error);
148
+ setValidation(mergedValidation);
149
+ if (mergedValidation.rows.length > 0) {
150
+ await beginEditSession();
151
+ try {
152
+ await addAllRows(mergedValidation);
153
+ } catch (error) {
154
+ await endEditSessionAndNotify(false, "failed");
155
+ setValidation(void 0);
156
+ onError?.({
157
+ errors: {
158
+ importError: createUploadError("import", `RPC import failed: ${toErrorMessage(error)}`)
159
+ }
160
+ });
161
+ }
162
+ }
163
+ }, [
164
+ onError,
165
+ addAllRows,
166
+ maxRows,
167
+ parseOptions,
168
+ beginEditSession,
169
+ closePendingEditSession,
170
+ table,
171
+ endEditSessionAndNotify,
172
+ schema
173
+ ]);
174
+ const handleFiles = useCallback(async (files)=>{
175
+ const file = files[0];
176
+ if (void 0 === file) return;
177
+ setIsProcessingFile(true);
178
+ onProcessingStarted?.();
179
+ try {
180
+ await processFile(file);
181
+ } catch (err) {
182
+ setValidation(void 0);
183
+ setActiveSessionTable(void 0);
184
+ const parseError = isCsvParseError(err) ? err : void 0;
185
+ const errorMessage = parseError ? `Validation failed: ${parseError.message}` : `Validation failed: ${toErrorMessage(err)}`;
186
+ const errors = {
187
+ errors: {
188
+ validationError: createUploadError("validation", errorMessage, parseError)
189
+ }
190
+ };
191
+ onError?.(errors);
192
+ } finally{
193
+ setIsProcessingFile(false);
194
+ }
195
+ }, [
196
+ onError,
197
+ onProcessingStarted,
198
+ processFile,
199
+ setActiveSessionTable
200
+ ]);
201
+ const onDrop = useCallback((_event, files)=>{
202
+ handleFiles(files);
203
+ }, [
204
+ handleFiles
205
+ ]);
206
+ const onTriggerChange = useCallback((_event, files)=>{
207
+ handleFiles(files);
208
+ }, [
209
+ handleFiles
210
+ ]);
211
+ const canImport = useMemo(()=>void 0 !== validation && 0 === validation.errors.length && validation.rows.length > 0 && !isProcessingFile && !isImporting, [
212
+ isImporting,
213
+ isProcessingFile,
214
+ validation
215
+ ]);
216
+ const importData = useCallback(async ()=>{
217
+ if (!canImport || void 0 === validation) return;
218
+ setIsImporting(true);
219
+ onError?.(void 0);
220
+ try {
221
+ const rpcResult = await endEditSessionAndNotify(true, "saved");
222
+ const fallbackTableData = {
223
+ columns: validation.columns,
224
+ rows: validation.rows.map((row)=>validation.columns.map((column)=>row[column] ?? ""))
225
+ };
226
+ onImported?.({
227
+ rpcResult,
228
+ tableData: normalizeTableData(rpcResult, fallbackTableData)
229
+ });
230
+ } catch (err) {
231
+ const errorMessage = `RPC import failed: ${String(err)}`;
232
+ const errors = {
233
+ errors: {
234
+ importError: createUploadError("import", errorMessage)
235
+ }
236
+ };
237
+ onError?.(errors);
238
+ } finally{
239
+ setIsImporting(false);
240
+ }
241
+ }, [
242
+ canImport,
243
+ endEditSessionAndNotify,
244
+ onError,
245
+ onImported,
246
+ validation
247
+ ]);
248
+ return {
249
+ canImport,
250
+ importData,
251
+ isImporting,
252
+ isProcessingFile,
253
+ onDrop,
254
+ onTriggerChange,
255
+ sessionTable,
256
+ schema,
257
+ validation
258
+ };
259
+ };
260
+ export { useCsvUpload };
@@ -0,0 +1,70 @@
1
+ import { useData } from "@vuu-ui/vuu-utils";
2
+ import { useEffect, useState } from "react";
3
+ const useCsvUploadSessionPreview = (sessionTable)=>{
4
+ const { VuuDataSource, getServerAPI } = useData();
5
+ const [previewConfig, setPreviewConfig] = useState();
6
+ const [previewDataSource, setPreviewDataSource] = useState();
7
+ const [previewError, setPreviewError] = useState();
8
+ const [isLoadingPreview, setIsLoadingPreview] = useState(false);
9
+ useEffect(()=>{
10
+ let cancelled = false;
11
+ if (!sessionTable) {
12
+ setPreviewDataSource(void 0);
13
+ setPreviewConfig(void 0);
14
+ setPreviewError(void 0);
15
+ setIsLoadingPreview(false);
16
+ return;
17
+ }
18
+ const loadSessionTablePreview = async ()=>{
19
+ setIsLoadingPreview(true);
20
+ setPreviewError(void 0);
21
+ try {
22
+ const server = await getServerAPI();
23
+ const schema = await server.getTableSchema(sessionTable);
24
+ if (cancelled) return;
25
+ const columnDescriptors = schema.columns.map(({ name, serverDataType })=>"vuuMsg" === name ? {
26
+ name,
27
+ serverDataType,
28
+ width: 300,
29
+ label: "Error"
30
+ } : {
31
+ name,
32
+ serverDataType
33
+ });
34
+ setPreviewConfig({
35
+ columns: columnDescriptors,
36
+ zebraStripes: true,
37
+ rowSeparators: true,
38
+ columnSeparators: true
39
+ });
40
+ setPreviewDataSource(new VuuDataSource({
41
+ columns: columnDescriptors.map(({ name })=>name),
42
+ table: sessionTable
43
+ }));
44
+ } catch (error) {
45
+ if (!cancelled) {
46
+ setPreviewDataSource(void 0);
47
+ setPreviewConfig(void 0);
48
+ setPreviewError(`Failed to load session table preview: ${String(error)}`);
49
+ }
50
+ } finally{
51
+ if (!cancelled) setIsLoadingPreview(false);
52
+ }
53
+ };
54
+ loadSessionTablePreview();
55
+ return ()=>{
56
+ cancelled = true;
57
+ };
58
+ }, [
59
+ VuuDataSource,
60
+ getServerAPI,
61
+ sessionTable
62
+ ]);
63
+ return {
64
+ isLoadingPreview,
65
+ previewConfig,
66
+ previewDataSource,
67
+ previewError
68
+ };
69
+ };
70
+ export { useCsvUploadSessionPreview };
@@ -0,0 +1,56 @@
1
+ const css = `
2
+ .vuuDatasourceStats {
3
+ --datasourceStats-background: var(--vuuDatasourceStats-background);
4
+ background: var(--datasourceStats-background, var(--salt-container-secondary-background));
5
+ font-size: var(--vuuDatasourceStats-fontSize, 10px);
6
+ align-items: center;
7
+ gap: var(--vuuDatasourceStats-gap, var(--salt-spacing-100));
8
+ height: var(--vuuDatasourceStats-height, 100%);
9
+ padding: 0 var(--salt-spacing-200);
10
+ grid-template: "panel-start panel-center panel-end"
11
+ / 1fr 1fr 1fr;
12
+ display: grid;
13
+
14
+ &.vuuDatasourceStats-withSelection {
15
+ --datasourceStats-background: var(--vuuDatasourceStats-withSelection-background, var(--vuuDatasourceStats-background));
16
+ }
17
+
18
+ & .vuuDatasourceStats-statsPanel {
19
+ align-items: center;
20
+ gap: var(--salt-spacing-100);
21
+ width: fit-content;
22
+ display: flex;
23
+ }
24
+
25
+ & .vuuDatasourceStats-rowStats {
26
+ grid-area: panel-start;
27
+ justify-self: flex-start;
28
+ }
29
+
30
+ & .vuuDatasourceStats-selected {
31
+ margin-left: var(--salt-spacing-100);
32
+ }
33
+
34
+ & .vuuDatasourceStats-label {
35
+ color: var(--salt-content-secondary-foreground);
36
+ white-space: nowrap;
37
+ }
38
+
39
+ & .vuuDatasourceStats-range {
40
+ white-space: nowrap;
41
+ }
42
+
43
+ & .vuuDatasourceStats-value {
44
+ color: var(--salt-content-primary-foreground);
45
+ }
46
+
47
+ & .vuuDatasourceStats-tooltray {
48
+ margin: 0 var(--salt-spacing-200);
49
+ grid-area: panel-end;
50
+ justify-self: end;
51
+ }
52
+ }
53
+
54
+
55
+ `;
56
+ export default css;
@@ -0,0 +1,89 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import clsx from "clsx";
3
+ import { useComponentCssInjection } from "@salt-ds/styles";
4
+ import { useWindow } from "@salt-ds/window";
5
+ import { useDatasourceStats } from "./useDatasourceStats.js";
6
+ import DatasourceStats from "./DatasourceStats.css";
7
+ const classBase = "vuuDatasourceStats";
8
+ const numberFormatter = new Intl.NumberFormat();
9
+ const DataSourceStats = ({ children, className, dataSource, showRowStats = true, tooltrayActions, ...htmlAttributes })=>{
10
+ const targetWindow = useWindow();
11
+ useComponentCssInjection({
12
+ testId: "vuu-datasource-stats",
13
+ css: DatasourceStats,
14
+ window: targetWindow
15
+ });
16
+ const { range, selectedCount, size } = useDatasourceStats({
17
+ dataSource,
18
+ showRowStats
19
+ });
20
+ const from = numberFormatter.format(range.from + 1);
21
+ const to = numberFormatter.format(Math.min(range.to, size));
22
+ const value = numberFormatter.format(size);
23
+ if (0 === size) return /*#__PURE__*/ jsx("div", {
24
+ ...htmlAttributes,
25
+ className: clsx(classBase, className),
26
+ children: /*#__PURE__*/ jsx("span", {
27
+ className: `${classBase}-label`,
28
+ children: "No Rows to display"
29
+ })
30
+ });
31
+ return /*#__PURE__*/ jsxs("div", {
32
+ ...htmlAttributes,
33
+ className: clsx(classBase, className, {
34
+ [`${classBase}-withSelection`]: selectedCount > 0
35
+ }),
36
+ children: [
37
+ showRowStats ? /*#__PURE__*/ jsxs("div", {
38
+ className: `${classBase}-statsPanel ${classBase}-rowStats`,
39
+ children: [
40
+ /*#__PURE__*/ jsx("span", {
41
+ className: `${classBase}-label`,
42
+ children: "Row count"
43
+ }),
44
+ /*#__PURE__*/ jsxs("span", {
45
+ className: `${classBase}-range`,
46
+ children: [
47
+ /*#__PURE__*/ jsx("span", {
48
+ className: `${classBase}-value`,
49
+ children: from
50
+ }),
51
+ /*#__PURE__*/ jsx("span", {
52
+ className: `${classBase}-label`,
53
+ children: "-"
54
+ }),
55
+ /*#__PURE__*/ jsx("span", {
56
+ className: `${classBase}-value`,
57
+ children: to
58
+ })
59
+ ]
60
+ }),
61
+ /*#__PURE__*/ jsx("span", {
62
+ className: `${classBase}-label`,
63
+ children: "of"
64
+ }),
65
+ /*#__PURE__*/ jsx("span", {
66
+ className: `${classBase}-value`,
67
+ children: value
68
+ }),
69
+ selectedCount > 0 ? /*#__PURE__*/ jsx("span", {
70
+ className: clsx(`${classBase}-label`, `${classBase}-selected`),
71
+ children: `${selectedCount.toLocaleString()} selected`
72
+ }) : null
73
+ ]
74
+ }) : null,
75
+ selectedCount > 0 ? /*#__PURE__*/ jsx("div", {
76
+ className: `${classBase}-statsPanel ${classBase}-selectionActions`,
77
+ children: children
78
+ }) : null,
79
+ tooltrayActions ? /*#__PURE__*/ jsx("div", {
80
+ className: `${classBase}-statsPanel ${classBase}-tooltray`,
81
+ children: /*#__PURE__*/ jsx("span", {
82
+ className: `${classBase}-actions`,
83
+ children: tooltrayActions
84
+ })
85
+ }) : null
86
+ ]
87
+ });
88
+ };
89
+ export { DataSourceStats };
@@ -0,0 +1,48 @@
1
+ import { formatDate } from "@vuu-ui/vuu-utils";
2
+ import { useCallback, useMemo, useState } from "react";
3
+ const timeFormatter = formatDate({
4
+ time: "hh:mm:ss"
5
+ });
6
+ const formatTime = (ts)=>{
7
+ if ("number" == typeof ts) return timeFormatter(new Date(ts));
8
+ };
9
+ const useDatasourceStats = ({ dataSource })=>{
10
+ const [selectedCount, setSelectedCount] = useState(0);
11
+ const [range, setRange] = useState(dataSource.range);
12
+ const [size, setSize] = useState(dataSource.size);
13
+ const [freezeTime, setFreezeTime] = useState(formatTime(dataSource.freezeTimestamp));
14
+ const handleFreeze = useCallback((isFrozen, freezeTimestamp)=>{
15
+ isFrozen ? setFreezeTime(formatTime(freezeTimestamp)) : setFreezeTime(void 0);
16
+ }, []);
17
+ const handleRowSelection = useCallback((count)=>{
18
+ setSelectedCount(count);
19
+ }, []);
20
+ const handleSize = useCallback((size)=>{
21
+ setSize(size);
22
+ }, []);
23
+ useMemo(()=>{
24
+ setSize(dataSource.size);
25
+ dataSource.on("resize", handleSize);
26
+ dataSource.on("range", setRange);
27
+ dataSource.on("freeze", handleFreeze);
28
+ dataSource.on("row-selection", handleRowSelection);
29
+ return ()=>{
30
+ dataSource.removeListener("resize", handleSize);
31
+ dataSource.removeListener("range", setRange);
32
+ dataSource.removeListener("freeze", handleFreeze);
33
+ dataSource.removeListener("row-selection", handleRowSelection);
34
+ };
35
+ }, [
36
+ dataSource,
37
+ handleFreeze,
38
+ handleRowSelection,
39
+ handleSize
40
+ ]);
41
+ return {
42
+ range,
43
+ selectedCount,
44
+ size,
45
+ freezeTime
46
+ };
47
+ };
48
+ export { useDatasourceStats };