@jmruthers/pace-core 0.5.95 → 0.5.97
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-XENXNMCP.js → DataTable-FB22ES46.js} +2 -2
- package/dist/{chunk-EPKHU5SS.js → chunk-QX5I62KP.js} +77 -11
- package/dist/chunk-QX5I62KP.js.map +1 -0
- package/dist/{chunk-V5CTX4FR.js → chunk-UBPRDNPY.js} +2 -2
- 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/docs/implementation-guides/data-tables.md +156 -1
- package/package.json +1 -1
- package/src/components/DataTable/DataTable.tsx +1 -1
- package/src/components/DataTable/components/DataTableCore.tsx +51 -18
- package/src/components/DataTable/components/DataTableModals.tsx +76 -5
- package/src/components/DataTable/utils/exportUtils.ts +18 -2
- package/dist/chunk-EPKHU5SS.js.map +0 -1
- /package/dist/{DataTable-XENXNMCP.js.map → DataTable-FB22ES46.js.map} +0 -0
- /package/dist/{chunk-V5CTX4FR.js.map → chunk-UBPRDNPY.js.map} +0 -0
package/docs/api/modules.md
CHANGED
|
@@ -694,7 +694,9 @@ function MealsTable() {
|
|
|
694
694
|
filtering: true, // Enable filtering
|
|
695
695
|
search: true,
|
|
696
696
|
pagination: true,
|
|
697
|
-
sorting: true
|
|
697
|
+
sorting: true,
|
|
698
|
+
export: true, // Enable export
|
|
699
|
+
import: true // Enable import
|
|
698
700
|
}}
|
|
699
701
|
rbac={{
|
|
700
702
|
pageName: 'meals'
|
|
@@ -1227,6 +1229,159 @@ function UserManagement() {
|
|
|
1227
1229
|
}
|
|
1228
1230
|
```
|
|
1229
1231
|
|
|
1232
|
+
## Export and Import
|
|
1233
|
+
|
|
1234
|
+
DataTable provides seamless CSV export and import functionality with automatic column mapping and support for reference fields.
|
|
1235
|
+
|
|
1236
|
+
### Basic Export/Import
|
|
1237
|
+
|
|
1238
|
+
When `export: true` and `import: true` are enabled in features, the DataTable automatically provides export and import buttons in the toolbar.
|
|
1239
|
+
|
|
1240
|
+
**Export**:
|
|
1241
|
+
- Exports all visible columns (respects column visibility settings)
|
|
1242
|
+
- Exports filtered data (respects current filters/search)
|
|
1243
|
+
- Exports all rows across all pages (not just current page)
|
|
1244
|
+
- Filename is automatically generated from table title: `{title}_{date}.csv`
|
|
1245
|
+
|
|
1246
|
+
**Import**:
|
|
1247
|
+
- Automatically maps CSV column headers to table columns
|
|
1248
|
+
- Case-insensitive header matching
|
|
1249
|
+
- Supports both display names and field names
|
|
1250
|
+
|
|
1251
|
+
### Export/Import with Reference Fields
|
|
1252
|
+
|
|
1253
|
+
For columns that display related data (using `accessorFn` with `editAccessorKey`), the export includes both the display value and the ID value for seamless round-trip import/export.
|
|
1254
|
+
|
|
1255
|
+
#### Example: Reference Field Column
|
|
1256
|
+
|
|
1257
|
+
```tsx
|
|
1258
|
+
const columns = [
|
|
1259
|
+
{
|
|
1260
|
+
id: "meal_code",
|
|
1261
|
+
accessorKey: "meal_code",
|
|
1262
|
+
header: "Meal Code",
|
|
1263
|
+
},
|
|
1264
|
+
{
|
|
1265
|
+
id: "meal_type",
|
|
1266
|
+
// Display function - shows the related meal type name
|
|
1267
|
+
accessorFn: (row) => row.mealtype?.mealtype_name || 'N/A',
|
|
1268
|
+
header: "Type",
|
|
1269
|
+
// Database field for storing/importing the ID
|
|
1270
|
+
editAccessorKey: 'meal_mealtype_id',
|
|
1271
|
+
fieldType: 'select',
|
|
1272
|
+
fieldOptions: mealTypes.map(mt => ({
|
|
1273
|
+
value: mt.mealtype_id,
|
|
1274
|
+
label: mt.mealtype_name
|
|
1275
|
+
}))
|
|
1276
|
+
},
|
|
1277
|
+
{
|
|
1278
|
+
id: "meal_date",
|
|
1279
|
+
accessorKey: "meal_date",
|
|
1280
|
+
header: "Date",
|
|
1281
|
+
},
|
|
1282
|
+
];
|
|
1283
|
+
```
|
|
1284
|
+
|
|
1285
|
+
#### Export Behavior
|
|
1286
|
+
|
|
1287
|
+
When you export this table, you'll get CSV columns like:
|
|
1288
|
+
|
|
1289
|
+
```csv
|
|
1290
|
+
Meal Code,Type,Type (ID),Date
|
|
1291
|
+
ptry,Pantry,abc-123-uuid,2024-04-11
|
|
1292
|
+
11bf,Breakfast,def-456-uuid,2024-04-11
|
|
1293
|
+
```
|
|
1294
|
+
|
|
1295
|
+
**Notice**:
|
|
1296
|
+
- "Type" column contains the display value (e.g., "Pantry", "Breakfast")
|
|
1297
|
+
- "Type (ID)" column contains the actual ID value (e.g., UUID) for importing
|
|
1298
|
+
- Both columns are included automatically
|
|
1299
|
+
|
|
1300
|
+
#### Import Behavior
|
|
1301
|
+
|
|
1302
|
+
The import automatically recognizes and maps:
|
|
1303
|
+
|
|
1304
|
+
| CSV Header | Maps To | Description |
|
|
1305
|
+
|------------|---------|-------------|
|
|
1306
|
+
| `Type` | `meal_mealtype_id` | Display column header |
|
|
1307
|
+
| `Type (ID)` | `meal_mealtype_id` | ID column header |
|
|
1308
|
+
| `meal_mealtype_id` | `meal_mealtype_id` | Direct field name |
|
|
1309
|
+
|
|
1310
|
+
**Round-Trip Support**: You can export data, modify it in Excel/CSV, and re-import it - the IDs will be automatically mapped correctly.
|
|
1311
|
+
|
|
1312
|
+
#### Complete Example
|
|
1313
|
+
|
|
1314
|
+
```tsx
|
|
1315
|
+
<DataTable
|
|
1316
|
+
data={meals}
|
|
1317
|
+
columns={columns}
|
|
1318
|
+
features={{
|
|
1319
|
+
export: true,
|
|
1320
|
+
import: true,
|
|
1321
|
+
// ... other features
|
|
1322
|
+
}}
|
|
1323
|
+
onImport={async (importedData) => {
|
|
1324
|
+
// Imported data is already mapped to your column structure
|
|
1325
|
+
// Reference fields use the editAccessorKey value
|
|
1326
|
+
for (const meal of importedData) {
|
|
1327
|
+
await createMeal({
|
|
1328
|
+
meal_code: meal.meal_code,
|
|
1329
|
+
meal_mealtype_id: meal.meal_mealtype_id, // Automatically mapped
|
|
1330
|
+
meal_date: meal.meal_date,
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
}}
|
|
1334
|
+
rbac={{
|
|
1335
|
+
pageName: 'meals'
|
|
1336
|
+
}}
|
|
1337
|
+
/>
|
|
1338
|
+
```
|
|
1339
|
+
|
|
1340
|
+
### Custom Export Filename
|
|
1341
|
+
|
|
1342
|
+
The export filename is automatically generated from the table `title` prop:
|
|
1343
|
+
|
|
1344
|
+
- If `title` is provided: `{title}_{date}.csv` (e.g., `meals_2024-04-11.csv`)
|
|
1345
|
+
- If no `title`: `data_export_{date}.csv`
|
|
1346
|
+
|
|
1347
|
+
To customize the filename, you can provide a custom `onExport` handler:
|
|
1348
|
+
|
|
1349
|
+
```tsx
|
|
1350
|
+
<DataTable
|
|
1351
|
+
title="Meals"
|
|
1352
|
+
columns={columns}
|
|
1353
|
+
features={{ export: true }}
|
|
1354
|
+
onExport={async () => {
|
|
1355
|
+
// Custom export logic
|
|
1356
|
+
const data = getFilteredData();
|
|
1357
|
+
await exportToCSV(data, columns, 'custom-export-name.csv');
|
|
1358
|
+
}}
|
|
1359
|
+
/>
|
|
1360
|
+
```
|
|
1361
|
+
|
|
1362
|
+
### Export Column Selection
|
|
1363
|
+
|
|
1364
|
+
**Important**: Only **visible columns** are exported. If a column is hidden (via column visibility controls), it will not be included in the export.
|
|
1365
|
+
|
|
1366
|
+
To ensure all columns are exported:
|
|
1367
|
+
- Make sure all desired columns are visible before exporting
|
|
1368
|
+
- Or use a custom `onExport` handler to export specific columns
|
|
1369
|
+
|
|
1370
|
+
### Import Column Mapping
|
|
1371
|
+
|
|
1372
|
+
The import automatically maps CSV columns using this priority:
|
|
1373
|
+
|
|
1374
|
+
1. **Column Header Match** (case-insensitive)
|
|
1375
|
+
- CSV header "Type" → maps to column with `header: "Type"`
|
|
1376
|
+
|
|
1377
|
+
2. **editAccessorKey Match** (for reference fields)
|
|
1378
|
+
- CSV header "Type" or "Type (ID)" → maps to `editAccessorKey` field
|
|
1379
|
+
|
|
1380
|
+
3. **accessorKey/id Match** (fallback)
|
|
1381
|
+
- CSV header matches column `accessorKey` or `id`
|
|
1382
|
+
|
|
1383
|
+
If no match is found, the CSV column name is used as-is (useful for new fields).
|
|
1384
|
+
|
|
1230
1385
|
### Form Integration
|
|
1231
1386
|
|
|
1232
1387
|
```tsx
|
package/package.json
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* - ✅ **Pagination** - Configurable pagination with custom initial page size and page size options
|
|
26
26
|
* - ✅ **Sorting** - Multi-column sorting with visual indicators (↕️ unsorted, ↑ ascending, ↓ descending)
|
|
27
27
|
* - ✅ **Filtering** - Column-specific filtering with multiple input types
|
|
28
|
-
* - ✅ **Export/Import** - CSV
|
|
28
|
+
* - ✅ **Export/Import** - CSV data export/import with automatic column mapping and reference field support
|
|
29
29
|
* - ✅ **Row Selection** - Single and multi-row selection for bulk operations
|
|
30
30
|
* - ✅ **Row Creation** - Add new rows with validation
|
|
31
31
|
* - ✅ **Row Editing** - Inline row editing with input field conversion
|
|
@@ -982,24 +982,51 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
982
982
|
});
|
|
983
983
|
|
|
984
984
|
// Map table columns back to original column definitions for export
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
985
|
+
// Also add ID columns for reference fields (columns with accessorFn + editAccessorKey)
|
|
986
|
+
const visibleColumns: Array<{
|
|
987
|
+
header?: string;
|
|
988
|
+
id?: string;
|
|
989
|
+
accessorKey?: string;
|
|
990
|
+
accessorFn?: (row: any) => any;
|
|
991
|
+
editAccessorKey?: string;
|
|
992
|
+
isIdColumn?: boolean;
|
|
993
|
+
}> = [];
|
|
994
|
+
|
|
995
|
+
visibleTableColumns.forEach(tableCol => {
|
|
996
|
+
// Find the original column definition that matches this table column
|
|
997
|
+
const originalCol = columns.find(col => {
|
|
998
|
+
const colId = col.id || col.accessorKey;
|
|
999
|
+
return colId && String(colId) === tableCol.id;
|
|
1000
|
+
});
|
|
1001
|
+
|
|
1002
|
+
if (!originalCol) return;
|
|
1003
|
+
|
|
1004
|
+
const hasAccessorFn = 'accessorFn' in originalCol && (originalCol as any).accessorFn;
|
|
1005
|
+
const editAccessorKey = originalCol.editAccessorKey;
|
|
1006
|
+
|
|
1007
|
+
// Add the display column (what's shown in the table)
|
|
1008
|
+
visibleColumns.push({
|
|
1009
|
+
...originalCol,
|
|
1010
|
+
header: typeof originalCol.header === 'string'
|
|
1011
|
+
? originalCol.header
|
|
1012
|
+
: originalCol.accessorKey || tableCol.id || 'Column',
|
|
1013
|
+
// Preserve accessorFn if present (for computed/reference fields)
|
|
1014
|
+
// accessorFn is part of ColumnDef from TanStack Table
|
|
1015
|
+
accessorFn: hasAccessorFn ? (originalCol as any).accessorFn : undefined,
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
// For reference fields (accessorFn + editAccessorKey), also add ID column
|
|
1019
|
+
// This allows round-trip import/export to work seamlessly
|
|
1020
|
+
if (hasAccessorFn && editAccessorKey) {
|
|
1021
|
+
visibleColumns.push({
|
|
1022
|
+
id: editAccessorKey,
|
|
1023
|
+
accessorKey: editAccessorKey,
|
|
1024
|
+
header: `${originalCol.header || 'ID'} (ID)`,
|
|
1025
|
+
isIdColumn: true,
|
|
1026
|
+
editAccessorKey: editAccessorKey,
|
|
991
1027
|
});
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
return {
|
|
996
|
-
...originalCol,
|
|
997
|
-
header: typeof originalCol.header === 'string'
|
|
998
|
-
? originalCol.header
|
|
999
|
-
: originalCol.accessorKey || tableCol.id || 'Column',
|
|
1000
|
-
};
|
|
1001
|
-
})
|
|
1002
|
-
.filter((col): col is NonNullable<typeof col> => col !== null);
|
|
1028
|
+
}
|
|
1029
|
+
});
|
|
1003
1030
|
|
|
1004
1031
|
// Generate filename with timestamp
|
|
1005
1032
|
const timestamp = new Date().toISOString().split('T')[0];
|
|
@@ -1249,7 +1276,7 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
1249
1276
|
<DataTableModals
|
|
1250
1277
|
showImportModal={state.showImportModal}
|
|
1251
1278
|
onCloseImportModal={() => stateActions.setImportModal(false)}
|
|
1252
|
-
onImport={async (data) => {
|
|
1279
|
+
onImport={async (data: TData[]) => {
|
|
1253
1280
|
if (onImport) {
|
|
1254
1281
|
const result = onImport(data);
|
|
1255
1282
|
if (result && typeof result.then === 'function') {
|
|
@@ -1259,6 +1286,12 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
1259
1286
|
stateActions.setImportModal(false);
|
|
1260
1287
|
}}
|
|
1261
1288
|
importModalConfig={importModalConfig}
|
|
1289
|
+
columns={columns.map(col => ({
|
|
1290
|
+
id: col.id,
|
|
1291
|
+
accessorKey: col.accessorKey,
|
|
1292
|
+
header: typeof col.header === 'string' ? col.header : undefined,
|
|
1293
|
+
editAccessorKey: col.editAccessorKey,
|
|
1294
|
+
}))}
|
|
1262
1295
|
/>
|
|
1263
1296
|
</>
|
|
1264
1297
|
);
|
|
@@ -24,20 +24,83 @@
|
|
|
24
24
|
import React, { useEffect } from 'react';
|
|
25
25
|
import { ImportModal, type ImportModalConfig } from './ImportModal';
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Maps CSV column data to table column structure
|
|
29
|
+
* Handles reference fields (columns with editAccessorKey) and direct accessorKey columns
|
|
30
|
+
*/
|
|
31
|
+
function mapCSVToTableColumns<TData extends Record<string, unknown> = Record<string, unknown>>(
|
|
32
|
+
csvData: Array<Record<string, unknown>>,
|
|
33
|
+
columns: Array<{
|
|
34
|
+
id?: string;
|
|
35
|
+
accessorKey?: string;
|
|
36
|
+
header?: string;
|
|
37
|
+
editAccessorKey?: string;
|
|
38
|
+
}>
|
|
39
|
+
): TData[] {
|
|
40
|
+
// Create a mapping from CSV headers to table field names
|
|
41
|
+
// Priority: editAccessorKey > accessorKey > id
|
|
42
|
+
const columnMap = new Map<string, string>();
|
|
43
|
+
|
|
44
|
+
columns.forEach(col => {
|
|
45
|
+
const fieldName = col.editAccessorKey || col.accessorKey || col.id;
|
|
46
|
+
const header = typeof col.header === 'string' ? col.header : '';
|
|
47
|
+
|
|
48
|
+
if (fieldName && header) {
|
|
49
|
+
// Map header to field name (case-insensitive)
|
|
50
|
+
columnMap.set(header.toLowerCase(), fieldName);
|
|
51
|
+
// Also map id/accessorKey if different from header
|
|
52
|
+
const colId = col.id || col.accessorKey;
|
|
53
|
+
if (colId && colId !== header && colId !== fieldName) {
|
|
54
|
+
columnMap.set(colId.toLowerCase(), fieldName);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// For reference fields with editAccessorKey, also map the ID column header
|
|
58
|
+
// This handles exports that include both "Type" and "Type (ID)" columns
|
|
59
|
+
if (col.editAccessorKey && header) {
|
|
60
|
+
const idColumnHeader = `${header} (ID)`;
|
|
61
|
+
columnMap.set(idColumnHeader.toLowerCase(), col.editAccessorKey);
|
|
62
|
+
// Also map the editAccessorKey directly (in case CSV uses the field name)
|
|
63
|
+
columnMap.set(col.editAccessorKey.toLowerCase(), col.editAccessorKey);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Transform CSV data using the mapping
|
|
69
|
+
return csvData.map(row => {
|
|
70
|
+
const mappedRow: Record<string, unknown> = {};
|
|
71
|
+
|
|
72
|
+
// For each CSV column, find the corresponding table field
|
|
73
|
+
Object.keys(row).forEach(csvHeader => {
|
|
74
|
+
const csvHeaderLower = csvHeader.toLowerCase();
|
|
75
|
+
const fieldName = columnMap.get(csvHeaderLower) || csvHeaderLower;
|
|
76
|
+
mappedRow[fieldName] = row[csvHeader];
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return mappedRow as TData;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
27
83
|
/**
|
|
28
84
|
* Props interface for the DataTableModals component
|
|
29
85
|
* @public
|
|
30
86
|
*/
|
|
31
|
-
export interface DataTableModalsProps {
|
|
87
|
+
export interface DataTableModalsProps<TData extends Record<string, unknown> = Record<string, unknown>> {
|
|
32
88
|
// Import modal
|
|
33
89
|
/** Whether the import modal is visible */
|
|
34
90
|
showImportModal: boolean;
|
|
35
91
|
/** Callback function when the import modal is closed */
|
|
36
92
|
onCloseImportModal: () => void;
|
|
37
93
|
/** Callback function when data is imported successfully */
|
|
38
|
-
onImport: (data:
|
|
94
|
+
onImport: (data: TData[]) => void | Promise<void>;
|
|
39
95
|
/** Configuration object for customizing import modal text content */
|
|
40
96
|
importModalConfig?: ImportModalConfig;
|
|
97
|
+
/** Column definitions for automatic column mapping during import */
|
|
98
|
+
columns?: Array<{
|
|
99
|
+
id?: string;
|
|
100
|
+
accessorKey?: string;
|
|
101
|
+
header?: string;
|
|
102
|
+
editAccessorKey?: string;
|
|
103
|
+
}>;
|
|
41
104
|
|
|
42
105
|
// Focus management
|
|
43
106
|
/** Function to store focus before opening modal */
|
|
@@ -72,14 +135,15 @@ export interface DataTableModalsProps {
|
|
|
72
135
|
* />
|
|
73
136
|
* ```
|
|
74
137
|
*/
|
|
75
|
-
export function DataTableModals({
|
|
138
|
+
export function DataTableModals<TData extends Record<string, unknown> = Record<string, unknown>>({
|
|
76
139
|
showImportModal,
|
|
77
140
|
onCloseImportModal,
|
|
78
141
|
onImport,
|
|
79
142
|
importModalConfig,
|
|
143
|
+
columns,
|
|
80
144
|
onStoreFocus,
|
|
81
145
|
onRestoreFocus,
|
|
82
|
-
}: DataTableModalsProps) {
|
|
146
|
+
}: DataTableModalsProps<TData>) {
|
|
83
147
|
// Handle focus management for import modal
|
|
84
148
|
useEffect(() => {
|
|
85
149
|
if (showImportModal) {
|
|
@@ -99,7 +163,14 @@ export function DataTableModals({
|
|
|
99
163
|
<ImportModal
|
|
100
164
|
isOpen={showImportModal}
|
|
101
165
|
onClose={onCloseImportModal}
|
|
102
|
-
onImport={
|
|
166
|
+
onImport={async (rawData: Array<Record<string, unknown>>) => {
|
|
167
|
+
// Automatically map CSV columns to table columns based on column definitions
|
|
168
|
+
const mappedData = columns ? mapCSVToTableColumns<TData>(rawData, columns) : (rawData as TData[]);
|
|
169
|
+
const result = onImport(mappedData);
|
|
170
|
+
if (result && typeof result.then === 'function') {
|
|
171
|
+
await result;
|
|
172
|
+
}
|
|
173
|
+
}}
|
|
103
174
|
config={importModalConfig}
|
|
104
175
|
/>
|
|
105
176
|
|