@jmruthers/pace-core 0.2.7 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DataTable-EEUDXPE5.js → DataTable-GX3XERFJ.js} +8 -4
- package/dist/{DataTable-C1AEm9Cx.d.ts → DataTable-ltTFXHS3.d.ts} +3 -1
- package/dist/{chunk-VYG4AXYW.js → chunk-5EL3KHOQ.js} +2 -2
- package/dist/{chunk-ETEJVKYK.js → chunk-6CR3MRZN.js} +1426 -62
- package/dist/chunk-6CR3MRZN.js.map +1 -0
- package/dist/chunk-AUE24LVR.js +268 -0
- package/dist/chunk-AUE24LVR.js.map +1 -0
- package/dist/chunk-COBPIXXQ.js +379 -0
- package/dist/chunk-COBPIXXQ.js.map +1 -0
- package/dist/{chunk-EWKPTNPO.js → chunk-GSNM5D6H.js} +388 -86
- package/dist/chunk-GSNM5D6H.js.map +1 -0
- package/dist/{chunk-2V3Y6YBC.js → chunk-OEGRKULD.js} +1 -42
- package/dist/chunk-OEGRKULD.js.map +1 -0
- package/dist/chunk-OYRY44Q2.js +62 -0
- package/dist/chunk-OYRY44Q2.js.map +1 -0
- package/dist/{chunk-RRUYHORU.js → chunk-T3XIA4AJ.js} +297 -433
- package/dist/chunk-T3XIA4AJ.js.map +1 -0
- package/dist/{chunk-HEMJ4SUJ.js → chunk-TGDCLPP2.js} +11 -7
- package/dist/{chunk-HEMJ4SUJ.js.map → chunk-TGDCLPP2.js.map} +1 -1
- package/dist/{chunk-HNDFPXUU.js → chunk-U6JDHVC2.js} +6 -4
- package/dist/{chunk-HNDFPXUU.js.map → chunk-U6JDHVC2.js.map} +1 -1
- package/dist/{chunk-TIVL4UQ7.js → chunk-XJK2J4N6.js} +6 -4
- package/dist/{chunk-TIVL4UQ7.js.map → chunk-XJK2J4N6.js.map} +1 -1
- package/dist/components.d.ts +2 -2
- package/dist/components.js +21 -20
- package/dist/components.js.map +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +7 -7
- package/dist/index.d.ts +2 -2
- package/dist/index.js +26 -25
- package/dist/index.js.map +1 -1
- package/dist/providers.js +8 -7
- package/dist/rbac/index.d.ts +806 -806
- package/dist/rbac/index.js +937 -1179
- package/dist/rbac/index.js.map +1 -1
- package/dist/{types-DiRQsGJs.d.ts → types-BRDU7N6w.d.ts} +12 -1
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +6 -6
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.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/DataTableAction.md +21 -8
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +46 -33
- package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
- package/docs/api/interfaces/EmptyStateConfig.md +5 -5
- package/docs/api/interfaces/EventContextType.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/EventProviderProps.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.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/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +2 -2
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/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/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/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/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +3 -3
- package/package.json +5 -2
- package/src/__tests__/REBUILD_PLAN.md +223 -0
- package/src/__tests__/TESTING_GUIDELINES.md +341 -0
- package/src/__tests__/fixtures/mocks.ts +93 -0
- package/src/__tests__/helpers/component-test-utils.tsx +145 -0
- package/src/__tests__/helpers/test-utils.tsx +117 -0
- package/src/__tests__/integration/UserProfile.test.tsx +128 -0
- package/src/__tests__/setup.ts +37 -225
- package/src/__tests__/templates/component.test.template.tsx +97 -75
- package/src/__tests__/templates/hook.test.template.ts +173 -0
- package/src/__tests__/types/test.types.ts +106 -0
- package/src/components/Alert/Alert.test.tsx +496 -0
- package/src/components/Avatar/Avatar.test.tsx +484 -0
- package/src/components/Button/Button.test.tsx +662 -0
- package/src/components/Card/Card.test.tsx +593 -0
- package/src/components/Checkbox/Checkbox.test.tsx +461 -0
- package/src/components/DataTable/DataTable.tsx +9 -1
- package/src/components/DataTable/components/AccessDeniedPage.tsx +168 -0
- package/src/components/DataTable/components/ActionButtons.tsx +18 -1
- package/src/components/DataTable/components/DataTableCore.tsx +97 -11
- package/src/components/DataTable/components/DataTableToolbar.tsx +22 -10
- package/src/components/DataTable/components/UnifiedTableBody.tsx +33 -4
- package/src/components/DataTable/examples/HierarchicalActionsExample.tsx +1 -0
- package/src/components/DataTable/examples/HierarchicalExample.tsx +3 -0
- package/src/components/DataTable/examples/InitialPageSizeExample.tsx +3 -0
- package/src/components/DataTable/examples/PerformanceExample.tsx +3 -0
- package/src/components/DataTable/types.ts +39 -1
- package/src/components/Dialog/Dialog.test.tsx +1139 -0
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +752 -0
- package/src/components/FileUpload/FileUpload.test.tsx +665 -0
- package/src/hooks/useCounter.test.ts +135 -0
- package/src/rbac/index.ts +3 -3
- package/dist/chunk-2V3Y6YBC.js.map +0 -1
- package/dist/chunk-BEZRLNK3.js +0 -1744
- package/dist/chunk-BEZRLNK3.js.map +0 -1
- package/dist/chunk-ETEJVKYK.js.map +0 -1
- package/dist/chunk-EWKPTNPO.js.map +0 -1
- package/dist/chunk-OHXGNT3K.js +0 -21
- package/dist/chunk-OHXGNT3K.js.map +0 -1
- package/dist/chunk-RRUYHORU.js.map +0 -1
- package/src/__tests__/README.md +0 -404
- package/src/__tests__/debug-provider.unit.test.tsx +0 -67
- package/src/__tests__/e2e/workflows.test.tsx +0 -373
- package/src/__tests__/hybridPermissions.unit.test.tsx +0 -474
- package/src/__tests__/index.integration.test.ts +0 -491
- package/src/__tests__/mocks/MockAuthProvider-standalone.tsx +0 -47
- package/src/__tests__/mocks/MockAuthProvider.tsx +0 -63
- package/src/__tests__/mocks/enhancedSupabaseMock.ts +0 -252
- package/src/__tests__/mocks/index.test.ts +0 -23
- package/src/__tests__/mocks/index.ts +0 -16
- package/src/__tests__/mocks/mockAuth.ts +0 -155
- package/src/__tests__/mocks/mockSupabase.ts +0 -83
- package/src/__tests__/mocks/mockSupabaseClient.ts +0 -63
- package/src/__tests__/mocks/providers.tsx +0 -22
- package/src/__tests__/patterns/__tests__/testPatterns.test.ts +0 -394
- package/src/__tests__/patterns/testPatterns.ts +0 -124
- package/src/__tests__/performance/componentPerformance.performance.test.ts +0 -27
- package/src/__tests__/performance/index.ts +0 -24
- package/src/__tests__/performance/performanceValidation.performance.test.ts +0 -15
- package/src/__tests__/security/security.unit.test.tsx +0 -7
- package/src/__tests__/security/securityValidation.security.test.tsx +0 -153
- package/src/__tests__/setupTests.d.ts +0 -1
- package/src/__tests__/shared/componentTestUtils.tsx +0 -475
- package/src/__tests__/shared/errorHandlingTestUtils.ts +0 -107
- package/src/__tests__/shared/index.ts +0 -81
- package/src/__tests__/shared/integrationTestUtils.tsx +0 -375
- package/src/__tests__/shared/performanceTestUtils.tsx +0 -476
- package/src/__tests__/shared/testUtils.optimized.tsx +0 -685
- package/src/__tests__/simple.test.tsx +0 -20
- package/src/__tests__/test-utils/dataFactories.ts +0 -60
- package/src/__tests__/test-utils/index.ts +0 -6
- package/src/__tests__/typeSafety.unit.test.ts +0 -65
- package/src/__tests__/unifiedAuth.unit.test.tsx +0 -151
- package/src/__tests__/utils/accessibilityHelpers.ts +0 -254
- package/src/__tests__/utils/assertions.ts +0 -50
- package/src/__tests__/utils/deterministicHelpers.ts +0 -31
- package/src/__tests__/utils/edgeCaseConfig.test.ts +0 -75
- package/src/__tests__/utils/edgeCaseConfig.ts +0 -98
- package/src/__tests__/utils/mockHelpers.ts +0 -149
- package/src/__tests__/utils/mockLoader.ts +0 -101
- package/src/__tests__/utils/performanceHelpers.ts +0 -55
- package/src/__tests__/utils/performanceTestHelpers.ts +0 -68
- package/src/__tests__/utils/testDataFactories.ts +0 -28
- package/src/__tests__/utils/testIsolation.ts +0 -67
- package/src/__tests__/utils/visualTestHelpers.ts +0 -20
- package/src/__tests__/visual/__snapshots__/componentSnapshots.visual.test.tsx.snap +0 -68
- package/src/__tests__/visual/__snapshots__/componentVisuals.visual.test.tsx.snap +0 -14
- package/src/__tests__/visual/__snapshots__/visualRegression.test.tsx.snap +0 -217
- package/src/__tests__/visual/__snapshots__/visualRegression.visual.test.tsx.snap +0 -24
- package/src/__tests__/visual/componentSnapshots.visual.test.tsx +0 -33
- package/src/__tests__/visual/componentVisuals.visual.test.tsx +0 -12
- package/src/__tests__/visual/visualRegression.visual.test.tsx +0 -20
- package/src/components/Alert/__tests__/Alert.unit.test.tsx +0 -381
- package/src/components/Avatar/__tests__/Avatar.unit.test.tsx +0 -232
- package/src/components/Button/__tests__/Button.accessibility.test.tsx +0 -131
- package/src/components/Button/__tests__/Button.comprehensive.test.tsx +0 -721
- package/src/components/Button/__tests__/Button.unit.test.tsx +0 -189
- package/src/components/Button/__tests__/EventSelector.integration.test.tsx +0 -285
- package/src/components/Card/__tests__/Card.accessibility.test.tsx +0 -394
- package/src/components/Card/__tests__/Card.comprehensive.test.tsx +0 -599
- package/src/components/Card/__tests__/Card.integration.test.tsx +0 -673
- package/src/components/Card/__tests__/Card.performance.test.tsx +0 -546
- package/src/components/Card/__tests__/Card.unit.test.tsx +0 -330
- package/src/components/Card/__tests__/Card.visual.test.tsx +0 -599
- package/src/components/Card/__tests__/README.md +0 -211
- package/src/components/Checkbox/__tests__/Checkbox.unit.test.tsx +0 -520
- package/src/components/DataTable/__tests__/DataTable.errorHandling.test.tsx +0 -251
- package/src/components/DataTable/__tests__/DataTable.hierarchical.test.tsx +0 -680
- package/src/components/DataTable/__tests__/DataTable.infinite-loop.test.tsx +0 -323
- package/src/components/DataTable/__tests__/DataTable.integration.test.tsx +0 -716
- package/src/components/DataTable/__tests__/DataTable.performance.test.tsx +0 -589
- package/src/components/DataTable/__tests__/DataTable.permissions.test.tsx +0 -316
- package/src/components/DataTable/__tests__/DataTable.regressionFixes.test.tsx +0 -546
- package/src/components/DataTable/__tests__/DataTable.selection.controlled.test.tsx +0 -386
- package/src/components/DataTable/__tests__/DataTable.selection.test.tsx +0 -338
- package/src/components/DataTable/__tests__/DataTable.sorting.test.tsx +0 -321
- package/src/components/DataTable/__tests__/DataTable.userWorkflows.test.tsx +0 -320
- package/src/components/DataTable/__tests__/DataTable.workflowValidation.test.tsx +0 -583
- package/src/components/DataTable/__tests__/DataTable.workflows.test.tsx +0 -711
- package/src/components/DataTable/__tests__/performance-regression.test.tsx +0 -777
- package/src/components/DataTable/__tests__/performance.test.tsx +0 -365
- package/src/components/DataTable/components/__tests__/ActionButtons.unit.test.tsx +0 -150
- package/src/components/DataTable/components/__tests__/BulkOperationsDropdown.test.tsx +0 -224
- package/src/components/DataTable/components/__tests__/ColumnVisibilityDropdown.unit.test.tsx +0 -244
- package/src/components/DataTable/components/__tests__/DataTable.accessibility.test.tsx +0 -629
- package/src/components/DataTable/components/__tests__/DataTable.integration.test.tsx +0 -470
- package/src/components/DataTable/components/__tests__/DataTable.performance.test.tsx +0 -160
- package/src/components/DataTable/components/__tests__/DataTable.real.test.tsx +0 -251
- package/src/components/DataTable/components/__tests__/DataTable.security.test.tsx +0 -171
- package/src/components/DataTable/components/__tests__/DataTable.unit.test.tsx +0 -290
- package/src/components/DataTable/components/__tests__/DataTableBody.unit.test.tsx +0 -147
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.unit.test.tsx +0 -182
- package/src/components/DataTable/components/__tests__/DataTableModals.unit.test.tsx +0 -123
- package/src/components/DataTable/components/__tests__/EditableRow.unit.test.tsx +0 -660
- package/src/components/DataTable/components/__tests__/EmptyState.unit.test.tsx +0 -256
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -498
- package/src/components/DataTable/components/__tests__/FilterRow.unit.test.tsx +0 -112
- package/src/components/DataTable/components/__tests__/FilteringToggle.unit.test.tsx +0 -133
- package/src/components/DataTable/components/__tests__/GroupHeader.unit.test.tsx +0 -172
- package/src/components/DataTable/components/__tests__/GroupingDropdown.unit.test.tsx +0 -222
- package/src/components/DataTable/components/__tests__/ImportModal.unit.test.tsx +0 -780
- package/src/components/DataTable/components/__tests__/LoadingState.unit.test.tsx +0 -65
- package/src/components/DataTable/components/__tests__/PaginationControls.unit.test.tsx +0 -634
- package/src/components/DataTable/components/__tests__/StateComponents.unit.test.tsx +0 -48
- package/src/components/DataTable/components/__tests__/UnifiedTableBody.hierarchical.test.tsx +0 -541
- package/src/components/DataTable/components/__tests__/ViewRowModal.unit.test.tsx +0 -228
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.unit.test.tsx +0 -568
- package/src/components/DataTable/core/__tests__/ActionManager.unit.test.ts +0 -405
- package/src/components/DataTable/core/__tests__/ArchitectureIntegration.unit.test.tsx +0 -445
- package/src/components/DataTable/core/__tests__/ColumnFactory.unit.test.ts +0 -288
- package/src/components/DataTable/core/__tests__/ColumnManager.unit.test.ts +0 -623
- package/src/components/DataTable/core/__tests__/DataManager.unit.test.ts +0 -431
- package/src/components/DataTable/core/__tests__/DataTableContext.unit.test.tsx +0 -433
- package/src/components/DataTable/core/__tests__/LocalDataAdapter.unit.test.ts +0 -422
- package/src/components/DataTable/core/__tests__/PluginRegistry.unit.test.tsx +0 -207
- package/src/components/DataTable/core/__tests__/StateManager.unit.test.ts +0 -278
- package/src/components/DataTable/examples/__tests__/PerformanceExample.unit.test.tsx +0 -281
- package/src/components/DataTable/hooks/__tests__/useColumnOrderPersistence.unit.test.ts +0 -407
- package/src/components/DataTable/hooks/__tests__/useColumnReordering.unit.test.ts +0 -679
- package/src/components/DataTable/utils/__tests__/debugTools.unit.test.ts +0 -267
- package/src/components/DataTable/utils/__tests__/errorHandling.unit.test.ts +0 -467
- package/src/components/DataTable/utils/__tests__/exportUtils.unit.test.ts +0 -380
- package/src/components/DataTable/utils/__tests__/flexibleImport.unit.test.ts +0 -233
- package/src/components/DataTable/utils/__tests__/performanceUtils.unit.test.ts +0 -414
- package/src/components/Dialog/__tests__/Dialog.accessibility.test.tsx +0 -521
- package/src/components/Dialog/__tests__/Dialog.auto-size.example.tsx +0 -157
- package/src/components/Dialog/__tests__/Dialog.enhanced.test.tsx +0 -538
- package/src/components/Dialog/__tests__/Dialog.unit.test.tsx +0 -1373
- package/src/components/Dialog/examples/__tests__/SmartDialogExample.unit.test.tsx +0 -151
- package/src/components/Dialog/utils/__tests__/safeHtml.unit.test.ts +0 -611
- package/src/components/ErrorBoundary/__tests__/ErrorBoundary.accessibility.test.tsx +0 -517
- package/src/components/ErrorBoundary/__tests__/ErrorBoundary.integration.test.tsx +0 -572
- package/src/components/ErrorBoundary/__tests__/ErrorBoundary.unit.test.tsx +0 -579
- package/src/components/EventSelector/__tests__/EventSelector.test.tsx +0 -528
- package/src/components/FileUpload/__tests__/FileUpload.integration.test.tsx +0 -992
- package/src/components/FileUpload/__tests__/FileUpload.real.test.tsx +0 -927
- package/src/components/FileUpload/__tests__/FileUpload.test.tsx +0 -855
- package/src/components/FileUpload/__tests__/FileUpload.unit.test.tsx +0 -1311
- package/src/components/FileUpload/__tests__/FileUpload.unmocked.test.tsx +0 -937
- package/src/components/Footer/__tests__/Footer.accessibility.test.tsx +0 -359
- package/src/components/Footer/__tests__/Footer.integration.test.tsx +0 -353
- package/src/components/Footer/__tests__/Footer.performance.test.tsx +0 -309
- package/src/components/Footer/__tests__/Footer.unit.test.tsx +0 -309
- package/src/components/Footer/__tests__/Footer.visual.test.tsx +0 -335
- package/src/components/Form/__tests__/Form.accessibility.test.tsx +0 -820
- package/src/components/Form/__tests__/Form.unit.test.tsx +0 -305
- package/src/components/Form/__tests__/FormErrorSummary.unit.test.tsx +0 -285
- package/src/components/Form/__tests__/FormFieldset.unit.test.tsx +0 -241
- package/src/components/Header/__tests__/Header.accessibility.test.tsx +0 -382
- package/src/components/Header/__tests__/Header.comprehensive.test.tsx +0 -509
- package/src/components/Header/__tests__/Header.unit.test.tsx +0 -335
- package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +0 -196
- package/src/components/InactivityWarningModal/__tests__/InactivityWarningModal.unit.test.tsx +0 -224
- package/src/components/Input/__tests__/Input.accessibility.test.tsx +0 -632
- package/src/components/Input/__tests__/Input.unit.test.tsx +0 -1121
- package/src/components/Label/__tests__/Label.accessibility.test.tsx +0 -239
- package/src/components/Label/__tests__/Label.unit.test.tsx +0 -331
- package/src/components/LoadingSpinner/__tests__/LoadingSpinner.accessibility.test.tsx +0 -116
- package/src/components/LoadingSpinner/__tests__/LoadingSpinner.unit.test.tsx +0 -144
- package/src/components/LoginForm/__tests__/LoginForm.accessibility.test.tsx +0 -201
- package/src/components/LoginForm/__tests__/LoginForm.unit.test.tsx +0 -119
- package/src/components/NavigationMenu/__tests__/NavigationMenu.accessibility.test.tsx +0 -378
- package/src/components/NavigationMenu/__tests__/NavigationMenu.enhanced.test.tsx +0 -768
- package/src/components/NavigationMenu/__tests__/NavigationMenu.integration.test.tsx +0 -576
- package/src/components/NavigationMenu/__tests__/NavigationMenu.performance.test.tsx +0 -585
- package/src/components/NavigationMenu/__tests__/NavigationMenu.real.component.test.tsx +0 -783
- package/src/components/NavigationMenu/__tests__/NavigationMenu.security.enhanced.test.tsx +0 -810
- package/src/components/NavigationMenu/__tests__/NavigationMenu.security.test.tsx +0 -494
- package/src/components/NavigationMenu/__tests__/NavigationMenu.unit.test.tsx +0 -331
- package/src/components/NavigationMenu/__tests__/NavigationMenu.userWorkflows.test.tsx +0 -347
- package/src/components/NavigationMenu/__tests__/NavigationMenu.workflows.test.tsx +0 -584
- package/src/components/OrganisationSelector/__tests__/OrganisationSelector.unit.test.tsx +0 -664
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.accessibility.test.tsx +0 -288
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +0 -893
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +0 -629
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +0 -782
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.unit.test.tsx +0 -904
- package/src/components/PaceLoginPage/__tests__/PaceLoginPage.accessibility.test.tsx +0 -463
- package/src/components/PaceLoginPage/__tests__/PaceLoginPage.integration.test.tsx +0 -586
- package/src/components/PaceLoginPage/__tests__/PaceLoginPage.unit.test.tsx +0 -533
- package/src/components/PasswordReset/__tests__/PasswordChangeForm.accessibility.test.tsx +0 -408
- package/src/components/PasswordReset/__tests__/PasswordChangeForm.unit.test.tsx +0 -561
- package/src/components/PasswordReset/__tests__/PasswordReset.integration.test.tsx +0 -304
- package/src/components/PasswordReset/__tests__/PasswordResetForm.accessibility.test.tsx +0 -20
- package/src/components/PasswordReset/__tests__/PasswordResetForm.unit.test.tsx +0 -523
- package/src/components/PasswordReset/__tests__/__mocks__/UnifiedAuthProvider.ts +0 -29
- package/src/components/Print/__tests__/Print.comprehensive.test.tsx +0 -331
- package/src/components/PrintButton/__tests__/PrintButton.unit.test.tsx +0 -429
- package/src/components/PrintButton/__tests__/PrintButtonGroup.unit.test.tsx +0 -277
- package/src/components/PrintButton/__tests__/PrintToolbar.unit.test.tsx +0 -264
- package/src/components/PrintCard/__tests__/PrintCard.unit.test.tsx +0 -233
- package/src/components/PrintCard/__tests__/PrintCardContent.test.tsx +0 -284
- package/src/components/PrintCard/__tests__/PrintCardGrid.unit.test.tsx +0 -214
- package/src/components/PrintCard/__tests__/PrintCardImage.unit.test.tsx +0 -264
- package/src/components/PrintDataTable/__tests__/PrintDataTable.unit.test.tsx +0 -361
- package/src/components/PrintDataTable/__tests__/PrintTableGroup.unit.test.tsx +0 -314
- package/src/components/PrintDataTable/__tests__/PrintTableRow.unit.test.tsx +0 -362
- package/src/components/PrintFooter/__tests__/PrintFooter.unit.test.tsx +0 -500
- package/src/components/PrintFooter/__tests__/PrintFooterContent.unit.test.tsx +0 -321
- package/src/components/PrintFooter/__tests__/PrintFooterInfo.unit.test.tsx +0 -335
- package/src/components/PrintFooter/__tests__/PrintPageNumber.unit.test.tsx +0 -340
- package/src/components/PrintGrid/__tests__/PrintGrid.unit.test.tsx +0 -340
- package/src/components/PrintGrid/__tests__/PrintGridBreakpoint.unit.test.tsx +0 -261
- package/src/components/PrintGrid/__tests__/PrintGridContainer.unit.test.tsx +0 -338
- package/src/components/PrintGrid/__tests__/PrintGridItem.unit.test.tsx +0 -338
- package/src/components/PrintHeader/__tests__/PrintCoverHeader.unit.test.tsx +0 -309
- package/src/components/PrintHeader/__tests__/PrintHeader.unit.test.tsx +0 -202
- package/src/components/PrintLayout/__tests__/PrintLayout.unit.test.tsx +0 -238
- package/src/components/PrintPageBreak/__tests__/PrintPageBreak.unit.test.tsx +0 -263
- package/src/components/PrintPageBreak/__tests__/PrintPageBreakGroup.unit.test.tsx +0 -239
- package/src/components/PrintPageBreak/__tests__/PrintPageBreakIndicator.unit.test.tsx +0 -235
- package/src/components/PrintSection/__tests__/PrintColumn.unit.test.tsx +0 -385
- package/src/components/PrintSection/__tests__/PrintDivider.unit.test.tsx +0 -373
- package/src/components/PrintSection/__tests__/PrintSection.unit.test.tsx +0 -390
- package/src/components/PrintSection/__tests__/PrintSectionContent.unit.test.tsx +0 -321
- package/src/components/PrintSection/__tests__/PrintSectionHeader.unit.test.tsx +0 -334
- package/src/components/PrintText/__tests__/PrintText.unit.test.tsx +0 -351
- package/src/components/Progress/__tests__/Progress.accessibility.test.tsx +0 -240
- package/src/components/Progress/__tests__/Progress.unit.test.tsx +0 -242
- package/src/components/PublicLayout/__tests__/EventLogo.test.tsx +0 -761
- package/src/components/PublicLayout/__tests__/PublicErrorBoundary.simplified.test.tsx +0 -228
- package/src/components/PublicLayout/__tests__/PublicErrorBoundary.test.tsx +0 -228
- package/src/components/PublicLayout/__tests__/PublicLoadingSpinner.test.tsx +0 -459
- package/src/components/PublicLayout/__tests__/PublicPageFooter.test.tsx +0 -362
- package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +0 -522
- package/src/components/PublicLayout/__tests__/PublicPageLayout.test.tsx +0 -599
- package/src/components/PublicLayout/__tests__/PublicPageProvider.test.tsx +0 -513
- package/src/components/RBAC/__tests__/PagePermissionGuard.unit.test.tsx +0 -683
- package/src/components/RBAC/__tests__/RBAC.integration.test.tsx +0 -573
- package/src/components/RBAC/__tests__/RBACGuard.unit.test.tsx +0 -467
- package/src/components/RBAC/__tests__/RBACProvider.accessibility.test.tsx +0 -475
- package/src/components/RBAC/__tests__/RBACProvider.advanced.test.tsx +0 -569
- package/src/components/RBAC/__tests__/RBACProvider.integration.test.tsx +0 -352
- package/src/components/RBAC/__tests__/RBACProvider.unit.test.tsx +0 -128
- package/src/components/RBAC/__tests__/RoleBasedContent.unit.test.tsx +0 -657
- package/src/components/Select/__tests__/SearchableSelect.unit.test.tsx +0 -437
- package/src/components/Select/__tests__/Select.accessibility.test.tsx +0 -1202
- package/src/components/Select/__tests__/Select.actual.test.tsx +0 -774
- package/src/components/Select/__tests__/Select.comprehensive.test.tsx +0 -837
- package/src/components/Select/__tests__/Select.enhanced.test.tsx +0 -1101
- package/src/components/Select/__tests__/Select.integration.test.tsx +0 -772
- package/src/components/Select/__tests__/Select.performance.test.tsx +0 -695
- package/src/components/Select/__tests__/Select.real-world.test.tsx +0 -1046
- package/src/components/Select/__tests__/Select.search-algorithms.test.tsx +0 -968
- package/src/components/Select/__tests__/Select.unit.test.tsx +0 -647
- package/src/components/Select/__tests__/Select.utils.test.tsx +0 -890
- package/src/components/Table/__tests__/Table.accessibility.test.tsx +0 -233
- package/src/components/Table/__tests__/Table.unit.test.tsx +0 -235
- package/src/components/Toast/__tests__/Toast.accessibility.test.tsx +0 -238
- package/src/components/Toast/__tests__/Toast.integration.test.tsx +0 -699
- package/src/components/Toast/__tests__/Toast.unit.test.tsx +0 -750
- package/src/components/Tooltip/__tests__/Tooltip.accessibility.test.tsx +0 -121
- package/src/components/Tooltip/__tests__/Tooltip.unit.test.tsx +0 -185
- package/src/components/UserMenu/__tests__/UserMenu.accessibility.test.tsx +0 -139
- package/src/components/UserMenu/__tests__/UserMenu.integration.test.tsx +0 -188
- package/src/components/UserMenu/__tests__/UserMenu.unit.test.tsx +0 -458
- package/src/components/__tests__/EdgeCaseTesting.enhanced.test.tsx +0 -524
- package/src/components/__tests__/ErrorTesting.enhanced.test.tsx +0 -455
- package/src/components/__tests__/SuperAdminGuard.test.tsx +0 -456
- package/src/components/__tests__/SuperAdminGuard.unit.test.tsx +0 -456
- package/src/components/examples/__tests__/PermissionExample.unit.test.tsx +0 -360
- package/src/hooks/__tests__/hooks.integration.test.tsx +0 -575
- package/src/hooks/__tests__/useApiFetch.unit.test.ts +0 -115
- package/src/hooks/__tests__/useComponentPerformance.unit.test.tsx +0 -133
- package/src/hooks/__tests__/useDebounce.unit.test.ts +0 -82
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +0 -293
- package/src/hooks/__tests__/useInactivityTracker.unit.test.ts +0 -385
- package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +0 -286
- package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +0 -838
- package/src/hooks/__tests__/usePermissionCache.unit.test.ts +0 -627
- package/src/hooks/__tests__/useRBAC.unit.test.ts +0 -911
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -537
- package/src/hooks/__tests__/useToast.unit.test.tsx +0 -62
- package/src/hooks/__tests__/useZodForm.unit.test.tsx +0 -37
- package/src/hooks/public/__tests__/usePublicEvent.test.tsx +0 -397
- package/src/hooks/public/__tests__/usePublicEventLogo.test.tsx +0 -690
- package/src/hooks/public/__tests__/usePublicRouteParams.test.tsx +0 -449
- package/src/providers/__tests__/EventProvider.unit.test.tsx +0 -768
- package/src/providers/__tests__/OrganisationProvider.basic.test.tsx +0 -116
- package/src/providers/__tests__/OrganisationProvider.unit.test.tsx +0 -1312
- package/src/providers/__tests__/UnifiedAuthProvider.inactivity.test.tsx +0 -601
- package/src/providers/__tests__/UnifiedAuthProvider.unit.test.tsx +0 -683
- package/src/providers/__tests__/index.unit.test.ts +0 -78
- package/src/rbac/__tests__/PagePermissionGuard.test.tsx +0 -673
- package/src/rbac/__tests__/README.md +0 -170
- package/src/rbac/__tests__/RoleBasedRouter.test.tsx +0 -709
- package/src/rbac/__tests__/TestContext.tsx +0 -72
- package/src/rbac/__tests__/__mocks__/cache.ts +0 -144
- package/src/rbac/__tests__/__mocks__/supabase.ts +0 -152
- package/src/rbac/__tests__/adapters-hooks-comprehensive.test.tsx +0 -782
- package/src/rbac/__tests__/adapters-hooks.test.tsx +0 -561
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +0 -963
- package/src/rbac/__tests__/adapters.test.tsx +0 -444
- package/src/rbac/__tests__/api.test.ts +0 -620
- package/src/rbac/__tests__/audit-observability-comprehensive.test.ts +0 -792
- package/src/rbac/__tests__/audit-observability.test.ts +0 -549
- package/src/rbac/__tests__/audit.test.ts +0 -616
- package/src/rbac/__tests__/build-contract-compliance-simple.test.ts +0 -230
- package/src/rbac/__tests__/cache-invalidation-comprehensive.test.ts +0 -889
- package/src/rbac/__tests__/cache-invalidation.test.ts +0 -457
- package/src/rbac/__tests__/cache.test.ts +0 -458
- package/src/rbac/__tests__/components-navigation-guard.enhanced.test.tsx +0 -859
- package/src/rbac/__tests__/components-navigation-guard.test.tsx +0 -895
- package/src/rbac/__tests__/components-navigation-provider.test.tsx +0 -692
- package/src/rbac/__tests__/components-page-permission-guard.test.tsx +0 -673
- package/src/rbac/__tests__/components-page-permission-provider.test.tsx +0 -614
- package/src/rbac/__tests__/components-permission-enforcer.enhanced.fixed.test.tsx +0 -836
- package/src/rbac/__tests__/components-permission-enforcer.enhanced.test.tsx +0 -837
- package/src/rbac/__tests__/components-permission-enforcer.test.tsx +0 -825
- package/src/rbac/__tests__/components-role-based-router.test.tsx +0 -709
- package/src/rbac/__tests__/components-secure-data-provider.test.tsx +0 -607
- package/src/rbac/__tests__/config.test.ts +0 -583
- package/src/rbac/__tests__/core-logic-unit.test.ts +0 -190
- package/src/rbac/__tests__/core-permission-logic-comprehensive.test.ts +0 -1467
- package/src/rbac/__tests__/core-permission-logic-fixed.test.ts +0 -151
- package/src/rbac/__tests__/core-permission-logic-simple.test.ts +0 -968
- package/src/rbac/__tests__/core-permission-logic.test.ts +0 -966
- package/src/rbac/__tests__/edge-cases-comprehensive.test.ts +0 -988
- package/src/rbac/__tests__/edge-cases.test.ts +0 -654
- package/src/rbac/__tests__/engine.test.ts +0 -361
- package/src/rbac/__tests__/engine.unit.test.ts +0 -361
- package/src/rbac/__tests__/hooks.enhanced.test.tsx +0 -979
- package/src/rbac/__tests__/hooks.fixed.test.tsx +0 -475
- package/src/rbac/__tests__/hooks.test.tsx +0 -385
- package/src/rbac/__tests__/index.test.ts +0 -269
- package/src/rbac/__tests__/integration.enhanced.test.tsx +0 -824
- package/src/rbac/__tests__/page-permission-guard-super-admin.test.tsx +0 -261
- package/src/rbac/__tests__/performance.enhanced.test.tsx +0 -724
- package/src/rbac/__tests__/permissions.test.ts +0 -383
- package/src/rbac/__tests__/requires-event.test.ts +0 -330
- package/src/rbac/__tests__/scope-isolation-comprehensive.test.ts +0 -1349
- package/src/rbac/__tests__/scope-isolation.test.ts +0 -755
- package/src/rbac/__tests__/secure-client-rls-comprehensive.test.ts +0 -592
- package/src/rbac/__tests__/secure-client-rls.test.ts +0 -377
- package/src/rbac/__tests__/security.test.ts +0 -296
- package/src/rbac/__tests__/setup.ts +0 -228
- package/src/rbac/__tests__/test-utils-enhanced.tsx +0 -400
- package/src/rbac/__tests__/types.test.ts +0 -685
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -631
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -667
- package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -647
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -496
- package/src/rbac/testing/__tests__/index.test.tsx +0 -342
- package/src/rbac/utils/__tests__/eventContext.test.ts +0 -428
- package/src/rbac/utils/__tests__/eventContext.unit.test.ts +0 -428
- package/src/styles/__tests__/styles.unit.test.ts +0 -164
- package/src/test-dom-cleanup.test.tsx +0 -38
- package/src/theming/__tests__/README.md +0 -335
- package/src/theming/__tests__/runtime.accessibility.test.ts +0 -474
- package/src/theming/__tests__/runtime.error.test.ts +0 -616
- package/src/theming/__tests__/runtime.integration.test.ts +0 -376
- package/src/theming/__tests__/runtime.performance.test.ts +0 -411
- package/src/theming/__tests__/runtime.unit.test.ts +0 -470
- package/src/types/__tests__/database.unit.test.ts +0 -489
- package/src/types/__tests__/guards.unit.test.ts +0 -146
- package/src/types/__tests__/index.unit.test.ts +0 -77
- package/src/types/__tests__/organisation.unit.test.ts +0 -713
- package/src/types/__tests__/rbac.unit.test.ts +0 -621
- package/src/types/__tests__/security.unit.test.ts +0 -347
- package/src/types/__tests__/supabase.unit.test.ts +0 -658
- package/src/types/__tests__/theme.unit.test.ts +0 -218
- package/src/types/__tests__/unified.unit.test.ts +0 -537
- package/src/types/__tests__/validation.unit.test.ts +0 -616
- package/src/utils/__tests__/appConfig.unit.test.ts +0 -55
- package/src/utils/__tests__/appNameResolver.unit.test.ts +0 -137
- package/src/utils/__tests__/audit.unit.test.ts +0 -69
- package/src/utils/__tests__/auth-utils.unit.test.ts +0 -70
- package/src/utils/__tests__/bundleAnalysis.unit.test.ts +0 -317
- package/src/utils/__tests__/cn.unit.test.ts +0 -34
- package/src/utils/__tests__/deviceFingerprint.unit.test.ts +0 -480
- package/src/utils/__tests__/dynamicUtils.unit.test.ts +0 -322
- package/src/utils/__tests__/formatDate.unit.test.ts +0 -109
- package/src/utils/__tests__/formatting.unit.test.ts +0 -66
- package/src/utils/__tests__/index.unit.test.ts +0 -251
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +0 -304
- package/src/utils/__tests__/organisationContext.unit.test.ts +0 -192
- package/src/utils/__tests__/performanceBudgets.unit.test.ts +0 -259
- package/src/utils/__tests__/permissionTypes.unit.test.ts +0 -250
- package/src/utils/__tests__/permissionUtils.unit.test.ts +0 -362
- package/src/utils/__tests__/sanitization.unit.test.ts +0 -346
- package/src/utils/__tests__/schemaUtils.unit.test.ts +0 -441
- package/src/utils/__tests__/secureDataAccess.unit.test.ts +0 -334
- package/src/utils/__tests__/secureErrors.unit.test.ts +0 -377
- package/src/utils/__tests__/secureStorage.unit.test.ts +0 -293
- package/src/utils/__tests__/security.unit.test.ts +0 -127
- package/src/utils/__tests__/securityMonitor.unit.test.ts +0 -280
- package/src/utils/__tests__/sessionTracking.unit.test.ts +0 -370
- package/src/utils/__tests__/validation.unit.test.ts +0 -84
- package/src/utils/__tests__/validationUtils.unit.test.ts +0 -571
- package/src/utils/print/__tests__/PrintDataProcessor.unit.test.ts +0 -219
- package/src/utils/print/__tests__/usePrintOptimization.unit.test.tsx +0 -353
- package/src/utils/storage/__tests__/config.unit.test.ts +0 -206
- package/src/utils/storage/__tests__/helpers.unit.test.ts +0 -648
- package/src/utils/storage/__tests__/index.unit.test.ts +0 -167
- package/src/utils/storage/__tests__/types.unit.test.ts +0 -441
- package/src/validation/__tests__/common.unit.test.ts +0 -101
- package/src/validation/__tests__/csrf.unit.test.ts +0 -302
- package/src/validation/__tests__/passwordSchema.unit.test.ts +0 -98
- package/src/validation/__tests__/sqlInjectionProtection.unit.test.ts +0 -466
- /package/dist/{DataTable-EEUDXPE5.js.map → DataTable-GX3XERFJ.js.map} +0 -0
- /package/dist/{chunk-VYG4AXYW.js.map → chunk-5EL3KHOQ.js.map} +0 -0
|
@@ -46,6 +46,9 @@ import { useDataTableState } from '../hooks/useDataTableState';
|
|
|
46
46
|
import { validateHierarchicalData, sortHierarchicalData } from '../utils/hierarchicalUtils';
|
|
47
47
|
import { sortHierarchicalData as sortHierarchicalDataWithSorting } from '../utils/hierarchicalSorting';
|
|
48
48
|
import { ColumnFactory } from '../core/ColumnFactory';
|
|
49
|
+
import { AccessDeniedPage } from './AccessDeniedPage';
|
|
50
|
+
import { useCan } from '../../../rbac/hooks';
|
|
51
|
+
import { useUnifiedAuth } from '../../../providers/UnifiedAuthProvider';
|
|
49
52
|
|
|
50
53
|
import type {
|
|
51
54
|
DataRecord,
|
|
@@ -63,7 +66,9 @@ import type {
|
|
|
63
66
|
AggregateConfig,
|
|
64
67
|
DataTableAction,
|
|
65
68
|
HierarchicalConfig,
|
|
66
|
-
HierarchicalDataRow
|
|
69
|
+
HierarchicalDataRow,
|
|
70
|
+
DataTableRBACConfig,
|
|
71
|
+
RBACContext
|
|
67
72
|
} from '../types';
|
|
68
73
|
import type { ImportModalConfig } from './ImportModal';
|
|
69
74
|
|
|
@@ -76,6 +81,10 @@ export interface DataTableCoreProps<TData extends DataRecord> {
|
|
|
76
81
|
data: TData[];
|
|
77
82
|
columns: DataTableColumn<TData>[];
|
|
78
83
|
|
|
84
|
+
// MANDATORY RBAC - NO OPTIONAL
|
|
85
|
+
/** RBAC configuration - REQUIRED for all DataTables */
|
|
86
|
+
rbac: DataTableRBACConfig;
|
|
87
|
+
|
|
79
88
|
// Display
|
|
80
89
|
title?: string;
|
|
81
90
|
description?: string;
|
|
@@ -105,6 +114,7 @@ export interface DataTableCoreProps<TData extends DataRecord> {
|
|
|
105
114
|
onDeleteRow?: (row: TData) => void;
|
|
106
115
|
onCreateRow?: (data: Partial<TData>) => void;
|
|
107
116
|
onImport?: (data: TData[]) => void | Promise<void>;
|
|
117
|
+
onExport?: () => void;
|
|
108
118
|
onRowSelectionChange?: (selection: Record<string, boolean>) => void;
|
|
109
119
|
selection?: Record<string, boolean>;
|
|
110
120
|
onDeleteSelected?: (selectedRows: Record<string, boolean>) => void;
|
|
@@ -126,6 +136,7 @@ export interface DataTableCoreProps<TData extends DataRecord> {
|
|
|
126
136
|
function DataTableInternal<TData extends DataRecord>({
|
|
127
137
|
data,
|
|
128
138
|
columns,
|
|
139
|
+
rbac, // MANDATORY RBAC
|
|
129
140
|
title,
|
|
130
141
|
description,
|
|
131
142
|
variant = 'default',
|
|
@@ -146,6 +157,7 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
146
157
|
onDeleteRow,
|
|
147
158
|
onCreateRow,
|
|
148
159
|
onImport,
|
|
160
|
+
onExport,
|
|
149
161
|
onRowSelectionChange,
|
|
150
162
|
selection,
|
|
151
163
|
onDeleteSelected,
|
|
@@ -158,6 +170,65 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
158
170
|
columnOrder: externalColumnOrder,
|
|
159
171
|
}: DataTableCoreProps<TData>) {
|
|
160
172
|
|
|
173
|
+
// ============================================================================
|
|
174
|
+
// MANDATORY RBAC ENFORCEMENT
|
|
175
|
+
// ============================================================================
|
|
176
|
+
|
|
177
|
+
// MANDATORY: Get authenticated user
|
|
178
|
+
const authResult = useUnifiedAuth();
|
|
179
|
+
const user = authResult.user;
|
|
180
|
+
|
|
181
|
+
// MANDATORY: Every DataTable must have a user and RBAC config
|
|
182
|
+
if (!user) {
|
|
183
|
+
throw new Error('DataTable requires authenticated user for RBAC');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!rbac?.resource) {
|
|
187
|
+
throw new Error('DataTable requires rbac.resource for permission checking');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const scope = {
|
|
191
|
+
organisationId: user?.user_metadata?.organisationId || user?.app_metadata?.organisationId,
|
|
192
|
+
eventId: user?.user_metadata?.eventId || user?.app_metadata?.eventId,
|
|
193
|
+
appId: user?.user_metadata?.appId || user?.app_metadata?.appId,
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// MANDATORY: Check all permissions upfront - ALWAYS enforce RBAC
|
|
197
|
+
const permissions = {
|
|
198
|
+
canRead: useCan(user.id, scope, `read:${rbac.resource}` as any, rbac.pageId),
|
|
199
|
+
canCreate: useCan(user.id, scope, `create:${rbac.resource}` as any, rbac.pageId),
|
|
200
|
+
canUpdate: useCan(user.id, scope, `update:${rbac.resource}` as any, rbac.pageId),
|
|
201
|
+
canDelete: useCan(user.id, scope, `delete:${rbac.resource}` as any, rbac.pageId),
|
|
202
|
+
canExport: useCan(user.id, scope, `manage:${rbac.resource}` as any, rbac.pageId), // Using manage for export/import
|
|
203
|
+
canImport: useCan(user.id, scope, `manage:${rbac.resource}` as any, rbac.pageId), // Using manage for export/import
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// MANDATORY: No data access without read permission
|
|
207
|
+
if (!permissions.canRead.can) {
|
|
208
|
+
return <AccessDeniedPage resource={rbac?.resource || 'test-resource'} operation="read" />;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// MANDATORY: Features are automatically filtered by permissions
|
|
212
|
+
const secureFeatures: DataTableFeatureConfig = {
|
|
213
|
+
...features,
|
|
214
|
+
creation: features.creation && permissions.canCreate.can,
|
|
215
|
+
editing: features.editing && permissions.canUpdate.can,
|
|
216
|
+
deletion: features.deletion && permissions.canDelete.can,
|
|
217
|
+
deleteSelected: features.deleteSelected && permissions.canDelete.can,
|
|
218
|
+
export: features.export && permissions.canExport.can,
|
|
219
|
+
import: features.import && permissions.canImport.can,
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// MANDATORY: Handlers are automatically secured
|
|
223
|
+
const secureHandlers = {
|
|
224
|
+
onEditRow: permissions.canUpdate.can ? onEditRow : undefined,
|
|
225
|
+
onDeleteRow: permissions.canDelete.can ? onDeleteRow : undefined,
|
|
226
|
+
onCreateRow: permissions.canCreate.can ? onCreateRow : undefined,
|
|
227
|
+
onImport: permissions.canImport.can ? onImport : undefined,
|
|
228
|
+
onExport: permissions.canExport.can ? onExport : undefined,
|
|
229
|
+
onDeleteSelected: permissions.canDelete.can ? onDeleteSelected : undefined,
|
|
230
|
+
};
|
|
231
|
+
|
|
161
232
|
// ============================================================================
|
|
162
233
|
// TABLE STATE MANAGEMENT (Single State Source)
|
|
163
234
|
// ============================================================================
|
|
@@ -415,11 +486,14 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
415
486
|
// Create a new array to avoid mutating the original
|
|
416
487
|
const result = [...actions];
|
|
417
488
|
|
|
418
|
-
// Add
|
|
419
|
-
if (
|
|
489
|
+
// Add Edit action with RBAC check
|
|
490
|
+
if (secureFeatures.editing && secureHandlers.onEditRow && !result.some(a => a.label === 'Edit')) {
|
|
420
491
|
result.push({
|
|
421
492
|
label: 'Edit',
|
|
422
493
|
onClick: (row: TData) => {
|
|
494
|
+
if (!permissions.canUpdate.can) {
|
|
495
|
+
throw new Error('Insufficient permissions to edit this resource');
|
|
496
|
+
}
|
|
423
497
|
// Get the row ID using the provided getRowId function or fallback to row.id
|
|
424
498
|
const rowId = getRowId ? getRowId(row, 0) : (row as any).id || String(0);
|
|
425
499
|
// Set the row into editing mode with the current row data
|
|
@@ -427,21 +501,29 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
427
501
|
},
|
|
428
502
|
icon: Edit,
|
|
429
503
|
testId: 'edit',
|
|
504
|
+
hidden: !permissions.canUpdate.can,
|
|
430
505
|
});
|
|
431
506
|
}
|
|
432
507
|
|
|
433
|
-
|
|
508
|
+
// Add Delete action with RBAC check
|
|
509
|
+
if (secureFeatures.deletion && secureHandlers.onDeleteRow && !result.some(a => a.label === 'Delete')) {
|
|
434
510
|
result.push({
|
|
435
511
|
label: 'Delete',
|
|
436
|
-
onClick:
|
|
512
|
+
onClick: (row: TData) => {
|
|
513
|
+
if (!permissions.canDelete.can) {
|
|
514
|
+
throw new Error('Insufficient permissions to delete this resource');
|
|
515
|
+
}
|
|
516
|
+
secureHandlers.onDeleteRow!(row);
|
|
517
|
+
},
|
|
437
518
|
icon: Trash,
|
|
438
519
|
testId: 'delete',
|
|
439
520
|
variant: 'destructive' as const,
|
|
521
|
+
hidden: !permissions.canDelete.can,
|
|
440
522
|
});
|
|
441
523
|
}
|
|
442
524
|
|
|
443
525
|
return result;
|
|
444
|
-
}, [actions,
|
|
526
|
+
}, [actions, secureFeatures, permissions, secureHandlers, getRowId, tableActions]);
|
|
445
527
|
|
|
446
528
|
// ============================================================================
|
|
447
529
|
// COLUMN PROCESSING
|
|
@@ -722,7 +804,7 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
722
804
|
)}
|
|
723
805
|
<>
|
|
724
806
|
<DataTableToolbar
|
|
725
|
-
features={
|
|
807
|
+
features={secureFeatures}
|
|
726
808
|
globalFilter={searchQuery}
|
|
727
809
|
onGlobalFilterChange={handleSearch}
|
|
728
810
|
columns={columns}
|
|
@@ -734,15 +816,17 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
734
816
|
onColumnVisibilityChange={(columnId, visible) => {
|
|
735
817
|
setColumnVisibility(prev => ({ ...prev, [columnId]: visible }));
|
|
736
818
|
}}
|
|
737
|
-
onCreateRow={
|
|
819
|
+
onCreateRow={secureFeatures.creation && secureHandlers.onCreateRow ? () => tableActions.setCreating(true) : undefined}
|
|
738
820
|
onImportClick={() => setShowImportModal(true)}
|
|
739
|
-
onExport={() => {
|
|
821
|
+
onExport={secureHandlers.onExport || (() => {
|
|
740
822
|
// Export logic here
|
|
741
|
-
}}
|
|
823
|
+
})}
|
|
742
824
|
rowSelection={rowSelection}
|
|
743
|
-
onDeleteSelected={onDeleteSelected}
|
|
825
|
+
onDeleteSelected={secureHandlers.onDeleteSelected}
|
|
744
826
|
onToggleFilterRow={() => setShowFilterRow(!showFilterRow)}
|
|
745
827
|
showFilterRow={showFilterRow}
|
|
828
|
+
rbac={rbac}
|
|
829
|
+
permissions={permissions}
|
|
746
830
|
/>
|
|
747
831
|
</>
|
|
748
832
|
</caption>
|
|
@@ -888,6 +972,8 @@ function DataTableInternal<TData extends DataRecord>({
|
|
|
888
972
|
hasAnyChildren: (finalTableData as any[]).some(row => row.isParent),
|
|
889
973
|
} : undefined}
|
|
890
974
|
actions={effectiveActions}
|
|
975
|
+
rbac={rbac}
|
|
976
|
+
permissions={permissions}
|
|
891
977
|
/>
|
|
892
978
|
|
|
893
979
|
{/* Table footer with pagination */}
|
|
@@ -58,7 +58,7 @@ import { Button } from '../../Button/Button';
|
|
|
58
58
|
import { Search, Plus, Upload, Download, Settings2, Filter, Trash2 } from 'lucide-react';
|
|
59
59
|
import { GroupingDropdown } from './GroupingDropdown';
|
|
60
60
|
import { ColumnVisibilityDropdown } from './ColumnVisibilityDropdown';
|
|
61
|
-
import type { DataTableColumn, DataRecord, SimpleColumn, DataTableFeatureConfig } from '../types';
|
|
61
|
+
import type { DataTableColumn, DataRecord, SimpleColumn, DataTableFeatureConfig, DataTableRBACConfig } from '../types';
|
|
62
62
|
import type { Column } from '@tanstack/react-table';
|
|
63
63
|
|
|
64
64
|
/**
|
|
@@ -70,6 +70,17 @@ interface DataTableToolbarProps<TData extends DataRecord = DataRecord> {
|
|
|
70
70
|
/** Feature configuration for the toolbar */
|
|
71
71
|
features: DataTableFeatureConfig;
|
|
72
72
|
|
|
73
|
+
// MANDATORY RBAC
|
|
74
|
+
/** RBAC configuration - REQUIRED for all DataTables */
|
|
75
|
+
rbac: DataTableRBACConfig;
|
|
76
|
+
/** Permission states for RBAC enforcement */
|
|
77
|
+
permissions: {
|
|
78
|
+
canCreate: { can: boolean; isLoading: boolean };
|
|
79
|
+
canImport: { can: boolean; isLoading: boolean };
|
|
80
|
+
canExport: { can: boolean; isLoading: boolean };
|
|
81
|
+
canDelete: { can: boolean; isLoading: boolean };
|
|
82
|
+
};
|
|
83
|
+
|
|
73
84
|
// Data and state
|
|
74
85
|
/** Current global filter value */
|
|
75
86
|
globalFilter: string;
|
|
@@ -132,6 +143,8 @@ interface DataTableToolbarProps<TData extends DataRecord = DataRecord> {
|
|
|
132
143
|
*/
|
|
133
144
|
export function DataTableToolbar<TData extends DataRecord>({
|
|
134
145
|
features,
|
|
146
|
+
rbac,
|
|
147
|
+
permissions,
|
|
135
148
|
globalFilter,
|
|
136
149
|
onGlobalFilterChange,
|
|
137
150
|
columns,
|
|
@@ -198,8 +211,8 @@ export function DataTableToolbar<TData extends DataRecord>({
|
|
|
198
211
|
</Button>
|
|
199
212
|
)}
|
|
200
213
|
|
|
201
|
-
{/* Row Creation */}
|
|
202
|
-
{features.creation && onCreateRow && (
|
|
214
|
+
{/* Row Creation - ONLY if user has create permission */}
|
|
215
|
+
{features.creation && permissions.canCreate.can && onCreateRow && (
|
|
203
216
|
<Button
|
|
204
217
|
variant="outline"
|
|
205
218
|
onClick={onCreateRow}
|
|
@@ -209,8 +222,8 @@ export function DataTableToolbar<TData extends DataRecord>({
|
|
|
209
222
|
</Button>
|
|
210
223
|
)}
|
|
211
224
|
|
|
212
|
-
|
|
213
|
-
{features.import && (
|
|
225
|
+
{/* Import - ONLY if user has import permission */}
|
|
226
|
+
{features.import && permissions.canImport.can && (
|
|
214
227
|
<Button
|
|
215
228
|
variant="outline"
|
|
216
229
|
onClick={onImportClick}
|
|
@@ -220,8 +233,8 @@ export function DataTableToolbar<TData extends DataRecord>({
|
|
|
220
233
|
</Button>
|
|
221
234
|
)}
|
|
222
235
|
|
|
223
|
-
{/* Export */}
|
|
224
|
-
{features.export && (
|
|
236
|
+
{/* Export - ONLY if user has export permission */}
|
|
237
|
+
{features.export && permissions.canExport.can && (
|
|
225
238
|
<Button
|
|
226
239
|
variant="outline"
|
|
227
240
|
onClick={onExport}
|
|
@@ -231,9 +244,8 @@ export function DataTableToolbar<TData extends DataRecord>({
|
|
|
231
244
|
</Button>
|
|
232
245
|
)}
|
|
233
246
|
|
|
234
|
-
|
|
235
|
-
{
|
|
236
|
-
{features.deleteSelected && features.deletion && features.selection && (
|
|
247
|
+
{/* Delete Selected - ONLY if user has delete permission */}
|
|
248
|
+
{features.deleteSelected && features.deletion && features.selection && permissions.canDelete.can && (
|
|
237
249
|
<Button
|
|
238
250
|
variant="destructive"
|
|
239
251
|
onClick={() => onDeleteSelected?.(rowSelection)}
|
|
@@ -131,6 +131,15 @@ interface UnifiedTableBodyProps<TData extends Record<string, any>> {
|
|
|
131
131
|
parentLabel?: string;
|
|
132
132
|
childLabel?: string;
|
|
133
133
|
}>;
|
|
134
|
+
// MANDATORY RBAC
|
|
135
|
+
rbac?: {
|
|
136
|
+
resource: string;
|
|
137
|
+
pageId?: string;
|
|
138
|
+
};
|
|
139
|
+
permissions?: {
|
|
140
|
+
canUpdate: { can: boolean; isLoading: boolean };
|
|
141
|
+
canDelete: { can: boolean; isLoading: boolean };
|
|
142
|
+
};
|
|
134
143
|
}
|
|
135
144
|
|
|
136
145
|
// Helper function to render the appropriate input type based on column configuration
|
|
@@ -218,7 +227,9 @@ const MemoizedRow = ({
|
|
|
218
227
|
grouping,
|
|
219
228
|
editingRowId,
|
|
220
229
|
hierarchical,
|
|
221
|
-
actions
|
|
230
|
+
actions,
|
|
231
|
+
rbac,
|
|
232
|
+
permissions
|
|
222
233
|
}: {
|
|
223
234
|
row: any;
|
|
224
235
|
style?: React.CSSProperties;
|
|
@@ -257,6 +268,14 @@ const MemoizedRow = ({
|
|
|
257
268
|
parentLabel?: string;
|
|
258
269
|
childLabel?: string;
|
|
259
270
|
}>;
|
|
271
|
+
rbac?: {
|
|
272
|
+
resource: string;
|
|
273
|
+
pageId?: string;
|
|
274
|
+
};
|
|
275
|
+
permissions?: {
|
|
276
|
+
canUpdate: { can: boolean; isLoading: boolean };
|
|
277
|
+
canDelete: { can: boolean; isLoading: boolean };
|
|
278
|
+
};
|
|
260
279
|
}) => {
|
|
261
280
|
const rowId = getRowId ? getRowId(row.original, row.index) : String(row.index);
|
|
262
281
|
|
|
@@ -327,10 +346,12 @@ const MemoizedRow = ({
|
|
|
327
346
|
) : cell.column.id === 'actions' ? (
|
|
328
347
|
<ActionButtons
|
|
329
348
|
row={row}
|
|
330
|
-
actions={actions}
|
|
349
|
+
actions={actions}
|
|
331
350
|
isEditing={isEditing}
|
|
332
351
|
isParent={isParent}
|
|
333
352
|
hierarchical={!!hierarchical}
|
|
353
|
+
rbac={rbac}
|
|
354
|
+
permissions={permissions}
|
|
334
355
|
/>
|
|
335
356
|
) : (
|
|
336
357
|
flexRender(cell.column.columnDef.cell, {
|
|
@@ -467,10 +488,12 @@ const MemoizedRow = ({
|
|
|
467
488
|
) : (
|
|
468
489
|
<ActionButtons
|
|
469
490
|
row={row}
|
|
470
|
-
actions={actions}
|
|
491
|
+
actions={actions}
|
|
471
492
|
isEditing={isEditing}
|
|
472
493
|
isParent={isParent}
|
|
473
494
|
hierarchical={!!hierarchical}
|
|
495
|
+
rbac={rbac}
|
|
496
|
+
permissions={permissions}
|
|
474
497
|
/>
|
|
475
498
|
)
|
|
476
499
|
) : (
|
|
@@ -529,7 +552,9 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
|
|
|
529
552
|
virtualHeight = 600,
|
|
530
553
|
forceVirtualization = false,
|
|
531
554
|
hierarchical,
|
|
532
|
-
actions = []
|
|
555
|
+
actions = [],
|
|
556
|
+
rbac,
|
|
557
|
+
permissions
|
|
533
558
|
}: UnifiedTableBodyProps<TData>) {
|
|
534
559
|
|
|
535
560
|
|
|
@@ -602,6 +627,8 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
|
|
|
602
627
|
editingRowId={editingRowId}
|
|
603
628
|
hierarchical={hierarchical}
|
|
604
629
|
actions={actions}
|
|
630
|
+
rbac={rbac}
|
|
631
|
+
permissions={permissions}
|
|
605
632
|
style={{
|
|
606
633
|
position: 'absolute',
|
|
607
634
|
top: 0,
|
|
@@ -633,6 +660,8 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
|
|
|
633
660
|
editingRowId={editingRowId}
|
|
634
661
|
hierarchical={hierarchical}
|
|
635
662
|
actions={actions}
|
|
663
|
+
rbac={rbac}
|
|
664
|
+
permissions={permissions}
|
|
636
665
|
/>
|
|
637
666
|
);
|
|
638
667
|
});
|
|
@@ -342,6 +342,7 @@ export function HierarchicalExample() {
|
|
|
342
342
|
columns={columns}
|
|
343
343
|
title="Dishes & Ingredients"
|
|
344
344
|
description="Manage dishes and their recipe ingredients with hierarchical view"
|
|
345
|
+
rbac={{ resource: 'dishes' }}
|
|
345
346
|
features={{
|
|
346
347
|
search: true,
|
|
347
348
|
pagination: true,
|
|
@@ -397,6 +398,7 @@ export function HierarchicalExampleMinimal() {
|
|
|
397
398
|
data={hierarchicalData}
|
|
398
399
|
columns={columns}
|
|
399
400
|
title="Dishes & Ingredients (Minimal)"
|
|
401
|
+
rbac={{ resource: 'dishes' }}
|
|
400
402
|
features={{
|
|
401
403
|
search: true,
|
|
402
404
|
pagination: true,
|
|
@@ -446,6 +448,7 @@ export function HierarchicalExampleExpanded() {
|
|
|
446
448
|
data={hierarchicalData}
|
|
447
449
|
columns={columns}
|
|
448
450
|
title="Dishes & Ingredients (Expanded)"
|
|
451
|
+
rbac={{ resource: 'dishes' }}
|
|
449
452
|
features={{
|
|
450
453
|
search: true,
|
|
451
454
|
pagination: true,
|
|
@@ -81,6 +81,7 @@ export function InitialPageSizeExample() {
|
|
|
81
81
|
columns={columns}
|
|
82
82
|
title="Users with Custom Page Size"
|
|
83
83
|
description="This table starts with 25 items per page"
|
|
84
|
+
rbac={{ resource: 'users' }}
|
|
84
85
|
features={{
|
|
85
86
|
search: true,
|
|
86
87
|
pagination: true,
|
|
@@ -113,6 +114,7 @@ export function InitialPageSizeExample() {
|
|
|
113
114
|
columns={columns}
|
|
114
115
|
title="Users with Default Page Size"
|
|
115
116
|
description="This table uses the default page size of 10"
|
|
117
|
+
rbac={{ resource: 'users' }}
|
|
116
118
|
features={{
|
|
117
119
|
search: true,
|
|
118
120
|
pagination: true,
|
|
@@ -146,6 +148,7 @@ export function InitialPageSizeExample() {
|
|
|
146
148
|
columns={columns}
|
|
147
149
|
title="Users with Invalid Page Size"
|
|
148
150
|
description="This table tries to use page size 15, but will fallback to 10"
|
|
151
|
+
rbac={{ resource: 'users' }}
|
|
149
152
|
features={{
|
|
150
153
|
search: true,
|
|
151
154
|
pagination: true,
|
|
@@ -155,6 +155,7 @@ export function ClientSidePerformanceExample() {
|
|
|
155
155
|
columns={columns}
|
|
156
156
|
title="Users Management"
|
|
157
157
|
description="High-performance client-side data table"
|
|
158
|
+
rbac={{ resource: 'users' }}
|
|
158
159
|
features={features}
|
|
159
160
|
|
|
160
161
|
// All features configured via features prop above
|
|
@@ -299,6 +300,7 @@ export function ServerSidePerformanceExample() {
|
|
|
299
300
|
columns={columns}
|
|
300
301
|
title="Large Dataset Management"
|
|
301
302
|
description="Server-side data processing for maximum performance"
|
|
303
|
+
rbac={{ resource: 'users' }}
|
|
302
304
|
features={serverFeatures}
|
|
303
305
|
|
|
304
306
|
// Server-side configuration
|
|
@@ -395,6 +397,7 @@ export function HybridPerformanceExample() {
|
|
|
395
397
|
columns={columns}
|
|
396
398
|
title="Hybrid Data Processing"
|
|
397
399
|
description="Balanced approach for medium-large datasets"
|
|
400
|
+
rbac={{ resource: 'users' }}
|
|
398
401
|
features={hybridFeatures}
|
|
399
402
|
|
|
400
403
|
// All features configured via features prop above
|
|
@@ -278,6 +278,8 @@ export interface DataTableAction<TData extends DataRecord> {
|
|
|
278
278
|
variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost';
|
|
279
279
|
/** Whether action is disabled */
|
|
280
280
|
disabled?: (row: TData) => boolean;
|
|
281
|
+
/** Whether action is hidden (for RBAC) */
|
|
282
|
+
hidden?: boolean;
|
|
281
283
|
/** Test ID for testing */
|
|
282
284
|
testId?: string;
|
|
283
285
|
/** Whether this action should only show for parent rows (hierarchical mode) */
|
|
@@ -393,12 +395,44 @@ export interface DataTableFeatureConfig {
|
|
|
393
395
|
|
|
394
396
|
|
|
395
397
|
|
|
398
|
+
// ============================================================================
|
|
399
|
+
// RBAC TYPES
|
|
400
|
+
// ============================================================================
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* RBAC configuration for DataTable - MANDATORY for all DataTables
|
|
404
|
+
*/
|
|
405
|
+
export interface DataTableRBACConfig {
|
|
406
|
+
/** Resource name for permission checking (e.g., 'users', 'events') - REQUIRED */
|
|
407
|
+
resource: string;
|
|
408
|
+
/** Optional page ID for granular permissions */
|
|
409
|
+
pageId?: string;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* RBAC context passed to server-side requests
|
|
414
|
+
*/
|
|
415
|
+
export interface RBACContext {
|
|
416
|
+
/** User ID for permission checking */
|
|
417
|
+
userId: string;
|
|
418
|
+
/** Organisation ID for scope isolation */
|
|
419
|
+
organisationId: string;
|
|
420
|
+
/** Event ID for event-based apps */
|
|
421
|
+
eventId?: string;
|
|
422
|
+
/** App ID for app-specific permissions */
|
|
423
|
+
appId?: string;
|
|
424
|
+
/** Resource being accessed */
|
|
425
|
+
resource: string;
|
|
426
|
+
/** Page ID for granular permissions */
|
|
427
|
+
pageId?: string;
|
|
428
|
+
}
|
|
429
|
+
|
|
396
430
|
// ============================================================================
|
|
397
431
|
// MAIN COMPONENT PROPS
|
|
398
432
|
// ============================================================================
|
|
399
433
|
|
|
400
434
|
/**
|
|
401
|
-
* Enhanced DataTable props with unified feature configuration
|
|
435
|
+
* Enhanced DataTable props with unified feature configuration and MANDATORY RBAC
|
|
402
436
|
*/
|
|
403
437
|
export interface DataTableProps<TData extends DataRecord> {
|
|
404
438
|
// Core data
|
|
@@ -406,6 +440,10 @@ export interface DataTableProps<TData extends DataRecord> {
|
|
|
406
440
|
data: TData[];
|
|
407
441
|
/** Column definitions - supports both DataTableColumn and SimpleColumn formats */
|
|
408
442
|
columns: (DataTableColumn<TData> | SimpleColumn<TData>)[];
|
|
443
|
+
|
|
444
|
+
// MANDATORY RBAC - NO OPTIONAL
|
|
445
|
+
/** RBAC configuration - REQUIRED for all DataTables */
|
|
446
|
+
rbac: DataTableRBACConfig;
|
|
409
447
|
|
|
410
448
|
// Display
|
|
411
449
|
/** Table title */
|