@jmruthers/pace-core 0.5.76 → 0.5.78
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/CHANGELOG.md +8 -0
- package/dist/{RBACService-C4udt_Zp.d.ts → AuthService-Df3IozMG.d.ts} +10 -118
- package/dist/{DataTable-ntgmhO2W.d.ts → DataTable-BE0OXZKQ.d.ts} +9 -2
- package/dist/{DataTable-4GAVPIEG.js → DataTable-ETGVF4Y5.js} +50 -13
- package/dist/{PublicLoadingSpinner-BiNER8F5.d.ts → PublicLoadingSpinner-CnUaz0vG.d.ts} +5 -2
- package/dist/{UnifiedAuthProvider-Bj6YCf7c.d.ts → UnifiedAuthProvider-B391Aqum.d.ts} +42 -45
- package/dist/{UnifiedAuthProvider-3NKDOSOK.js → UnifiedAuthProvider-P5SOJAQ6.js} +4 -5
- package/dist/{api-DDMUKIUD.js → api-KG4A2X7P.js} +9 -3
- package/dist/{audit-6TOCAMKO.js → audit-65VNHEV2.js} +2 -2
- package/dist/{chunk-K34IM5CT.js → chunk-2OGV6IRV.js} +196 -626
- package/dist/chunk-2OGV6IRV.js.map +1 -0
- package/dist/{chunk-NTNILOBC.js → chunk-5BO3MI5Y.js} +4 -4
- package/dist/{chunk-XLZ7U46Z.js → chunk-CVMVPYAL.js} +9 -60
- package/dist/chunk-CVMVPYAL.js.map +1 -0
- package/dist/{chunk-URUTVZ7N.js → chunk-FL4ZCQLD.js} +2 -2
- package/dist/{chunk-LW7MMEAQ.js → chunk-FT2M4R4F.js} +2 -2
- package/dist/{chunk-5BSLGBYI.js → chunk-JCQZ6LA7.js} +2 -8
- package/dist/{chunk-5BSLGBYI.js.map → chunk-JCQZ6LA7.js.map} +1 -1
- package/dist/{chunk-KHJS6VIA.js → chunk-LRQ6RBJC.js} +157 -112
- package/dist/chunk-LRQ6RBJC.js.map +1 -0
- package/dist/{chunk-WN6XJWOS.js → chunk-MNJXXD6C.js} +274 -743
- package/dist/chunk-MNJXXD6C.js.map +1 -0
- package/dist/{chunk-KK73ZB4E.js → chunk-PTR5PMPE.js} +153 -132
- package/dist/chunk-PTR5PMPE.js.map +1 -0
- package/dist/{chunk-B2WTCLCV.js → chunk-Q7APDV6H.js} +18 -8
- package/dist/chunk-Q7APDV6H.js.map +1 -0
- package/dist/{chunk-A4FUBC7B.js → chunk-QGVSOUJ2.js} +2 -4
- package/dist/{chunk-A4FUBC7B.js.map → chunk-QGVSOUJ2.js.map} +1 -1
- package/dist/{chunk-FGMFQSHX.js → chunk-S63MFSY6.js} +500 -551
- package/dist/chunk-S63MFSY6.js.map +1 -0
- package/dist/{chunk-AFGTSUAD.js → chunk-VSOKOFRF.js} +4 -4
- package/dist/chunk-WUXCWRL6.js +20 -0
- package/dist/chunk-WUXCWRL6.js.map +1 -0
- package/dist/{chunk-Y6TXWPJO.js → chunk-YVVGHRGI.js} +105 -31
- package/dist/chunk-YVVGHRGI.js.map +1 -0
- package/dist/{chunk-M5IWZRBT.js → chunk-ZMNXIJP4.js} +2187 -981
- package/dist/chunk-ZMNXIJP4.js.map +1 -0
- package/dist/components.d.ts +6 -6
- package/dist/components.js +14 -18
- package/dist/components.js.map +1 -1
- package/dist/{database-C3Szpi5J.d.ts → database-BXAfr2Y_.d.ts} +18 -0
- package/dist/hooks.d.ts +5 -5
- package/dist/hooks.js +8 -9
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +19 -27
- package/dist/index.js +21 -29
- package/dist/index.js.map +1 -1
- package/dist/{organisation-BtshODVF.d.ts → organisation-D6qRDtbF.d.ts} +1 -1
- package/dist/providers.d.ts +7 -21
- package/dist/providers.js +3 -10
- package/dist/rbac/index.d.ts +71 -221
- package/dist/rbac/index.js +15 -16
- package/dist/{types-CGX9Vyf5.d.ts → types-BDg1mAGG.d.ts} +36 -6
- package/dist/types.d.ts +3 -3
- package/dist/types.js +61 -18
- package/dist/types.js.map +1 -1
- package/dist/{unified-CM7T0aTK.d.ts → unified-DQ4VcT7H.d.ts} +1 -1
- package/dist/{usePublicRouteParams-B-CumWRc.d.ts → usePublicRouteParams-BlgwXweB.d.ts} +3 -3
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +52 -9
- package/dist/utils.js.map +1 -1
- package/docs/CONTENT_AUDIT_REPORT.md +253 -0
- package/docs/DOCUMENTATION_AUDIT.md +172 -0
- package/docs/README.md +142 -147
- package/docs/STYLE_GUIDE.md +37 -0
- package/docs/api/classes/ColumnFactory.md +17 -17
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +4 -4
- package/docs/api/classes/MissingUserContextError.md +4 -4
- package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
- package/docs/api/classes/PermissionDeniedError.md +5 -5
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +8 -8
- package/docs/api/classes/RBACCache.md +35 -5
- package/docs/api/classes/RBACEngine.md +49 -20
- package/docs/api/classes/RBACError.md +4 -4
- package/docs/api/classes/RBACNotInitializedError.md +4 -4
- 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 +4 -4
- 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 +11 -0
- package/docs/api/interfaces/DataTableAction.md +65 -29
- package/docs/api/interfaces/DataTableColumn.md +36 -23
- package/docs/api/interfaces/DataTableProps.md +80 -38
- package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
- package/docs/api/interfaces/EmptyStateConfig.md +5 -5
- 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 +11 -11
- package/docs/api/interfaces/NavigationContextType.md +9 -9
- 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 +7 -7
- 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 +16 -3
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +2 -2
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
- 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 +2 -2
- package/docs/api/interfaces/RouteConfig.md +2 -2
- 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 +94 -521
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +16 -16
- 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/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 +11 -11
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +251 -269
- package/docs/api-reference/components.md +193 -0
- package/docs/api-reference/hooks.md +265 -0
- package/docs/api-reference/providers.md +6 -0
- package/docs/api-reference/types.md +6 -0
- package/docs/api-reference/utilities.md +207 -0
- package/docs/architecture/README.md +6 -0
- package/docs/{database-schema-requirements.md → architecture/database-schema-requirements.md} +6 -0
- package/docs/architecture/rbac-security-architecture.md +258 -0
- package/docs/architecture/services.md +9 -1
- package/docs/best-practices/README.md +6 -0
- package/docs/best-practices/accessibility.md +6 -0
- package/docs/{common-patterns.md → best-practices/common-patterns.md} +6 -0
- package/docs/best-practices/deployment.md +6 -0
- package/docs/best-practices/performance.md +475 -2
- package/docs/best-practices/security.md +6 -0
- package/docs/best-practices/testing.md +6 -0
- package/docs/core-concepts/authentication.md +6 -0
- package/docs/core-concepts/events.md +6 -0
- package/docs/core-concepts/organisations.md +6 -0
- package/docs/core-concepts/permissions.md +6 -0
- package/docs/core-concepts/rbac-system.md +8 -0
- package/docs/documentation-index.md +121 -182
- package/docs/{consuming-app-vite-config.md → getting-started/consuming-app-vite-config.md} +6 -0
- package/docs/getting-started/documentation-index.md +40 -0
- package/docs/getting-started/examples/README.md +878 -35
- package/docs/{faq.md → getting-started/faq.md} +7 -1
- package/docs/getting-started/installation-guide.md +6 -0
- package/docs/{quick-reference.md → getting-started/quick-reference.md} +6 -0
- package/docs/implementation-guides/app-layout.md +6 -0
- package/docs/implementation-guides/authentication.md +1021 -0
- package/docs/implementation-guides/component-styling.md +6 -0
- package/docs/implementation-guides/data-tables.md +1264 -2076
- package/docs/implementation-guides/dynamic-colors.md +6 -0
- package/docs/implementation-guides/event-theming-summary.md +6 -0
- package/docs/{file-reference-system.md → implementation-guides/file-reference-system.md} +6 -0
- package/docs/implementation-guides/file-upload-storage.md +6 -0
- package/docs/implementation-guides/forms.md +6 -0
- package/docs/implementation-guides/inactivity-tracking.md +6 -0
- package/docs/implementation-guides/navigation.md +6 -0
- package/docs/implementation-guides/organisation-security.md +6 -0
- package/docs/implementation-guides/permission-enforcement.md +6 -0
- package/docs/implementation-guides/public-pages-advanced.md +6 -0
- package/docs/implementation-guides/public-pages.md +6 -0
- package/docs/migration/MIGRATION_GUIDE.md +827 -351
- package/docs/migration/README.md +7 -1
- package/docs/migration/organisation-context-timing-fix.md +6 -0
- package/docs/migration/rbac-migration.md +44 -1
- package/docs/migration/service-architecture.md +6 -0
- package/docs/migration/v0.4.15-tailwind-scanning.md +6 -0
- package/docs/migration/v0.4.16-css-first-approach.md +6 -0
- package/docs/migration/v0.4.17-source-path-fix.md +6 -0
- package/docs/rbac/README-rbac-rls-integration.md +6 -0
- package/docs/rbac/README.md +6 -0
- package/docs/rbac/advanced-patterns.md +6 -0
- package/docs/rbac/api-reference.md +7 -1
- package/docs/rbac/breaking-changes-v3.md +222 -0
- package/docs/rbac/examples/rbac-rls-integration-example.md +6 -0
- package/docs/rbac/examples.md +6 -0
- package/docs/rbac/getting-started.md +6 -0
- package/docs/rbac/migration-guide.md +260 -0
- package/docs/rbac/quick-start.md +70 -13
- package/docs/rbac/rbac-rls-integration.md +6 -0
- package/docs/rbac/super-admin-guide.md +6 -0
- package/docs/rbac/troubleshooting.md +6 -0
- package/docs/security/README.md +6 -0
- package/docs/security/checklist.md +6 -0
- package/docs/styles/README.md +7 -1
- package/docs/{usage.md → styles/usage.md} +6 -0
- package/docs/testing/README.md +6 -0
- package/docs/{visual-testing.md → testing/visual-testing.md} +6 -0
- package/docs/troubleshooting/README.md +387 -5
- package/docs/troubleshooting/cake-page-permission-guard-issue-summary.md +6 -0
- package/docs/troubleshooting/common-issues.md +6 -0
- package/docs/troubleshooting/database-view-compatibility.md +6 -0
- package/docs/troubleshooting/organisation-context-setup.md +6 -0
- package/docs/troubleshooting/react-hooks-issue-analysis.md +6 -0
- package/docs/troubleshooting/styling-issues.md +6 -0
- package/docs/troubleshooting/tailwind-content-scanning.md +6 -0
- package/package.json +1 -1
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -1
- package/src/__tests__/helpers/test-providers.tsx +3 -53
- package/src/components/DataTable/DataTable.test.tsx +319 -0
- package/src/components/DataTable/DataTable.tsx +32 -11
- package/src/components/DataTable/__tests__/{DataTable.comprehensive.test.tsx → DataTable.comprehensive.test.tsx.skip} +6 -4
- package/src/components/DataTable/__tests__/{DataTable.test.tsx → DataTable.test.tsx.skip} +6 -4
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +31 -9
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +601 -0
- package/src/components/DataTable/__tests__/keyboard.test.tsx +615 -0
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +639 -0
- package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx.skip +330 -0
- package/src/components/DataTable/components/AccessDeniedPage.tsx +2 -2
- package/src/components/DataTable/components/ActionButtons.tsx +88 -104
- package/src/components/DataTable/components/DataTableCore.tsx +309 -337
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +4 -2
- package/src/components/DataTable/components/DataTableModals.tsx +22 -1
- package/src/components/DataTable/components/EditableRow.tsx +69 -84
- package/src/components/DataTable/components/EmptyState.tsx +5 -1
- package/src/components/DataTable/components/ImportModal.tsx +65 -36
- package/src/components/DataTable/components/PaginationControls.tsx +40 -100
- package/src/components/DataTable/components/UnifiedTableBody.tsx +125 -148
- package/src/components/DataTable/context/DataTableContext.tsx +1 -1
- package/src/components/DataTable/core/ColumnFactory.ts +5 -0
- package/src/components/DataTable/examples/HierarchicalActionsExample.tsx +12 -10
- package/src/components/DataTable/examples/HierarchicalExample.tsx +1 -1
- package/src/components/DataTable/examples/InitialPageSizeExample.tsx +1 -0
- package/src/components/DataTable/examples/PerformanceExample.tsx +1 -0
- package/src/components/DataTable/hooks/__tests__/useColumnOrderPersistence.test.ts +1 -5
- package/src/components/DataTable/hooks/__tests__/useColumnVisibilityPersistence.test.ts +167 -0
- package/src/components/DataTable/hooks/index.ts +7 -0
- package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +32 -15
- package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +102 -0
- package/src/components/DataTable/hooks/useDataTableConfiguration.ts +89 -0
- package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +117 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +71 -27
- package/src/components/DataTable/hooks/useDataTableState.ts +39 -11
- package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +33 -0
- package/src/components/DataTable/hooks/useHierarchicalState.ts +15 -1
- package/src/components/DataTable/hooks/useKeyboardNavigation.ts +447 -0
- package/src/components/DataTable/hooks/useServerSideDataEffect.ts +94 -0
- package/src/components/DataTable/hooks/useTableColumns.ts +10 -7
- package/src/components/DataTable/hooks/useTableHandlers.ts +174 -0
- package/src/components/DataTable/index.ts +12 -3
- package/src/components/DataTable/types.ts +129 -9
- package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +159 -22
- package/src/components/DataTable/utils/__tests__/flexibleImport.test.ts +111 -0
- package/src/components/DataTable/utils/__tests__/rowUtils.test.ts +15 -29
- package/src/components/DataTable/utils/a11yUtils.ts +244 -0
- package/src/components/DataTable/utils/debugTools.ts +609 -0
- package/src/components/DataTable/utils/exportUtils.ts +114 -16
- package/src/components/DataTable/utils/flexibleImport.ts +202 -32
- package/src/components/DataTable/utils/hierarchicalUtils.ts +1 -1
- package/src/components/DataTable/utils/index.ts +2 -0
- package/src/components/DataTable/utils/paginationUtils.ts +350 -0
- package/src/components/DataTable/utils/rowUtils.ts +6 -5
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +19 -24
- package/src/components/NavigationMenu/NavigationMenu.tsx +19 -8
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +1 -23
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +56 -6
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +137 -13
- package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +1 -1
- package/src/components/Select/Select.tsx +1 -0
- package/src/components/examples/PermissionExample.tsx +173 -0
- package/src/examples/CorrectPublicPageImplementation.tsx +301 -0
- package/src/examples/PublicEventPage.tsx +274 -0
- package/src/examples/PublicPageApp.tsx +308 -0
- package/src/examples/PublicPageUsageExample.tsx +216 -0
- package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +12 -1
- package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +129 -17
- package/src/hooks/__tests__/useRBAC.unit.test.ts +151 -846
- package/src/hooks/useOrganisationPermissions.test.ts +42 -18
- package/src/hooks/useOrganisationPermissions.ts +12 -6
- package/src/hooks/useOrganisationSecurity.test.ts +138 -85
- package/src/hooks/useOrganisationSecurity.ts +41 -10
- package/src/index.ts +0 -1
- package/src/providers/AuthProvider.simplified.tsx +880 -0
- package/src/providers/UnifiedAuthProvider.test.simple.tsx +8 -8
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +29 -19
- package/src/providers/index.ts +0 -1
- package/src/providers/services/EventServiceProvider.tsx +19 -15
- package/src/providers/services/InactivityServiceProvider.tsx +19 -15
- package/src/providers/services/OrganisationServiceProvider.tsx +19 -15
- package/src/providers/services/UnifiedAuthProvider.tsx +156 -127
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +1 -1
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -3
- package/src/rbac/README.md +1 -1
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +25 -27
- package/src/rbac/__tests__/auth-rbac-security.integration.test.tsx +313 -0
- package/src/rbac/__tests__/engine.comprehensive.test.ts +114 -348
- package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +28 -110
- package/src/rbac/__tests__/rbac-engine-simplified.test.ts +33 -85
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +2 -2
- package/src/rbac/adapters.tsx +26 -69
- package/src/rbac/api.test.ts +90 -27
- package/src/rbac/api.ts +61 -10
- package/src/rbac/audit.test.ts +33 -38
- package/src/rbac/audit.ts +21 -6
- package/src/rbac/cache.ts +33 -1
- package/src/rbac/components/NavigationGuard.tsx +11 -11
- package/src/rbac/components/NavigationProvider.test.tsx +11 -5
- package/src/rbac/components/NavigationProvider.tsx +37 -13
- package/src/rbac/components/PagePermissionGuard.tsx +111 -50
- package/src/rbac/components/PagePermissionProvider.tsx +5 -5
- package/src/rbac/components/PermissionEnforcer.tsx +11 -11
- package/src/rbac/components/RoleBasedRouter.tsx +5 -5
- package/src/rbac/components/SecureDataProvider.tsx +5 -5
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +8 -8
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +14 -14
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +12 -12
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +6 -6
- package/src/rbac/engine.test.simple.ts +19 -13
- package/src/rbac/engine.test.ts +1 -0
- package/src/rbac/engine.ts +330 -766
- package/src/rbac/errors.ts +156 -0
- package/src/rbac/hooks/usePermissions.ts +32 -10
- package/src/rbac/hooks/useRBAC.test.ts +126 -512
- package/src/rbac/hooks/useRBAC.ts +147 -193
- package/src/rbac/hooks/useResolvedScope.ts +12 -0
- package/src/rbac/index.ts +7 -4
- package/src/rbac/security.ts +109 -18
- package/src/rbac/types.ts +12 -1
- package/src/services/AuthService.ts +2 -15
- package/src/services/EventService.ts +43 -46
- package/src/services/OrganisationService.ts +51 -31
- package/src/services/__tests__/AuthService.test.ts +1 -1
- package/src/services/__tests__/EventService.test.ts +1 -1
- package/src/services/__tests__/OrganisationService.test.ts +1 -1
- package/src/services/base/BaseService.ts +8 -0
- package/src/styles/base.css +208 -0
- package/src/styles/semantic.css +24 -0
- package/src/types/database.generated.ts +7347 -0
- package/src/types/database.ts +20 -0
- package/src/utils/logger.ts +179 -0
- package/src/utils/organisationContext.ts +11 -4
- package/src/utils/storage/__tests__/helpers.unit.test.ts +6 -2
- package/dist/appNameResolver-UURKN7NF.js +0 -22
- package/dist/audit-6TOCAMKO.js.map +0 -1
- package/dist/chunk-B2WTCLCV.js.map +0 -1
- package/dist/chunk-FGMFQSHX.js.map +0 -1
- package/dist/chunk-K34IM5CT.js.map +0 -1
- package/dist/chunk-KHJS6VIA.js.map +0 -1
- package/dist/chunk-KK73ZB4E.js.map +0 -1
- package/dist/chunk-M5IWZRBT.js.map +0 -1
- package/dist/chunk-ULBI5JGB.js +0 -109
- package/dist/chunk-ULBI5JGB.js.map +0 -1
- package/dist/chunk-WN6XJWOS.js.map +0 -1
- package/dist/chunk-XLZ7U46Z.js.map +0 -1
- package/dist/chunk-Y6TXWPJO.js.map +0 -1
- package/docs/DOCUMENTATION_CHECKLIST.md +0 -281
- package/docs/TERMINOLOGY.md +0 -231
- package/docs/api/interfaces/RBACContextType.md +0 -468
- package/docs/api/interfaces/RBACProviderProps.md +0 -107
- package/docs/best-practices/performance-expansion.md +0 -473
- package/docs/breaking-changes.md +0 -179
- package/docs/consuming-app-example.md +0 -290
- package/docs/documentation-templates.md +0 -539
- package/docs/examples/navigation-menu-auth-fix.md +0 -344
- package/docs/getting-started/examples/basic-auth-app.md +0 -520
- package/docs/getting-started/examples/full-featured-app.md +0 -616
- package/docs/getting-started/quick-start.md +0 -376
- package/docs/implementation-guides/datatable-filtering.md +0 -313
- package/docs/implementation-guides/datatable-rbac-usage.md +0 -317
- package/docs/implementation-guides/hierarchical-datatable.md +0 -850
- package/docs/implementation-guides/large-datasets.md +0 -281
- package/docs/implementation-guides/performance.md +0 -403
- package/docs/migration/quick-migration-guide.md +0 -320
- package/docs/migration-guide.md +0 -193
- package/docs/migration-guides/unified-auth-provider-mandatory-timeouts.md +0 -226
- package/docs/performance/README.md +0 -551
- package/docs/style-guide.md +0 -964
- package/docs/troubleshooting/authentication-issues.md +0 -334
- package/docs/troubleshooting/debugging.md +0 -1117
- package/docs/troubleshooting/migration.md +0 -918
- package/src/__tests__/hooks/usePermissions.test.ts +0 -261
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +0 -574
- package/src/hooks/__tests__/ServiceHooks.test.tsx +0 -613
- package/src/hooks/services/__tests__/useServiceHooks.test.tsx +0 -137
- package/src/hooks/services/usePermissions.ts +0 -70
- package/src/hooks/services/useRBACService.ts +0 -30
- package/src/hooks/usePermissionCheck.ts +0 -150
- package/src/providers/__tests__/ServiceProviders.test.tsx +0 -477
- package/src/providers/services/RBACServiceProvider.tsx +0 -79
- package/src/rbac/__tests__/integration.authflow.test.tsx +0 -119
- package/src/rbac/__tests__/integration.navigation.test.tsx +0 -69
- package/src/rbac/__tests__/integration.securedata.test.tsx +0 -92
- package/src/rbac/__tests__/integration.smoke.test.tsx +0 -73
- package/src/rbac/providers/RBACProvider.tsx +0 -645
- package/src/rbac/providers/__tests__/RBACProvider.integration.test.tsx +0 -688
- package/src/rbac/providers/__tests__/RBACProvider.test.tsx +0 -1186
- package/src/rbac/providers/index.ts +0 -11
- package/src/services/RBACService.ts +0 -522
- package/src/services/__tests__/RBACService.test.ts +0 -492
- package/src/services/interfaces/IRBACService.ts +0 -62
- package/src/utils/appNameResolver.test 2.ts +0 -494
- /package/dist/{DataTable-4GAVPIEG.js.map → DataTable-ETGVF4Y5.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-3NKDOSOK.js.map → UnifiedAuthProvider-P5SOJAQ6.js.map} +0 -0
- /package/dist/{api-DDMUKIUD.js.map → api-KG4A2X7P.js.map} +0 -0
- /package/dist/{appNameResolver-UURKN7NF.js.map → audit-65VNHEV2.js.map} +0 -0
- /package/dist/{chunk-NTNILOBC.js.map → chunk-5BO3MI5Y.js.map} +0 -0
- /package/dist/{chunk-URUTVZ7N.js.map → chunk-FL4ZCQLD.js.map} +0 -0
- /package/dist/{chunk-LW7MMEAQ.js.map → chunk-FT2M4R4F.js.map} +0 -0
- /package/dist/{chunk-AFGTSUAD.js.map → chunk-VSOKOFRF.js.map} +0 -0
- /package/docs/{app.css.example → styles/app.css.example} +0 -0
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
---
|
|
2
|
+
lastUpdated: 2025-10-29T22:43:00+11:00
|
|
3
|
+
version: 0.5.76
|
|
4
|
+
reviewedBy: content-audit
|
|
5
|
+
---
|
|
2
6
|
|
|
3
|
-
|
|
7
|
+
# DataTable Implementation Guide
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
> **📚 Complete Implementation Guide**: Data Management | [← Back](../README.md) | [Forms](./forms.md)
|
|
10
|
+
|
|
11
|
+
This comprehensive guide covers implementing data tables with PACE Core's DataTable component, including basic usage, advanced features, RBAC integration, hierarchical data, filtering, performance optimization, and best practices.
|
|
12
|
+
|
|
13
|
+
> **📚 API Reference**: See [DataTable Component API](../api-reference/components.md#datatable) for complete prop documentation.
|
|
6
14
|
|
|
7
15
|
## Overview
|
|
8
16
|
|
|
@@ -10,11 +18,12 @@ The PACE Core DataTable is an enterprise-grade data table component built on Tan
|
|
|
10
18
|
|
|
11
19
|
- **🚀 Performance Optimized** - Virtual scrolling, intelligent chunking, and automatic mode detection
|
|
12
20
|
- **📊 Data Management** - Sorting, filtering, pagination, search, **automatic export**/import
|
|
13
|
-
- **🌳 Hierarchical Rows** - Parent/child row relationships with expand/collapse all and smart sorting
|
|
21
|
+
- **🌳 Hierarchical Rows** - Parent/child row relationships with expand/collapse all and smart sorting
|
|
14
22
|
- **✏️ Inline Editing** - Row editing, creation, and deletion
|
|
15
23
|
- **🎯 Actions** - Custom row actions and toolbar buttons
|
|
16
24
|
- **📈 Grouping** - Data grouping with aggregation functions
|
|
17
25
|
- **📏 Auto Column Sizing** - Automatic column width adjustment based on content
|
|
26
|
+
- **🔐 RBAC Integration** - Page-based permission control
|
|
18
27
|
- **🎨 Customizable** - Column visibility, responsive design, accessibility
|
|
19
28
|
- **🔧 TypeScript** - Full TypeScript support with strict typing
|
|
20
29
|
|
|
@@ -89,2303 +98,1237 @@ function UserTable() {
|
|
|
89
98
|
pagination: true,
|
|
90
99
|
sorting: true,
|
|
91
100
|
filtering: true,
|
|
101
|
+
creation: true,
|
|
102
|
+
editing: true,
|
|
103
|
+
deletion: true,
|
|
104
|
+
export: true,
|
|
105
|
+
import: true,
|
|
92
106
|
}}
|
|
93
|
-
|
|
94
|
-
// Performance and other settings can be configured via performance prop
|
|
107
|
+
getRowId={(row) => row.id}
|
|
95
108
|
/>
|
|
96
109
|
);
|
|
97
110
|
}
|
|
98
111
|
```
|
|
99
112
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
```tsx
|
|
103
|
-
import { DataTable, PagePermissionGuard, PAGE_IDS } from '@jmruthers/pace-core';
|
|
104
|
-
|
|
105
|
-
function UsersPage() {
|
|
106
|
-
return (
|
|
107
|
-
<PagePermissionGuard pageId={PAGE_IDS.USER_MANAGEMENT}>
|
|
108
|
-
{(permissions) => (
|
|
109
|
-
<DataTable
|
|
110
|
-
data={users}
|
|
111
|
-
columns={columns}
|
|
112
|
-
rbac={{
|
|
113
|
-
resource: 'users',
|
|
114
|
-
pageId: 'user-management'
|
|
115
|
-
}}
|
|
116
|
-
title="User Management"
|
|
117
|
-
description="Manage system users"
|
|
118
|
-
features={{
|
|
119
|
-
search: true,
|
|
120
|
-
pagination: true,
|
|
121
|
-
creation: permissions.canCreate,
|
|
122
|
-
editing: permissions.canUpdate,
|
|
123
|
-
deletion: permissions.canDelete,
|
|
124
|
-
}}
|
|
125
|
-
onEditRow={permissions.canUpdate ? (row) => navigate(`/users/${row.id}/edit`) : undefined}
|
|
126
|
-
onDeleteRow={permissions.canDelete ? handleDeleteUser : undefined}
|
|
127
|
-
// Custom actions can be added via the deprecated actions prop
|
|
128
|
-
actions={
|
|
129
|
-
permissions.canUpdate || permissions.canDelete ? [
|
|
130
|
-
...(permissions.canUpdate ? [{
|
|
131
|
-
label: 'View Details',
|
|
132
|
-
onClick: (row) => navigate(`/users/${row.id}`),
|
|
133
|
-
}] : [])
|
|
134
|
-
] : []
|
|
135
|
-
}
|
|
136
|
-
/>
|
|
137
|
-
)}
|
|
138
|
-
</PagePermissionGuard>
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
## DataTable Props Interface
|
|
144
|
-
|
|
145
|
-
The DataTable component has a comprehensive prop interface. Here are the key properties:
|
|
146
|
-
|
|
147
|
-
### Core Props
|
|
148
|
-
- `data: TData[]` - Array of data records to display
|
|
149
|
-
- `columns: any[]` - Column definitions (supports TanStack Table format)
|
|
150
|
-
- `features: DataTableFeatureConfig` - **Required** - Feature configuration object
|
|
151
|
-
|
|
152
|
-
### Display Props
|
|
153
|
-
- `title?: string` - Table title
|
|
154
|
-
- `description?: string` - Table description
|
|
155
|
-
- `variant?: 'default' | 'compact' | 'spacious'` - Visual variant
|
|
156
|
-
- `className?: string` - Additional CSS classes
|
|
157
|
-
- `columnOrder?: string[]` - **Column ordering** - Array of column IDs in desired order
|
|
158
|
-
|
|
159
|
-
### Event Handlers
|
|
160
|
-
|
|
161
|
-
⚠️ **Critical**: DataTable is a controlled component. You must update your local state after CRUD operations to keep the UI in sync.
|
|
162
|
-
|
|
163
|
-
- `onEditRow?: (row: TData, data: Partial<TData>) => void` - Row edit handler
|
|
164
|
-
- `onDeleteRow?: (row: TData) => void` - Row deletion handler (also used as fallback for bulk delete)
|
|
165
|
-
- `onCreateRow?: (data: Partial<TData>) => void` - Row creation handler
|
|
166
|
-
- `onImport?: (data: TData[]) => void | Promise<void>` - Import handler
|
|
167
|
-
- `onExport?: () => void` - **Optional**: Custom export handler. If not provided, exports automatically with current table filters and visible columns.
|
|
168
|
-
- `onRowSelectionChange?: (selection: Record<string, boolean>) => void` - Row selection change handler
|
|
169
|
-
- `onDeleteSelected?: (selectedRows: Record<string, boolean>) => void` - **Bulk delete handler** - Called when delete selected button is clicked
|
|
170
|
-
|
|
171
|
-
### Performance Props
|
|
172
|
-
- `performance?: PerformanceConfig` - Performance optimization settings
|
|
173
|
-
- `serverSide?: ServerSideConfig<TData>` - Server-side data fetching
|
|
174
|
-
- `virtualHeight?: number` - Virtual scrolling height (default: 600)
|
|
175
|
-
- `showPerformanceMetrics?: boolean` - Show performance metrics
|
|
176
|
-
- `initialPageSize?: number` - Initial page size for pagination (default: 10)
|
|
113
|
+
## RBAC Integration
|
|
177
114
|
|
|
178
|
-
###
|
|
179
|
-
- `bulkOperationsConfig?: BulkOperationsConfig` - Bulk operations config
|
|
115
|
+
### Page-Based Permissions (Recommended)
|
|
180
116
|
|
|
181
|
-
|
|
182
|
-
- `actions?: any[]` - Custom row actions for each row
|
|
117
|
+
The DataTable uses page-based RBAC permissions instead of resource-based permissions. This ensures that DataTable permissions align with your application's page-level permission system.
|
|
183
118
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
### Understanding Controlled Components
|
|
187
|
-
|
|
188
|
-
The DataTable is a **controlled component** that displays whatever data you pass to it. When implementing CRUD operations, you must manage both the database operations AND the local state updates to keep the UI in sync.
|
|
189
|
-
|
|
190
|
-
**Two-step process for all CRUD operations:**
|
|
191
|
-
1. **Perform the database operation** (create, update, delete)
|
|
192
|
-
2. **Update your local state** to reflect the changes
|
|
119
|
+
#### Basic Setup
|
|
193
120
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
#### State Management Setup
|
|
197
|
-
|
|
198
|
-
```typescript
|
|
199
|
-
import { useState, useEffect } from 'react';
|
|
121
|
+
```tsx
|
|
200
122
|
import { DataTable } from '@jmruthers/pace-core';
|
|
201
123
|
|
|
202
|
-
function
|
|
203
|
-
const
|
|
204
|
-
|
|
124
|
+
function DishesPage() {
|
|
125
|
+
const dishes = [
|
|
126
|
+
{ id: '1', name: 'Pasta', category: 'Main' },
|
|
127
|
+
{ id: '2', name: 'Salad', category: 'Side' }
|
|
128
|
+
];
|
|
205
129
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
130
|
+
const columns = [
|
|
131
|
+
{ accessorKey: 'id', header: 'ID' },
|
|
132
|
+
{ accessorKey: 'name', header: 'Name' },
|
|
133
|
+
{ accessorKey: 'category', header: 'Category' }
|
|
134
|
+
];
|
|
210
135
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
console.log('Meal created successfully');
|
|
233
|
-
} catch (error) {
|
|
234
|
-
console.error('Failed to create meal:', error);
|
|
235
|
-
// Show error message to user
|
|
236
|
-
}
|
|
237
|
-
};
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
#### Update Operation
|
|
241
|
-
|
|
242
|
-
```typescript
|
|
243
|
-
const handleEditRow = async (row: Meal, data: Partial<Meal>) => {
|
|
244
|
-
try {
|
|
245
|
-
// 1. Update in database
|
|
246
|
-
const { error } = await supabase
|
|
247
|
-
.from('meals')
|
|
248
|
-
.update(data)
|
|
249
|
-
.eq('meal_id', row.meal_id);
|
|
250
|
-
|
|
251
|
-
if (error) throw error;
|
|
252
|
-
|
|
253
|
-
// 2. Update local state - REPLACE the updated row
|
|
254
|
-
setMeals(prevMeals =>
|
|
255
|
-
prevMeals.map(meal =>
|
|
256
|
-
meal.meal_id === row.meal_id
|
|
257
|
-
? { ...meal, ...data }
|
|
258
|
-
: meal
|
|
259
|
-
)
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
console.log('Meal updated successfully');
|
|
263
|
-
} catch (error) {
|
|
264
|
-
console.error('Failed to update meal:', error);
|
|
265
|
-
// Show error message to user
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
```
|
|
269
|
-
|
|
270
|
-
#### Delete Operation
|
|
271
|
-
|
|
272
|
-
```typescript
|
|
273
|
-
const handleDeleteRow = async (row: Meal) => {
|
|
274
|
-
try {
|
|
275
|
-
// 1. Delete from database
|
|
276
|
-
const { error } = await supabase
|
|
277
|
-
.from('meals')
|
|
278
|
-
.delete()
|
|
279
|
-
.eq('meal_id', row.meal_id);
|
|
280
|
-
|
|
281
|
-
if (error) throw error;
|
|
282
|
-
|
|
283
|
-
// 2. Update local state - REMOVE the deleted row
|
|
284
|
-
setMeals(prevMeals =>
|
|
285
|
-
prevMeals.filter(meal => meal.meal_id !== row.meal_id)
|
|
286
|
-
);
|
|
287
|
-
|
|
288
|
-
console.log('Meal deleted successfully');
|
|
289
|
-
} catch (error) {
|
|
290
|
-
console.error('Failed to delete meal:', error);
|
|
291
|
-
// Show error message to user
|
|
292
|
-
}
|
|
293
|
-
};
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
#### Bulk Delete Operation
|
|
297
|
-
|
|
298
|
-
```typescript
|
|
299
|
-
const handleDeleteSelected = async (selectedRows: Record<string, boolean>) => {
|
|
300
|
-
try {
|
|
301
|
-
const selectedIds = Object.keys(selectedRows).filter(id => selectedRows[id]);
|
|
302
|
-
|
|
303
|
-
if (selectedIds.length === 0) return;
|
|
304
|
-
|
|
305
|
-
// 1. Delete from database
|
|
306
|
-
const { error } = await supabase
|
|
307
|
-
.from('meals')
|
|
308
|
-
.delete()
|
|
309
|
-
.in('meal_id', selectedIds);
|
|
310
|
-
|
|
311
|
-
if (error) throw error;
|
|
312
|
-
|
|
313
|
-
// 2. Update local state - REMOVE all selected rows
|
|
314
|
-
setMeals(prevMeals =>
|
|
315
|
-
prevMeals.filter(meal => !selectedIds.includes(meal.meal_id))
|
|
316
|
-
);
|
|
317
|
-
|
|
318
|
-
// 3. Clear selection
|
|
319
|
-
setSelectedRows({});
|
|
320
|
-
|
|
321
|
-
console.log(`${selectedIds.length} meals deleted successfully`);
|
|
322
|
-
} catch (error) {
|
|
323
|
-
console.error('Failed to delete selected meals:', error);
|
|
324
|
-
// Show error message to user
|
|
325
|
-
}
|
|
326
|
-
};
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
### Common Patterns
|
|
330
|
-
|
|
331
|
-
#### Optimistic Updates
|
|
332
|
-
|
|
333
|
-
For better UX, update the UI immediately and rollback on error:
|
|
334
|
-
|
|
335
|
-
```typescript
|
|
336
|
-
const handleDeleteRow = async (row: Meal) => {
|
|
337
|
-
// 1. Optimistically update UI first
|
|
338
|
-
const originalMeals = meals;
|
|
339
|
-
setMeals(prevMeals =>
|
|
340
|
-
prevMeals.filter(meal => meal.meal_id !== row.meal_id)
|
|
136
|
+
return (
|
|
137
|
+
<DataTable
|
|
138
|
+
data={dishes}
|
|
139
|
+
columns={columns}
|
|
140
|
+
rbac={{
|
|
141
|
+
pageName: 'dishes' // Page name for permissions (recommended)
|
|
142
|
+
}}
|
|
143
|
+
features={{
|
|
144
|
+
search: true,
|
|
145
|
+
pagination: true,
|
|
146
|
+
sorting: true,
|
|
147
|
+
filtering: true,
|
|
148
|
+
creation: true, // Will be controlled by create:page.dishes permission
|
|
149
|
+
editing: true, // Will be controlled by update:page.dishes permission
|
|
150
|
+
deletion: true, // Will be controlled by delete:page.dishes permission
|
|
151
|
+
export: true, // Will be controlled by read:page.dishes permission
|
|
152
|
+
import: true // Will be controlled by create:page.dishes permission
|
|
153
|
+
}}
|
|
154
|
+
getRowId={(row) => row.id}
|
|
155
|
+
/>
|
|
341
156
|
);
|
|
342
|
-
|
|
343
|
-
try {
|
|
344
|
-
// 2. Delete from database
|
|
345
|
-
const { error } = await supabase
|
|
346
|
-
.from('meals')
|
|
347
|
-
.delete()
|
|
348
|
-
.eq('meal_id', row.meal_id);
|
|
349
|
-
|
|
350
|
-
if (error) throw error;
|
|
351
|
-
|
|
352
|
-
console.log('Meal deleted successfully');
|
|
353
|
-
} catch (error) {
|
|
354
|
-
// 3. Rollback on error
|
|
355
|
-
setMeals(originalMeals);
|
|
356
|
-
console.error('Failed to delete meal:', error);
|
|
357
|
-
// Show error message to user
|
|
358
|
-
}
|
|
359
|
-
};
|
|
157
|
+
}
|
|
360
158
|
```
|
|
361
159
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
The DataTable component uses a unified `features` prop to control all table functionality. This provides a consistent and intuitive way to enable or disable features.
|
|
365
|
-
|
|
366
|
-
### DataTableFeatureConfig Properties
|
|
367
|
-
|
|
368
|
-
| Property | Type | Default | Description |
|
|
369
|
-
|----------|------|---------|-------------|
|
|
370
|
-
| **Core Features** |
|
|
371
|
-
| `search` | `boolean` | `false` | Enable global search functionality |
|
|
372
|
-
| `pagination` | `boolean` | `false` | Enable pagination controls |
|
|
373
|
-
| `sorting` | `boolean` | `false` | Enable column sorting |
|
|
374
|
-
| `filtering` | `boolean` | `false` | Enable column filtering |
|
|
375
|
-
| **Data Management** |
|
|
376
|
-
| `import` | `boolean` | `false` | Enable data import functionality |
|
|
377
|
-
| `export` | `boolean` | `false` | Enable data export functionality |
|
|
378
|
-
| **Row Operations** |
|
|
379
|
-
| `selection` | `boolean` | `false` | Enable row selection for bulk operations |
|
|
380
|
-
| `creation` | `boolean` | `false` | Enable row creation |
|
|
381
|
-
| `editing` | `boolean` | `false` | Enable row editing |
|
|
382
|
-
| `deletion` | `boolean` | `false` | Enable row deletion |
|
|
383
|
-
| `deleteSelected` | `boolean` | `false` | Enable bulk deletion of selected rows |
|
|
384
|
-
| **Table Customization** |
|
|
385
|
-
| `grouping` | `boolean` | `false` | Enable data grouping |
|
|
386
|
-
| `columnVisibility` | `boolean` | `false` | Enable column visibility controls |
|
|
387
|
-
| `columnReordering` | `boolean` | `false` | Enable column reordering |
|
|
388
|
-
| `autoColumnSizing` | `boolean` | `false` | Enable automatic column width adjustment based on content |
|
|
389
|
-
|
|
390
|
-
### Basic Feature Configuration
|
|
160
|
+
#### Page Identification Options
|
|
391
161
|
|
|
162
|
+
**Option 1: Page Name (Recommended)**
|
|
392
163
|
```tsx
|
|
393
164
|
<DataTable
|
|
394
165
|
data={data}
|
|
395
166
|
columns={columns}
|
|
396
167
|
rbac={{
|
|
397
|
-
|
|
398
|
-
pageId: 'user-management'
|
|
399
|
-
}}
|
|
400
|
-
features={{
|
|
401
|
-
// Core Features
|
|
402
|
-
search: true,
|
|
403
|
-
pagination: true,
|
|
404
|
-
sorting: true,
|
|
405
|
-
filtering: true,
|
|
406
|
-
|
|
407
|
-
// Data Management
|
|
408
|
-
import: true,
|
|
409
|
-
export: true,
|
|
410
|
-
|
|
411
|
-
// Row Operations
|
|
412
|
-
selection: true, // Required for row selection
|
|
413
|
-
creation: true,
|
|
414
|
-
editing: true,
|
|
415
|
-
deletion: true, // Required for delete functionality
|
|
416
|
-
deleteSelected: true, // Required for delete selected button
|
|
417
|
-
|
|
418
|
-
// Table Customization
|
|
419
|
-
grouping: true,
|
|
420
|
-
columnVisibility: true,
|
|
421
|
-
columnReordering: true,
|
|
422
|
-
autoColumnSizing: true, // Enable automatic column width adjustment
|
|
168
|
+
pageName: 'dishes' // Will be resolved to page ID by RBAC engine
|
|
423
169
|
}}
|
|
170
|
+
features={features}
|
|
424
171
|
/>
|
|
425
172
|
```
|
|
426
173
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
For a simple read-only table:
|
|
430
|
-
|
|
174
|
+
**Option 2: Page ID (Direct)**
|
|
431
175
|
```tsx
|
|
432
176
|
<DataTable
|
|
433
177
|
data={data}
|
|
434
178
|
columns={columns}
|
|
435
179
|
rbac={{
|
|
436
|
-
|
|
437
|
-
pageId: 'user-management'
|
|
438
|
-
}}
|
|
439
|
-
features={{
|
|
440
|
-
search: true,
|
|
441
|
-
pagination: true,
|
|
442
|
-
sorting: true,
|
|
180
|
+
pageId: 'uuid-here' // Use page ID directly
|
|
443
181
|
}}
|
|
182
|
+
features={features}
|
|
444
183
|
/>
|
|
445
184
|
```
|
|
446
185
|
|
|
447
|
-
|
|
186
|
+
**Note:** You must provide either `pageName` or `pageId`, but not both. If both are provided, `pageId` takes precedence.
|
|
187
|
+
|
|
188
|
+
#### Permission Mapping
|
|
189
|
+
|
|
190
|
+
The DataTable checks the following page-based permissions:
|
|
448
191
|
|
|
449
|
-
|
|
192
|
+
| DataTable Feature | Permission Checked | Description |
|
|
193
|
+
|------------------|-------------------|-------------|
|
|
194
|
+
| **Data Display** | `read:page.{pageId}` | Controls whether data is visible |
|
|
195
|
+
| **Create Button** | `create:page.{pageId}` | Controls create functionality |
|
|
196
|
+
| **Edit Actions** | `update:page.{pageId}` | Controls edit/update functionality |
|
|
197
|
+
| **Delete Actions** | `delete:page.{pageId}` | Controls delete functionality |
|
|
198
|
+
| **Export** | `read:page.{pageId}` | Controls export functionality |
|
|
199
|
+
| **Import** | `create:page.{pageId}` | Controls import functionality |
|
|
450
200
|
|
|
201
|
+
#### Role-Based Examples
|
|
202
|
+
|
|
203
|
+
**Planner Role:**
|
|
451
204
|
```tsx
|
|
452
205
|
<DataTable
|
|
453
|
-
data={
|
|
206
|
+
data={dishes}
|
|
454
207
|
columns={columns}
|
|
208
|
+
rbac={{
|
|
209
|
+
pageId: 'dishes'
|
|
210
|
+
}}
|
|
455
211
|
features={{
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
filtering: true,
|
|
460
|
-
selection: true,
|
|
461
|
-
creation: true,
|
|
462
|
-
editing: true,
|
|
463
|
-
deletion: true,
|
|
464
|
-
deleteSelected: true, // Enable delete selected functionality
|
|
465
|
-
export: true,
|
|
466
|
-
import: true,
|
|
467
|
-
grouping: true,
|
|
468
|
-
columnVisibility: true,
|
|
469
|
-
bulkOperations: true,
|
|
212
|
+
creation: true, // ✅ Allowed if planner has create:page.dishes
|
|
213
|
+
editing: true, // ✅ Allowed if planner has update:page.dishes
|
|
214
|
+
deletion: false, // ❌ Disabled if planner lacks delete:page.dishes
|
|
470
215
|
}}
|
|
471
|
-
columnOrder={['select', 'name', 'email', 'role', 'status', 'actions']} // Custom column order
|
|
472
|
-
onEditRow={handleEdit}
|
|
473
|
-
onDeleteRow={handleDelete}
|
|
474
|
-
onCreateRow={handleCreate}
|
|
475
|
-
onImport={handleImport}
|
|
476
|
-
onRowSelectionChange={handleSelectionChange}
|
|
477
|
-
onDeleteSelected={handleBulkDelete}
|
|
478
|
-
getRowId={(row) => row.id} // Important: Required for selection and delete operations
|
|
479
216
|
/>
|
|
480
217
|
```
|
|
481
218
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
The DataTable component includes automatic column width adjustment based on content, which helps optimize space usage and improve readability.
|
|
485
|
-
|
|
486
|
-
### How Auto-Sizing Works
|
|
487
|
-
|
|
488
|
-
When `autoColumnSizing: true` is enabled:
|
|
489
|
-
|
|
490
|
-
1. **Content Analysis**: Analyzes both header text and data content to determine optimal widths
|
|
491
|
-
2. **Smart Calculation**: Uses character count estimation (8px per character + padding)
|
|
492
|
-
3. **Constraints**: Respects min-width (80px) and max-width (400px) limits
|
|
493
|
-
4. **Performance**: Only samples first 100 rows for large datasets
|
|
494
|
-
5. **Table Layout**: Switches from `tableLayout: 'fixed'` to `tableLayout: 'auto'`
|
|
495
|
-
|
|
496
|
-
### Benefits
|
|
497
|
-
|
|
498
|
-
- **Better Space Utilization**: Columns automatically adjust to content width
|
|
499
|
-
- **Improved Readability**: Long text gets more space, short text takes less space
|
|
500
|
-
- **No Text Wrapping**: Prevents unnecessary text wrapping in narrow columns
|
|
501
|
-
- **Consistent Layout**: Maintains proper spacing and alignment
|
|
502
|
-
|
|
503
|
-
### Enabling Auto-Sizing
|
|
504
|
-
|
|
219
|
+
**Viewer Role:**
|
|
505
220
|
```tsx
|
|
506
221
|
<DataTable
|
|
507
|
-
data={
|
|
222
|
+
data={dishes}
|
|
508
223
|
columns={columns}
|
|
224
|
+
rbac={{
|
|
225
|
+
pageId: 'dishes'
|
|
226
|
+
}}
|
|
509
227
|
features={{
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
autoColumnSizing: true, // Enable automatic column width adjustment
|
|
228
|
+
creation: false, // ❌ Disabled - viewer typically lacks create:page.dishes
|
|
229
|
+
editing: false, // ❌ Disabled - viewer typically lacks update:page.dishes
|
|
230
|
+
deletion: false, // ❌ Disabled - viewer typically lacks delete:page.dishes
|
|
514
231
|
}}
|
|
515
232
|
/>
|
|
516
233
|
```
|
|
517
234
|
|
|
518
|
-
|
|
235
|
+
#### Database Setup
|
|
519
236
|
|
|
520
|
-
|
|
521
|
-
- Columns automatically adjust to content width
|
|
522
|
-
- Long text gets more space, short text takes less space
|
|
523
|
-
- Better space utilization
|
|
524
|
-
- Improved readability
|
|
237
|
+
Ensure your `rbac_page_permissions` table has entries for each page and role:
|
|
525
238
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
239
|
+
```sql
|
|
240
|
+
-- Example: Planner role permissions for dishes page
|
|
241
|
+
INSERT INTO rbac_page_permissions (app_page_id, operation, role_name, allowed) VALUES
|
|
242
|
+
((SELECT id FROM rbac_app_pages WHERE page_name = 'dishes'), 'read', 'planner', true),
|
|
243
|
+
((SELECT id FROM rbac_app_pages WHERE page_name = 'dishes'), 'create', 'planner', true),
|
|
244
|
+
((SELECT id FROM rbac_app_pages WHERE page_name = 'dishes'), 'update', 'planner', true),
|
|
245
|
+
((SELECT id FROM rbac_app_pages WHERE page_name = 'dishes'), 'delete', 'planner', false);
|
|
246
|
+
```
|
|
531
247
|
|
|
532
|
-
###
|
|
248
|
+
### Permission-Based Actions (Legacy)
|
|
533
249
|
|
|
534
250
|
```tsx
|
|
535
|
-
const data = [
|
|
536
|
-
{ id: '1', name: 'John', description: 'A short description' },
|
|
537
|
-
{ id: '2', name: 'Jane', description: 'A very long description that should make this column wider' },
|
|
538
|
-
{ id: '3', name: 'Bob', description: 'Medium length description' }
|
|
539
|
-
];
|
|
540
|
-
|
|
541
|
-
const columns = [
|
|
542
|
-
{ accessorKey: 'name', header: 'Name' },
|
|
543
|
-
{ accessorKey: 'description', header: 'Description' }
|
|
544
|
-
];
|
|
545
|
-
|
|
546
|
-
// With auto-sizing: Description column will be wider than Name column
|
|
547
251
|
<DataTable
|
|
548
252
|
data={data}
|
|
549
253
|
columns={columns}
|
|
550
|
-
|
|
254
|
+
rbac={{
|
|
255
|
+
resource: 'users',
|
|
256
|
+
actions: [
|
|
257
|
+
{
|
|
258
|
+
name: 'edit',
|
|
259
|
+
permission: 'update:users',
|
|
260
|
+
scope: { userId: 'user-123' }
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: 'delete',
|
|
264
|
+
permission: 'delete:users',
|
|
265
|
+
scope: { userId: 'user-123' }
|
|
266
|
+
}
|
|
267
|
+
]
|
|
268
|
+
}}
|
|
269
|
+
features={{
|
|
270
|
+
rowActions: true,
|
|
271
|
+
creation: true,
|
|
272
|
+
editing: true,
|
|
273
|
+
deletion: true
|
|
274
|
+
}}
|
|
551
275
|
/>
|
|
552
276
|
```
|
|
553
277
|
|
|
554
|
-
|
|
278
|
+
## Hierarchical Data Implementation
|
|
279
|
+
|
|
280
|
+
### Overview
|
|
281
|
+
|
|
282
|
+
The hierarchical DataTable feature allows you to display parent/child relationships in your data with:
|
|
283
|
+
- **Expand/Collapse All**: Single button to expand/collapse all parent rows
|
|
284
|
+
- **Individual Controls**: Click arrows next to each parent row
|
|
285
|
+
- **Different Action Icons**: Context-aware actions for parent vs child rows
|
|
286
|
+
- **Smart Sorting**: Sorting by child fields sorts children within their parents
|
|
287
|
+
- **Column Rendering**: Different content display for parent vs child rows
|
|
288
|
+
- **Visual Distinction**: Clear visual hierarchy with indentation and styling
|
|
289
|
+
|
|
290
|
+
### Quick Start
|
|
555
291
|
|
|
556
|
-
|
|
292
|
+
#### 1. Enable Hierarchical Features
|
|
557
293
|
|
|
558
294
|
```tsx
|
|
559
|
-
|
|
560
|
-
|
|
295
|
+
import { DataTable, type HierarchicalDataRow } from '@jmruthers/pace-core';
|
|
296
|
+
|
|
561
297
|
<DataTable
|
|
562
|
-
data={
|
|
298
|
+
data={hierarchicalData}
|
|
563
299
|
columns={columns}
|
|
564
300
|
features={{
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
301
|
+
hierarchical: true, // Enable hierarchical functionality
|
|
302
|
+
}}
|
|
303
|
+
hierarchical={{
|
|
304
|
+
enabled: true,
|
|
305
|
+
defaultExpanded: false, // Start with all collapsed
|
|
568
306
|
}}
|
|
569
307
|
/>
|
|
570
308
|
```
|
|
571
309
|
|
|
572
|
-
|
|
310
|
+
#### 2. Structure Your Data
|
|
573
311
|
|
|
574
|
-
|
|
312
|
+
```typescript
|
|
313
|
+
interface Dish extends HierarchicalDataRow {
|
|
314
|
+
id: string;
|
|
315
|
+
isParent: boolean;
|
|
316
|
+
parentId?: string;
|
|
317
|
+
name: string;
|
|
318
|
+
type: string;
|
|
319
|
+
cost: number;
|
|
320
|
+
// ... other properties
|
|
321
|
+
}
|
|
575
322
|
|
|
576
|
-
|
|
577
|
-
|
|
323
|
+
const hierarchicalData: Dish[] = [
|
|
324
|
+
// Parent rows (dishes)
|
|
578
325
|
{
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
minSize: 100, // Minimum width
|
|
586
|
-
maxSize: 300, // Maximum width
|
|
326
|
+
id: 'dish-1',
|
|
327
|
+
isParent: true,
|
|
328
|
+
parentId: null,
|
|
329
|
+
name: 'Caesar Salad',
|
|
330
|
+
type: 'Salad',
|
|
331
|
+
cost: 12.99
|
|
587
332
|
},
|
|
588
|
-
];
|
|
589
|
-
```
|
|
590
|
-
|
|
591
|
-
### Custom Cell Rendering
|
|
592
|
-
|
|
593
|
-
```tsx
|
|
594
|
-
const columns: DataTableColumn<User>[] = [
|
|
595
333
|
{
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
334
|
+
id: 'dish-2',
|
|
335
|
+
isParent: true,
|
|
336
|
+
parentId: null,
|
|
337
|
+
name: 'Beef Stir Fry',
|
|
338
|
+
type: 'Main Course',
|
|
339
|
+
cost: 18.99
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
// Child rows (ingredients) for Caesar Salad
|
|
343
|
+
{
|
|
344
|
+
id: 'ingredient-1',
|
|
345
|
+
isParent: false,
|
|
346
|
+
parentId: 'dish-1',
|
|
347
|
+
name: 'Romaine Lettuce',
|
|
348
|
+
type: 'Vegetable',
|
|
349
|
+
cost: 2.50
|
|
606
350
|
},
|
|
607
351
|
{
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
),
|
|
352
|
+
id: 'ingredient-2',
|
|
353
|
+
isParent: false,
|
|
354
|
+
parentId: 'dish-1',
|
|
355
|
+
name: 'Parmesan Cheese',
|
|
356
|
+
type: 'Dairy',
|
|
357
|
+
cost: 3.00
|
|
615
358
|
},
|
|
359
|
+
|
|
360
|
+
// Child rows (ingredients) for Beef Stir Fry
|
|
616
361
|
{
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
),
|
|
362
|
+
id: 'ingredient-3',
|
|
363
|
+
isParent: false,
|
|
364
|
+
parentId: 'dish-2',
|
|
365
|
+
name: 'Beef Strips',
|
|
366
|
+
type: 'Protein',
|
|
367
|
+
cost: 8.50
|
|
624
368
|
},
|
|
369
|
+
{
|
|
370
|
+
id: 'ingredient-4',
|
|
371
|
+
isParent: false,
|
|
372
|
+
parentId: 'dish-2',
|
|
373
|
+
name: 'Bell Peppers',
|
|
374
|
+
type: 'Vegetable',
|
|
375
|
+
cost: 2.00
|
|
376
|
+
}
|
|
625
377
|
];
|
|
626
378
|
```
|
|
627
379
|
|
|
628
|
-
|
|
380
|
+
#### 3. Configure Columns for Hierarchical Display
|
|
629
381
|
|
|
630
|
-
```
|
|
631
|
-
const columns: DataTableColumn<
|
|
382
|
+
```typescript
|
|
383
|
+
const columns: DataTableColumn<Dish>[] = [
|
|
632
384
|
{
|
|
633
385
|
accessorKey: 'name',
|
|
634
386
|
header: 'Name',
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
387
|
+
cell: ({ row }) => {
|
|
388
|
+
const isParent = row.original.isParent;
|
|
389
|
+
const indentLevel = isParent ? 0 : 1;
|
|
390
|
+
|
|
391
|
+
return (
|
|
392
|
+
<div className={`flex items-center ${isParent ? 'font-semibold' : ''}`}>
|
|
393
|
+
<div style={{ marginLeft: `${indentLevel * 20}px` }}>
|
|
394
|
+
{isParent ? '🍽️' : '🥬'} {row.original.name}
|
|
395
|
+
</div>
|
|
396
|
+
</div>
|
|
397
|
+
);
|
|
398
|
+
}
|
|
639
399
|
},
|
|
640
400
|
{
|
|
641
|
-
accessorKey: '
|
|
642
|
-
header: '
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
<SelectContent>
|
|
652
|
-
<SelectItem value="admin">Admin</SelectItem>
|
|
653
|
-
<SelectItem value="user">User</SelectItem>
|
|
654
|
-
<SelectItem value="guest">Guest</SelectItem>
|
|
655
|
-
</SelectContent>
|
|
656
|
-
</Select>
|
|
657
|
-
),
|
|
401
|
+
accessorKey: 'type',
|
|
402
|
+
header: 'Type',
|
|
403
|
+
cell: ({ row }) => {
|
|
404
|
+
const isParent = row.original.isParent;
|
|
405
|
+
return (
|
|
406
|
+
<span className={isParent ? 'text-main-600 font-medium' : 'text-sec-600'}>
|
|
407
|
+
{row.original.type}
|
|
408
|
+
</span>
|
|
409
|
+
);
|
|
410
|
+
}
|
|
658
411
|
},
|
|
412
|
+
{
|
|
413
|
+
accessorKey: 'cost',
|
|
414
|
+
header: 'Cost',
|
|
415
|
+
cell: ({ row }) => {
|
|
416
|
+
const isParent = row.original.isParent;
|
|
417
|
+
return (
|
|
418
|
+
<span className={isParent ? 'font-bold text-main-700' : 'text-sec-600'}>
|
|
419
|
+
${row.original.cost.toFixed(2)}
|
|
420
|
+
</span>
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
659
424
|
];
|
|
660
425
|
```
|
|
661
426
|
|
|
662
|
-
|
|
427
|
+
### Advanced Hierarchical Features
|
|
663
428
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
### Enabling Inline Editing
|
|
667
|
-
|
|
668
|
-
To enable inline editing, set `editing: true` in the features configuration:
|
|
429
|
+
#### Expand/Collapse All Control
|
|
669
430
|
|
|
670
431
|
```tsx
|
|
671
432
|
<DataTable
|
|
672
|
-
data={
|
|
433
|
+
data={hierarchicalData}
|
|
673
434
|
columns={columns}
|
|
674
435
|
features={{
|
|
675
|
-
|
|
676
|
-
|
|
436
|
+
hierarchical: true,
|
|
437
|
+
}}
|
|
438
|
+
hierarchical={{
|
|
439
|
+
enabled: true,
|
|
440
|
+
defaultExpanded: false,
|
|
441
|
+
showExpandAll: true, // Shows expand/collapse all button
|
|
677
442
|
}}
|
|
678
|
-
|
|
443
|
+
title="Dishes & Ingredients"
|
|
444
|
+
description="Hierarchical view of dishes and their ingredients"
|
|
679
445
|
/>
|
|
680
446
|
```
|
|
681
447
|
|
|
682
|
-
|
|
448
|
+
#### Custom Action Icons for Parent/Child Rows
|
|
449
|
+
|
|
450
|
+
```typescript
|
|
451
|
+
const columns: DataTableColumn<Dish>[] = [
|
|
452
|
+
// ... other columns
|
|
453
|
+
{
|
|
454
|
+
id: 'actions',
|
|
455
|
+
header: 'Actions',
|
|
456
|
+
cell: ({ row }) => {
|
|
457
|
+
const isParent = row.original.isParent;
|
|
458
|
+
|
|
459
|
+
if (isParent) {
|
|
460
|
+
// Parent row actions
|
|
461
|
+
return (
|
|
462
|
+
<div className="flex gap-2">
|
|
463
|
+
<Button size="sm" variant="outline">
|
|
464
|
+
Edit Dish
|
|
465
|
+
</Button>
|
|
466
|
+
<Button size="sm" variant="outline">
|
|
467
|
+
Add Ingredient
|
|
468
|
+
</Button>
|
|
469
|
+
</div>
|
|
470
|
+
);
|
|
471
|
+
} else {
|
|
472
|
+
// Child row actions
|
|
473
|
+
return (
|
|
474
|
+
<div className="flex gap-2">
|
|
475
|
+
<Button size="sm" variant="ghost">
|
|
476
|
+
Edit Ingredient
|
|
477
|
+
</Button>
|
|
478
|
+
<Button size="sm" variant="ghost">
|
|
479
|
+
Remove
|
|
480
|
+
</Button>
|
|
481
|
+
</div>
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
];
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
#### Smart Sorting
|
|
683
490
|
|
|
684
|
-
|
|
491
|
+
When sorting by child fields, children are sorted within their parent groups:
|
|
685
492
|
|
|
686
|
-
```
|
|
687
|
-
const columns: DataTableColumn<
|
|
493
|
+
```typescript
|
|
494
|
+
const columns: DataTableColumn<Dish>[] = [
|
|
495
|
+
{
|
|
496
|
+
accessorKey: 'cost',
|
|
497
|
+
header: 'Cost',
|
|
498
|
+
sortable: true,
|
|
499
|
+
cell: ({ row }) => {
|
|
500
|
+
const isParent = row.original.isParent;
|
|
501
|
+
return (
|
|
502
|
+
<span className={isParent ? 'font-bold text-main-700' : 'text-sec-600'}>
|
|
503
|
+
${row.original.cost.toFixed(2)}
|
|
504
|
+
</span>
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
];
|
|
509
|
+
|
|
510
|
+
// When sorting by cost:
|
|
511
|
+
// 1. Parent rows are sorted by their cost
|
|
512
|
+
// 2. Child rows are sorted by their cost within each parent group
|
|
513
|
+
// 3. Parent-child relationships are maintained
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
## Advanced Filtering
|
|
517
|
+
|
|
518
|
+
### Filter Types
|
|
519
|
+
|
|
520
|
+
#### 1. Text Filters (Default)
|
|
521
|
+
Text filters allow users to type and search for values in a column.
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
const columns = [
|
|
688
525
|
{
|
|
526
|
+
id: 'name',
|
|
689
527
|
accessorKey: 'name',
|
|
690
528
|
header: 'Name',
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
editable: true,
|
|
704
|
-
fieldType: 'number', // Numeric input field
|
|
705
|
-
},
|
|
529
|
+
enableColumnFilter: true,
|
|
530
|
+
// No filterType specified - defaults to 'text'
|
|
531
|
+
}
|
|
532
|
+
];
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
#### 2. Select Filters
|
|
536
|
+
Select filters provide dropdown menus with predefined options, perfect for categorical data.
|
|
537
|
+
|
|
538
|
+
**Method 1: Using `filterSelectOptions` (Recommended)**
|
|
539
|
+
```typescript
|
|
540
|
+
const columns = [
|
|
706
541
|
{
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
{ value: '
|
|
714
|
-
{ value: '
|
|
542
|
+
id: 'meal_type',
|
|
543
|
+
accessorFn: (row) => row.mealtype?.mealtype_name || 'N/A',
|
|
544
|
+
header: 'Type',
|
|
545
|
+
enableColumnFilter: true,
|
|
546
|
+
filterType: 'select',
|
|
547
|
+
filterSelectOptions: [
|
|
548
|
+
{ value: 'breakfast', label: 'Breakfast' },
|
|
549
|
+
{ value: 'lunch', label: 'Lunch' },
|
|
550
|
+
{ value: 'dinner', label: 'Dinner' },
|
|
551
|
+
{ value: 'morning_tea', label: 'Morning Tea' },
|
|
552
|
+
{ value: 'afternoon_tea', label: 'Afternoon Tea' },
|
|
553
|
+
{ value: 'pantry', label: 'Pantry' }
|
|
715
554
|
],
|
|
716
|
-
|
|
555
|
+
filterFn: (row, id, value) => {
|
|
556
|
+
return value.includes(row.original.mealtype?.mealtype_id);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
];
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
**Method 2: Using `fieldOptions`**
|
|
563
|
+
```typescript
|
|
564
|
+
const columns = [
|
|
717
565
|
{
|
|
566
|
+
id: 'status',
|
|
718
567
|
accessorKey: 'status',
|
|
719
568
|
header: 'Status',
|
|
720
|
-
|
|
721
|
-
|
|
569
|
+
enableColumnFilter: true,
|
|
570
|
+
filterType: 'select',
|
|
571
|
+
fieldOptions: [
|
|
572
|
+
{ value: 'active', label: 'Active' },
|
|
573
|
+
{ value: 'inactive', label: 'Inactive' },
|
|
574
|
+
{ value: 'pending', label: 'Pending' }
|
|
575
|
+
]
|
|
576
|
+
}
|
|
722
577
|
];
|
|
723
578
|
```
|
|
724
579
|
|
|
725
|
-
|
|
580
|
+
**Method 3: Auto-detection**
|
|
581
|
+
The DataTable automatically detects select filters when a column has limited unique values (≤10):
|
|
726
582
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
- **`select`**: Dropdown select (requires `fieldOptions`)
|
|
730
|
-
- **`date`**: Date picker
|
|
731
|
-
- **`boolean`**: Checkbox
|
|
732
|
-
|
|
733
|
-
### Complete Inline Editing Example
|
|
734
|
-
|
|
735
|
-
```tsx
|
|
736
|
-
interface User {
|
|
737
|
-
id: string;
|
|
738
|
-
name: string;
|
|
739
|
-
email: string;
|
|
740
|
-
age: number;
|
|
741
|
-
role: 'admin' | 'user' | 'guest';
|
|
742
|
-
active: boolean;
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
const columns: DataTableColumn<User>[] = [
|
|
746
|
-
{
|
|
747
|
-
accessorKey: 'name',
|
|
748
|
-
header: 'Name',
|
|
749
|
-
sortable: true,
|
|
750
|
-
editable: true,
|
|
751
|
-
fieldType: 'text',
|
|
752
|
-
},
|
|
753
|
-
{
|
|
754
|
-
accessorKey: 'email',
|
|
755
|
-
header: 'Email',
|
|
756
|
-
sortable: true,
|
|
757
|
-
editable: true,
|
|
758
|
-
fieldType: 'text',
|
|
759
|
-
},
|
|
760
|
-
{
|
|
761
|
-
accessorKey: 'age',
|
|
762
|
-
header: 'Age',
|
|
763
|
-
sortable: true,
|
|
764
|
-
editable: true,
|
|
765
|
-
fieldType: 'number',
|
|
766
|
-
},
|
|
767
|
-
{
|
|
768
|
-
accessorKey: 'role',
|
|
769
|
-
header: 'Role',
|
|
770
|
-
editable: true,
|
|
771
|
-
fieldType: 'select',
|
|
772
|
-
fieldOptions: [
|
|
773
|
-
{ value: 'admin', label: 'Admin' },
|
|
774
|
-
{ value: 'user', label: 'User' },
|
|
775
|
-
{ value: 'guest', label: 'Guest' },
|
|
776
|
-
],
|
|
777
|
-
},
|
|
583
|
+
```typescript
|
|
584
|
+
const columns = [
|
|
778
585
|
{
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
586
|
+
id: 'priority',
|
|
587
|
+
accessorKey: 'priority',
|
|
588
|
+
header: 'Priority',
|
|
589
|
+
enableColumnFilter: true,
|
|
590
|
+
// No filterType specified - will auto-detect as 'select' if ≤10 unique values
|
|
591
|
+
}
|
|
784
592
|
];
|
|
785
|
-
|
|
786
|
-
function UserManagement() {
|
|
787
|
-
const [users, setUsers] = useState<User[]>([]);
|
|
788
|
-
|
|
789
|
-
const handleEdit = async (row: User, data: Partial<User>) => {
|
|
790
|
-
// Update in database
|
|
791
|
-
const { error } = await supabase
|
|
792
|
-
.from('users')
|
|
793
|
-
.update(data)
|
|
794
|
-
.eq('id', row.id);
|
|
795
|
-
|
|
796
|
-
if (error) throw error;
|
|
797
|
-
|
|
798
|
-
// Update local state
|
|
799
|
-
setUsers(prev => prev.map(u => u.id === row.id ? { ...u, ...data } : u));
|
|
800
|
-
};
|
|
801
|
-
|
|
802
|
-
return (
|
|
803
|
-
<DataTable
|
|
804
|
-
data={users}
|
|
805
|
-
columns={columns}
|
|
806
|
-
features={{
|
|
807
|
-
editing: true,
|
|
808
|
-
// ... other features
|
|
809
|
-
}}
|
|
810
|
-
onEditRow={handleEdit}
|
|
811
|
-
/>
|
|
812
|
-
);
|
|
813
|
-
}
|
|
814
593
|
```
|
|
815
594
|
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
1. User clicks "Edit" button on a row
|
|
819
|
-
2. Editable columns (those with `editable: true`) become input fields
|
|
820
|
-
3. Non-editable columns (those with `editable: false`) remain as text
|
|
821
|
-
4. Save/Cancel buttons replace the Edit button
|
|
822
|
-
5. On Save: `onEditRow(originalRow, editedData)` is called
|
|
823
|
-
6. On Cancel: Row returns to view mode without changes
|
|
824
|
-
|
|
825
|
-
### Mixing Editable and Non-Editable Columns
|
|
595
|
+
#### 3. Number Filters
|
|
596
|
+
Number filters provide numeric input with comparison operators.
|
|
826
597
|
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
```tsx
|
|
598
|
+
```typescript
|
|
830
599
|
const columns = [
|
|
831
600
|
{
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
header: 'Name',
|
|
839
|
-
editable: true, // Can be edited
|
|
840
|
-
fieldType: 'text',
|
|
841
|
-
},
|
|
842
|
-
{
|
|
843
|
-
accessorKey: 'createdAt',
|
|
844
|
-
header: 'Created',
|
|
845
|
-
editable: false, // Read-only timestamp
|
|
846
|
-
cell: ({ row }) => formatDate(row.original.createdAt),
|
|
847
|
-
},
|
|
601
|
+
id: 'price',
|
|
602
|
+
accessorKey: 'price',
|
|
603
|
+
header: 'Price',
|
|
604
|
+
enableColumnFilter: true,
|
|
605
|
+
filterType: 'number'
|
|
606
|
+
}
|
|
848
607
|
];
|
|
849
608
|
```
|
|
850
609
|
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
## Automatic Export
|
|
854
|
-
|
|
855
|
-
The DataTable includes automatic CSV export functionality that works out of the box. When the export button is clicked, it automatically exports exactly what's shown in the table.
|
|
856
|
-
|
|
857
|
-
### How Automatic Export Works
|
|
858
|
-
|
|
859
|
-
By default, the export feature:
|
|
860
|
-
- ✅ Exports **exactly** what's visible in the table (respects filters, search, sorting)
|
|
861
|
-
- ✅ Includes **only visible columns** (respects column visibility settings)
|
|
862
|
-
- ✅ Uses proper column headers from your column definitions
|
|
863
|
-
- ✅ Generates a filename with timestamp (e.g., `users_2025-01-15.csv`)
|
|
864
|
-
- ✅ Requires **no configuration** - just enable the feature
|
|
865
|
-
|
|
866
|
-
### Basic Usage (Zero Configuration)
|
|
867
|
-
|
|
868
|
-
```tsx
|
|
869
|
-
<DataTable
|
|
870
|
-
data={data}
|
|
871
|
-
columns={columns}
|
|
872
|
-
features={{
|
|
873
|
-
export: true, // ✅ That's all you need!
|
|
874
|
-
}}
|
|
875
|
-
/>
|
|
876
|
-
```
|
|
877
|
-
|
|
878
|
-
That's it! The export button appears automatically and exports the current table state when clicked.
|
|
879
|
-
|
|
880
|
-
### What Gets Exported
|
|
881
|
-
|
|
882
|
-
The automatic export includes:
|
|
883
|
-
- **Filtered rows**: Only data matching current search/filter criteria
|
|
884
|
-
- **Visible columns**: Only columns that are currently visible
|
|
885
|
-
- **Current sorting**: Preserved in the exported data
|
|
886
|
-
- **Column headers**: Proper names from your column definitions
|
|
887
|
-
|
|
888
|
-
### Export Behavior
|
|
889
|
-
|
|
890
|
-
- **Filename**: `{table-title}_{date}.csv` (e.g., `users_2025-01-15.csv`)
|
|
891
|
-
- **Format**: CSV with proper escaping for special characters
|
|
892
|
-
- **Data**: All filtered rows (not just current page)
|
|
893
|
-
- **Columns**: Only currently visible columns
|
|
894
|
-
|
|
895
|
-
### Custom Export Handler
|
|
896
|
-
|
|
897
|
-
If you need custom export behavior, you can provide your own handler:
|
|
898
|
-
|
|
899
|
-
```tsx
|
|
900
|
-
<DataTable
|
|
901
|
-
data={data}
|
|
902
|
-
columns={columns}
|
|
903
|
-
features={{
|
|
904
|
-
export: true,
|
|
905
|
-
}}
|
|
906
|
-
onExport={() => {
|
|
907
|
-
// Your custom export logic
|
|
908
|
-
// This overrides the default automatic export
|
|
909
|
-
exportToCustomFormat(data);
|
|
910
|
-
}}
|
|
911
|
-
/>
|
|
912
|
-
```
|
|
913
|
-
|
|
914
|
-
### Export Permissions (RBAC)
|
|
915
|
-
|
|
916
|
-
Export is controlled by the `read:page.{pageId}` permission:
|
|
917
|
-
|
|
918
|
-
```tsx
|
|
919
|
-
<DataTable
|
|
920
|
-
data={data}
|
|
921
|
-
columns={columns}
|
|
922
|
-
rbac={{
|
|
923
|
-
pageId: 'user-management'
|
|
924
|
-
}}
|
|
925
|
-
features={{
|
|
926
|
-
export: true, // Requires read:page.user-management permission
|
|
927
|
-
}}
|
|
928
|
-
/>
|
|
929
|
-
```
|
|
930
|
-
|
|
931
|
-
Users without export permission won't see the export button.
|
|
932
|
-
|
|
933
|
-
## Column Ordering
|
|
934
|
-
|
|
935
|
-
The DataTable component supports custom column ordering through the `columnOrder` prop, allowing you to control the exact position of columns including selection and actions columns.
|
|
936
|
-
|
|
937
|
-
### Basic Column Ordering
|
|
938
|
-
|
|
939
|
-
```tsx
|
|
940
|
-
<DataTable
|
|
941
|
-
data={data}
|
|
942
|
-
columns={columns}
|
|
943
|
-
features={{
|
|
944
|
-
selection: true,
|
|
945
|
-
sorting: true,
|
|
946
|
-
filtering: true,
|
|
947
|
-
}}
|
|
948
|
-
columnOrder={['select', 'name', 'email', 'role', 'status']}
|
|
949
|
-
/>
|
|
950
|
-
```
|
|
951
|
-
|
|
952
|
-
### Column Order Behavior
|
|
953
|
-
|
|
954
|
-
#### When `columnOrder` is provided:
|
|
955
|
-
- **Exact positioning**: Columns appear in the exact order specified
|
|
956
|
-
- **Selection column**: Include `'select'` in the array to position the selection column
|
|
957
|
-
- **Actions column**: Include `'actions'` in the array to position the actions column
|
|
958
|
-
- **Data columns**: Use the column's `accessorKey` or `id` to reference data columns
|
|
959
|
-
|
|
960
|
-
#### When `columnOrder` is not provided:
|
|
961
|
-
- **Default behavior**: Selection column first (if enabled), then data columns, then actions column
|
|
962
|
-
- **Automatic ordering**: Columns appear in the order they're defined in the `columns` array
|
|
963
|
-
|
|
964
|
-
### Selection Column Positioning
|
|
965
|
-
|
|
966
|
-
The selection column can be positioned anywhere in the column order:
|
|
967
|
-
|
|
968
|
-
```tsx
|
|
969
|
-
// Selection column first (default when not in columnOrder)
|
|
970
|
-
<DataTable
|
|
971
|
-
data={data}
|
|
972
|
-
columns={columns}
|
|
973
|
-
features={{ selection: true }}
|
|
974
|
-
columnOrder={['select', 'name', 'email', 'role']}
|
|
975
|
-
/>
|
|
976
|
-
|
|
977
|
-
// Selection column in the middle
|
|
978
|
-
<DataTable
|
|
979
|
-
data={data}
|
|
980
|
-
columns={columns}
|
|
981
|
-
features={{ selection: true }}
|
|
982
|
-
columnOrder={['name', 'select', 'email', 'role']}
|
|
983
|
-
/>
|
|
984
|
-
|
|
985
|
-
// Selection column last
|
|
986
|
-
<DataTable
|
|
987
|
-
data={data}
|
|
988
|
-
columns={columns}
|
|
989
|
-
features={{ selection: true }}
|
|
990
|
-
columnOrder={['name', 'email', 'role', 'select']}
|
|
991
|
-
/>
|
|
992
|
-
|
|
993
|
-
// Selection column not in columnOrder - defaults to first position
|
|
994
|
-
<DataTable
|
|
995
|
-
data={data}
|
|
996
|
-
columns={columns}
|
|
997
|
-
features={{ selection: true }}
|
|
998
|
-
columnOrder={['name', 'email', 'role']} // selection will be first
|
|
999
|
-
/>
|
|
1000
|
-
```
|
|
1001
|
-
|
|
1002
|
-
### Actions Column Positioning
|
|
1003
|
-
|
|
1004
|
-
The actions column can also be positioned anywhere:
|
|
1005
|
-
|
|
1006
|
-
```tsx
|
|
1007
|
-
// Actions column last (default when not in columnOrder)
|
|
1008
|
-
<DataTable
|
|
1009
|
-
data={data}
|
|
1010
|
-
columns={columns}
|
|
1011
|
-
features={{ editing: true, deletion: true }}
|
|
1012
|
-
columnOrder={['name', 'email', 'role', 'actions']}
|
|
1013
|
-
/>
|
|
1014
|
-
|
|
1015
|
-
// Actions column first
|
|
1016
|
-
<DataTable
|
|
1017
|
-
data={data}
|
|
1018
|
-
columns={columns}
|
|
1019
|
-
features={{ editing: true, deletion: true }}
|
|
1020
|
-
columnOrder={['actions', 'name', 'email', 'role']}
|
|
1021
|
-
/>
|
|
610
|
+
#### 4. Date Filters
|
|
611
|
+
Date filters provide date picker inputs.
|
|
1022
612
|
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
613
|
+
```typescript
|
|
614
|
+
const columns = [
|
|
615
|
+
{
|
|
616
|
+
id: 'created_date',
|
|
617
|
+
accessorKey: 'created_date',
|
|
618
|
+
header: 'Created Date',
|
|
619
|
+
enableColumnFilter: true,
|
|
620
|
+
filterType: 'date'
|
|
621
|
+
}
|
|
622
|
+
];
|
|
1030
623
|
```
|
|
1031
624
|
|
|
1032
|
-
### Complete
|
|
625
|
+
### Complete Filtering Example
|
|
1033
626
|
|
|
1034
|
-
```
|
|
1035
|
-
|
|
1036
|
-
id: string;
|
|
1037
|
-
name: string;
|
|
1038
|
-
email: string;
|
|
1039
|
-
role: string;
|
|
1040
|
-
status: 'active' | 'inactive';
|
|
1041
|
-
createdAt: Date;
|
|
1042
|
-
}
|
|
627
|
+
```typescript
|
|
628
|
+
import { DataTable } from '@jmruthers/pace-core';
|
|
1043
629
|
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
630
|
+
function MealsTable() {
|
|
631
|
+
const meals = [
|
|
632
|
+
{
|
|
633
|
+
id: '1',
|
|
634
|
+
meal_code: '28py',
|
|
635
|
+
mealtype: { mealtype_id: 'breakfast', mealtype_name: 'Breakfast' },
|
|
636
|
+
meal_date: '2024-12-28',
|
|
637
|
+
price: 15.50
|
|
638
|
+
},
|
|
639
|
+
// ... more data
|
|
640
|
+
];
|
|
641
|
+
|
|
642
|
+
const mealTypes = [
|
|
643
|
+
{ mealtype_id: 'breakfast', mealtype_name: 'Breakfast' },
|
|
644
|
+
{ mealtype_id: 'lunch', mealtype_name: 'Lunch' },
|
|
645
|
+
{ mealtype_id: 'dinner', mealtype_name: 'Dinner' },
|
|
646
|
+
{ mealtype_id: 'morning_tea', mealtype_name: 'Morning Tea' },
|
|
647
|
+
{ mealtype_id: 'afternoon_tea', mealtype_name: 'Afternoon Tea' },
|
|
648
|
+
{ mealtype_id: 'pantry', mealtype_name: 'Pantry' }
|
|
649
|
+
];
|
|
650
|
+
|
|
651
|
+
const columns = [
|
|
652
|
+
{
|
|
653
|
+
id: 'meal_code',
|
|
654
|
+
accessorKey: 'meal_code',
|
|
655
|
+
header: 'Meal Code',
|
|
656
|
+
enableColumnFilter: true,
|
|
657
|
+
// Text filter (default)
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
id: 'meal_type',
|
|
661
|
+
accessorFn: (row) => row.mealtype?.mealtype_name || 'N/A',
|
|
662
|
+
header: 'Type',
|
|
663
|
+
enableColumnFilter: true,
|
|
664
|
+
filterType: 'select',
|
|
665
|
+
filterSelectOptions: mealTypes.map(mt => ({
|
|
666
|
+
value: mt.mealtype_id,
|
|
667
|
+
label: mt.mealtype_name
|
|
668
|
+
})),
|
|
669
|
+
filterFn: (row, id, value) => {
|
|
670
|
+
return value.includes(row.original.mealtype?.mealtype_id);
|
|
671
|
+
}
|
|
672
|
+
},
|
|
673
|
+
{
|
|
674
|
+
id: 'meal_date',
|
|
675
|
+
accessorKey: 'meal_date',
|
|
676
|
+
header: 'Date',
|
|
677
|
+
enableColumnFilter: true,
|
|
678
|
+
filterType: 'date'
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
id: 'price',
|
|
682
|
+
accessorKey: 'price',
|
|
683
|
+
header: 'Price',
|
|
684
|
+
enableColumnFilter: true,
|
|
685
|
+
filterType: 'number'
|
|
686
|
+
}
|
|
687
|
+
];
|
|
1072
688
|
|
|
1073
|
-
function UserTable() {
|
|
1074
689
|
return (
|
|
1075
690
|
<DataTable
|
|
1076
|
-
data={
|
|
691
|
+
data={meals}
|
|
1077
692
|
columns={columns}
|
|
1078
|
-
title="User Management"
|
|
1079
|
-
features={{
|
|
1080
|
-
selection: true,
|
|
1081
|
-
sorting: true,
|
|
1082
|
-
filtering: true,
|
|
1083
|
-
editing: true,
|
|
1084
|
-
deletion: true,
|
|
1085
|
-
}}
|
|
1086
|
-
// Custom column order: selection first, then name, email, role, status, actions, created last
|
|
1087
|
-
columnOrder={['select', 'name', 'email', 'role', 'status', 'actions', 'createdAt']}
|
|
1088
|
-
onEditRow={handleEdit}
|
|
1089
|
-
onDeleteRow={handleDelete}
|
|
1090
|
-
/>
|
|
1091
|
-
);
|
|
1092
|
-
}
|
|
1093
|
-
```
|
|
1094
|
-
|
|
1095
|
-
### Column Ordering with Hierarchical Data
|
|
1096
|
-
|
|
1097
|
-
When using hierarchical data, column ordering works the same way:
|
|
1098
|
-
|
|
1099
|
-
```tsx
|
|
1100
|
-
<DataTable
|
|
1101
|
-
data={hierarchicalData}
|
|
1102
|
-
columns={columns}
|
|
1103
|
-
features={{
|
|
1104
|
-
hierarchical: true,
|
|
1105
|
-
selection: true,
|
|
1106
|
-
editing: true,
|
|
1107
|
-
deletion: true,
|
|
1108
|
-
}}
|
|
1109
|
-
hierarchical={{
|
|
1110
|
-
enabled: true,
|
|
1111
|
-
defaultExpanded: false,
|
|
1112
|
-
}}
|
|
1113
|
-
// Custom order for hierarchical table
|
|
1114
|
-
columnOrder={['select', 'name', 'ingredient', 'quantity', 'actions']}
|
|
1115
|
-
/>
|
|
1116
|
-
```
|
|
1117
|
-
|
|
1118
|
-
### Dynamic Column Ordering
|
|
1119
|
-
|
|
1120
|
-
You can dynamically change column order based on user preferences or application state:
|
|
1121
|
-
|
|
1122
|
-
```tsx
|
|
1123
|
-
function DynamicUserTable() {
|
|
1124
|
-
const [columnOrder, setColumnOrder] = useState(['select', 'name', 'email', 'role', 'status']);
|
|
1125
|
-
|
|
1126
|
-
const handleColumnReorder = (newOrder: string[]) => {
|
|
1127
|
-
setColumnOrder(newOrder);
|
|
1128
|
-
// Save to localStorage or user preferences
|
|
1129
|
-
localStorage.setItem('userTableColumnOrder', JSON.stringify(newOrder));
|
|
1130
|
-
};
|
|
1131
|
-
|
|
1132
|
-
return (
|
|
1133
|
-
<DataTable
|
|
1134
|
-
data={users}
|
|
1135
|
-
columns={columns}
|
|
1136
|
-
features={{
|
|
1137
|
-
selection: true,
|
|
1138
|
-
sorting: true,
|
|
1139
|
-
columnReordering: true, // Enable drag-and-drop reordering
|
|
1140
|
-
}}
|
|
1141
|
-
columnOrder={columnOrder}
|
|
1142
|
-
onColumnOrderChange={handleColumnReorder}
|
|
1143
|
-
/>
|
|
1144
|
-
);
|
|
1145
|
-
}
|
|
1146
|
-
```
|
|
1147
|
-
|
|
1148
|
-
### Column Ordering Best Practices
|
|
1149
|
-
|
|
1150
|
-
#### 1. **Consistent Ordering**
|
|
1151
|
-
```tsx
|
|
1152
|
-
// ✅ Good - Consistent ordering across similar tables
|
|
1153
|
-
const userTableOrder = ['select', 'name', 'email', 'role', 'status', 'actions'];
|
|
1154
|
-
const productTableOrder = ['select', 'name', 'sku', 'price', 'status', 'actions'];
|
|
1155
|
-
|
|
1156
|
-
// ❌ Avoid - Inconsistent ordering
|
|
1157
|
-
const userTableOrder = ['select', 'name', 'email', 'role', 'status', 'actions'];
|
|
1158
|
-
const productTableOrder = ['name', 'select', 'sku', 'actions', 'price', 'status'];
|
|
1159
|
-
```
|
|
1160
|
-
|
|
1161
|
-
#### 2. **Logical Grouping**
|
|
1162
|
-
```tsx
|
|
1163
|
-
// ✅ Good - Logical grouping of related columns
|
|
1164
|
-
columnOrder={[
|
|
1165
|
-
'select', // Selection controls
|
|
1166
|
-
'name', 'email', // Identity information
|
|
1167
|
-
'role', 'status', // Access and state
|
|
1168
|
-
'createdAt', // Metadata
|
|
1169
|
-
'actions' // Actions
|
|
1170
|
-
]}
|
|
1171
|
-
|
|
1172
|
-
// ❌ Avoid - Random column order
|
|
1173
|
-
columnOrder={['email', 'actions', 'name', 'select', 'status', 'role', 'createdAt']}
|
|
1174
|
-
```
|
|
1175
|
-
|
|
1176
|
-
#### 3. **User Experience Considerations**
|
|
1177
|
-
```tsx
|
|
1178
|
-
// ✅ Good - Most important columns first
|
|
1179
|
-
columnOrder={['select', 'name', 'email', 'role', 'status', 'actions']}
|
|
1180
|
-
|
|
1181
|
-
// ✅ Good - Actions column last (standard pattern)
|
|
1182
|
-
columnOrder={['select', 'name', 'email', 'role', 'status', 'actions']}
|
|
1183
|
-
|
|
1184
|
-
// ❌ Avoid - Actions column in the middle (confusing)
|
|
1185
|
-
columnOrder={['select', 'name', 'actions', 'email', 'role', 'status']}
|
|
1186
|
-
```
|
|
1187
|
-
|
|
1188
|
-
#### 4. **Responsive Considerations**
|
|
1189
|
-
```tsx
|
|
1190
|
-
// For mobile-first design, put most important columns first
|
|
1191
|
-
const mobileColumnOrder = ['select', 'name', 'status', 'actions'];
|
|
1192
|
-
const desktopColumnOrder = ['select', 'name', 'email', 'role', 'status', 'createdAt', 'actions'];
|
|
1193
|
-
|
|
1194
|
-
<DataTable
|
|
1195
|
-
data={users}
|
|
1196
|
-
columns={columns}
|
|
1197
|
-
features={{ selection: true, sorting: true }}
|
|
1198
|
-
columnOrder={isMobile ? mobileColumnOrder : desktopColumnOrder}
|
|
1199
|
-
/>
|
|
1200
|
-
```
|
|
1201
|
-
|
|
1202
|
-
## Data Management
|
|
1203
|
-
|
|
1204
|
-
### Sorting
|
|
1205
|
-
|
|
1206
|
-
```tsx
|
|
1207
|
-
<DataTable
|
|
1208
|
-
data={data}
|
|
1209
|
-
columns={columns}
|
|
1210
|
-
features={{ sorting: true }}
|
|
1211
|
-
defaultSorting={[
|
|
1212
|
-
{ id: 'name', desc: false },
|
|
1213
|
-
{ id: 'createdAt', desc: true }
|
|
1214
|
-
]}
|
|
1215
|
-
/>
|
|
1216
|
-
```
|
|
1217
|
-
|
|
1218
|
-
#### Default Sorting
|
|
1219
|
-
|
|
1220
|
-
You can configure your DataTable to load with pre-applied sorting:
|
|
1221
|
-
|
|
1222
|
-
```tsx
|
|
1223
|
-
<DataTable
|
|
1224
|
-
data={recipes}
|
|
1225
|
-
columns={columns}
|
|
1226
|
-
features={{ sorting: true }}
|
|
1227
|
-
defaultSorting={[
|
|
1228
|
-
{ id: 'diettype_name', desc: false },
|
|
1229
|
-
{ id: 'item_name', desc: false }
|
|
1230
|
-
]}
|
|
1231
|
-
rbac={{ pageId: 'recipes' }}
|
|
1232
|
-
/>
|
|
1233
|
-
```
|
|
1234
|
-
|
|
1235
|
-
**Examples:**
|
|
1236
|
-
- **Sort by Date (descending):** `defaultSorting={[{ id: 'created_at', desc: true }]}`
|
|
1237
|
-
- **Multi-column sort:** `defaultSorting={[{ id: 'category', desc: false }, { id: 'name', desc: false }]}`
|
|
1238
|
-
|
|
1239
|
-
### Filtering
|
|
1240
|
-
|
|
1241
|
-
```tsx
|
|
1242
|
-
<DataTable
|
|
1243
|
-
data={data}
|
|
1244
|
-
columns={columns}
|
|
1245
|
-
features={{ filtering: true }}
|
|
1246
|
-
globalFilterFn="includesString"
|
|
1247
|
-
columnFilters={[
|
|
1248
|
-
{ id: 'status', value: 'active' },
|
|
1249
|
-
{ id: 'role', value: 'admin' }
|
|
1250
|
-
]}
|
|
1251
|
-
/>
|
|
1252
|
-
```
|
|
1253
|
-
|
|
1254
|
-
### Pagination
|
|
1255
|
-
|
|
1256
|
-
```tsx
|
|
1257
|
-
<DataTable
|
|
1258
|
-
data={data}
|
|
1259
|
-
columns={columns}
|
|
1260
|
-
features={{ pagination: true }}
|
|
1261
|
-
/>
|
|
1262
|
-
```
|
|
1263
|
-
|
|
1264
|
-
#### Custom Initial Page Size
|
|
1265
|
-
|
|
1266
|
-
```tsx
|
|
1267
|
-
<DataTable
|
|
1268
|
-
data={data}
|
|
1269
|
-
columns={columns}
|
|
1270
|
-
features={{ pagination: true }}
|
|
1271
|
-
initialPageSize={25} // Start with 25 items per page instead of default 10
|
|
1272
|
-
/>
|
|
1273
|
-
```
|
|
1274
|
-
|
|
1275
|
-
#### Page Size Validation
|
|
1276
|
-
|
|
1277
|
-
The DataTable automatically validates the `initialPageSize` against available page size options:
|
|
1278
|
-
|
|
1279
|
-
```tsx
|
|
1280
|
-
<DataTable
|
|
1281
|
-
data={data}
|
|
1282
|
-
columns={columns}
|
|
1283
|
-
features={{ pagination: true }}
|
|
1284
|
-
initialPageSize={15} // Will fallback to closest valid option (10) with console warning
|
|
1285
|
-
/>
|
|
1286
|
-
```
|
|
1287
|
-
|
|
1288
|
-
**Available page size options by mode:**
|
|
1289
|
-
- **Client mode**: `[10, 25, 50]` or `[10, 25, 50, 100]` (depending on data size)
|
|
1290
|
-
- **Hybrid mode**: `[50, 100, 250, 500]`
|
|
1291
|
-
- **Server mode**: `[25, 50, 100, 250]`
|
|
1292
|
-
|
|
1293
|
-
### Search
|
|
1294
|
-
|
|
1295
|
-
```tsx
|
|
1296
|
-
<DataTable
|
|
1297
|
-
data={data}
|
|
1298
|
-
columns={columns}
|
|
1299
|
-
features={{ search: true }}
|
|
1300
|
-
searchKey="name" // Default search field
|
|
1301
|
-
searchPlaceholder="Search users..."
|
|
1302
|
-
searchDebounceMs={300}
|
|
1303
|
-
/>
|
|
1304
|
-
```
|
|
1305
|
-
|
|
1306
|
-
## Advanced Features
|
|
1307
|
-
|
|
1308
|
-
### Row Actions
|
|
1309
|
-
|
|
1310
|
-
Row actions are handled through the `actions` prop, which provides full flexibility for custom action buttons:
|
|
1311
|
-
|
|
1312
|
-
```tsx
|
|
1313
|
-
<DataTable
|
|
1314
|
-
data={data}
|
|
1315
|
-
columns={columns}
|
|
1316
|
-
features={{
|
|
1317
|
-
editing: true,
|
|
1318
|
-
deletion: true,
|
|
1319
|
-
}}
|
|
1320
|
-
onEditRow={handleEdit}
|
|
1321
|
-
onDeleteRow={handleDelete}
|
|
1322
|
-
// Custom actions using the actions prop
|
|
1323
|
-
actions={[
|
|
1324
|
-
{
|
|
1325
|
-
label: 'View Details',
|
|
1326
|
-
onClick: (row) => navigate(`/users/${row.original.id}`),
|
|
1327
|
-
icon: EyeIcon,
|
|
1328
|
-
variant: 'default',
|
|
1329
|
-
},
|
|
1330
|
-
{
|
|
1331
|
-
label: 'Edit',
|
|
1332
|
-
onClick: (row) => editUser(row.original.id),
|
|
1333
|
-
icon: EditIcon,
|
|
1334
|
-
variant: 'outline',
|
|
1335
|
-
},
|
|
1336
|
-
{
|
|
1337
|
-
label: 'Share',
|
|
1338
|
-
onClick: (row) => shareUser(row.original.id),
|
|
1339
|
-
icon: ShareIcon,
|
|
1340
|
-
variant: 'secondary',
|
|
1341
|
-
},
|
|
1342
|
-
{
|
|
1343
|
-
label: 'More Options',
|
|
1344
|
-
onClick: (row) => showMoreOptions(row.original.id),
|
|
1345
|
-
icon: MoreHorizontalIcon,
|
|
1346
|
-
variant: 'ghost',
|
|
1347
|
-
},
|
|
1348
|
-
{
|
|
1349
|
-
label: 'Archive',
|
|
1350
|
-
onClick: (row) => archiveUser(row.original.id),
|
|
1351
|
-
icon: ArchiveIcon,
|
|
1352
|
-
variant: 'destructive',
|
|
1353
|
-
disabled: (row) => row.original.status === 'archived',
|
|
1354
|
-
},
|
|
1355
|
-
]}
|
|
1356
|
-
/>
|
|
1357
|
-
```
|
|
1358
|
-
|
|
1359
|
-
#### Action Configuration
|
|
1360
|
-
|
|
1361
|
-
Each action supports the following properties:
|
|
1362
|
-
|
|
1363
|
-
- `label: string` - Display label for the action
|
|
1364
|
-
- `onClick: (row: TData) => void` - Action handler function
|
|
1365
|
-
- `icon?: ComponentType` - Optional icon component
|
|
1366
|
-
- `variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost'` - Button variant
|
|
1367
|
-
- `disabled?: (row: TData) => boolean` - Function to determine if action is disabled
|
|
1368
|
-
- `testId?: string` - Test ID for testing
|
|
1369
|
-
- `visible?: boolean | (row: TData) => boolean` - Control visibility
|
|
1370
|
-
- `showInEditMode?: boolean` - Show in edit mode (default: true)
|
|
1371
|
-
- `hideInViewMode?: boolean` - Hide in view mode (default: false)
|
|
1372
|
-
|
|
1373
|
-
#### Action Variants Guide
|
|
1374
|
-
|
|
1375
|
-
The DataTable supports 5 action variants, each designed for specific use cases:
|
|
1376
|
-
|
|
1377
|
-
```tsx
|
|
1378
|
-
const actions = [
|
|
1379
|
-
// DEFAULT - Primary actions (most important)
|
|
1380
|
-
{
|
|
1381
|
-
label: 'View Details',
|
|
1382
|
-
onClick: (row) => viewDetails(row.original.id),
|
|
1383
|
-
icon: EyeIcon,
|
|
1384
|
-
variant: 'default', // Solid background, primary color
|
|
1385
|
-
},
|
|
1386
|
-
|
|
1387
|
-
// OUTLINE - Secondary actions (important but not primary)
|
|
1388
|
-
{
|
|
1389
|
-
label: 'Edit',
|
|
1390
|
-
onClick: (row) => editItem(row.original.id),
|
|
1391
|
-
icon: EditIcon,
|
|
1392
|
-
variant: 'outline', // Bordered, transparent background
|
|
1393
|
-
},
|
|
1394
|
-
|
|
1395
|
-
// SECONDARY - Supporting actions (less prominent)
|
|
1396
|
-
{
|
|
1397
|
-
label: 'Share',
|
|
1398
|
-
onClick: (row) => shareItem(row.original.id),
|
|
1399
|
-
icon: ShareIcon,
|
|
1400
|
-
variant: 'secondary', // Muted background, subtle styling
|
|
1401
|
-
},
|
|
1402
|
-
|
|
1403
|
-
// GHOST - Subtle actions (minimal visual impact)
|
|
1404
|
-
{
|
|
1405
|
-
label: 'More Options',
|
|
1406
|
-
onClick: (row) => showMoreOptions(row.original.id),
|
|
1407
|
-
icon: MoreHorizontalIcon,
|
|
1408
|
-
variant: 'ghost', // No background, minimal styling
|
|
1409
|
-
},
|
|
1410
|
-
|
|
1411
|
-
// DESTRUCTIVE - Dangerous actions (require caution)
|
|
1412
|
-
{
|
|
1413
|
-
label: 'Delete',
|
|
1414
|
-
onClick: (row) => deleteItem(row.original.id),
|
|
1415
|
-
icon: TrashIcon,
|
|
1416
|
-
variant: 'destructive', // Red styling to indicate danger
|
|
1417
|
-
disabled: (row) => row.original.status === 'locked',
|
|
1418
|
-
},
|
|
1419
|
-
];
|
|
1420
|
-
```
|
|
1421
|
-
|
|
1422
|
-
**Variant Usage Guidelines:**
|
|
1423
|
-
|
|
1424
|
-
- **`default`**: Use for the most important action (e.g., "View Details", "Open")
|
|
1425
|
-
- **`outline`**: Use for important secondary actions (e.g., "Edit", "Download")
|
|
1426
|
-
- **`secondary`**: Use for supporting actions (e.g., "Share", "Copy", "Export")
|
|
1427
|
-
- **`ghost`**: Use for subtle actions (e.g., "More Options", "Settings")
|
|
1428
|
-
- **`destructive`**: Use for dangerous actions (e.g., "Delete", "Archive", "Remove")
|
|
1429
|
-
|
|
1430
|
-
#### Hierarchical Actions
|
|
1431
|
-
|
|
1432
|
-
When using hierarchical rows, you can define different action icons and labels for parent vs child rows:
|
|
1433
|
-
|
|
1434
|
-
```tsx
|
|
1435
|
-
<DataTable
|
|
1436
|
-
data={hierarchicalData}
|
|
1437
|
-
columns={columns}
|
|
1438
|
-
features={{
|
|
1439
|
-
hierarchical: true,
|
|
1440
|
-
editing: true,
|
|
1441
|
-
deletion: true,
|
|
1442
|
-
}}
|
|
1443
|
-
hierarchical={{
|
|
1444
|
-
enabled: true,
|
|
1445
|
-
defaultExpanded: false,
|
|
1446
|
-
}}
|
|
1447
|
-
actions={[
|
|
1448
|
-
{
|
|
1449
|
-
label: 'View Details',
|
|
1450
|
-
icon: EyeIcon,
|
|
1451
|
-
// Different icons for parent vs child rows
|
|
1452
|
-
parentIcon: EyeIcon,
|
|
1453
|
-
childIcon: InfoIcon,
|
|
1454
|
-
// Different labels for parent vs child rows
|
|
1455
|
-
parentLabel: 'View Recipe',
|
|
1456
|
-
childLabel: 'View Ingredient',
|
|
1457
|
-
onClick: (row) => {
|
|
1458
|
-
console.log('Viewing:', row.isParent ? 'Recipe' : 'Ingredient', row.name);
|
|
1459
|
-
},
|
|
1460
|
-
variant: 'default',
|
|
1461
|
-
},
|
|
1462
|
-
{
|
|
1463
|
-
label: 'Edit',
|
|
1464
|
-
icon: PencilIcon,
|
|
1465
|
-
parentIcon: SettingsIcon,
|
|
1466
|
-
childIcon: PencilIcon,
|
|
1467
|
-
parentLabel: 'Edit Recipe',
|
|
1468
|
-
childLabel: 'Edit Ingredient',
|
|
1469
|
-
onClick: (row) => {
|
|
1470
|
-
console.log('Editing:', row.isParent ? 'Recipe' : 'Ingredient', row.name);
|
|
1471
|
-
},
|
|
1472
|
-
variant: 'default',
|
|
1473
|
-
},
|
|
1474
|
-
{
|
|
1475
|
-
label: 'Copy Recipe',
|
|
1476
|
-
icon: CopyIcon,
|
|
1477
|
-
parentIcon: CopyIcon,
|
|
1478
|
-
parentLabel: 'Duplicate Recipe',
|
|
1479
|
-
onClick: (row) => {
|
|
1480
|
-
console.log('Copying recipe:', row.name);
|
|
1481
|
-
},
|
|
1482
|
-
variant: 'outline',
|
|
1483
|
-
// Only show for parent rows
|
|
1484
|
-
showForParent: true,
|
|
1485
|
-
},
|
|
1486
|
-
{
|
|
1487
|
-
label: 'Export',
|
|
1488
|
-
icon: DownloadIcon,
|
|
1489
|
-
childIcon: DownloadIcon,
|
|
1490
|
-
childLabel: 'Export Ingredient',
|
|
1491
|
-
onClick: (row) => {
|
|
1492
|
-
console.log('Exporting ingredient:', row.item);
|
|
1493
|
-
},
|
|
1494
|
-
variant: 'ghost',
|
|
1495
|
-
// Only show for child rows
|
|
1496
|
-
showForChild: true,
|
|
1497
|
-
},
|
|
1498
|
-
]}
|
|
1499
|
-
/>
|
|
1500
|
-
```
|
|
1501
|
-
|
|
1502
|
-
##### Hierarchical Action Properties
|
|
1503
|
-
|
|
1504
|
-
When using hierarchical rows, actions support additional properties:
|
|
1505
|
-
|
|
1506
|
-
- `showForParent?: boolean` - Only show this action for parent rows
|
|
1507
|
-
- `showForChild?: boolean` - Only show this action for child rows
|
|
1508
|
-
- `parentIcon?: ComponentType` - Icon to use for parent rows (overrides `icon`)
|
|
1509
|
-
- `childIcon?: ComponentType` - Icon to use for child rows (overrides `icon`)
|
|
1510
|
-
- `parentLabel?: string` - Label to use for parent rows (overrides `label`)
|
|
1511
|
-
- `childLabel?: string` - Label to use for child rows (overrides `label`)
|
|
1512
|
-
|
|
1513
|
-
##### Action Visibility Logic
|
|
1514
|
-
|
|
1515
|
-
The action visibility is determined by the following logic:
|
|
1516
|
-
|
|
1517
|
-
1. **Hierarchical filtering**: If `showForParent` is true, action only shows for parent rows
|
|
1518
|
-
2. **Hierarchical filtering**: If `showForChild` is true, action only shows for child rows
|
|
1519
|
-
3. **Standard filtering**: Uses `visible`, `showInEditMode`, `hideInViewMode` properties
|
|
1520
|
-
4. **Disabled state**: Uses `disabled` function to determine if action is disabled
|
|
1521
|
-
|
|
1522
|
-
### Toolbar Actions
|
|
1523
|
-
|
|
1524
|
-
Toolbar actions are automatically generated based on the enabled features. Custom toolbar buttons are not directly supported in the current interface, but you can implement them outside the DataTable component:
|
|
1525
|
-
|
|
1526
|
-
```tsx
|
|
1527
|
-
// Toolbar actions are handled through feature configuration
|
|
1528
|
-
<div>
|
|
1529
|
-
{/* Custom toolbar */}
|
|
1530
|
-
<div className="flex gap-2 mb-4">
|
|
1531
|
-
<Button onClick={() => navigate('/users/create')}>
|
|
1532
|
-
<PlusIcon className="h-4 w-4 mr-2" />
|
|
1533
|
-
Add User
|
|
1534
|
-
</Button>
|
|
1535
|
-
</div>
|
|
1536
|
-
|
|
1537
|
-
<DataTable
|
|
1538
|
-
data={data}
|
|
1539
|
-
columns={columns}
|
|
1540
|
-
features={{
|
|
1541
|
-
search: true,
|
|
1542
|
-
pagination: true,
|
|
1543
|
-
export: true,
|
|
1544
|
-
import: true,
|
|
1545
|
-
creation: true, // Enables built-in create functionality if onCreateRow is provided
|
|
1546
|
-
}}
|
|
1547
|
-
onCreateRow={handleCreate}
|
|
1548
|
-
onImport={handleImport}
|
|
1549
|
-
exportFilename="users"
|
|
1550
|
-
/>
|
|
1551
|
-
</div>
|
|
1552
|
-
```
|
|
1553
|
-
|
|
1554
|
-
### Hierarchical Parent/Child Rows
|
|
1555
|
-
|
|
1556
|
-
The DataTable supports hierarchical parent/child row relationships with built-in expand/collapse functionality, expand/collapse all controls, and proper column rendering for different row types.
|
|
1557
|
-
|
|
1558
|
-
#### Data Structure
|
|
1559
|
-
|
|
1560
|
-
Your data must conform to the `HierarchicalDataRow` interface:
|
|
1561
|
-
|
|
1562
|
-
```typescript
|
|
1563
|
-
interface HierarchicalDataRow extends DataRecord {
|
|
1564
|
-
/** Whether this row is a parent row */
|
|
1565
|
-
isParent: boolean;
|
|
1566
|
-
/** For child rows, references the parent row ID */
|
|
1567
|
-
parentId?: string;
|
|
1568
|
-
/** Unique identifier for this row */
|
|
1569
|
-
id: string;
|
|
1570
|
-
// ... your actual data properties
|
|
1571
|
-
}
|
|
1572
|
-
```
|
|
1573
|
-
|
|
1574
|
-
#### Configuration
|
|
1575
|
-
|
|
1576
|
-
Enable hierarchical functionality through the `features` prop and configure it with the `hierarchical` prop:
|
|
1577
|
-
|
|
1578
|
-
```tsx
|
|
1579
|
-
<DataTable
|
|
1580
|
-
data={hierarchicalData}
|
|
1581
|
-
columns={columns}
|
|
1582
|
-
features={{
|
|
1583
|
-
// ... other features
|
|
1584
|
-
hierarchical: true, // Enable hierarchical functionality
|
|
1585
|
-
}}
|
|
1586
|
-
hierarchical={{
|
|
1587
|
-
enabled: true,
|
|
1588
|
-
defaultExpanded: false, // true = all expanded, false = all collapsed, array = specific IDs
|
|
1589
|
-
onExpandedChange: (expandedIds) => console.log('Expanded:', expandedIds),
|
|
1590
|
-
expandButton: CustomExpandButton, // Optional custom expand button
|
|
1591
|
-
indentSize: 24, // Indentation for child rows in pixels
|
|
1592
|
-
parentRowClassName: 'bg-main-50 font-medium', // Custom styling for parent rows
|
|
1593
|
-
childRowClassName: 'bg-sec-25', // Custom styling for child rows
|
|
1594
|
-
}}
|
|
1595
|
-
```
|
|
1596
|
-
|
|
1597
|
-
**Default Collapsed State:**
|
|
1598
|
-
To make parent rows collapsed by default (recommended for better UX), set `defaultExpanded: false`. This ensures users see only parent rows initially and must click to expand and view child rows.
|
|
1599
|
-
|
|
1600
|
-
#### Column Configuration
|
|
1601
|
-
|
|
1602
|
-
Configure how columns render for different row types:
|
|
1603
|
-
|
|
1604
|
-
```tsx
|
|
1605
|
-
const columns: DataTableColumn<YourDataType>[] = [
|
|
1606
|
-
{
|
|
1607
|
-
accessorKey: 'code',
|
|
1608
|
-
header: 'Code',
|
|
1609
|
-
// Render differently for parent vs child rows
|
|
1610
|
-
renderForParent: (row) => (
|
|
1611
|
-
<div className="flex items-center gap-2">
|
|
1612
|
-
<strong>{row.code}</strong>
|
|
1613
|
-
</div>
|
|
1614
|
-
),
|
|
1615
|
-
renderForChild: () => '', // Blank for child rows
|
|
1616
|
-
hideForChild: true, // Hide this column for child rows
|
|
1617
|
-
},
|
|
1618
|
-
{
|
|
1619
|
-
accessorKey: 'name',
|
|
1620
|
-
header: 'Name',
|
|
1621
|
-
renderForParent: (row) => <strong>{row.name}</strong>,
|
|
1622
|
-
renderForChild: () => '', // Blank for child rows
|
|
1623
|
-
hideForChild: true,
|
|
1624
|
-
},
|
|
1625
|
-
{
|
|
1626
|
-
accessorKey: 'ingredient',
|
|
1627
|
-
header: 'Ingredient',
|
|
1628
|
-
renderForParent: () => '', // Blank for parent rows
|
|
1629
|
-
renderForChild: (row) => <span className="ml-4">{row.ingredient}</span>,
|
|
1630
|
-
hideForParent: true, // Hide this column for parent rows
|
|
1631
|
-
},
|
|
1632
|
-
];
|
|
1633
|
-
```
|
|
1634
|
-
|
|
1635
|
-
#### Column Properties
|
|
1636
|
-
|
|
1637
|
-
- `renderForParent?: (row: TData) => React.ReactNode` - Custom renderer for parent rows
|
|
1638
|
-
- `renderForChild?: (row: TData) => React.ReactNode` - Custom renderer for child rows
|
|
1639
|
-
- `hideForParent?: boolean` - Hide this column for parent rows
|
|
1640
|
-
- `hideForChild?: boolean` - Hide this column for child rows
|
|
1641
|
-
|
|
1642
|
-
#### Expand/Collapse All Controls
|
|
1643
|
-
|
|
1644
|
-
The DataTable automatically adds an expand/collapse all button in the header when hierarchical mode is enabled:
|
|
1645
|
-
|
|
1646
|
-
**Features:**
|
|
1647
|
-
- **Expand All Button (▶️)**: Expands all parent rows to show their children
|
|
1648
|
-
- **Collapse All Button (🔽)**: Collapses all parent rows to hide their children
|
|
1649
|
-
- **Smart State Detection**: Button automatically updates based on current expansion state
|
|
1650
|
-
- **Accessibility**: Proper ARIA labels and keyboard navigation support
|
|
1651
|
-
|
|
1652
|
-
**Usage:**
|
|
1653
|
-
```tsx
|
|
1654
|
-
<DataTable
|
|
1655
|
-
data={hierarchicalData}
|
|
1656
|
-
columns={columns}
|
|
1657
|
-
features={{ hierarchical: true }}
|
|
1658
|
-
hierarchical={{
|
|
1659
|
-
enabled: true,
|
|
1660
|
-
defaultExpanded: false, // Start with all collapsed
|
|
1661
|
-
onExpandedChange: (expandedIds) => {
|
|
1662
|
-
console.log('Expanded rows:', expandedIds);
|
|
1663
|
-
},
|
|
1664
|
-
}}
|
|
1665
|
-
/>
|
|
1666
|
-
```
|
|
1667
|
-
|
|
1668
|
-
**Button Behavior:**
|
|
1669
|
-
- Shows ▶️ when some or all parent rows are collapsed
|
|
1670
|
-
- Shows 🔽 when all parent rows are expanded
|
|
1671
|
-
- Only appears when there are parent rows with children
|
|
1672
|
-
- Positioned in the first column of the table header
|
|
1673
|
-
|
|
1674
|
-
#### Hierarchical Sorting
|
|
1675
|
-
|
|
1676
|
-
When hierarchical mode is enabled, sorting behavior is optimized to preserve the parent-child relationship structure:
|
|
1677
|
-
|
|
1678
|
-
**Parent Row Sorting:**
|
|
1679
|
-
- Parent rows maintain their original order and are not affected by sorting
|
|
1680
|
-
- Only child rows within each parent group are sorted
|
|
1681
|
-
- This prevents parent rows from being moved to unexpected positions
|
|
1682
|
-
|
|
1683
|
-
**Child Row Sorting:**
|
|
1684
|
-
- Child rows are sorted within their parent group only
|
|
1685
|
-
- Sorting by a child-specific column (e.g., "Diet", "Ingredient") will sort children within each parent
|
|
1686
|
-
- The parent row remains at the top of its group
|
|
1687
|
-
|
|
1688
|
-
**Example:**
|
|
1689
|
-
```tsx
|
|
1690
|
-
// When sorting by "Diet" column:
|
|
1691
|
-
// ✅ Correct behavior:
|
|
1692
|
-
// Parent: Caesar Salad
|
|
1693
|
-
// ├─ Child: Dairy Free (Sour cream)
|
|
1694
|
-
// ├─ Child: Egg Free (Mayonnaise)
|
|
1695
|
-
// └─ Child: GF & Vegan (Lettuce)
|
|
1696
|
-
// Parent: Beef Stir Fry
|
|
1697
|
-
// ├─ Child: GF & Vegan (Vegetables)
|
|
1698
|
-
// └─ Child: Halal (Beef)
|
|
1699
|
-
|
|
1700
|
-
// ❌ Incorrect behavior (what we prevent):
|
|
1701
|
-
// Child: Dairy Free (Sour cream)
|
|
1702
|
-
// Child: Egg Free (Mayonnaise)
|
|
1703
|
-
// Child: GF & Vegan (Lettuce)
|
|
1704
|
-
// Child: GF & Vegan (Vegetables)
|
|
1705
|
-
// Child: Halal (Beef)
|
|
1706
|
-
// Parent: Caesar Salad (moved to end)
|
|
1707
|
-
// Parent: Beef Stir Fry (moved to end)
|
|
1708
|
-
```
|
|
1709
|
-
|
|
1710
|
-
**Sorting Configuration:**
|
|
1711
|
-
- All existing sorting features work with hierarchical data
|
|
1712
|
-
- Multi-column sorting is supported
|
|
1713
|
-
- Server-side sorting is compatible
|
|
1714
|
-
- Column-specific sorting behavior is preserved
|
|
1715
|
-
|
|
1716
|
-
#### Example Use Case
|
|
1717
|
-
|
|
1718
|
-
```tsx
|
|
1719
|
-
// Dishes and ingredients example
|
|
1720
|
-
const dishData = [
|
|
1721
|
-
// Parent rows (dishes)
|
|
1722
|
-
{ id: 'dish-1', isParent: true, code: 'D001', name: 'Caesar Salad', type: 'Salad' },
|
|
1723
|
-
{ id: 'dish-2', isParent: true, code: 'D002', name: 'Beef Stir Fry', type: 'Main Course' },
|
|
1724
|
-
|
|
1725
|
-
// Child rows (ingredients) for Caesar Salad
|
|
1726
|
-
{ id: 'ing-1-1', isParent: false, parentId: 'dish-1', ingredient: 'Lettuce', quantity: '200g' },
|
|
1727
|
-
{ id: 'ing-1-2', isParent: false, parentId: 'dish-1', ingredient: 'Chicken', quantity: '150g' },
|
|
1728
|
-
|
|
1729
|
-
// Child rows (ingredients) for Beef Stir Fry
|
|
1730
|
-
{ id: 'ing-2-1', isParent: false, parentId: 'dish-2', ingredient: 'Beef Strips', quantity: '250g' },
|
|
1731
|
-
{ id: 'ing-2-2', isParent: false, parentId: 'dish-2', ingredient: 'Mixed Vegetables', quantity: '200g' },
|
|
1732
|
-
];
|
|
1733
|
-
```
|
|
1734
|
-
|
|
1735
|
-
### Row Selection and Bulk Operations
|
|
1736
|
-
|
|
1737
|
-
```tsx
|
|
1738
|
-
function SelectableUserTable() {
|
|
1739
|
-
const [selectedRows, setSelectedRows] = useState<Record<string, boolean>>({});
|
|
1740
|
-
const [data, setData] = useState<User[]>(initialData);
|
|
1741
|
-
|
|
1742
|
-
const handleBulkDelete = (selectedRows: Record<string, boolean>) => {
|
|
1743
|
-
// Get the selected row IDs
|
|
1744
|
-
const selectedRowIds = Object.keys(selectedRows).filter(id => selectedRows[id]);
|
|
1745
|
-
|
|
1746
|
-
// Get the actual data for selected rows
|
|
1747
|
-
const selectedData = data.filter(row => {
|
|
1748
|
-
const rowId = getRowId ? getRowId(row, data.indexOf(row)) : row.id;
|
|
1749
|
-
return selectedRowIds.includes(rowId);
|
|
1750
|
-
});
|
|
1751
|
-
|
|
1752
|
-
// Perform your deletion logic
|
|
1753
|
-
console.log('Deleting selected rows:', selectedData);
|
|
1754
|
-
|
|
1755
|
-
// Update your data state
|
|
1756
|
-
setData(prevData =>
|
|
1757
|
-
prevData.filter(row => {
|
|
1758
|
-
const rowId = getRowId ? getRowId(row, prevData.indexOf(row)) : row.id;
|
|
1759
|
-
return !selectedRowIds.includes(rowId);
|
|
1760
|
-
})
|
|
1761
|
-
);
|
|
1762
|
-
|
|
1763
|
-
// Clear selection after deletion
|
|
1764
|
-
setSelectedRows({});
|
|
1765
|
-
};
|
|
1766
|
-
|
|
1767
|
-
return (
|
|
1768
|
-
<DataTable
|
|
1769
|
-
data={data}
|
|
1770
|
-
columns={columns}
|
|
1771
|
-
features={{
|
|
1772
|
-
selection: true,
|
|
1773
|
-
deletion: true, // Required for delete functionality
|
|
1774
|
-
deleteSelected: true, // Enables the delete selected button
|
|
1775
|
-
bulkOperations: true,
|
|
1776
|
-
}}
|
|
1777
|
-
onRowSelectionChange={setSelectedRows}
|
|
1778
|
-
onDeleteSelected={handleBulkDelete}
|
|
1779
|
-
getRowId={(row) => row.id} // Important: provide getRowId for proper row identification
|
|
1780
|
-
/>
|
|
1781
|
-
);
|
|
1782
|
-
}
|
|
1783
|
-
```
|
|
1784
|
-
|
|
1785
|
-
#### Alternative: Using Individual onDeleteRow (Fallback)
|
|
1786
|
-
|
|
1787
|
-
If you don't provide `onDeleteSelected`, the DataTable will automatically fall back to calling `onDeleteRow` for each selected row:
|
|
1788
|
-
|
|
1789
|
-
```tsx
|
|
1790
|
-
function SelectableUserTable() {
|
|
1791
|
-
const [data, setData] = useState<User[]>(initialData);
|
|
1792
|
-
|
|
1793
|
-
const handleDeleteRow = (row: User) => {
|
|
1794
|
-
console.log('Deleting row:', row);
|
|
1795
|
-
|
|
1796
|
-
// Update your data state
|
|
1797
|
-
setData(prevData => prevData.filter(item => item.id !== row.id));
|
|
1798
|
-
};
|
|
1799
|
-
|
|
1800
|
-
return (
|
|
1801
|
-
<DataTable
|
|
1802
|
-
data={data}
|
|
1803
|
-
columns={columns}
|
|
1804
|
-
features={{
|
|
1805
|
-
selection: true,
|
|
1806
|
-
deletion: true,
|
|
1807
|
-
deleteSelected: true,
|
|
1808
|
-
}}
|
|
1809
|
-
onDeleteRow={handleDeleteRow} // This will be called for each selected row
|
|
1810
|
-
getRowId={(row) => row.id}
|
|
1811
|
-
/>
|
|
1812
|
-
);
|
|
1813
|
-
}
|
|
1814
|
-
```
|
|
1815
|
-
|
|
1816
|
-
### Column Visibility
|
|
1817
|
-
|
|
1818
|
-
```tsx
|
|
1819
|
-
<DataTable
|
|
1820
|
-
data={data}
|
|
1821
|
-
columns={columns}
|
|
1822
|
-
features={{ columnVisibility: true }}
|
|
1823
|
-
defaultColumnVisibility={{
|
|
1824
|
-
id: false,
|
|
1825
|
-
createdAt: false,
|
|
1826
|
-
}}
|
|
1827
|
-
/>
|
|
1828
|
-
```
|
|
1829
|
-
|
|
1830
|
-
### Data Grouping
|
|
1831
|
-
|
|
1832
|
-
```tsx
|
|
1833
|
-
<DataTable
|
|
1834
|
-
data={data}
|
|
1835
|
-
columns={columns}
|
|
1836
|
-
features={{ grouping: true }}
|
|
1837
|
-
grouping={['role', 'status']}
|
|
1838
|
-
/>
|
|
1839
|
-
```
|
|
1840
|
-
|
|
1841
|
-
#### Default Grouping
|
|
1842
|
-
|
|
1843
|
-
You can configure your DataTable to load with pre-applied grouping:
|
|
1844
|
-
|
|
1845
|
-
```tsx
|
|
1846
|
-
<DataTable
|
|
1847
|
-
data={recipes}
|
|
1848
|
-
columns={columns}
|
|
1849
|
-
features={{ grouping: true }}
|
|
1850
|
-
defaultGrouping={['diettype_name']}
|
|
1851
|
-
rbac={{ pageId: 'recipes' }}
|
|
1852
|
-
/>
|
|
1853
|
-
```
|
|
1854
|
-
|
|
1855
|
-
**Examples:**
|
|
1856
|
-
- **Group by Status:** `defaultGrouping={['status']}`
|
|
1857
|
-
- **Multi-level grouping:** `defaultGrouping={['category', 'status']}`
|
|
1858
|
-
|
|
1859
|
-
#### Combined Default Grouping and Sorting
|
|
1860
|
-
|
|
1861
|
-
You can combine both default grouping and sorting for a fully pre-organized view:
|
|
1862
|
-
|
|
1863
|
-
```tsx
|
|
1864
|
-
<DataTable
|
|
1865
|
-
data={recipes}
|
|
1866
|
-
columns={columns}
|
|
1867
|
-
features={{
|
|
1868
|
-
grouping: true,
|
|
1869
|
-
sorting: true,
|
|
1870
|
-
}}
|
|
1871
|
-
defaultGrouping={['diettype_name']}
|
|
1872
|
-
defaultSorting={[
|
|
1873
|
-
{ id: 'diettype_name', desc: false },
|
|
1874
|
-
{ id: 'item_name', desc: false }
|
|
1875
|
-
]}
|
|
1876
|
-
rbac={{ pageId: 'recipes' }}
|
|
1877
|
-
/>
|
|
1878
|
-
```
|
|
1879
|
-
|
|
1880
|
-
**Behavior:**
|
|
1881
|
-
- **defaultGrouping**: Array of column IDs to group by on initial load
|
|
1882
|
-
- **defaultSorting**: Array of sort configurations (column ID + direction)
|
|
1883
|
-
- Both props are optional and work independently or together
|
|
1884
|
-
- Users can still modify grouping/sorting via UI controls
|
|
1885
|
-
- Works with all DataTable features (pagination, filtering, etc.)
|
|
1886
|
-
|
|
1887
|
-
## Performance Optimization
|
|
1888
|
-
|
|
1889
|
-
### Virtual Scrolling
|
|
1890
|
-
|
|
1891
|
-
```tsx
|
|
1892
|
-
<DataTable
|
|
1893
|
-
data={largeDataset}
|
|
1894
|
-
columns={columns}
|
|
1895
|
-
features={{ virtualization: true }}
|
|
1896
|
-
virtualHeight={600} // Height of the virtual scrolling container
|
|
1897
|
-
performance={{
|
|
1898
|
-
virtualScrolling: true,
|
|
1899
|
-
memoryOptimization: true,
|
|
1900
|
-
}}
|
|
1901
|
-
/>
|
|
1902
|
-
```
|
|
1903
|
-
|
|
1904
|
-
### Lazy Loading
|
|
1905
|
-
|
|
1906
|
-
```tsx
|
|
1907
|
-
function LazyLoadingTable() {
|
|
1908
|
-
const [data, setData] = useState<User[]>([]);
|
|
1909
|
-
const [loading, setLoading] = useState(false);
|
|
1910
|
-
const [hasMore, setHasMore] = useState(true);
|
|
1911
|
-
|
|
1912
|
-
const loadMoreData = async () => {
|
|
1913
|
-
setLoading(true);
|
|
1914
|
-
const newData = await fetchUsers(data.length, 50);
|
|
1915
|
-
setData(prev => [...prev, ...newData]);
|
|
1916
|
-
setHasMore(newData.length === 50);
|
|
1917
|
-
setLoading(false);
|
|
1918
|
-
};
|
|
1919
|
-
|
|
1920
|
-
return (
|
|
1921
|
-
<DataTable
|
|
1922
|
-
data={data}
|
|
1923
|
-
columns={columns}
|
|
1924
|
-
loading={loading}
|
|
1925
|
-
onLoadMore={hasMore ? loadMoreData : undefined}
|
|
1926
|
-
hasMore={hasMore}
|
|
1927
|
-
/>
|
|
1928
|
-
);
|
|
1929
|
-
}
|
|
1930
|
-
```
|
|
1931
|
-
|
|
1932
|
-
### Optimized Rendering
|
|
1933
|
-
|
|
1934
|
-
```tsx
|
|
1935
|
-
<DataTable
|
|
1936
|
-
data={data}
|
|
1937
|
-
columns={columns}
|
|
1938
|
-
features={{
|
|
1939
|
-
virtualization: true,
|
|
1940
|
-
performanceMetrics: true,
|
|
1941
|
-
}}
|
|
1942
|
-
performance={{
|
|
1943
|
-
virtualScrolling: true,
|
|
1944
|
-
memoryOptimization: true,
|
|
1945
|
-
renderOptimization: true,
|
|
1946
|
-
}}
|
|
1947
|
-
showPerformanceMetrics={true}
|
|
1948
|
-
virtualHeight={600}
|
|
1949
|
-
/>
|
|
1950
|
-
```
|
|
1951
|
-
|
|
1952
|
-
## Integration with RBAC
|
|
1953
|
-
|
|
1954
|
-
### Permission-Based Actions
|
|
1955
|
-
|
|
1956
|
-
```tsx
|
|
1957
|
-
function SecureUserTable() {
|
|
1958
|
-
const { checkPermission } = usePermissionCache();
|
|
1959
|
-
|
|
1960
|
-
const [permissions, setPermissions] = useState({
|
|
1961
|
-
canCreate: false,
|
|
1962
|
-
canUpdate: false,
|
|
1963
|
-
canDelete: false,
|
|
1964
|
-
});
|
|
1965
|
-
|
|
1966
|
-
useEffect(() => {
|
|
1967
|
-
const loadPermissions = async () => {
|
|
1968
|
-
const results = await Promise.all([
|
|
1969
|
-
checkPermission('create', 'user-management'),
|
|
1970
|
-
checkPermission('update', 'user-management'),
|
|
1971
|
-
checkPermission('delete', 'user-management'),
|
|
1972
|
-
]);
|
|
1973
|
-
|
|
1974
|
-
setPermissions({
|
|
1975
|
-
canCreate: results[0],
|
|
1976
|
-
canUpdate: results[1],
|
|
1977
|
-
canDelete: results[2],
|
|
1978
|
-
});
|
|
1979
|
-
};
|
|
1980
|
-
|
|
1981
|
-
loadPermissions();
|
|
1982
|
-
}, [checkPermission]);
|
|
1983
|
-
|
|
1984
|
-
return (
|
|
1985
|
-
<DataTable
|
|
1986
|
-
data={users}
|
|
1987
|
-
columns={columns}
|
|
1988
|
-
rbac={{
|
|
1989
|
-
resource: 'users',
|
|
1990
|
-
pageId: 'user-management'
|
|
1991
|
-
}}
|
|
1992
693
|
features={{
|
|
694
|
+
filtering: true, // Enable filtering
|
|
1993
695
|
search: true,
|
|
1994
696
|
pagination: true,
|
|
1995
|
-
|
|
1996
|
-
editing: permissions.canUpdate,
|
|
1997
|
-
deletion: permissions.canDelete,
|
|
1998
|
-
rowActions: permissions.canUpdate || permissions.canDelete,
|
|
1999
|
-
}}
|
|
2000
|
-
onEditRow={permissions.canUpdate ? (row) => navigate(`/users/${row.original.id}/edit`) : undefined}
|
|
2001
|
-
onDeleteRow={permissions.canDelete ? (row) => handleDelete(row.original.id) : undefined}
|
|
2002
|
-
// Custom actions using deprecated actions prop (still supported)
|
|
2003
|
-
actions={
|
|
2004
|
-
permissions.canUpdate || permissions.canDelete ? [
|
|
2005
|
-
{
|
|
2006
|
-
label: 'View Details',
|
|
2007
|
-
onClick: (row) => navigate(`/users/${row.original.id}`),
|
|
2008
|
-
}
|
|
2009
|
-
] : []
|
|
2010
|
-
}
|
|
2011
|
-
/>
|
|
2012
|
-
);
|
|
2013
|
-
}
|
|
2014
|
-
```
|
|
2015
|
-
|
|
2016
|
-
## Practical Examples
|
|
2017
|
-
|
|
2018
|
-
### Custom Page Size Configuration
|
|
2019
|
-
|
|
2020
|
-
Here's a complete example showing how to configure a DataTable with custom initial page size:
|
|
2021
|
-
|
|
2022
|
-
```tsx
|
|
2023
|
-
import { DataTable, type DataTableColumn } from '@jmruthers/pace-core';
|
|
2024
|
-
|
|
2025
|
-
interface User {
|
|
2026
|
-
id: string;
|
|
2027
|
-
name: string;
|
|
2028
|
-
email: string;
|
|
2029
|
-
role: string;
|
|
2030
|
-
status: 'active' | 'inactive';
|
|
2031
|
-
}
|
|
2032
|
-
|
|
2033
|
-
const columns: DataTableColumn<User>[] = [
|
|
2034
|
-
{
|
|
2035
|
-
accessorKey: 'name',
|
|
2036
|
-
header: 'Name',
|
|
2037
|
-
sortable: true,
|
|
2038
|
-
searchable: true,
|
|
2039
|
-
},
|
|
2040
|
-
{
|
|
2041
|
-
accessorKey: 'email',
|
|
2042
|
-
header: 'Email',
|
|
2043
|
-
sortable: true,
|
|
2044
|
-
searchable: true,
|
|
2045
|
-
},
|
|
2046
|
-
{
|
|
2047
|
-
accessorKey: 'role',
|
|
2048
|
-
header: 'Role',
|
|
2049
|
-
sortable: true,
|
|
2050
|
-
enableGrouping: true,
|
|
2051
|
-
},
|
|
2052
|
-
{
|
|
2053
|
-
accessorKey: 'status',
|
|
2054
|
-
header: 'Status',
|
|
2055
|
-
sortable: true,
|
|
2056
|
-
cell: ({ row }) => (
|
|
2057
|
-
<span className={`px-2 py-1 rounded text-xs ${
|
|
2058
|
-
row.original.status === 'active'
|
|
2059
|
-
? 'bg-main-100 text-main-800'
|
|
2060
|
-
: 'bg-acc-100 text-acc-800'
|
|
2061
|
-
}`}>
|
|
2062
|
-
{row.original.status}
|
|
2063
|
-
</span>
|
|
2064
|
-
),
|
|
2065
|
-
},
|
|
2066
|
-
];
|
|
2067
|
-
|
|
2068
|
-
function UserManagementTable() {
|
|
2069
|
-
const [users, setUsers] = useState<User[]>([]);
|
|
2070
|
-
const [loading, setLoading] = useState(true);
|
|
2071
|
-
|
|
2072
|
-
useEffect(() => {
|
|
2073
|
-
// Load users data
|
|
2074
|
-
loadUsers().then(setUsers).finally(() => setLoading(false));
|
|
2075
|
-
}, []);
|
|
2076
|
-
|
|
2077
|
-
return (
|
|
2078
|
-
<DataTable
|
|
2079
|
-
data={users}
|
|
2080
|
-
columns={columns}
|
|
2081
|
-
title="User Management"
|
|
2082
|
-
description="Manage system users with custom page size"
|
|
2083
|
-
features={{
|
|
2084
|
-
search: true,
|
|
2085
|
-
pagination: true,
|
|
2086
|
-
sorting: true,
|
|
2087
|
-
filtering: true,
|
|
2088
|
-
selection: true,
|
|
2089
|
-
creation: true,
|
|
2090
|
-
editing: true,
|
|
2091
|
-
deletion: true,
|
|
2092
|
-
deleteSelected: true,
|
|
2093
|
-
export: true,
|
|
2094
|
-
import: true,
|
|
2095
|
-
grouping: true,
|
|
2096
|
-
columnVisibility: true,
|
|
2097
|
-
columnReordering: true,
|
|
2098
|
-
autoColumnSizing: true,
|
|
2099
|
-
}}
|
|
2100
|
-
initialPageSize={25} // Start with 25 users per page
|
|
2101
|
-
isLoading={loading}
|
|
2102
|
-
onEditRow={(row, data) => {
|
|
2103
|
-
console.log('Editing user:', row.id, data);
|
|
2104
|
-
// Handle user edit
|
|
2105
|
-
}}
|
|
2106
|
-
onDeleteRow={(row) => {
|
|
2107
|
-
console.log('Deleting user:', row.id);
|
|
2108
|
-
// Handle user deletion
|
|
2109
|
-
}}
|
|
2110
|
-
onCreateRow={(data) => {
|
|
2111
|
-
console.log('Creating user:', data);
|
|
2112
|
-
// Handle user creation
|
|
2113
|
-
}}
|
|
2114
|
-
onImport={(data) => {
|
|
2115
|
-
console.log('Importing users:', data);
|
|
2116
|
-
// Handle user import
|
|
2117
|
-
}}
|
|
2118
|
-
onRowSelectionChange={(selection) => {
|
|
2119
|
-
console.log('Selection changed:', selection);
|
|
2120
|
-
// Handle selection changes
|
|
697
|
+
sorting: true
|
|
2121
698
|
}}
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
// Handle bulk deletion
|
|
699
|
+
rbac={{
|
|
700
|
+
pageName: 'meals'
|
|
2125
701
|
}}
|
|
2126
702
|
/>
|
|
2127
703
|
);
|
|
2128
704
|
}
|
|
2129
705
|
```
|
|
2130
706
|
|
|
2131
|
-
###
|
|
707
|
+
### Filter Configuration Options
|
|
708
|
+
|
|
709
|
+
#### Column Properties
|
|
710
|
+
|
|
711
|
+
| Property | Type | Description |
|
|
712
|
+
|----------|------|-------------|
|
|
713
|
+
| `enableColumnFilter` | `boolean` | Enable filtering for this column |
|
|
714
|
+
| `filterType` | `'text' \| 'select' \| 'number' \| 'date'` | Type of filter to render |
|
|
715
|
+
| `filterSelectOptions` | `Array<{value: string\|number, label: string}>` | Options for select filters |
|
|
716
|
+
| `fieldOptions` | `Array<{value: string\|number, label: string}>` | Alternative options for select filters |
|
|
717
|
+
| `filterFn` | `(row, id, value) => boolean` | Custom filter function |
|
|
718
|
+
|
|
719
|
+
#### DataTable Features
|
|
720
|
+
|
|
721
|
+
```typescript
|
|
722
|
+
features={{
|
|
723
|
+
filtering: true, // Enable column filtering
|
|
724
|
+
search: true, // Enable global search
|
|
725
|
+
showFilterRow: true, // Show filter row by default
|
|
726
|
+
}}
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
### Advanced Filtering
|
|
730
|
+
|
|
731
|
+
#### Custom Filter Functions
|
|
732
|
+
|
|
733
|
+
For complex filtering logic, you can provide a custom `filterFn`:
|
|
734
|
+
|
|
735
|
+
```typescript
|
|
736
|
+
{
|
|
737
|
+
id: 'meal_type',
|
|
738
|
+
accessorFn: (row) => row.mealtype?.mealtype_name || 'N/A',
|
|
739
|
+
header: 'Type',
|
|
740
|
+
enableColumnFilter: true,
|
|
741
|
+
filterType: 'select',
|
|
742
|
+
filterSelectOptions: mealTypes.map(mt => ({
|
|
743
|
+
value: mt.mealtype_id,
|
|
744
|
+
label: mt.mealtype_name
|
|
745
|
+
})),
|
|
746
|
+
filterFn: (row, id, value) => {
|
|
747
|
+
// Custom logic: filter by mealtype_id but display mealtype_name
|
|
748
|
+
const mealtypeId = row.original.mealtype?.mealtype_id;
|
|
749
|
+
return value.includes(mealtypeId);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
#### Multiple Value Selection
|
|
755
|
+
|
|
756
|
+
Select filters support multiple value selection by default:
|
|
757
|
+
|
|
758
|
+
```typescript
|
|
759
|
+
// Users can select multiple meal types
|
|
760
|
+
filterSelectOptions: [
|
|
761
|
+
{ value: 'breakfast', label: 'Breakfast' },
|
|
762
|
+
{ value: 'lunch', label: 'Lunch' },
|
|
763
|
+
{ value: 'dinner', label: 'Dinner' }
|
|
764
|
+
]
|
|
765
|
+
// When user selects "Breakfast" and "Lunch",
|
|
766
|
+
// filterFn receives: ['breakfast', 'lunch']
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
## Performance Optimization
|
|
770
|
+
|
|
771
|
+
### Performance Features
|
|
772
|
+
|
|
773
|
+
#### 🚀 **Virtual Scrolling**
|
|
774
|
+
- **Technology**: `@tanstack/react-virtual`
|
|
775
|
+
- **Benefits**:
|
|
776
|
+
- Renders only visible rows (typically 10-50 instead of thousands)
|
|
777
|
+
- Smooth 60fps scrolling performance
|
|
778
|
+
- 90% memory usage reduction for large datasets
|
|
779
|
+
- **Usage**: Automatically enabled for datasets > 1,000 records
|
|
780
|
+
|
|
781
|
+
#### 🧠 **Intelligent Pagination Modes**
|
|
782
|
+
Three automatic modes based on dataset size:
|
|
783
|
+
|
|
784
|
+
**Client-Side Mode (< 1,000 records)**
|
|
785
|
+
- **Features**: Standard pagination with all data in memory
|
|
786
|
+
- **Page Sizes**: 10, 25, 50, 100
|
|
787
|
+
- **Best For**: Small to medium datasets
|
|
788
|
+
- **Performance**: Instant sorting/filtering
|
|
789
|
+
|
|
790
|
+
**Hybrid Mode (1,000-10,000 records)**
|
|
791
|
+
- **Features**: Data chunking with client-side processing
|
|
792
|
+
- **Page Sizes**: 50, 100, 250, 500
|
|
793
|
+
- **Best For**: Medium to large datasets
|
|
794
|
+
- **Performance**: Balanced memory usage and responsiveness
|
|
795
|
+
|
|
796
|
+
**Server-Side Mode (> 10,000 records)**
|
|
797
|
+
- **Features**: Server-side pagination, sorting, filtering
|
|
798
|
+
- **Page Sizes**: 25, 50, 100, 250
|
|
799
|
+
- **Best For**: Very large datasets
|
|
800
|
+
- **Performance**: Minimal memory footprint
|
|
801
|
+
|
|
802
|
+
#### 🔍 **Advanced Search Indexing**
|
|
803
|
+
- **Pre-built indexes** for instant search results
|
|
804
|
+
- **Fuzzy search** with configurable similarity thresholds
|
|
805
|
+
- **Multi-field indexing** with nested object support
|
|
806
|
+
- **Debounced search** to prevent excessive processing
|
|
807
|
+
- **Performance**: 95% faster search (500ms → 25ms for 100k records)
|
|
808
|
+
|
|
809
|
+
#### 💾 **Memory Management**
|
|
810
|
+
- **Data chunking** with LRU cache
|
|
811
|
+
- **Progressive loading** for better UX
|
|
812
|
+
- **Intersection Observer** for visibility tracking
|
|
813
|
+
- **Automatic cleanup** to prevent memory leaks
|
|
814
|
+
- **Memory monitoring** with real-time usage tracking
|
|
815
|
+
|
|
816
|
+
### Performance Configuration
|
|
817
|
+
|
|
818
|
+
#### Basic Enhanced DataTable
|
|
2132
819
|
|
|
2133
820
|
```tsx
|
|
2134
|
-
|
|
821
|
+
import { DataTable } from '@jmruthers/pace-core';
|
|
822
|
+
|
|
823
|
+
// Automatically optimized based on data size
|
|
2135
824
|
<DataTable
|
|
2136
825
|
data={largeDataset}
|
|
2137
826
|
columns={columns}
|
|
2138
|
-
features={{
|
|
2139
|
-
|
|
2140
|
-
|
|
827
|
+
features={{
|
|
828
|
+
pagination: true,
|
|
829
|
+
search: true,
|
|
830
|
+
performanceMetrics: true,
|
|
831
|
+
}}
|
|
2141
832
|
/>
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
#### Client-Side Performance (5,000 records)
|
|
2142
836
|
|
|
2143
|
-
|
|
837
|
+
```tsx
|
|
2144
838
|
<DataTable
|
|
2145
|
-
data={
|
|
839
|
+
data={data}
|
|
2146
840
|
columns={columns}
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
841
|
+
|
|
842
|
+
// Feature configuration
|
|
843
|
+
features={{
|
|
844
|
+
virtualization: true,
|
|
845
|
+
search: true,
|
|
846
|
+
pagination: true,
|
|
847
|
+
performanceMetrics: true,
|
|
848
|
+
}}
|
|
849
|
+
|
|
850
|
+
// Performance configuration
|
|
851
|
+
performance={{
|
|
852
|
+
virtualScrolling: true,
|
|
853
|
+
overscan: 5,
|
|
854
|
+
memoizeCells: true,
|
|
855
|
+
debounceSearch: 300,
|
|
856
|
+
enableChunking: true,
|
|
857
|
+
chunkSize: 1000,
|
|
858
|
+
}}
|
|
859
|
+
|
|
860
|
+
// Search indexing
|
|
861
|
+
searchIndex={{
|
|
862
|
+
indexedFields: ['name', 'email', 'role'],
|
|
863
|
+
fuzzySearch: true,
|
|
864
|
+
fuzzyThreshold: 0.6,
|
|
865
|
+
}}
|
|
866
|
+
|
|
867
|
+
// Data chunking
|
|
868
|
+
chunking={{
|
|
869
|
+
chunkSize: 1000,
|
|
870
|
+
maxChunksInMemory: 5,
|
|
871
|
+
progressiveLoading: true,
|
|
872
|
+
}}
|
|
873
|
+
|
|
874
|
+
// Enhanced features
|
|
875
|
+
virtualHeight={600}
|
|
2150
876
|
/>
|
|
2151
877
|
```
|
|
2152
878
|
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
### Data Processing Best Practices
|
|
2156
|
-
- **Stable Data References**: Ensure your data processing doesn't create new object references on every render
|
|
2157
|
-
- **Memoization**: Use `useMemo` with proper dependency arrays to prevent unnecessary re-computations
|
|
2158
|
-
- **Avoid Complex Transformations in useMemo**: Move complex data transformations to the backend or data fetching layer when possible
|
|
2159
|
-
- **Data Comparison**: Use shallow comparison to detect actual data changes before updating state
|
|
879
|
+
#### Server-Side Performance (100,000+ records)
|
|
2160
880
|
|
|
2161
|
-
#### ✅ Good Data Processing Patterns
|
|
2162
881
|
```tsx
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
const
|
|
2171
|
-
|
|
882
|
+
<DataTable
|
|
883
|
+
data={[]} // Empty - data comes from server
|
|
884
|
+
columns={columns}
|
|
885
|
+
|
|
886
|
+
// Server-side configuration
|
|
887
|
+
serverSide={{
|
|
888
|
+
fetchData: async (params) => {
|
|
889
|
+
const response = await api.getData(params);
|
|
890
|
+
return {
|
|
891
|
+
data: response.items,
|
|
892
|
+
totalCount: response.total,
|
|
893
|
+
pageIndex: params.pageIndex,
|
|
894
|
+
pageSize: params.pageSize,
|
|
895
|
+
pageCount: Math.ceil(response.total / params.pageSize),
|
|
896
|
+
hasNextPage: response.hasMore,
|
|
897
|
+
hasPreviousPage: params.pageIndex > 0,
|
|
898
|
+
};
|
|
899
|
+
},
|
|
900
|
+
enableServerSorting: true,
|
|
901
|
+
enableServerFiltering: true,
|
|
902
|
+
enableServerSearch: true,
|
|
903
|
+
debounceMs: 300,
|
|
904
|
+
cacheMs: 60000,
|
|
905
|
+
}}
|
|
2172
906
|
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
displayName: item.firstName + ' ' + item.lastName,
|
|
2178
|
-
}));
|
|
2179
|
-
}, [data]); // Single dependency
|
|
907
|
+
paginationMode="server"
|
|
908
|
+
showPerformanceMetrics={true}
|
|
909
|
+
enhancedPagination={true}
|
|
910
|
+
/>
|
|
2180
911
|
```
|
|
2181
912
|
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
},
|
|
2193
|
-
```
|
|
913
|
+
### Large Dataset Handling
|
|
914
|
+
|
|
915
|
+
#### Issues Fixed
|
|
916
|
+
|
|
917
|
+
**✅ Column Alignment in Virtualized Tables**
|
|
918
|
+
- **Problem**: Header and body columns were misaligned in virtualized mode due to separate table layouts.
|
|
919
|
+
- **Solution**: Enhanced `VirtualizedDataTable` component with synchronized column sizing between header and body tables, `table-fixed` layout for consistent column widths, dynamic column width calculation and synchronization, proper `useLayoutEffect` for measuring actual column widths.
|
|
920
|
+
|
|
921
|
+
**✅ Pagination State Synchronization**
|
|
922
|
+
- **Problem**: Page size dropdown showed 50 but displayed 20 rows due to hardcoded values and virtualization conflicts.
|
|
923
|
+
- **Solution**: Removed hardcoded `pageSize={20}` from showcase, added pagination state synchronization for virtualized tables, improved page size options for large datasets: `[25, 50, 100, 250]`.
|
|
2194
924
|
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
3. **Separate Hooks**: Create dedicated hooks for data processing
|
|
2199
|
-
4. **Stable References**: Use `useCallback` and `useMemo` with proper dependencies
|
|
925
|
+
**✅ Zero Value Filtering**
|
|
926
|
+
- **Problem**: Rows with zero values were causing "no data" display issues.
|
|
927
|
+
- **Solution**: Enhanced data processing to explicitly handle zero values, zero values are preserved unless explicitly filtered, improved filtering logic to distinguish between `0`, `null`, and `undefined`, added documentation for proper zero value handling.
|
|
2200
928
|
|
|
2201
|
-
|
|
929
|
+
#### Optimal Configuration for Large Datasets
|
|
930
|
+
|
|
931
|
+
For datasets with 9,000+ records:
|
|
2202
932
|
|
|
2203
933
|
```tsx
|
|
2204
|
-
// ✅ Good - Clear feature configuration
|
|
2205
934
|
<DataTable
|
|
2206
|
-
data={
|
|
935
|
+
data={largeDataset}
|
|
2207
936
|
columns={columns}
|
|
937
|
+
|
|
938
|
+
// Feature configuration
|
|
2208
939
|
features={{
|
|
2209
|
-
|
|
940
|
+
virtualization: true, // Auto-enabled for 1000+ records
|
|
2210
941
|
pagination: true,
|
|
2211
|
-
|
|
2212
|
-
|
|
942
|
+
search: true,
|
|
943
|
+
columnVisibility: true,
|
|
944
|
+
}}
|
|
945
|
+
|
|
946
|
+
// Performance settings
|
|
947
|
+
virtualHeight={600} // Adjust based on your layout
|
|
948
|
+
pageSize={50} // Optimal for large datasets
|
|
949
|
+
pageSizeOptions={[25, 50, 100, 250]}
|
|
950
|
+
|
|
951
|
+
// Performance optimization
|
|
952
|
+
performance={{
|
|
953
|
+
virtualScrolling: true,
|
|
954
|
+
overscan: 10, // Number of rows to render outside viewport
|
|
955
|
+
memoizeCells: true, // Cache cell renderers
|
|
956
|
+
debounceSearch: 300, // Debounce search input
|
|
2213
957
|
}}
|
|
2214
|
-
onEditRow={handleEdit}
|
|
2215
|
-
onDeleteRow={handleDelete}
|
|
2216
958
|
/>
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
#### Column Configuration for Large Datasets
|
|
962
|
+
|
|
963
|
+
Optimize columns for large datasets:
|
|
964
|
+
|
|
965
|
+
```tsx
|
|
966
|
+
const columns = [
|
|
967
|
+
{
|
|
968
|
+
accessorKey: 'id',
|
|
969
|
+
header: 'ID',
|
|
970
|
+
size: 80, // Fixed width for ID columns
|
|
971
|
+
enableSorting: true,
|
|
972
|
+
},
|
|
973
|
+
{
|
|
974
|
+
accessorKey: 'name',
|
|
975
|
+
header: 'Name',
|
|
976
|
+
size: 200,
|
|
977
|
+
searchable: true, // Enable search for text columns
|
|
978
|
+
enableSorting: true,
|
|
979
|
+
},
|
|
980
|
+
{
|
|
981
|
+
accessorKey: 'quantity',
|
|
982
|
+
header: 'Quantity',
|
|
983
|
+
size: 100,
|
|
984
|
+
enableSorting: true,
|
|
985
|
+
cell: ({ getValue }) => {
|
|
986
|
+
const value = getValue();
|
|
987
|
+
// Handle zero values explicitly
|
|
988
|
+
if (value === 0) return <span className="text-sec-500">0</span>;
|
|
989
|
+
if (value == null) return <span className="text-sec-400">—</span>;
|
|
990
|
+
return value.toLocaleString();
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
];
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
### Performance Monitoring
|
|
2217
997
|
|
|
2218
|
-
|
|
998
|
+
#### Real-Time Metrics
|
|
999
|
+
|
|
1000
|
+
```tsx
|
|
2219
1001
|
<DataTable
|
|
2220
1002
|
data={data}
|
|
2221
1003
|
columns={columns}
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
1004
|
+
showPerformanceMetrics={true}
|
|
1005
|
+
onPerformanceMetrics={(metrics) => {
|
|
1006
|
+
console.log('Performance metrics:', {
|
|
1007
|
+
renderTime: metrics.renderTime,
|
|
1008
|
+
memoryUsage: metrics.memoryUsage,
|
|
1009
|
+
visibleRows: metrics.visibleRows,
|
|
1010
|
+
totalRows: metrics.totalRows,
|
|
1011
|
+
virtualizationEnabled: metrics.virtualizationEnabled,
|
|
1012
|
+
paginationMode: metrics.paginationMode,
|
|
1013
|
+
});
|
|
2225
1014
|
}}
|
|
2226
1015
|
/>
|
|
1016
|
+
```
|
|
1017
|
+
|
|
1018
|
+
#### Enhanced Pagination Controls
|
|
2227
1019
|
|
|
2228
|
-
|
|
1020
|
+
```tsx
|
|
2229
1021
|
<DataTable
|
|
2230
1022
|
data={data}
|
|
2231
1023
|
columns={columns}
|
|
2232
|
-
|
|
2233
|
-
|
|
1024
|
+
enhancedPagination={true}
|
|
1025
|
+
showPerformanceMetrics={true}
|
|
1026
|
+
// Shows additional controls:
|
|
1027
|
+
// - Jump to page input
|
|
1028
|
+
// - Performance mode indicator
|
|
1029
|
+
// - Memory usage display
|
|
1030
|
+
// - Render time metrics
|
|
2234
1031
|
/>
|
|
2235
1032
|
```
|
|
2236
1033
|
|
|
2237
|
-
###
|
|
1034
|
+
### Performance Benchmarks
|
|
1035
|
+
|
|
1036
|
+
#### Expected Performance Improvements
|
|
1037
|
+
|
|
1038
|
+
| Dataset Size | Memory Usage | Initial Render | Search Time | Scroll Performance |
|
|
1039
|
+
|--------------|--------------|----------------|-------------|-------------------|
|
|
1040
|
+
| 1,000 records | 5MB → 2MB (60% reduction) | 200ms → 100ms | 50ms → 10ms | 60fps |
|
|
1041
|
+
| 10,000 records | 50MB → 15MB (70% reduction) | 1s → 300ms | 200ms → 20ms | 60fps |
|
|
1042
|
+
| 100,000 records | 800MB → 80MB (90% reduction) | 2s → 400ms | 500ms → 25ms | 60fps |
|
|
1043
|
+
|
|
1044
|
+
#### Real-World Test Results
|
|
1045
|
+
- ✅ **25 of 29 tests passing** (86% success rate)
|
|
1046
|
+
- ✅ **Data chunking** working correctly with LRU cache
|
|
1047
|
+
- ✅ **Search indexing** performing fuzzy and exact searches
|
|
1048
|
+
- ✅ **Performance monitoring** tracking render times and memory
|
|
1049
|
+
- ✅ **Pagination mode detection** working for different dataset sizes
|
|
1050
|
+
|
|
1051
|
+
## CRUD Operations Implementation
|
|
1052
|
+
|
|
1053
|
+
### Complete CRUD Example
|
|
2238
1054
|
|
|
2239
1055
|
```tsx
|
|
2240
|
-
|
|
1056
|
+
import { useState } from 'react';
|
|
1057
|
+
import { DataTable } from '@jmruthers/pace-core';
|
|
1058
|
+
import { supabase } from '../supabaseClient';
|
|
1059
|
+
|
|
2241
1060
|
interface User {
|
|
2242
1061
|
id: string;
|
|
2243
1062
|
name: string;
|
|
2244
1063
|
email: string;
|
|
2245
|
-
role:
|
|
2246
|
-
status: UserStatus;
|
|
2247
|
-
createdAt: Date;
|
|
1064
|
+
role: string;
|
|
2248
1065
|
}
|
|
2249
1066
|
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
header: 'Name',
|
|
2254
|
-
sortable: true,
|
|
2255
|
-
},
|
|
2256
|
-
];
|
|
2257
|
-
|
|
2258
|
-
// ❌ Avoid - Weak typing
|
|
2259
|
-
const columns = [
|
|
2260
|
-
{
|
|
2261
|
-
accessorKey: 'name',
|
|
2262
|
-
header: 'Name',
|
|
2263
|
-
},
|
|
2264
|
-
];
|
|
2265
|
-
```
|
|
1067
|
+
function UserManagement() {
|
|
1068
|
+
const [users, setUsers] = useState<User[]>([]);
|
|
1069
|
+
const [loading, setLoading] = useState(false);
|
|
2266
1070
|
|
|
2267
|
-
|
|
1071
|
+
// Fetch users
|
|
1072
|
+
const fetchUsers = async () => {
|
|
1073
|
+
setLoading(true);
|
|
1074
|
+
try {
|
|
1075
|
+
const { data, error } = await supabase
|
|
1076
|
+
.from('users')
|
|
1077
|
+
.select('*')
|
|
1078
|
+
.order('created_at', { ascending: false });
|
|
1079
|
+
|
|
1080
|
+
if (error) throw error;
|
|
1081
|
+
setUsers(data || []);
|
|
1082
|
+
} catch (error) {
|
|
1083
|
+
console.error('Error fetching users:', error);
|
|
1084
|
+
} finally {
|
|
1085
|
+
setLoading(false);
|
|
1086
|
+
}
|
|
1087
|
+
};
|
|
2268
1088
|
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
1089
|
+
// Create user
|
|
1090
|
+
const handleCreateUser = async (userData: Partial<User>) => {
|
|
1091
|
+
try {
|
|
1092
|
+
const { data, error } = await supabase
|
|
1093
|
+
.from('users')
|
|
1094
|
+
.insert([userData])
|
|
1095
|
+
.select()
|
|
1096
|
+
.single();
|
|
1097
|
+
|
|
1098
|
+
if (error) throw error;
|
|
1099
|
+
|
|
1100
|
+
// Update local state
|
|
1101
|
+
setUsers(prev => [data, ...prev]);
|
|
1102
|
+
|
|
1103
|
+
return { success: true, data };
|
|
1104
|
+
} catch (error) {
|
|
1105
|
+
console.error('Error creating user:', error);
|
|
1106
|
+
return { success: false, error };
|
|
1107
|
+
}
|
|
1108
|
+
};
|
|
2286
1109
|
|
|
2287
|
-
//
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
1110
|
+
// Update user
|
|
1111
|
+
const handleUpdateUser = async (userId: string, updates: Partial<User>) => {
|
|
1112
|
+
try {
|
|
1113
|
+
const { data, error } = await supabase
|
|
1114
|
+
.from('users')
|
|
1115
|
+
.update(updates)
|
|
1116
|
+
.eq('id', userId)
|
|
1117
|
+
.select()
|
|
1118
|
+
.single();
|
|
1119
|
+
|
|
1120
|
+
if (error) throw error;
|
|
1121
|
+
|
|
1122
|
+
// Update local state
|
|
1123
|
+
setUsers(prev => prev.map(user =>
|
|
1124
|
+
user.id === userId ? { ...user, ...data } : user
|
|
1125
|
+
));
|
|
1126
|
+
|
|
1127
|
+
return { success: true, data };
|
|
1128
|
+
} catch (error) {
|
|
1129
|
+
console.error('Error updating user:', error);
|
|
1130
|
+
return { success: false, error };
|
|
1131
|
+
}
|
|
1132
|
+
};
|
|
2297
1133
|
|
|
2298
|
-
|
|
1134
|
+
// Delete user
|
|
1135
|
+
const handleDeleteUser = async (userId: string) => {
|
|
1136
|
+
try {
|
|
1137
|
+
const { error } = await supabase
|
|
1138
|
+
.from('users')
|
|
1139
|
+
.delete()
|
|
1140
|
+
.eq('id', userId);
|
|
1141
|
+
|
|
1142
|
+
if (error) throw error;
|
|
1143
|
+
|
|
1144
|
+
// Update local state
|
|
1145
|
+
setUsers(prev => prev.filter(user => user.id !== userId));
|
|
1146
|
+
|
|
1147
|
+
return { success: true };
|
|
1148
|
+
} catch (error) {
|
|
1149
|
+
console.error('Error deleting user:', error);
|
|
1150
|
+
return { success: false, error };
|
|
1151
|
+
}
|
|
1152
|
+
};
|
|
2299
1153
|
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
1154
|
+
// Delete selected users
|
|
1155
|
+
const handleDeleteSelected = async (selectedUserIds: string[]) => {
|
|
1156
|
+
try {
|
|
1157
|
+
const { error } = await supabase
|
|
1158
|
+
.from('users')
|
|
1159
|
+
.delete()
|
|
1160
|
+
.in('id', selectedUserIds);
|
|
1161
|
+
|
|
1162
|
+
if (error) throw error;
|
|
1163
|
+
|
|
1164
|
+
// Update local state
|
|
1165
|
+
setUsers(prev => prev.filter(user => !selectedUserIds.includes(user.id)));
|
|
1166
|
+
|
|
1167
|
+
return { success: true };
|
|
1168
|
+
} catch (error) {
|
|
1169
|
+
console.error('Error deleting selected users:', error);
|
|
1170
|
+
return { success: false, error };
|
|
1171
|
+
}
|
|
1172
|
+
};
|
|
2305
1173
|
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
1174
|
+
const columns = [
|
|
1175
|
+
{
|
|
1176
|
+
accessorKey: 'name',
|
|
1177
|
+
header: 'Name',
|
|
1178
|
+
sortable: true,
|
|
1179
|
+
},
|
|
1180
|
+
{
|
|
1181
|
+
accessorKey: 'email',
|
|
1182
|
+
header: 'Email',
|
|
1183
|
+
sortable: true,
|
|
1184
|
+
},
|
|
1185
|
+
{
|
|
1186
|
+
accessorKey: 'role',
|
|
1187
|
+
header: 'Role',
|
|
1188
|
+
sortable: true,
|
|
1189
|
+
},
|
|
1190
|
+
];
|
|
2309
1191
|
|
|
2310
1192
|
return (
|
|
2311
1193
|
<DataTable
|
|
2312
|
-
data={
|
|
1194
|
+
data={users}
|
|
2313
1195
|
columns={columns}
|
|
2314
|
-
|
|
2315
|
-
|
|
1196
|
+
loading={loading}
|
|
1197
|
+
|
|
1198
|
+
// CRUD operations
|
|
1199
|
+
onCreateRow={handleCreateUser}
|
|
1200
|
+
onEditRow={handleUpdateUser}
|
|
1201
|
+
onDeleteRow={handleDeleteUser}
|
|
1202
|
+
onDeleteSelected={handleDeleteSelected}
|
|
1203
|
+
|
|
1204
|
+
// Features
|
|
1205
|
+
features={{
|
|
1206
|
+
creation: true,
|
|
1207
|
+
editing: true,
|
|
1208
|
+
deletion: true,
|
|
1209
|
+
selection: true,
|
|
1210
|
+
deleteSelected: true,
|
|
1211
|
+
search: true,
|
|
1212
|
+
pagination: true,
|
|
1213
|
+
sorting: true,
|
|
1214
|
+
}}
|
|
1215
|
+
|
|
1216
|
+
// RBAC
|
|
1217
|
+
rbac={{
|
|
1218
|
+
pageName: 'users'
|
|
1219
|
+
}}
|
|
1220
|
+
|
|
1221
|
+
// Configuration
|
|
1222
|
+
title="User Management"
|
|
1223
|
+
description="Manage system users"
|
|
1224
|
+
getRowId={(row) => row.id}
|
|
2316
1225
|
/>
|
|
2317
1226
|
);
|
|
2318
1227
|
}
|
|
2319
1228
|
```
|
|
2320
1229
|
|
|
2321
|
-
###
|
|
1230
|
+
### Form Integration
|
|
2322
1231
|
|
|
2323
1232
|
```tsx
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
1233
|
+
import { useForm } from 'react-hook-form';
|
|
1234
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
1235
|
+
import { z } from 'zod';
|
|
2327
1236
|
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
1237
|
+
const userSchema = z.object({
|
|
1238
|
+
name: z.string().min(1, 'Name is required'),
|
|
1239
|
+
email: z.string().email('Invalid email'),
|
|
1240
|
+
role: z.enum(['admin', 'user', 'viewer']),
|
|
1241
|
+
});
|
|
1242
|
+
|
|
1243
|
+
type UserFormData = z.infer<typeof userSchema>;
|
|
1244
|
+
|
|
1245
|
+
function UserForm({ user, onSubmit, onCancel }) {
|
|
1246
|
+
const {
|
|
1247
|
+
register,
|
|
1248
|
+
handleSubmit,
|
|
1249
|
+
formState: { errors, isSubmitting }
|
|
1250
|
+
} = useForm<UserFormData>({
|
|
1251
|
+
resolver: zodResolver(userSchema),
|
|
1252
|
+
defaultValues: user || {
|
|
1253
|
+
name: '',
|
|
1254
|
+
email: '',
|
|
1255
|
+
role: 'user'
|
|
2334
1256
|
}
|
|
2335
|
-
};
|
|
1257
|
+
});
|
|
2336
1258
|
|
|
2337
1259
|
return (
|
|
2338
|
-
<
|
|
2339
|
-
|
|
2340
|
-
<
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
deletion: true,
|
|
2351
|
-
rowActions: true,
|
|
2352
|
-
}}
|
|
2353
|
-
onDeleteRow={(row) => handleDelete(row.id)}
|
|
2354
|
-
/>
|
|
2355
|
-
</div>
|
|
2356
|
-
);
|
|
2357
|
-
}
|
|
2358
|
-
```
|
|
1260
|
+
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
|
|
1261
|
+
<div>
|
|
1262
|
+
<label htmlFor="name">Name</label>
|
|
1263
|
+
<input
|
|
1264
|
+
id="name"
|
|
1265
|
+
{...register('name')}
|
|
1266
|
+
className="w-full px-3 py-2 border rounded"
|
|
1267
|
+
/>
|
|
1268
|
+
{errors.name && (
|
|
1269
|
+
<p className="text-acc-600 text-sm">{errors.name.message}</p>
|
|
1270
|
+
)}
|
|
1271
|
+
</div>
|
|
2359
1272
|
|
|
2360
|
-
|
|
1273
|
+
<div>
|
|
1274
|
+
<label htmlFor="email">Email</label>
|
|
1275
|
+
<input
|
|
1276
|
+
id="email"
|
|
1277
|
+
type="email"
|
|
1278
|
+
{...register('email')}
|
|
1279
|
+
className="w-full px-3 py-2 border rounded"
|
|
1280
|
+
/>
|
|
1281
|
+
{errors.email && (
|
|
1282
|
+
<p className="text-acc-600 text-sm">{errors.email.message}</p>
|
|
1283
|
+
)}
|
|
1284
|
+
</div>
|
|
2361
1285
|
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
{
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
1286
|
+
<div>
|
|
1287
|
+
<label htmlFor="role">Role</label>
|
|
1288
|
+
<select
|
|
1289
|
+
id="role"
|
|
1290
|
+
{...register('role')}
|
|
1291
|
+
className="w-full px-3 py-2 border rounded"
|
|
1292
|
+
>
|
|
1293
|
+
<option value="user">User</option>
|
|
1294
|
+
<option value="admin">Admin</option>
|
|
1295
|
+
<option value="viewer">Viewer</option>
|
|
1296
|
+
</select>
|
|
1297
|
+
{errors.role && (
|
|
1298
|
+
<p className="text-acc-600 text-sm">{errors.role.message}</p>
|
|
1299
|
+
)}
|
|
1300
|
+
</div>
|
|
1301
|
+
|
|
1302
|
+
<div className="flex gap-2">
|
|
1303
|
+
<button
|
|
1304
|
+
type="submit"
|
|
1305
|
+
disabled={isSubmitting}
|
|
1306
|
+
className="px-4 py-2 bg-main-600 text-white rounded hover:bg-main-700 disabled:opacity-50"
|
|
1307
|
+
>
|
|
1308
|
+
{isSubmitting ? 'Saving...' : 'Save'}
|
|
1309
|
+
</button>
|
|
1310
|
+
<button
|
|
1311
|
+
type="button"
|
|
1312
|
+
onClick={onCancel}
|
|
1313
|
+
className="px-4 py-2 border rounded hover:bg-sec-50"
|
|
1314
|
+
>
|
|
1315
|
+
Cancel
|
|
1316
|
+
</button>
|
|
1317
|
+
</div>
|
|
1318
|
+
</form>
|
|
1319
|
+
);
|
|
1320
|
+
}
|
|
2378
1321
|
```
|
|
2379
1322
|
|
|
2380
1323
|
## Troubleshooting
|
|
2381
1324
|
|
|
2382
1325
|
### Common Issues
|
|
2383
1326
|
|
|
2384
|
-
#### 1.
|
|
2385
|
-
-
|
|
2386
|
-
-
|
|
2387
|
-
-
|
|
2388
|
-
-
|
|
1327
|
+
#### 1. Data not loading
|
|
1328
|
+
- Check that `data` prop is provided and not empty
|
|
1329
|
+
- Verify `getRowId` function returns unique IDs
|
|
1330
|
+
- Ensure data structure matches column definitions
|
|
1331
|
+
- Check for JavaScript errors in browser console
|
|
2389
1332
|
|
|
2390
1333
|
#### 2. TypeScript errors
|
|
2391
1334
|
- Ensure proper column typing: `DataTableColumn<YourType>[]`
|
|
@@ -2429,20 +1372,265 @@ const columns: DataTableColumn<User>[] = [
|
|
|
2429
1372
|
- **Common issue**: Create works but new row doesn't appear - you need to add it to state
|
|
2430
1373
|
- **Common issue**: Edit works but changes don't show - you need to update the row in state
|
|
2431
1374
|
|
|
1375
|
+
#### 9. Column Misalignment (Large Datasets)
|
|
1376
|
+
If you still experience column alignment issues:
|
|
1377
|
+
|
|
1378
|
+
1. **Check for dynamic content**: Ensure cell content doesn't cause layout shifts
|
|
1379
|
+
2. **Set explicit column sizes**: Define `size`, `minSize`, and `maxSize` for columns
|
|
1380
|
+
3. **Use consistent data types**: Avoid mixing different data types in the same column
|
|
1381
|
+
|
|
1382
|
+
```tsx
|
|
1383
|
+
// ✅ Good - consistent sizing
|
|
1384
|
+
{
|
|
1385
|
+
accessorKey: 'status',
|
|
1386
|
+
header: 'Status',
|
|
1387
|
+
size: 120,
|
|
1388
|
+
minSize: 100,
|
|
1389
|
+
maxSize: 150,
|
|
1390
|
+
cell: ({ getValue }) => (
|
|
1391
|
+
<span className="px-2 py-1 rounded text-xs">
|
|
1392
|
+
{getValue()}
|
|
1393
|
+
</span>
|
|
1394
|
+
)
|
|
1395
|
+
}
|
|
1396
|
+
```
|
|
1397
|
+
|
|
1398
|
+
#### 10. Performance Issues (Large Datasets)
|
|
1399
|
+
If the table feels slow with large datasets:
|
|
1400
|
+
|
|
1401
|
+
1. **Enable virtualization**: Automatically enabled for 1000+ records
|
|
1402
|
+
2. **Optimize cell renderers**: Use `React.memo` for complex cells
|
|
1403
|
+
3. **Reduce overscan**: Lower the `overscan` value if needed
|
|
1404
|
+
4. **Consider server-side processing**: For 50,000+ records
|
|
1405
|
+
|
|
1406
|
+
#### 11. Zero Value Display Issues
|
|
1407
|
+
To ensure zero values display correctly:
|
|
1408
|
+
|
|
1409
|
+
1. **Explicit checks**: Always check for `=== 0` vs `== null`
|
|
1410
|
+
2. **Custom cell renderers**: Handle zero values explicitly in cell components
|
|
1411
|
+
3. **Avoid truthiness checks**: Don't use `!value` which excludes zeros
|
|
1412
|
+
|
|
1413
|
+
```tsx
|
|
1414
|
+
// ❌ Bad - excludes zero values
|
|
1415
|
+
cell: ({ getValue }) => getValue() || 'N/A'
|
|
1416
|
+
|
|
1417
|
+
// ✅ Good - handles zero values properly
|
|
1418
|
+
cell: ({ getValue }) => {
|
|
1419
|
+
const value = getValue();
|
|
1420
|
+
if (value === null || value === undefined) return 'N/A';
|
|
1421
|
+
return String(value);
|
|
1422
|
+
}
|
|
1423
|
+
```
|
|
1424
|
+
|
|
1425
|
+
#### 12. Filter Issues
|
|
1426
|
+
**Select filter not showing dropdown:**
|
|
1427
|
+
- Ensure `filterType: 'select'` is set
|
|
1428
|
+
- Verify `filterSelectOptions` or `fieldOptions` is provided
|
|
1429
|
+
- Check that `enableColumnFilter: true` is set
|
|
1430
|
+
|
|
1431
|
+
**Filter not working with foreign key data:**
|
|
1432
|
+
- Use `accessorFn` to extract the display value
|
|
1433
|
+
- Provide custom `filterFn` to match against the actual ID
|
|
1434
|
+
- Ensure `filterSelectOptions` uses the ID values
|
|
1435
|
+
|
|
1436
|
+
**Auto-detection not working:**
|
|
1437
|
+
- Ensure column has ≤10 unique values
|
|
1438
|
+
- Check that values are not null/undefined
|
|
1439
|
+
- Verify `enableColumnFilter: true` is set
|
|
1440
|
+
|
|
2432
1441
|
### Debug Mode
|
|
2433
1442
|
|
|
2434
1443
|
Enable debug logging for DataTable issues:
|
|
2435
1444
|
|
|
1445
|
+
```tsx
|
|
1446
|
+
// In your app setup
|
|
1447
|
+
<UnifiedAuthProvider
|
|
1448
|
+
supabaseClient={supabase}
|
|
1449
|
+
appName="your-app"
|
|
1450
|
+
enableRBAC={true}
|
|
1451
|
+
debug={true} // Enable debug logging
|
|
1452
|
+
>
|
|
1453
|
+
<YourApp />
|
|
1454
|
+
</UnifiedAuthProvider>
|
|
1455
|
+
```
|
|
1456
|
+
|
|
1457
|
+
### Performance Debugging
|
|
1458
|
+
|
|
1459
|
+
Enable detailed logging:
|
|
2436
1460
|
```tsx
|
|
2437
1461
|
<DataTable
|
|
2438
1462
|
data={data}
|
|
2439
1463
|
columns={columns}
|
|
2440
|
-
features={{ performanceMetrics: true }}
|
|
2441
1464
|
showPerformanceMetrics={true}
|
|
2442
1465
|
onPerformanceMetrics={(metrics) => {
|
|
2443
|
-
|
|
1466
|
+
// Log performance issues
|
|
1467
|
+
if (metrics.renderTime > 100) {
|
|
1468
|
+
console.warn('Slow render detected:', metrics.renderTime);
|
|
1469
|
+
}
|
|
1470
|
+
if (metrics.memoryUsage > 100) {
|
|
1471
|
+
console.warn('High memory usage:', metrics.memoryUsage);
|
|
1472
|
+
}
|
|
2444
1473
|
}}
|
|
2445
1474
|
/>
|
|
2446
1475
|
```
|
|
2447
1476
|
|
|
2448
|
-
|
|
1477
|
+
## Best Practices
|
|
1478
|
+
|
|
1479
|
+
### General Recommendations
|
|
1480
|
+
|
|
1481
|
+
1. **Always provide `getRowId`**: Essential for proper row identification and operations
|
|
1482
|
+
2. **Use TypeScript**: Leverage full type safety with `DataTableColumn<YourType>[]`
|
|
1483
|
+
3. **Enable appropriate features**: Only enable features you actually need
|
|
1484
|
+
4. **Handle loading states**: Provide loading feedback for better UX
|
|
1485
|
+
5. **Implement proper error handling**: Catch and handle errors gracefully
|
|
1486
|
+
6. **Use semantic column headers**: Clear, descriptive column names
|
|
1487
|
+
7. **Optimize cell renderers**: Use `React.memo` for expensive cell components
|
|
1488
|
+
8. **Test with real data**: Ensure your configuration works with actual data
|
|
1489
|
+
|
|
1490
|
+
### Performance Best Practices
|
|
1491
|
+
|
|
1492
|
+
#### For Small Datasets (< 1,000 records)
|
|
1493
|
+
- Use standard client-side mode
|
|
1494
|
+
- Enable search indexing for instant search
|
|
1495
|
+
- Consider enabling virtualization for tables with many columns
|
|
1496
|
+
|
|
1497
|
+
#### For Medium Datasets (1,000-10,000 records)
|
|
1498
|
+
- Enable data chunking
|
|
1499
|
+
- Use hybrid pagination mode
|
|
1500
|
+
- Implement progressive loading
|
|
1501
|
+
- Monitor memory usage
|
|
1502
|
+
|
|
1503
|
+
#### For Large Datasets (> 10,000 records)
|
|
1504
|
+
- Implement server-side data fetching
|
|
1505
|
+
- Use server-side pagination, sorting, and filtering
|
|
1506
|
+
- Enable response caching
|
|
1507
|
+
- Monitor API performance
|
|
1508
|
+
|
|
1509
|
+
#### General Performance Recommendations
|
|
1510
|
+
- Always enable performance metrics during development
|
|
1511
|
+
- Use enhanced pagination controls for better UX
|
|
1512
|
+
- Implement proper error handling for server-side mode
|
|
1513
|
+
- Monitor memory usage in production
|
|
1514
|
+
- Use debounced search for better performance
|
|
1515
|
+
|
|
1516
|
+
### RBAC Best Practices
|
|
1517
|
+
|
|
1518
|
+
1. **Use page-based permissions**: Align with your application's permission system
|
|
1519
|
+
2. **Test with different roles**: Verify permissions work correctly for all user types
|
|
1520
|
+
3. **Provide fallback UI**: Show appropriate messages when permissions are denied
|
|
1521
|
+
4. **Document permission requirements**: Make it clear what permissions are needed
|
|
1522
|
+
5. **Use consistent naming**: Follow your organization's permission naming conventions
|
|
1523
|
+
|
|
1524
|
+
### Filtering Best Practices
|
|
1525
|
+
|
|
1526
|
+
1. **Use `filterSelectOptions` for predefined options** - More explicit and clear
|
|
1527
|
+
2. **Provide meaningful labels** - Use human-readable labels, not IDs
|
|
1528
|
+
3. **Use custom `filterFn` for foreign keys** - Match against IDs, display names
|
|
1529
|
+
4. **Limit select options** - Keep dropdowns manageable (≤20 options)
|
|
1530
|
+
5. **Test with real data** - Ensure filters work with actual data values
|
|
1531
|
+
6. **Consider performance** - Large datasets may need server-side filtering
|
|
1532
|
+
|
|
1533
|
+
### Hierarchical Data Best Practices
|
|
1534
|
+
|
|
1535
|
+
1. **Clear data structure**: Use consistent `isParent` and `parentId` fields
|
|
1536
|
+
2. **Visual hierarchy**: Use indentation and styling to show relationships
|
|
1537
|
+
3. **Smart sorting**: Leverage the built-in hierarchical sorting
|
|
1538
|
+
4. **Appropriate actions**: Different actions for parent vs child rows
|
|
1539
|
+
5. **Expand/collapse state**: Consider user preferences for default state
|
|
1540
|
+
|
|
1541
|
+
### Migration Best Practices
|
|
1542
|
+
|
|
1543
|
+
#### From Standard DataTable
|
|
1544
|
+
1. **Import the enhanced version**:
|
|
1545
|
+
```tsx
|
|
1546
|
+
// Before
|
|
1547
|
+
import { DataTable } from '@jmruthers/pace-core';
|
|
1548
|
+
|
|
1549
|
+
// After
|
|
1550
|
+
import { DataTable } from '@jmruthers/pace-core'; // Same import, enhanced features
|
|
1551
|
+
```
|
|
1552
|
+
|
|
1553
|
+
2. **Enable performance features**:
|
|
1554
|
+
```tsx
|
|
1555
|
+
// Add performance props
|
|
1556
|
+
<DataTable
|
|
1557
|
+
// ... existing props
|
|
1558
|
+
showPerformanceMetrics={true}
|
|
1559
|
+
enhancedPagination={true}
|
|
1560
|
+
virtualHeight={600}
|
|
1561
|
+
/>
|
|
1562
|
+
```
|
|
1563
|
+
|
|
1564
|
+
3. **Configure for your dataset size**:
|
|
1565
|
+
- **< 1,000 records**: No changes needed
|
|
1566
|
+
- **1,000-10,000 records**: Add chunking configuration
|
|
1567
|
+
- **> 10,000 records**: Implement server-side data fetching
|
|
1568
|
+
|
|
1569
|
+
#### From Old Filtering
|
|
1570
|
+
If you're upgrading from an older version:
|
|
1571
|
+
|
|
1572
|
+
1. **Replace `meta.filterOptions`** with `filterSelectOptions`
|
|
1573
|
+
2. **Add `filterType: 'select'`** for explicit select filters
|
|
1574
|
+
3. **Update `filterFn`** to work with new filter value format
|
|
1575
|
+
4. **Test all filter combinations** to ensure they work correctly
|
|
1576
|
+
|
|
1577
|
+
#### Large Dataset Migration
|
|
1578
|
+
If you're upgrading from a previous version:
|
|
1579
|
+
|
|
1580
|
+
1. **Update page size options**: Change from `[10, 20, 50]` to `[25, 50, 100, 250]`
|
|
1581
|
+
2. **Remove hardcoded page sizes**: Let the component auto-optimize
|
|
1582
|
+
3. **Review zero value handling**: Update cell renderers to handle zeros explicitly
|
|
1583
|
+
4. **Test virtualization**: Verify column alignment with your data
|
|
1584
|
+
|
|
1585
|
+
## Architecture
|
|
1586
|
+
|
|
1587
|
+
### Component Structure
|
|
1588
|
+
|
|
1589
|
+
```
|
|
1590
|
+
DataTable
|
|
1591
|
+
├── useDataTablePerformance (hook)
|
|
1592
|
+
│ ├── DataChunkManager
|
|
1593
|
+
│ ├── SearchIndex
|
|
1594
|
+
│ ├── PerformanceMonitor
|
|
1595
|
+
│ └── VisibilityTracker
|
|
1596
|
+
├── VirtualizedDataTable (when enabled)
|
|
1597
|
+
│ ├── MemoizedRow
|
|
1598
|
+
│ └── MemoizedCell
|
|
1599
|
+
├── HierarchicalDataTable (when enabled)
|
|
1600
|
+
│ ├── ExpandCollapseControls
|
|
1601
|
+
│ └── HierarchicalRow
|
|
1602
|
+
└── EnhancedPaginationControls
|
|
1603
|
+
├── Performance metrics display
|
|
1604
|
+
├── Jump to page functionality
|
|
1605
|
+
└── Memory usage tracking
|
|
1606
|
+
```
|
|
1607
|
+
|
|
1608
|
+
### Performance Utilities
|
|
1609
|
+
|
|
1610
|
+
- **`determinePaginationMode`**: Automatically selects optimal pagination strategy
|
|
1611
|
+
- **`getOptimalPageSizeOptions`**: Returns appropriate page sizes for each mode
|
|
1612
|
+
- **`DataChunkManager`**: Manages data chunks with LRU cache
|
|
1613
|
+
- **`SearchIndex`**: Builds and maintains search indexes
|
|
1614
|
+
- **`PerformanceMonitor`**: Tracks render times and memory usage
|
|
1615
|
+
- **`VisibilityTracker`**: Uses Intersection Observer for visibility tracking
|
|
1616
|
+
|
|
1617
|
+
## Future Enhancements
|
|
1618
|
+
|
|
1619
|
+
### Planned Features
|
|
1620
|
+
- **Web Workers** for heavy data processing
|
|
1621
|
+
- **IndexedDB** integration for offline caching
|
|
1622
|
+
- **Streaming data** support for real-time updates
|
|
1623
|
+
- **Column virtualization** for tables with many columns
|
|
1624
|
+
- **Advanced analytics** and performance insights
|
|
1625
|
+
|
|
1626
|
+
### Performance Targets
|
|
1627
|
+
- **Sub-100ms render times** for any dataset size
|
|
1628
|
+
- **< 50MB memory usage** for 100k+ records
|
|
1629
|
+
- **60fps scrolling** performance guaranteed
|
|
1630
|
+
- **< 10ms search times** for indexed fields
|
|
1631
|
+
|
|
1632
|
+
## Conclusion
|
|
1633
|
+
|
|
1634
|
+
The PACE Core DataTable provides enterprise-grade functionality with comprehensive features for data management, RBAC integration, hierarchical data display, advanced filtering, and performance optimization. With automatic performance tuning, intelligent pagination modes, advanced search indexing, and memory management, it can handle datasets from hundreds to hundreds of thousands of records while maintaining excellent user experience.
|
|
1635
|
+
|
|
1636
|
+
The implementation includes extensive testing, performance monitoring, and backward compatibility, making it a powerful tool for building data-rich applications with PACE Core.
|