@jmruthers/pace-core 0.5.97 → 0.5.99
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/dist/{DataTable-FB22ES46.js → DataTable-VSJWLCVT.js} +4 -2
- package/dist/{chunk-UBPRDNPY.js → chunk-2PWJ6NFH.js} +2 -2
- package/dist/{chunk-QX5I62KP.js → chunk-D7CZVI3K.js} +167 -17
- package/dist/{chunk-QX5I62KP.js.map → chunk-D7CZVI3K.js.map} +1 -1
- package/dist/components.js +2 -2
- package/dist/index.js +2 -2
- package/dist/utils.js +1 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +1 -1
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +1 -1
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UseEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +2 -2
- package/package.json +1 -1
- package/src/components/DataTable/components/DataTableCore.tsx +60 -16
- package/src/components/DataTable/components/DataTableModals.tsx +67 -8
- package/src/components/DataTable/utils/exportUtils.ts +115 -0
- /package/dist/{DataTable-FB22ES46.js.map → DataTable-VSJWLCVT.js.map} +0 -0
- /package/dist/{chunk-UBPRDNPY.js.map → chunk-2PWJ6NFH.js.map} +0 -0
package/docs/api/modules.md
CHANGED
package/package.json
CHANGED
|
@@ -43,7 +43,7 @@ import { ColumnFactory } from '../core/ColumnFactory';
|
|
|
43
43
|
import { AccessDeniedPage } from './AccessDeniedPage';
|
|
44
44
|
import { useCan, useResolvedScope } from '../../../rbac/hooks';
|
|
45
45
|
import { toast } from '../../../hooks/useToast';
|
|
46
|
-
import { exportToCSV } from '../utils/exportUtils';
|
|
46
|
+
import { exportToCSV, exportToCSVWithTableRows } from '../utils/exportUtils';
|
|
47
47
|
import { useUnifiedAuth } from '../../../providers/UnifiedAuthProvider';
|
|
48
48
|
import { Scope } from '../../../rbac/types';
|
|
49
49
|
import { useDataTablePermissions } from '../hooks/useDataTablePermissions';
|
|
@@ -969,8 +969,8 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
969
969
|
onExport={secureHandlers.onExport || (async () => {
|
|
970
970
|
try {
|
|
971
971
|
// Automatic export: exports exactly what's shown in the table
|
|
972
|
-
// Get the
|
|
973
|
-
const
|
|
972
|
+
// Get the table rows (which have getValue() that properly evaluates accessorFn)
|
|
973
|
+
const tableRows = table.getFilteredRowModel().rows;
|
|
974
974
|
|
|
975
975
|
// Get only visible columns by checking the actual table columns
|
|
976
976
|
// This approach is more reliable because it uses the table's actual column registry
|
|
@@ -981,8 +981,8 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
981
981
|
return !isSystemColumn && col.getIsVisible();
|
|
982
982
|
});
|
|
983
983
|
|
|
984
|
-
// Map table columns
|
|
985
|
-
//
|
|
984
|
+
// Map table columns to export columns
|
|
985
|
+
// Use TanStack Table's getValue() which properly handles accessorFn
|
|
986
986
|
const visibleColumns: Array<{
|
|
987
987
|
header?: string;
|
|
988
988
|
id?: string;
|
|
@@ -992,6 +992,9 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
992
992
|
isIdColumn?: boolean;
|
|
993
993
|
}> = [];
|
|
994
994
|
|
|
995
|
+
// Store mapping of column IDs to table column instances for getValue() calls
|
|
996
|
+
const columnIdToTableColumn = new Map<string, typeof visibleTableColumns[0]>();
|
|
997
|
+
|
|
995
998
|
visibleTableColumns.forEach(tableCol => {
|
|
996
999
|
// Find the original column definition that matches this table column
|
|
997
1000
|
const originalCol = columns.find(col => {
|
|
@@ -1001,27 +1004,38 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
1001
1004
|
|
|
1002
1005
|
if (!originalCol) return;
|
|
1003
1006
|
|
|
1007
|
+
// Store the table column for getValue() calls
|
|
1008
|
+
columnIdToTableColumn.set(tableCol.id, tableCol);
|
|
1009
|
+
|
|
1004
1010
|
const hasAccessorFn = 'accessorFn' in originalCol && (originalCol as any).accessorFn;
|
|
1005
1011
|
const editAccessorKey = originalCol.editAccessorKey;
|
|
1006
1012
|
|
|
1007
1013
|
// Add the display column (what's shown in the table)
|
|
1014
|
+
// For columns with accessorFn, we'll use the table's getValue() method instead of calling accessorFn directly
|
|
1015
|
+
const displayHeader = typeof originalCol.header === 'string'
|
|
1016
|
+
? originalCol.header
|
|
1017
|
+
: originalCol.accessorKey || tableCol.id || 'Column';
|
|
1018
|
+
|
|
1008
1019
|
visibleColumns.push({
|
|
1009
1020
|
...originalCol,
|
|
1010
|
-
header:
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
// Preserve accessorFn if present (
|
|
1014
|
-
// accessorFn is part of ColumnDef from TanStack Table
|
|
1021
|
+
header: displayHeader,
|
|
1022
|
+
// Store table column ID for getValue() lookup
|
|
1023
|
+
id: tableCol.id,
|
|
1024
|
+
// Preserve accessorFn if present (will use getValue() if available)
|
|
1015
1025
|
accessorFn: hasAccessorFn ? (originalCol as any).accessorFn : undefined,
|
|
1016
1026
|
});
|
|
1017
1027
|
|
|
1018
1028
|
// For reference fields (accessorFn + editAccessorKey), also add ID column
|
|
1019
1029
|
// This allows round-trip import/export to work seamlessly
|
|
1020
1030
|
if (hasAccessorFn && editAccessorKey) {
|
|
1031
|
+
// Use the same string fallback logic as the display column to ensure
|
|
1032
|
+
// JSX headers are converted to strings (e.g., accessorKey or column ID)
|
|
1033
|
+
// This prevents "[object Object] (ID)" in the CSV header
|
|
1034
|
+
const idHeader = `${displayHeader} (ID)`;
|
|
1021
1035
|
visibleColumns.push({
|
|
1022
1036
|
id: editAccessorKey,
|
|
1023
1037
|
accessorKey: editAccessorKey,
|
|
1024
|
-
header:
|
|
1038
|
+
header: idHeader,
|
|
1025
1039
|
isIdColumn: true,
|
|
1026
1040
|
editAccessorKey: editAccessorKey,
|
|
1027
1041
|
});
|
|
@@ -1032,8 +1046,9 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
1032
1046
|
const timestamp = new Date().toISOString().split('T')[0];
|
|
1033
1047
|
const filename = title ? `${title.replace(/[^a-z0-9]/gi, '_').toLowerCase()}_${timestamp}.csv` : `data_export_${timestamp}.csv`;
|
|
1034
1048
|
|
|
1035
|
-
// Export
|
|
1036
|
-
|
|
1049
|
+
// Export using table rows with getValue() for proper accessorFn evaluation
|
|
1050
|
+
// This ensures we get the same values that are displayed in the table
|
|
1051
|
+
await exportToCSVWithTableRows(tableRows, visibleColumns, columnIdToTableColumn, filename);
|
|
1037
1052
|
|
|
1038
1053
|
// Show success toast notification
|
|
1039
1054
|
toast({
|
|
@@ -1278,10 +1293,39 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
1278
1293
|
onCloseImportModal={() => stateActions.setImportModal(false)}
|
|
1279
1294
|
onImport={async (data: TData[]) => {
|
|
1280
1295
|
if (onImport) {
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1296
|
+
try {
|
|
1297
|
+
console.log('[DataTableCore] onImport called with', data.length, 'rows');
|
|
1298
|
+
const result = onImport(data);
|
|
1299
|
+
if (result && typeof result.then === 'function') {
|
|
1300
|
+
await result;
|
|
1301
|
+
}
|
|
1302
|
+
console.log('[DataTableCore] onImport completed successfully');
|
|
1303
|
+
|
|
1304
|
+
// Show success toast
|
|
1305
|
+
toast({
|
|
1306
|
+
title: "Import Successful",
|
|
1307
|
+
description: `Successfully imported ${data.length} ${data.length === 1 ? 'row' : 'rows'}`,
|
|
1308
|
+
variant: "default"
|
|
1309
|
+
});
|
|
1310
|
+
} catch (error) {
|
|
1311
|
+
console.error('[DataTableCore] Import error:', error);
|
|
1312
|
+
toast({
|
|
1313
|
+
title: "Import Failed",
|
|
1314
|
+
description: error instanceof Error ? error.message : 'Failed to import data',
|
|
1315
|
+
variant: "destructive"
|
|
1316
|
+
});
|
|
1317
|
+
// Don't close modal on error so user can see the error
|
|
1318
|
+
return;
|
|
1284
1319
|
}
|
|
1320
|
+
} else {
|
|
1321
|
+
console.error('[DataTableCore] onImport handler not provided');
|
|
1322
|
+
toast({
|
|
1323
|
+
title: "Import Not Configured",
|
|
1324
|
+
description: "Import functionality requires an onImport handler to be provided.",
|
|
1325
|
+
variant: "destructive"
|
|
1326
|
+
});
|
|
1327
|
+
// Don't close modal so user can see the error
|
|
1328
|
+
return;
|
|
1285
1329
|
}
|
|
1286
1330
|
stateActions.setImportModal(false);
|
|
1287
1331
|
}}
|
|
@@ -41,6 +41,8 @@ function mapCSVToTableColumns<TData extends Record<string, unknown> = Record<str
|
|
|
41
41
|
// Priority: editAccessorKey > accessorKey > id
|
|
42
42
|
const columnMap = new Map<string, string>();
|
|
43
43
|
|
|
44
|
+
console.log('[mapCSVToTableColumns] Building column map from', columns.length, 'column definitions');
|
|
45
|
+
|
|
44
46
|
columns.forEach(col => {
|
|
45
47
|
const fieldName = col.editAccessorKey || col.accessorKey || col.id;
|
|
46
48
|
const header = typeof col.header === 'string' ? col.header : '';
|
|
@@ -48,10 +50,13 @@ function mapCSVToTableColumns<TData extends Record<string, unknown> = Record<str
|
|
|
48
50
|
if (fieldName && header) {
|
|
49
51
|
// Map header to field name (case-insensitive)
|
|
50
52
|
columnMap.set(header.toLowerCase(), fieldName);
|
|
53
|
+
console.log(`[mapCSVToTableColumns] Mapped "${header}" -> "${fieldName}"`);
|
|
54
|
+
|
|
51
55
|
// Also map id/accessorKey if different from header
|
|
52
56
|
const colId = col.id || col.accessorKey;
|
|
53
57
|
if (colId && colId !== header && colId !== fieldName) {
|
|
54
58
|
columnMap.set(colId.toLowerCase(), fieldName);
|
|
59
|
+
console.log(`[mapCSVToTableColumns] Also mapped "${colId}" -> "${fieldName}"`);
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
// For reference fields with editAccessorKey, also map the ID column header
|
|
@@ -59,25 +64,57 @@ function mapCSVToTableColumns<TData extends Record<string, unknown> = Record<str
|
|
|
59
64
|
if (col.editAccessorKey && header) {
|
|
60
65
|
const idColumnHeader = `${header} (ID)`;
|
|
61
66
|
columnMap.set(idColumnHeader.toLowerCase(), col.editAccessorKey);
|
|
67
|
+
console.log(`[mapCSVToTableColumns] Mapped ID column "${idColumnHeader}" -> "${col.editAccessorKey}"`);
|
|
62
68
|
// Also map the editAccessorKey directly (in case CSV uses the field name)
|
|
63
69
|
columnMap.set(col.editAccessorKey.toLowerCase(), col.editAccessorKey);
|
|
70
|
+
console.log(`[mapCSVToTableColumns] Also mapped "${col.editAccessorKey}" -> "${col.editAccessorKey}"`);
|
|
64
71
|
}
|
|
72
|
+
} else {
|
|
73
|
+
console.warn('[mapCSVToTableColumns] Skipping column with missing fieldName or header:', col);
|
|
65
74
|
}
|
|
66
75
|
});
|
|
67
76
|
|
|
77
|
+
if (csvData.length === 0) {
|
|
78
|
+
console.warn('[mapCSVToTableColumns] No CSV data to map');
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const csvHeaders = Object.keys(csvData[0]);
|
|
83
|
+
console.log('[mapCSVToTableColumns] CSV headers found:', csvHeaders);
|
|
84
|
+
console.log('[mapCSVToTableColumns] Column map size:', columnMap.size);
|
|
85
|
+
|
|
68
86
|
// Transform CSV data using the mapping
|
|
69
|
-
|
|
87
|
+
const mappedData = csvData.map((row, index) => {
|
|
70
88
|
const mappedRow: Record<string, unknown> = {};
|
|
71
89
|
|
|
72
90
|
// For each CSV column, find the corresponding table field
|
|
73
91
|
Object.keys(row).forEach(csvHeader => {
|
|
74
92
|
const csvHeaderLower = csvHeader.toLowerCase();
|
|
75
|
-
const fieldName = columnMap.get(csvHeaderLower)
|
|
76
|
-
|
|
93
|
+
const fieldName = columnMap.get(csvHeaderLower);
|
|
94
|
+
|
|
95
|
+
if (fieldName) {
|
|
96
|
+
mappedRow[fieldName] = row[csvHeader];
|
|
97
|
+
if (index === 0) {
|
|
98
|
+
console.log(`[mapCSVToTableColumns] Row 0: "${csvHeader}" -> "${fieldName}"`);
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
// If no mapping found, use the CSV header as-is (lowercase)
|
|
102
|
+
mappedRow[csvHeaderLower] = row[csvHeader];
|
|
103
|
+
if (index === 0) {
|
|
104
|
+
console.warn(`[mapCSVToTableColumns] No mapping found for "${csvHeader}", using as-is`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
77
107
|
});
|
|
78
108
|
|
|
79
109
|
return mappedRow as TData;
|
|
80
110
|
});
|
|
111
|
+
|
|
112
|
+
console.log('[mapCSVToTableColumns] Mapped', mappedData.length, 'rows');
|
|
113
|
+
if (mappedData.length > 0) {
|
|
114
|
+
console.log('[mapCSVToTableColumns] First mapped row keys:', Object.keys(mappedData[0]));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return mappedData;
|
|
81
118
|
}
|
|
82
119
|
|
|
83
120
|
/**
|
|
@@ -164,11 +201,33 @@ export function DataTableModals<TData extends Record<string, unknown> = Record<s
|
|
|
164
201
|
isOpen={showImportModal}
|
|
165
202
|
onClose={onCloseImportModal}
|
|
166
203
|
onImport={async (rawData: Array<Record<string, unknown>>) => {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
204
|
+
try {
|
|
205
|
+
// Automatically map CSV columns to table columns based on column definitions
|
|
206
|
+
let mappedData: TData[];
|
|
207
|
+
if (columns && columns.length > 0) {
|
|
208
|
+
console.log('[DataTableModals] Mapping CSV data with', columns.length, 'column definitions');
|
|
209
|
+
console.log('[DataTableModals] Raw CSV headers:', rawData.length > 0 ? Object.keys(rawData[0]) : []);
|
|
210
|
+
mappedData = mapCSVToTableColumns<TData>(rawData, columns);
|
|
211
|
+
console.log('[DataTableModals] Mapped data sample:', mappedData.length > 0 ? mappedData[0] : null);
|
|
212
|
+
} else {
|
|
213
|
+
console.warn('[DataTableModals] No columns provided for mapping, using raw data');
|
|
214
|
+
mappedData = rawData as TData[];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!onImport) {
|
|
218
|
+
console.error('[DataTableModals] onImport callback is not provided');
|
|
219
|
+
throw new Error('Import handler is not configured. Please provide an onImport callback.');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
console.log('[DataTableModals] Calling onImport with', mappedData.length, 'rows');
|
|
223
|
+
const result = onImport(mappedData);
|
|
224
|
+
if (result && typeof result.then === 'function') {
|
|
225
|
+
await result;
|
|
226
|
+
}
|
|
227
|
+
console.log('[DataTableModals] Import completed successfully');
|
|
228
|
+
} catch (error) {
|
|
229
|
+
console.error('[DataTableModals] Import error:', error);
|
|
230
|
+
throw error; // Re-throw to let ImportModal handle it
|
|
172
231
|
}
|
|
173
232
|
}}
|
|
174
233
|
config={importModalConfig}
|
|
@@ -180,6 +180,121 @@ export function generateCSVContent<TData extends DataRecord>(
|
|
|
180
180
|
return csvContent;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Exports DataTable data using TanStack Table rows with getValue() for proper accessorFn evaluation
|
|
185
|
+
* This ensures computed columns (with accessorFn) export correctly with their displayed values
|
|
186
|
+
*/
|
|
187
|
+
export async function exportToCSVWithTableRows(
|
|
188
|
+
tableRows: Array<{
|
|
189
|
+
original: any;
|
|
190
|
+
getValue: (columnId: string) => any;
|
|
191
|
+
id: string;
|
|
192
|
+
}>,
|
|
193
|
+
columns: ExportColumn[],
|
|
194
|
+
columnIdToTableColumn: Map<string, any>,
|
|
195
|
+
filename: string = "download.csv",
|
|
196
|
+
options: {
|
|
197
|
+
locale?: string;
|
|
198
|
+
sanitizeForSecurity?: boolean;
|
|
199
|
+
} = {}
|
|
200
|
+
): Promise<void> {
|
|
201
|
+
const logger = createLogger('ExportUtils');
|
|
202
|
+
return new Promise((resolve, reject) => {
|
|
203
|
+
try {
|
|
204
|
+
if (typeof window === 'undefined') {
|
|
205
|
+
throw new Error('CSV export is only available in browser environments');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!tableRows || tableRows.length === 0) {
|
|
209
|
+
throw new Error('No data to export');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!columns || columns.length === 0) {
|
|
213
|
+
throw new Error('No columns defined for export');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Create CSV header row
|
|
217
|
+
const headers = columns.map(col => {
|
|
218
|
+
const headerValue = col.header || col.id || "Column";
|
|
219
|
+
return escapeCSVValue(headerValue, options.sanitizeForSecurity !== false);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Format data into CSV rows using table row getValue() for proper accessorFn evaluation
|
|
223
|
+
const csvData = tableRows.map(tableRow => {
|
|
224
|
+
return columns.map(col => {
|
|
225
|
+
let value: any;
|
|
226
|
+
|
|
227
|
+
// For ID columns (reference field IDs), get directly from original data
|
|
228
|
+
if (col.isIdColumn && col.accessorKey) {
|
|
229
|
+
value = tableRow.original[col.accessorKey];
|
|
230
|
+
}
|
|
231
|
+
// For columns with accessorFn, use TanStack Table's getValue() which properly evaluates it
|
|
232
|
+
else if (col.id && columnIdToTableColumn.has(col.id)) {
|
|
233
|
+
try {
|
|
234
|
+
// Use getValue() which properly evaluates accessorFn with the table's context
|
|
235
|
+
value = tableRow.getValue(col.id);
|
|
236
|
+
} catch (error) {
|
|
237
|
+
// Fallback: try accessorFn directly if getValue() fails
|
|
238
|
+
if (col.accessorFn) {
|
|
239
|
+
try {
|
|
240
|
+
value = col.accessorFn(tableRow.original);
|
|
241
|
+
} catch (accessorError) {
|
|
242
|
+
console.warn('Error evaluating accessorFn for column:', col.id || col.header, accessorError);
|
|
243
|
+
value = undefined;
|
|
244
|
+
}
|
|
245
|
+
} else {
|
|
246
|
+
// Direct property access
|
|
247
|
+
const key = col.accessorKey || col.id;
|
|
248
|
+
value = key ? tableRow.original[key] : undefined;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// Fallback to direct property access
|
|
253
|
+
else {
|
|
254
|
+
const key = col.accessorKey || col.id;
|
|
255
|
+
value = key ? tableRow.original[key] : undefined;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Format according to locale if provided
|
|
259
|
+
if (options.locale && (typeof value === 'number' || value instanceof Date || typeof value === 'boolean')) {
|
|
260
|
+
value = formatLocaleValue(value, options.locale);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Escape the value for CSV
|
|
264
|
+
return escapeCSVValue(value, options.sanitizeForSecurity !== false);
|
|
265
|
+
}).join(",");
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
// Combine header and data
|
|
269
|
+
const csvContent = [headers.join(","), ...csvData].join("\n");
|
|
270
|
+
|
|
271
|
+
// Create and trigger download
|
|
272
|
+
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
|
|
273
|
+
const link = document.createElement("a");
|
|
274
|
+
const url = URL.createObjectURL(blob);
|
|
275
|
+
|
|
276
|
+
link.setAttribute("href", url);
|
|
277
|
+
link.setAttribute("download", filename);
|
|
278
|
+
link.style.display = "none";
|
|
279
|
+
|
|
280
|
+
// Handle click event
|
|
281
|
+
link.onclick = () => {
|
|
282
|
+
setTimeout(() => {
|
|
283
|
+
URL.revokeObjectURL(url);
|
|
284
|
+
resolve();
|
|
285
|
+
}, 100);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
document.body.appendChild(link);
|
|
289
|
+
link.click();
|
|
290
|
+
document.body.removeChild(link);
|
|
291
|
+
} catch (error) {
|
|
292
|
+
logger.error('Failed to export data to CSV:', error);
|
|
293
|
+
reject(error);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
183
298
|
/**
|
|
184
299
|
* Exports DataTable data to CSV format
|
|
185
300
|
*
|
|
File without changes
|
|
File without changes
|