@jmruthers/pace-core 0.6.1 → 0.6.3
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 +88 -10
- package/cursor-rules/00-pace-core-compliance.mdc +46 -87
- package/cursor-rules/01-standards-compliance.mdc +16 -47
- package/cursor-rules/02-project-structure.mdc +4 -4
- package/cursor-rules/03-solid-principles.mdc +45 -164
- package/cursor-rules/04-testing-standards.mdc +22 -69
- package/cursor-rules/05-bug-reports-and-features.mdc +2 -2
- package/cursor-rules/06-code-quality.mdc +42 -125
- package/cursor-rules/07-tech-stack-compliance.mdc +33 -128
- package/cursor-rules/08-markup-quality.mdc +452 -0
- package/cursor-rules/CHANGELOG.md +18 -0
- package/cursor-rules/README.md +2 -1
- package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-Cb34EQs3.d.ts} +63 -1
- package/dist/{DataTable-CH1U5Tpy.d.ts → DataTable-BMRU8a1j.d.ts} +33 -1
- package/dist/{DataTable-DQ7RSOHE.js → DataTable-THFPBKTP.js} +12 -10
- package/dist/{PublicPageProvider-ce4xlHYA.d.ts → PublicPageProvider-DEMpysFR.d.ts} +394 -171
- package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CKvHP1MK.d.ts} +30 -8
- package/dist/{UnifiedAuthProvider-ATAP5UTR.js → UnifiedAuthProvider-KAGUYQ4J.js} +5 -4
- package/dist/{api-N774RPUA.js → api-IAGWF3ZG.js} +10 -10
- package/dist/{audit-B5P6FFIR.js → audit-V53FV5AG.js} +2 -2
- package/dist/{chunk-JBKQ3SAO.js → chunk-2T2IG7T7.js} +107 -57
- package/dist/chunk-2T2IG7T7.js.map +1 -0
- package/dist/{chunk-3QRJFVBR.js → chunk-6SOIHG6Z.js} +1 -1
- package/dist/chunk-6SOIHG6Z.js.map +1 -0
- package/dist/{chunk-3XTALGJF.js → chunk-6Z7LTB3D.js} +69 -240
- package/dist/chunk-6Z7LTB3D.js.map +1 -0
- package/dist/{chunk-4ZC4GX36.js → chunk-CNCQDFLN.js} +199 -46
- package/dist/chunk-CNCQDFLN.js.map +1 -0
- package/dist/chunk-DGUM43GV.js +11 -0
- package/dist/{chunk-BYFSK72L.js → chunk-DWUBLJJM.js} +361 -187
- package/dist/chunk-DWUBLJJM.js.map +1 -0
- package/dist/{chunk-LXQLPRQ2.js → chunk-FFQEQTNW.js} +6 -8
- package/dist/chunk-FFQEQTNW.js.map +1 -0
- package/dist/chunk-FMUCXFII.js +76 -0
- package/dist/chunk-FMUCXFII.js.map +1 -0
- package/dist/{chunk-4N5C5XZU.js → chunk-HFZBI76P.js} +4 -4
- package/dist/chunk-HFZBI76P.js.map +1 -0
- package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
- package/dist/chunk-L4OXEN46.js.map +1 -0
- package/dist/{chunk-R77UEZ4E.js → chunk-M43Y4SSO.js} +1 -1
- package/dist/chunk-M43Y4SSO.js.map +1 -0
- package/dist/{chunk-I7PSE6JW.js → chunk-M7MPQISP.js} +3 -76
- package/dist/chunk-M7MPQISP.js.map +1 -0
- package/dist/chunk-PQBSKX33.js +7793 -0
- package/dist/chunk-PQBSKX33.js.map +1 -0
- package/dist/chunk-QRPVRXYT.js +226 -0
- package/dist/chunk-QRPVRXYT.js.map +1 -0
- package/dist/{chunk-KNC55RTG.js → chunk-RWEBCB47.js} +194 -416
- package/dist/chunk-RWEBCB47.js.map +1 -0
- package/dist/{chunk-XM25TVIE.js → chunk-YDQHOZNA.js} +843 -388
- package/dist/chunk-YDQHOZNA.js.map +1 -0
- package/dist/{chunk-GLK6VM3F.js → chunk-ZNIWI3UC.js} +739 -737
- package/dist/chunk-ZNIWI3UC.js.map +1 -0
- package/dist/components.d.ts +5 -5
- package/dist/components.js +18 -16
- package/dist/components.js.map +1 -1
- package/dist/contextValidator-3JNZKUTX.js +9 -0
- package/dist/contextValidator-3JNZKUTX.js.map +1 -0
- package/dist/eslint-rules/pace-core-compliance.cjs +106 -0
- package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
- package/dist/hooks.d.ts +55 -122
- package/dist/hooks.js +10 -13
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +60 -13
- package/dist/index.js +30 -25
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +21 -3
- package/dist/providers.js +4 -3
- package/dist/rbac/index.d.ts +210 -139
- package/dist/rbac/index.js +17 -13
- package/dist/styles/index.js +1 -1
- package/dist/theming/runtime.d.ts +1 -13
- package/dist/theming/runtime.js +2 -2
- package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
- package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
- package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
- package/dist/types.d.ts +2 -2
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-BJAlWfuJ.d.ts → usePublicRouteParams-i3qtoBgg.d.ts} +38 -17
- package/dist/utils.d.ts +4 -5
- package/dist/utils.js +17 -19
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +21 -17
- package/docs/api/modules.md +4191 -2967
- package/docs/architecture/database-schema-requirements.md +161 -0
- package/docs/components/context-selector.md +126 -0
- package/docs/core-concepts/rbac-system.md +3 -3
- package/docs/documentation-index.md +2 -4
- package/docs/getting-started/cursor-rules.md +2 -1
- package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
- package/docs/migration/MIGRATION_GUIDE.md +2 -24
- package/docs/migration/RBAC_SCOPE_MIGRATION.md +385 -0
- package/docs/migration/README.md +52 -6
- package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
- package/docs/migration/database-changes-december-2025.md +3 -3
- package/docs/pace-mint-fix-auto-selection.md +218 -0
- package/docs/pace-mint-rbac-setup.md +391 -0
- package/docs/rbac/event-based-apps.md +1 -1
- package/docs/rbac/getting-started.md +1 -1
- package/docs/rbac/quick-start.md +1 -1
- package/docs/rbac/secure-client-protection.md +330 -0
- package/docs/standards/README.md +1 -0
- package/package.json +4 -3
- package/scripts/audit/core/checks/accessibility.cjs +197 -0
- package/scripts/audit/core/checks/api-usage.cjs +191 -0
- package/scripts/audit/core/checks/bundle.cjs +142 -0
- package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +784 -685
- package/scripts/audit/core/checks/config.cjs +54 -0
- package/scripts/audit/core/checks/coverage.cjs +84 -0
- package/scripts/audit/core/checks/dependencies.cjs +985 -0
- package/scripts/audit/core/checks/documentation.cjs +268 -0
- package/scripts/audit/core/checks/environment.cjs +116 -0
- package/scripts/audit/core/checks/error-handling.cjs +340 -0
- package/scripts/audit/core/checks/forms.cjs +172 -0
- package/scripts/audit/core/checks/heuristics.cjs +68 -0
- package/scripts/audit/core/checks/hooks.cjs +334 -0
- package/scripts/audit/core/checks/imports.cjs +244 -0
- package/scripts/audit/core/checks/performance.cjs +325 -0
- package/scripts/audit/core/checks/routes.cjs +117 -0
- package/scripts/audit/core/checks/state.cjs +130 -0
- package/scripts/audit/core/checks/structure.cjs +65 -0
- package/scripts/audit/core/checks/style.cjs +584 -0
- package/scripts/audit/core/checks/testing.cjs +122 -0
- package/scripts/audit/core/checks/typescript.cjs +61 -0
- package/scripts/audit/core/scanner.cjs +199 -0
- package/scripts/audit/core/utils.cjs +137 -0
- package/scripts/audit/index.cjs +223 -0
- package/scripts/audit/reporters/console.cjs +151 -0
- package/scripts/audit/reporters/json.cjs +54 -0
- package/scripts/audit/reporters/markdown.cjs +124 -0
- package/scripts/audit-consuming-app.cjs +61 -936
- package/scripts/build-docs/build-decision.js +240 -0
- package/scripts/build-docs/cache-utils.js +105 -0
- package/scripts/build-docs/content-normalization.js +150 -0
- package/scripts/build-docs/file-utils.js +105 -0
- package/scripts/build-docs/git-utils.js +86 -0
- package/scripts/build-docs/hash-utils.js +116 -0
- package/scripts/build-docs/typedoc-runner.js +220 -0
- package/scripts/build-docs-incremental.js +77 -913
- package/scripts/utils/command-runner.js +16 -11
- package/scripts/validate-formats.js +61 -56
- package/scripts/validate-master.js +74 -69
- package/scripts/validate-pre-publish.js +70 -65
- package/src/__tests__/hooks/usePermissions.test.ts +2 -2
- package/src/components/Alert/Alert.test.tsx +12 -18
- package/src/components/Alert/Alert.tsx +5 -7
- package/src/components/Avatar/Avatar.test.tsx +4 -4
- package/src/components/Badge/Badge.tsx +14 -0
- package/src/components/Button/Button.tsx +22 -0
- package/src/components/Calendar/Calendar.tsx +8 -2
- package/src/components/Card/Card.tsx +4 -0
- package/src/components/Checkbox/Checkbox.test.tsx +12 -12
- package/src/components/Checkbox/Checkbox.tsx +2 -2
- package/src/components/ContextSelector/ContextSelector.tsx +384 -0
- package/src/components/ContextSelector/index.ts +3 -0
- package/src/components/DataTable/DataTable.tsx +38 -4
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +18 -4
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
- package/src/components/DataTable/components/AccessDeniedPage.tsx +16 -25
- package/src/components/DataTable/components/ActionButtons.tsx +10 -7
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
- package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
- package/src/components/DataTable/components/DataTableBody.tsx +8 -0
- package/src/components/DataTable/components/DataTableCore.tsx +196 -554
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
- package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
- package/src/components/DataTable/components/DataTableModals.tsx +8 -0
- package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
- package/src/components/DataTable/components/EditFields.tsx +307 -0
- package/src/components/DataTable/components/EditableRow.tsx +8 -0
- package/src/components/DataTable/components/EmptyState.tsx +10 -0
- package/src/components/DataTable/components/FilterRow.tsx +12 -0
- package/src/components/DataTable/components/GroupHeader.tsx +12 -0
- package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
- package/src/components/DataTable/components/ImportModal.tsx +7 -0
- package/src/components/DataTable/components/LoadingState.tsx +6 -0
- package/src/components/DataTable/components/PaginationControls.tsx +16 -1
- package/src/components/DataTable/components/RowComponent.tsx +391 -0
- package/src/components/DataTable/components/UnifiedTableBody.tsx +63 -851
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
- package/src/components/DataTable/components/cellValueUtils.ts +40 -0
- package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
- package/src/components/DataTable/context/DataTableContext.tsx +50 -0
- package/src/components/DataTable/core/ColumnFactory.ts +31 -0
- package/src/components/DataTable/core/DataTableContext.tsx +32 -1
- package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
- package/src/components/DataTable/hooks/useColumnReordering.ts +12 -0
- package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
- package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +127 -33
- package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
- package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
- package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
- package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
- package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
- package/src/components/DataTable/styles.ts +6 -6
- package/src/components/DataTable/types.ts +6 -10
- package/src/components/DataTable/utils/a11yUtils.ts +7 -0
- package/src/components/DataTable/utils/debugTools.ts +18 -113
- package/src/components/DataTable/utils/errorHandling.ts +12 -0
- package/src/components/DataTable/utils/exportUtils.ts +9 -0
- package/src/components/DataTable/utils/flexibleImport.ts +12 -48
- package/src/components/DataTable/utils/paginationUtils.ts +8 -0
- package/src/components/DataTable/utils/performanceUtils.ts +5 -1
- package/src/components/Dialog/Dialog.tsx +31 -3
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +45 -5
- package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
- package/src/components/ErrorBoundary/index.ts +27 -2
- package/src/components/FileDisplay/FileDisplay.tsx +74 -28
- package/src/components/FileUpload/FileUpload.tsx +22 -2
- package/src/components/Footer/Footer.test.tsx +16 -16
- package/src/components/Footer/Footer.tsx +14 -11
- package/src/components/Form/Form.tsx +1 -0
- package/src/components/Header/Header.test.tsx +43 -73
- package/src/components/Header/Header.tsx +59 -49
- package/src/components/Input/Input.test.tsx +2 -2
- package/src/components/Input/Input.tsx +8 -4
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
- package/src/components/LoginForm/LoginForm.tsx +4 -0
- package/src/components/NavigationMenu/NavigationMenu.tsx +14 -513
- package/src/components/NavigationMenu/types.ts +56 -0
- package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
- package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +10 -19
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +2 -2
- package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +5 -5
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +13 -11
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +167 -44
- package/src/components/PaceAppLayout/README.md +14 -17
- package/src/components/PaceAppLayout/test-setup.tsx +3 -4
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +3 -0
- package/src/components/PasswordChange/PasswordChangeForm.tsx +9 -0
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
- package/src/components/PublicLayout/PublicPageLayout.tsx +2 -5
- package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
- package/src/components/Select/Select.tsx +80 -434
- package/src/components/Select/context.ts +23 -0
- package/src/components/Select/hooks/useSelectEvents.ts +87 -0
- package/src/components/Select/hooks/useSelectSearch.ts +91 -0
- package/src/components/Select/hooks/useSelectState.ts +104 -0
- package/src/components/Select/index.ts +9 -1
- package/src/components/Select/types.ts +123 -0
- package/src/components/Select/utils/text.ts +26 -0
- package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +4 -5
- package/src/components/Switch/Switch.tsx +4 -4
- package/src/components/Tabs/Tabs.tsx +1 -1
- package/src/components/Toast/Toast.tsx +4 -0
- package/src/components/Tooltip/Tooltip.tsx +2 -2
- package/src/components/UserMenu/UserMenu.test.tsx +24 -11
- package/src/components/UserMenu/UserMenu.tsx +21 -18
- package/src/components/index.ts +7 -7
- package/src/eslint-rules/pace-core-compliance.cjs +106 -0
- package/src/hooks/__tests__/index.unit.test.ts +2 -5
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +4 -98
- package/src/hooks/index.ts +1 -2
- package/src/hooks/public/usePublicEvent.ts +4 -0
- package/src/hooks/public/usePublicEventLogo.ts +4 -0
- package/src/hooks/public/usePublicFileDisplay.ts +4 -0
- package/src/hooks/public/usePublicRouteParams.ts +4 -0
- package/src/hooks/services/useAuth.ts +32 -0
- package/src/hooks/services/useCurrentEvent.ts +6 -0
- package/src/hooks/services/useCurrentOrganisation.ts +6 -0
- package/src/hooks/useAppConfig.ts +15 -30
- package/src/hooks/useDebounce.ts +9 -0
- package/src/hooks/useEventTheme.ts +6 -0
- package/src/hooks/useFileDisplay.ts +81 -50
- package/src/hooks/useFileReference.ts +25 -7
- package/src/hooks/useFileUrl.ts +11 -1
- package/src/hooks/useFocusManagement.ts +14 -0
- package/src/hooks/useFocusTrap.ts +3 -0
- package/src/hooks/useInactivityTracker.ts +3 -0
- package/src/hooks/useKeyboardShortcuts.ts +4 -0
- package/src/hooks/useOrganisationPermissions.ts +4 -0
- package/src/hooks/useOrganisationSecurity.ts +4 -0
- package/src/hooks/usePerformanceMonitor.ts +4 -0
- package/src/hooks/usePermissionCache.ts +7 -0
- package/src/hooks/useQueryCache.ts +12 -1
- package/src/hooks/useSessionRestoration.ts +4 -0
- package/src/hooks/useStorage.ts +4 -0
- package/src/hooks/useToast.ts +1 -1
- package/src/index.ts +6 -6
- package/src/providers/__tests__/OrganisationProvider.test.tsx +92 -70
- package/src/providers/services/AuthServiceProvider.tsx +35 -7
- package/src/providers/services/EventServiceProvider.tsx +51 -5
- package/src/providers/services/InactivityServiceProvider.tsx +18 -0
- package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
- package/src/providers/services/UnifiedAuthProvider.tsx +126 -134
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +29 -13
- package/src/rbac/README.md +1 -1
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +1 -1
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
- package/src/rbac/adapters.tsx +12 -3
- package/src/rbac/api.test.ts +59 -51
- package/src/rbac/api.ts +246 -167
- package/src/rbac/components/NavigationProvider.tsx +4 -1
- package/src/rbac/components/PagePermissionGuard.tsx +185 -17
- package/src/rbac/components/RoleBasedRouter.tsx +5 -1
- package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
- package/src/rbac/components/SecureDataProvider.tsx +20 -5
- package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
- package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
- package/src/rbac/engine.ts +38 -14
- package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +32 -21
- package/src/rbac/hooks/permissions/index.ts +7 -0
- package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
- package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
- package/src/rbac/hooks/permissions/useCan.ts +377 -0
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
- package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
- package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
- package/src/rbac/hooks/useCan.test.ts +64 -66
- package/src/rbac/hooks/usePermissions.ts +14 -995
- package/src/rbac/hooks/useRBAC.test.ts +1 -5
- package/src/rbac/hooks/useRBAC.ts +36 -37
- package/src/rbac/hooks/useResolvedScope.test.ts +120 -35
- package/src/rbac/hooks/useResolvedScope.ts +35 -40
- package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
- package/src/rbac/hooks/useResourcePermissions.ts +14 -4
- package/src/rbac/hooks/useSecureSupabase.ts +27 -7
- package/src/rbac/index.ts +7 -0
- package/src/rbac/permissions.ts +0 -30
- package/src/rbac/secureClient.test.ts +22 -18
- package/src/rbac/secureClient.ts +294 -68
- package/src/rbac/security.ts +0 -17
- package/src/rbac/types.ts +9 -0
- package/src/rbac/utils/__tests__/contextValidator.test.ts +64 -86
- package/src/rbac/utils/clientSecurity.ts +93 -0
- package/src/rbac/utils/contextValidator.ts +77 -168
- package/src/services/AuthService.ts +39 -7
- package/src/services/EventService.ts +186 -54
- package/src/services/OrganisationService.ts +81 -14
- package/src/services/__tests__/EventService.test.ts +1 -2
- package/src/services/base/BaseService.ts +3 -0
- package/src/theming/__tests__/parseEventColours.test.ts +6 -9
- package/src/theming/parseEventColours.ts +5 -19
- package/src/types/vitest-globals.d.ts +51 -26
- package/src/utils/__mocks__/supabaseMock.ts +1 -3
- package/src/utils/__tests__/formatting.unit.test.ts +4 -4
- package/src/utils/__tests__/index.unit.test.ts +2 -2
- package/src/utils/audit/audit.ts +0 -3
- package/src/utils/core/cn.ts +1 -1
- package/src/utils/dynamic/dynamicUtils.ts +7 -4
- package/src/utils/file-reference/index.ts +53 -1
- package/src/utils/formatting/formatting.ts +8 -18
- package/src/utils/index.ts +0 -1
- package/dist/chunk-3QRJFVBR.js.map +0 -1
- package/dist/chunk-3XTALGJF.js.map +0 -1
- package/dist/chunk-4N5C5XZU.js.map +0 -1
- package/dist/chunk-4ZC4GX36.js.map +0 -1
- package/dist/chunk-7D4SUZUM.js +0 -38
- package/dist/chunk-BYFSK72L.js.map +0 -1
- package/dist/chunk-EXUD6RNJ.js +0 -451
- package/dist/chunk-EXUD6RNJ.js.map +0 -1
- package/dist/chunk-GLK6VM3F.js.map +0 -1
- package/dist/chunk-I7PSE6JW.js.map +0 -1
- package/dist/chunk-JBKQ3SAO.js.map +0 -1
- package/dist/chunk-KNC55RTG.js.map +0 -1
- package/dist/chunk-LXQLPRQ2.js.map +0 -1
- package/dist/chunk-R77UEZ4E.js.map +0 -1
- package/dist/chunk-SQGMNID3.js.map +0 -1
- package/dist/chunk-T33XF5ZC.js +0 -12922
- package/dist/chunk-T33XF5ZC.js.map +0 -1
- package/dist/chunk-XM25TVIE.js.map +0 -1
- package/docs/api/classes/ColumnFactory.md +0 -243
- package/docs/api/classes/ErrorBoundary.md +0 -144
- package/docs/api/classes/InvalidScopeError.md +0 -73
- package/docs/api/classes/Logger.md +0 -178
- package/docs/api/classes/MissingUserContextError.md +0 -66
- package/docs/api/classes/OrganisationContextRequiredError.md +0 -66
- package/docs/api/classes/PermissionDeniedError.md +0 -73
- package/docs/api/classes/RBACAuditManager.md +0 -297
- package/docs/api/classes/RBACCache.md +0 -322
- package/docs/api/classes/RBACEngine.md +0 -171
- package/docs/api/classes/RBACError.md +0 -76
- package/docs/api/classes/RBACNotInitializedError.md +0 -66
- package/docs/api/classes/SecureSupabaseClient.md +0 -160
- package/docs/api/classes/StorageUtils.md +0 -328
- package/docs/api/enums/FileCategory.md +0 -184
- package/docs/api/enums/LogLevel.md +0 -54
- package/docs/api/enums/RBACErrorCode.md +0 -228
- package/docs/api/enums/RPCFunction.md +0 -118
- package/docs/api/interfaces/AddressFieldProps.md +0 -241
- package/docs/api/interfaces/AddressFieldRef.md +0 -94
- package/docs/api/interfaces/AggregateConfig.md +0 -43
- package/docs/api/interfaces/AutocompleteOptions.md +0 -75
- package/docs/api/interfaces/AvatarProps.md +0 -128
- package/docs/api/interfaces/BadgeProps.md +0 -27
- package/docs/api/interfaces/ButtonProps.md +0 -53
- package/docs/api/interfaces/CalendarProps.md +0 -70
- package/docs/api/interfaces/CardProps.md +0 -66
- package/docs/api/interfaces/ColorPalette.md +0 -7
- package/docs/api/interfaces/ColorShade.md +0 -66
- package/docs/api/interfaces/ComplianceResult.md +0 -30
- package/docs/api/interfaces/DataAccessRecord.md +0 -96
- package/docs/api/interfaces/DataRecord.md +0 -11
- package/docs/api/interfaces/DataTableAction.md +0 -249
- package/docs/api/interfaces/DataTableColumn.md +0 -504
- package/docs/api/interfaces/DataTableProps.md +0 -625
- package/docs/api/interfaces/DataTableToolbarButton.md +0 -96
- package/docs/api/interfaces/DatabaseComplianceResult.md +0 -85
- package/docs/api/interfaces/DatabaseIssue.md +0 -41
- package/docs/api/interfaces/EmptyStateConfig.md +0 -61
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +0 -235
- package/docs/api/interfaces/EventAppRoleData.md +0 -71
- package/docs/api/interfaces/ExportColumn.md +0 -90
- package/docs/api/interfaces/ExportOptions.md +0 -126
- package/docs/api/interfaces/FileDisplayProps.md +0 -249
- package/docs/api/interfaces/FileMetadata.md +0 -129
- package/docs/api/interfaces/FileReference.md +0 -118
- package/docs/api/interfaces/FileSizeLimits.md +0 -7
- package/docs/api/interfaces/FileUploadOptions.md +0 -139
- package/docs/api/interfaces/FileUploadProps.md +0 -293
- package/docs/api/interfaces/FooterProps.md +0 -105
- package/docs/api/interfaces/FormFieldProps.md +0 -166
- package/docs/api/interfaces/FormProps.md +0 -113
- package/docs/api/interfaces/GrantEventAppRoleParams.md +0 -122
- package/docs/api/interfaces/InactivityWarningModalProps.md +0 -115
- package/docs/api/interfaces/InputProps.md +0 -53
- package/docs/api/interfaces/LabelProps.md +0 -107
- package/docs/api/interfaces/LoggerConfig.md +0 -62
- package/docs/api/interfaces/LoginFormProps.md +0 -184
- package/docs/api/interfaces/NavigationAccessRecord.md +0 -107
- package/docs/api/interfaces/NavigationContextType.md +0 -164
- package/docs/api/interfaces/NavigationGuardProps.md +0 -139
- package/docs/api/interfaces/NavigationItem.md +0 -120
- package/docs/api/interfaces/NavigationMenuProps.md +0 -221
- package/docs/api/interfaces/NavigationProviderProps.md +0 -117
- package/docs/api/interfaces/Organisation.md +0 -140
- package/docs/api/interfaces/OrganisationContextType.md +0 -388
- package/docs/api/interfaces/OrganisationMembership.md +0 -140
- package/docs/api/interfaces/OrganisationProviderProps.md +0 -76
- package/docs/api/interfaces/OrganisationSecurityError.md +0 -62
- package/docs/api/interfaces/PaceAppLayoutProps.md +0 -406
- package/docs/api/interfaces/PaceLoginPageProps.md +0 -47
- package/docs/api/interfaces/PageAccessRecord.md +0 -85
- package/docs/api/interfaces/PagePermissionContextType.md +0 -140
- package/docs/api/interfaces/PagePermissionGuardProps.md +0 -153
- package/docs/api/interfaces/PagePermissionProviderProps.md +0 -119
- package/docs/api/interfaces/PaletteData.md +0 -41
- package/docs/api/interfaces/ParsedAddress.md +0 -120
- package/docs/api/interfaces/PermissionEnforcerProps.md +0 -153
- package/docs/api/interfaces/ProgressProps.md +0 -42
- package/docs/api/interfaces/ProtectedRouteProps.md +0 -97
- package/docs/api/interfaces/PublicPageFooterProps.md +0 -112
- package/docs/api/interfaces/PublicPageHeaderProps.md +0 -125
- package/docs/api/interfaces/PublicPageLayoutProps.md +0 -198
- package/docs/api/interfaces/QuickFix.md +0 -52
- package/docs/api/interfaces/RBACAccessValidateParams.md +0 -52
- package/docs/api/interfaces/RBACAccessValidateResult.md +0 -41
- package/docs/api/interfaces/RBACAuditLogParams.md +0 -85
- package/docs/api/interfaces/RBACAuditLogResult.md +0 -52
- package/docs/api/interfaces/RBACConfig.md +0 -133
- package/docs/api/interfaces/RBACContext.md +0 -52
- package/docs/api/interfaces/RBACLogger.md +0 -112
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +0 -74
- package/docs/api/interfaces/RBACPerformanceMetrics.md +0 -138
- package/docs/api/interfaces/RBACPermissionCheckParams.md +0 -74
- package/docs/api/interfaces/RBACPermissionCheckResult.md +0 -52
- package/docs/api/interfaces/RBACPermissionsGetParams.md +0 -63
- package/docs/api/interfaces/RBACPermissionsGetResult.md +0 -63
- package/docs/api/interfaces/RBACResult.md +0 -58
- package/docs/api/interfaces/RBACRoleGrantParams.md +0 -63
- package/docs/api/interfaces/RBACRoleGrantResult.md +0 -52
- package/docs/api/interfaces/RBACRoleRevokeParams.md +0 -63
- package/docs/api/interfaces/RBACRoleRevokeResult.md +0 -52
- package/docs/api/interfaces/RBACRoleValidateParams.md +0 -52
- package/docs/api/interfaces/RBACRoleValidateResult.md +0 -63
- package/docs/api/interfaces/RBACRolesListParams.md +0 -52
- package/docs/api/interfaces/RBACRolesListResult.md +0 -74
- package/docs/api/interfaces/RBACSessionTrackParams.md +0 -74
- package/docs/api/interfaces/RBACSessionTrackResult.md +0 -52
- package/docs/api/interfaces/ResourcePermissions.md +0 -155
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +0 -100
- package/docs/api/interfaces/RoleBasedRouterContextType.md +0 -151
- package/docs/api/interfaces/RoleBasedRouterProps.md +0 -156
- package/docs/api/interfaces/RoleManagementResult.md +0 -52
- package/docs/api/interfaces/RouteAccessRecord.md +0 -107
- package/docs/api/interfaces/RouteConfig.md +0 -134
- package/docs/api/interfaces/RuntimeComplianceResult.md +0 -55
- package/docs/api/interfaces/SecureDataContextType.md +0 -168
- package/docs/api/interfaces/SecureDataProviderProps.md +0 -132
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +0 -34
- package/docs/api/interfaces/SetupIssue.md +0 -41
- package/docs/api/interfaces/StorageConfig.md +0 -41
- package/docs/api/interfaces/StorageFileInfo.md +0 -74
- package/docs/api/interfaces/StorageFileMetadata.md +0 -151
- package/docs/api/interfaces/StorageListOptions.md +0 -99
- package/docs/api/interfaces/StorageListResult.md +0 -41
- package/docs/api/interfaces/StorageUploadOptions.md +0 -101
- package/docs/api/interfaces/StorageUploadResult.md +0 -63
- package/docs/api/interfaces/StorageUrlOptions.md +0 -60
- package/docs/api/interfaces/StyleImport.md +0 -19
- package/docs/api/interfaces/SwitchProps.md +0 -34
- package/docs/api/interfaces/TabsContentProps.md +0 -9
- package/docs/api/interfaces/TabsListProps.md +0 -9
- package/docs/api/interfaces/TabsProps.md +0 -9
- package/docs/api/interfaces/TabsTriggerProps.md +0 -50
- package/docs/api/interfaces/TextareaProps.md +0 -53
- package/docs/api/interfaces/ToastActionElement.md +0 -9
- package/docs/api/interfaces/ToastProps.md +0 -9
- package/docs/api/interfaces/UnifiedAuthContextType.md +0 -820
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +0 -171
- package/docs/api/interfaces/UseFormDialogOptions.md +0 -62
- package/docs/api/interfaces/UseFormDialogReturn.md +0 -117
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +0 -136
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +0 -123
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +0 -87
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +0 -81
- package/docs/api/interfaces/UsePublicEventOptions.md +0 -34
- package/docs/api/interfaces/UsePublicEventReturn.md +0 -68
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +0 -47
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +0 -120
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +0 -94
- package/docs/api/interfaces/UseResolvedScopeOptions.md +0 -47
- package/docs/api/interfaces/UseResolvedScopeReturn.md +0 -47
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +0 -34
- package/docs/api/interfaces/UserEventAccess.md +0 -118
- package/docs/api/interfaces/UserMenuProps.md +0 -86
- package/docs/api/interfaces/UserProfile.md +0 -63
- package/docs/migration/quick-migration-guide.md +0 -356
- package/docs/migration/service-architecture.md +0 -281
- package/src/components/EventSelector/EventSelector.test.tsx +0 -720
- package/src/components/EventSelector/EventSelector.tsx +0 -420
- package/src/components/EventSelector/index.ts +0 -3
- package/src/components/OrganisationSelector/OrganisationSelector.test.tsx +0 -784
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +0 -324
- package/src/components/OrganisationSelector/index.ts +0 -9
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
- package/src/hooks/useSecureDataAccess.test.ts +0 -559
- package/src/hooks/useSecureDataAccess.ts +0 -681
- /package/dist/{DataTable-DQ7RSOHE.js.map → DataTable-THFPBKTP.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-ATAP5UTR.js.map → UnifiedAuthProvider-KAGUYQ4J.js.map} +0 -0
- /package/dist/{api-N774RPUA.js.map → api-IAGWF3ZG.js.map} +0 -0
- /package/dist/{audit-B5P6FFIR.js.map → audit-V53FV5AG.js.map} +0 -0
- /package/dist/{chunk-7D4SUZUM.js.map → chunk-DGUM43GV.js.map} +0 -0
- /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
- /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
- /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
- /package/docs/migration/{REACT_19_MIGRATION.md → V0.6.0_REACT_19_MIGRATION.md} +0 -0
|
@@ -12,21 +12,11 @@ import React, { useMemo, useCallback, useEffect, useRef } from 'react';
|
|
|
12
12
|
import { useReactTable } from '@tanstack/react-table';
|
|
13
13
|
import type {
|
|
14
14
|
SortingState,
|
|
15
|
-
GroupingState,
|
|
16
|
-
ExpandedState,
|
|
17
|
-
PaginationState,
|
|
18
15
|
} from '@tanstack/react-table';
|
|
19
|
-
import { Edit, Trash
|
|
20
|
-
import { cn } from '../../../utils/core/cn';
|
|
21
|
-
import { Checkbox } from '../../Checkbox/Checkbox';
|
|
22
|
-
import { Button } from '../../Button/Button';
|
|
23
|
-
import { getTableClasses, getMainContainerClasses } from '../styles';
|
|
16
|
+
import { Edit, Trash } from 'lucide-react';
|
|
24
17
|
import { useDataTablePerformance } from '../../../hooks/useDataTablePerformance';
|
|
25
|
-
import { DataTableToolbar } from './DataTableToolbar';
|
|
26
|
-
import { UnifiedTableBody } from './UnifiedTableBody';
|
|
27
|
-
import { PaginationControls, EnhancedPaginationControls } from './PaginationControls';
|
|
28
18
|
import { LoadingState } from './LoadingState';
|
|
29
|
-
import {
|
|
19
|
+
import { DataTableLayout } from './DataTableLayout';
|
|
30
20
|
import { DataTableErrorBoundary } from './DataTableErrorBoundary';
|
|
31
21
|
import { useColumnOrderPersistence } from '../hooks/useColumnOrderPersistence';
|
|
32
22
|
import { useColumnVisibilityPersistence } from '../hooks/useColumnVisibilityPersistence';
|
|
@@ -39,20 +29,19 @@ import { useDataTableConfiguration } from '../hooks/useDataTableConfiguration';
|
|
|
39
29
|
import type { TableStateSnapshot } from '../hooks/useTableHandlers';
|
|
40
30
|
import { ColumnFactory } from '../core/ColumnFactory';
|
|
41
31
|
import { AccessDeniedPage } from './AccessDeniedPage';
|
|
42
|
-
import { useCan, useResolvedScope } from '../../../rbac/hooks';
|
|
43
32
|
// NOTE: All toast() calls in this component use the default timeout (5 seconds).
|
|
44
33
|
// Do NOT set duration or timeout properties - let the toast system use its default.
|
|
45
34
|
import { toast } from '../../../hooks/useToast';
|
|
46
|
-
import { exportToCSV, exportToCSVWithTableRows } from '../utils/exportUtils';
|
|
47
|
-
import type { ExportOptions } from '../types';
|
|
48
35
|
import { useUnifiedAuth } from '../../../providers/services/UnifiedAuthProvider';
|
|
49
|
-
import { Scope } from '../../../rbac/types';
|
|
50
36
|
import { useDataTablePermissions } from '../hooks/useDataTablePermissions';
|
|
51
37
|
import { useTableColumns } from '../hooks/useTableColumns';
|
|
52
|
-
import { initializeLiveRegion
|
|
38
|
+
import { initializeLiveRegion } from '../utils/a11yUtils';
|
|
53
39
|
import { useKeyboardNavigation } from '../hooks/useKeyboardNavigation';
|
|
54
40
|
import { getRowIdSafe } from '../utils/rowUtils';
|
|
55
41
|
import { createLogger } from '../../../utils/core/logger';
|
|
42
|
+
import { usePermissionTracking } from './hooks/usePermissionTracking';
|
|
43
|
+
import { useImportModalFocus } from './hooks/useImportModalFocus';
|
|
44
|
+
import { toCellValueRecord } from './cellValueUtils';
|
|
56
45
|
|
|
57
46
|
import { normalizeDataTableFeatures } from '../types';
|
|
58
47
|
import type {
|
|
@@ -67,50 +56,23 @@ import type {
|
|
|
67
56
|
DataTableFeatureConfig,
|
|
68
57
|
NormalizedDataTableFeatureConfig,
|
|
69
58
|
DataTableColumn,
|
|
70
|
-
SimpleColumn,
|
|
71
59
|
AggregateConfig,
|
|
72
60
|
DataTableAction,
|
|
73
61
|
HierarchicalConfig,
|
|
74
62
|
DataTableRBACConfig,
|
|
75
|
-
CellValue
|
|
76
63
|
} from '../types';
|
|
77
64
|
import type { ImportModalConfig } from './ImportModal';
|
|
78
65
|
|
|
79
|
-
const isCellValue = (value: unknown): value is CellValue => {
|
|
80
|
-
if (value === null || value === undefined) {
|
|
81
|
-
return true;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (value instanceof Date) {
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const valueType = typeof value;
|
|
89
|
-
return valueType === 'string' || valueType === 'number' || valueType === 'boolean';
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const toCellValueRecord = <TData extends DataRecord>(row: TData): Record<string, CellValue> => {
|
|
93
|
-
if (typeof row !== 'object' || row === null) {
|
|
94
|
-
return {};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return Object.entries(row).reduce<Record<string, CellValue>>((accumulator, [key, entryValue]) => {
|
|
98
|
-
if (isCellValue(entryValue)) {
|
|
99
|
-
accumulator[key] = entryValue;
|
|
100
|
-
} else if (entryValue && typeof entryValue === 'object' && 'toString' in entryValue) {
|
|
101
|
-
accumulator[key] = String(entryValue) as CellValue;
|
|
102
|
-
} else {
|
|
103
|
-
accumulator[key] = entryValue as CellValue;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return accumulator;
|
|
107
|
-
}, {});
|
|
108
|
-
};
|
|
109
|
-
|
|
110
66
|
// ============================================================================
|
|
111
67
|
// CORE COMPONENT PROPS
|
|
112
68
|
// ============================================================================
|
|
113
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Core DataTable component props.
|
|
72
|
+
* This is the internal component that handles all DataTable functionality.
|
|
73
|
+
*
|
|
74
|
+
* @template TData - The type of data records in the table
|
|
75
|
+
*/
|
|
114
76
|
export interface DataTableCoreProps<TData extends DataRecord> {
|
|
115
77
|
// Core data
|
|
116
78
|
data: TData[];
|
|
@@ -234,6 +196,16 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
234
196
|
|
|
235
197
|
// MANDATORY: Get permissions and secure features
|
|
236
198
|
const { permissions, secureFeatures, effectivePageId } = useDataTablePermissions(rbac, requestedFeatures);
|
|
199
|
+
|
|
200
|
+
const {
|
|
201
|
+
permissionElapsed,
|
|
202
|
+
shouldAllowRenderAfterTimeout,
|
|
203
|
+
isPermissionLoading,
|
|
204
|
+
} = usePermissionTracking({
|
|
205
|
+
permissions,
|
|
206
|
+
effectivePageId,
|
|
207
|
+
logger,
|
|
208
|
+
});
|
|
237
209
|
|
|
238
210
|
|
|
239
211
|
// ============================================================================
|
|
@@ -360,43 +332,7 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
360
332
|
}
|
|
361
333
|
);
|
|
362
334
|
|
|
363
|
-
const lastFocusedElementRef =
|
|
364
|
-
const wasImportModalOpenRef = useRef(false);
|
|
365
|
-
|
|
366
|
-
// Store focus when modals open, restore when they close
|
|
367
|
-
useEffect(() => {
|
|
368
|
-
if (state.showImportModal) {
|
|
369
|
-
wasImportModalOpenRef.current = true;
|
|
370
|
-
keyboardNavigation.storeFocus();
|
|
371
|
-
if (document.activeElement instanceof HTMLElement) {
|
|
372
|
-
lastFocusedElementRef.current = document.activeElement;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}, [state.showImportModal, keyboardNavigation]);
|
|
376
|
-
|
|
377
|
-
useEffect(() => {
|
|
378
|
-
if (!state.showImportModal) {
|
|
379
|
-
if (!wasImportModalOpenRef.current) {
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
wasImportModalOpenRef.current = false;
|
|
383
|
-
// Restore focus after modal closes
|
|
384
|
-
setTimeout(() => {
|
|
385
|
-
const storedElement = lastFocusedElementRef.current;
|
|
386
|
-
lastFocusedElementRef.current = null;
|
|
387
|
-
|
|
388
|
-
const elementToRestore = storedElement?.isConnected
|
|
389
|
-
? storedElement
|
|
390
|
-
: document.querySelector<HTMLElement>('[data-restore-target="datatable-import-button"]');
|
|
391
|
-
|
|
392
|
-
if (elementToRestore && typeof elementToRestore.focus === 'function') {
|
|
393
|
-
elementToRestore.focus();
|
|
394
|
-
} else {
|
|
395
|
-
keyboardNavigation.restoreFocus();
|
|
396
|
-
}
|
|
397
|
-
}, 100); // Small delay to ensure modal is fully closed
|
|
398
|
-
}
|
|
399
|
-
}, [state.showImportModal, keyboardNavigation]);
|
|
335
|
+
const { lastFocusedElementRef } = useImportModalFocus(state.showImportModal, keyboardNavigation);
|
|
400
336
|
|
|
401
337
|
// ============================================================================
|
|
402
338
|
// HIERARCHICAL DATA VALIDATION AND PROCESSING - ALWAYS call these hooks
|
|
@@ -538,7 +474,13 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
538
474
|
finalDataCount
|
|
539
475
|
]);
|
|
540
476
|
|
|
541
|
-
|
|
477
|
+
// React 19 fix: Use useMemo to ensure isLoading updates when props change
|
|
478
|
+
// This prevents the component from getting stuck in loading state when externalIsLoading
|
|
479
|
+
// changes from true to false in React 19 with automatic memoization
|
|
480
|
+
const isLoading = useMemo(
|
|
481
|
+
() => externalIsLoading || performanceLoading,
|
|
482
|
+
[externalIsLoading, performanceLoading]
|
|
483
|
+
);
|
|
542
484
|
|
|
543
485
|
// ============================================================================
|
|
544
486
|
// DATA PROCESSING - ALWAYS call these hooks
|
|
@@ -771,15 +713,31 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
771
713
|
onLayoutChange,
|
|
772
714
|
});
|
|
773
715
|
|
|
716
|
+
// PERFORMANCE FIX: If permissions still loading after timeout, filter data to empty array
|
|
717
|
+
// This allows table structure to render but keeps data hidden until permissions confirm
|
|
718
|
+
// SECURITY: Data remains protected - only table structure (headers) will show
|
|
719
|
+
const safeTableData = useMemo(() => {
|
|
720
|
+
if (permissions.canRead.isLoading && shouldAllowRenderAfterTimeout) {
|
|
721
|
+
// Permissions still loading after timeout - return empty array to hide data
|
|
722
|
+
return [] as TData[];
|
|
723
|
+
}
|
|
724
|
+
if (!permissions.canRead.can) {
|
|
725
|
+
// Permissions denied - return empty array
|
|
726
|
+
return [] as TData[];
|
|
727
|
+
}
|
|
728
|
+
// Permissions confirmed - return actual data
|
|
729
|
+
return finalTableData as TData[];
|
|
730
|
+
}, [finalTableData, permissions.canRead.isLoading, permissions.canRead.can, shouldAllowRenderAfterTimeout]);
|
|
731
|
+
|
|
774
732
|
const tableConfig = useDataTableConfiguration({
|
|
775
|
-
data:
|
|
733
|
+
data: safeTableData,
|
|
776
734
|
columns: enhancedColumns,
|
|
777
735
|
stateSnapshot: tableStateSnapshot,
|
|
778
736
|
handlers: tableHandlers,
|
|
779
737
|
features: secureFeatures,
|
|
780
738
|
getRowId: resolvedGetRowId,
|
|
781
739
|
finalPaginationMode,
|
|
782
|
-
finalDataCount,
|
|
740
|
+
finalDataCount: safeTableData.length > 0 ? finalDataCount : 0,
|
|
783
741
|
pageSize: effectivePageSize,
|
|
784
742
|
hasServerSideConfig: !!serverSide,
|
|
785
743
|
});
|
|
@@ -791,497 +749,172 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
791
749
|
// RBAC VALIDATION AND EARLY RETURNS - AFTER ALL HOOKS
|
|
792
750
|
// ============================================================================
|
|
793
751
|
|
|
752
|
+
// DIAGNOSTIC: Log render state for debugging
|
|
753
|
+
const renderDiagnostics = {
|
|
754
|
+
hasUser: !!user,
|
|
755
|
+
userId: user?.id,
|
|
756
|
+
permissionLoading: permissions.canRead.isLoading,
|
|
757
|
+
permissionCan: permissions.canRead.can,
|
|
758
|
+
permissionError: permissions.canRead.error,
|
|
759
|
+
effectivePageId,
|
|
760
|
+
externalIsLoading,
|
|
761
|
+
performanceLoading,
|
|
762
|
+
computedIsLoading: isLoading,
|
|
763
|
+
dataLength: data.length,
|
|
764
|
+
finalTableDataLength: finalTableData.length,
|
|
765
|
+
columnsLength: columns.length,
|
|
766
|
+
tableRowsCount: table?.getRowModel().rows.length || 0,
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
// Log diagnostics in development mode
|
|
770
|
+
if (process.env.NODE_ENV === 'development') {
|
|
771
|
+
logger.debug('DataTable render diagnostics:', renderDiagnostics);
|
|
772
|
+
}
|
|
773
|
+
|
|
794
774
|
// MANDATORY: Every DataTable must have a user
|
|
795
775
|
if (!user) {
|
|
776
|
+
logger.error('DataTable render blocked: No user', renderDiagnostics);
|
|
796
777
|
throw new Error('DataTable requires authenticated user for RBAC');
|
|
797
778
|
}
|
|
798
779
|
|
|
780
|
+
// PERFORMANCE FIX: Allow rendering after timeout to prevent infinite blocking
|
|
781
|
+
// After 3 seconds, allow table to render but keep data hidden until permissions confirm
|
|
782
|
+
// This provides better UX while maintaining security (data remains protected)
|
|
783
|
+
// Note: permissionElapsed and shouldAllowRenderAfterTimeout are calculated above for useMemo
|
|
799
784
|
// Wait for permission check to complete before making access decisions
|
|
800
|
-
|
|
785
|
+
// BUT: After 3s timeout, allow table structure to render (data will remain hidden)
|
|
786
|
+
if (isPermissionLoading) {
|
|
787
|
+
// Enhanced diagnostics for hanging permission checks
|
|
788
|
+
if (permissionElapsed > 10000) {
|
|
789
|
+
logger.error('DataTableCore', 'DataTable: Permission check hanging (>10s)', {
|
|
790
|
+
...renderDiagnostics,
|
|
791
|
+
permissionState: {
|
|
792
|
+
can: permissions.canRead.can,
|
|
793
|
+
isLoading: permissions.canRead.isLoading,
|
|
794
|
+
error: permissions.canRead.error?.message,
|
|
795
|
+
},
|
|
796
|
+
elapsedMs: permissionElapsed,
|
|
797
|
+
diagnostic: 'Permission check has been loading for over 10 seconds. This likely indicates a hanging database query or network issue. Check browser network tab for pending requests to Supabase.',
|
|
798
|
+
recommendation: 'Check: 1) Browser network tab for pending requests, 2) Supabase connection, 3) Database query performance, 4) Super admin check completion',
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
if (process.env.NODE_ENV === 'development') {
|
|
803
|
+
logger.debug('DataTable render blocked: Permissions loading', {
|
|
804
|
+
...renderDiagnostics,
|
|
805
|
+
permissionState: permissions.canRead,
|
|
806
|
+
elapsedMs: permissionElapsed,
|
|
807
|
+
});
|
|
808
|
+
}
|
|
801
809
|
return <LoadingComponent />;
|
|
802
810
|
}
|
|
811
|
+
|
|
812
|
+
// If timeout reached but permissions still loading, log warning and proceed with caution
|
|
813
|
+
if (permissions.canRead.isLoading && shouldAllowRenderAfterTimeout) {
|
|
814
|
+
logger.warn('DataTable: Rendering after timeout - permissions still loading. Data will remain hidden until confirmed.', {
|
|
815
|
+
pageId: effectivePageId,
|
|
816
|
+
elapsedMs: permissionElapsed,
|
|
817
|
+
permissionState: permissions.canRead,
|
|
818
|
+
});
|
|
819
|
+
// Continue to render check below - we'll show empty state until permissions confirm
|
|
820
|
+
}
|
|
803
821
|
|
|
804
|
-
// MANDATORY: No data access without read permission
|
|
805
|
-
|
|
822
|
+
// MANDATORY: No data access without read permission
|
|
823
|
+
// SECURITY: If permissions are still loading after timeout, allow table structure but hide data
|
|
824
|
+
// If permissions are confirmed as denied, show access denied page
|
|
825
|
+
if (!permissions.canRead.isLoading && !permissions.canRead.can) {
|
|
806
826
|
logger.warn('Access denied - no read permission:', {
|
|
807
827
|
canRead: permissions.canRead,
|
|
808
828
|
pageId: effectivePageId,
|
|
809
829
|
isLoading: permissions.canRead.isLoading,
|
|
830
|
+
diagnostics: renderDiagnostics,
|
|
810
831
|
});
|
|
811
832
|
return <AccessDeniedPage resource={effectivePageId || 'unknown-page'} operation="read" />;
|
|
812
833
|
}
|
|
834
|
+
|
|
835
|
+
// If permissions still loading after timeout, proceed to render but data will be empty/hidden
|
|
836
|
+
// The table structure will render, but rows will be empty until permissions confirm
|
|
837
|
+
if (permissions.canRead.isLoading && shouldAllowRenderAfterTimeout) {
|
|
838
|
+
// Log that we're proceeding with timeout
|
|
839
|
+
logger.debug('DataTable: Proceeding to render after timeout - permissions still loading', {
|
|
840
|
+
pageId: effectivePageId,
|
|
841
|
+
elapsedMs: permissionElapsed,
|
|
842
|
+
});
|
|
843
|
+
// Continue to render - data will be empty until permissions confirm
|
|
844
|
+
}
|
|
813
845
|
|
|
814
846
|
// ============================================================================
|
|
815
847
|
// RENDER
|
|
816
848
|
// ============================================================================
|
|
817
849
|
|
|
818
850
|
if (isLoading) {
|
|
851
|
+
if (process.env.NODE_ENV === 'development') {
|
|
852
|
+
logger.debug('DataTable render blocked: External isLoading', {
|
|
853
|
+
...renderDiagnostics,
|
|
854
|
+
isLoadingSource: {
|
|
855
|
+
externalIsLoading,
|
|
856
|
+
performanceLoading,
|
|
857
|
+
computed: isLoading,
|
|
858
|
+
},
|
|
859
|
+
});
|
|
860
|
+
}
|
|
819
861
|
return <LoadingComponent />;
|
|
820
862
|
}
|
|
821
863
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
:
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
864
|
+
// DIAGNOSTIC: Log successful render path
|
|
865
|
+
if (process.env.NODE_ENV === 'development') {
|
|
866
|
+
logger.debug('DataTable proceeding to render:', {
|
|
867
|
+
...renderDiagnostics,
|
|
868
|
+
willRender: true,
|
|
869
|
+
tableState: {
|
|
870
|
+
rowCount: table?.getRowModel().rows.length || 0,
|
|
871
|
+
columnCount: table?.getVisibleFlatColumns().length || 0,
|
|
872
|
+
paginationMode: finalPaginationMode,
|
|
873
|
+
},
|
|
874
|
+
});
|
|
875
|
+
}
|
|
833
876
|
|
|
834
877
|
return (
|
|
835
|
-
|
|
836
|
-
{
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
stateActions.setImportModal(true);
|
|
876
|
-
}}
|
|
877
|
-
onExport={async () => {
|
|
878
|
-
try {
|
|
879
|
-
// Prepare export options with all available data
|
|
880
|
-
// Get the table rows (which have getValue() that properly evaluates accessorFn)
|
|
881
|
-
const tableRows = table.getFilteredRowModel().rows;
|
|
882
|
-
|
|
883
|
-
// Get only visible columns by checking the actual table columns
|
|
884
|
-
// This approach is more reliable because it uses the table's actual column registry
|
|
885
|
-
const tableColumns = table.getAllColumns();
|
|
886
|
-
const visibleTableColumns = tableColumns.filter(col => {
|
|
887
|
-
// Exclude system columns (selection, actions) and only include data columns
|
|
888
|
-
const isSystemColumn = col.id === 'select' || col.id === 'actions';
|
|
889
|
-
return !isSystemColumn && col.getIsVisible();
|
|
890
|
-
});
|
|
891
|
-
|
|
892
|
-
// Map table columns to visible columns
|
|
893
|
-
const visibleColumns: DataTableColumn<TData>[] = [];
|
|
894
|
-
|
|
895
|
-
// Store mapping of column IDs to table column instances for getValue() calls
|
|
896
|
-
const columnIdToTableColumn = new Map<string, typeof visibleTableColumns[0]>();
|
|
897
|
-
|
|
898
|
-
visibleTableColumns.forEach(tableCol => {
|
|
899
|
-
// Find the original column definition that matches this table column
|
|
900
|
-
const originalCol = columns.find(col => {
|
|
901
|
-
const colId = col.id || col.accessorKey;
|
|
902
|
-
return colId && String(colId) === tableCol.id;
|
|
903
|
-
});
|
|
904
|
-
|
|
905
|
-
if (!originalCol) return;
|
|
906
|
-
|
|
907
|
-
// Store the table column for getValue() calls
|
|
908
|
-
columnIdToTableColumn.set(tableCol.id, tableCol);
|
|
909
|
-
|
|
910
|
-
// Add the display column (what's shown in the table)
|
|
911
|
-
visibleColumns.push(originalCol);
|
|
912
|
-
});
|
|
913
|
-
|
|
914
|
-
// Generate filename with timestamp
|
|
915
|
-
const timestamp = new Date().toISOString().split('T')[0];
|
|
916
|
-
const filename = title ? `${title.replace(/[^a-z0-9]/gi, '_').toLowerCase()}_${timestamp}.csv` : `data_export_${timestamp}.csv`;
|
|
917
|
-
|
|
918
|
-
// Create export options
|
|
919
|
-
const exportOptions: ExportOptions<TData> = {
|
|
920
|
-
tableRows,
|
|
921
|
-
allColumns: columns,
|
|
922
|
-
visibleColumns,
|
|
923
|
-
columnIdToTableColumn,
|
|
924
|
-
data,
|
|
925
|
-
filename,
|
|
926
|
-
table
|
|
927
|
-
};
|
|
928
|
-
|
|
929
|
-
// If custom handler provided, call it with options
|
|
930
|
-
if (secureHandlers.onExport) {
|
|
931
|
-
await secureHandlers.onExport(exportOptions);
|
|
932
|
-
return;
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
// Default export: exports exactly what's shown in the table
|
|
936
|
-
// Convert visible columns to ExportColumn format
|
|
937
|
-
const exportColumns: Array<{
|
|
938
|
-
header?: string;
|
|
939
|
-
id?: string;
|
|
940
|
-
accessorKey?: string;
|
|
941
|
-
accessorFn?: (row: any) => any;
|
|
942
|
-
editAccessorKey?: string;
|
|
943
|
-
isIdColumn?: boolean;
|
|
944
|
-
}> = exportOptions.visibleColumns.map(col => {
|
|
945
|
-
const colId = col.id || col.accessorKey;
|
|
946
|
-
const hasAccessorFn = 'accessorFn' in col && (col as any).accessorFn;
|
|
947
|
-
|
|
948
|
-
return {
|
|
949
|
-
...col,
|
|
950
|
-
header: typeof col.header === 'string'
|
|
951
|
-
? col.header
|
|
952
|
-
: col.accessorKey || colId || 'Column',
|
|
953
|
-
id: colId ? String(colId) : undefined,
|
|
954
|
-
accessorFn: hasAccessorFn ? (col as any).accessorFn : undefined,
|
|
955
|
-
};
|
|
956
|
-
});
|
|
957
|
-
|
|
958
|
-
// Export using table rows with getValue() for proper accessorFn evaluation
|
|
959
|
-
// This ensures we get the same values that are displayed in the table
|
|
960
|
-
await exportToCSVWithTableRows(
|
|
961
|
-
exportOptions.tableRows,
|
|
962
|
-
exportColumns,
|
|
963
|
-
exportOptions.columnIdToTableColumn,
|
|
964
|
-
exportOptions.filename
|
|
965
|
-
);
|
|
966
|
-
|
|
967
|
-
// Show success toast notification
|
|
968
|
-
// NOTE: Toast notifications use default timeout (5 seconds) - do not set duration property
|
|
969
|
-
toast({
|
|
970
|
-
title: "Export Successful",
|
|
971
|
-
description: `Data exported to ${exportOptions.filename}`,
|
|
972
|
-
variant: "default"
|
|
973
|
-
});
|
|
974
|
-
} catch (error) {
|
|
975
|
-
logger.error('Failed to export data:', error);
|
|
976
|
-
|
|
977
|
-
// Show error toast notification to user
|
|
978
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
979
|
-
toast({
|
|
980
|
-
title: "Export Failed",
|
|
981
|
-
description: `Failed to export data: ${errorMessage}`,
|
|
982
|
-
variant: "destructive"
|
|
983
|
-
});
|
|
984
|
-
}
|
|
985
|
-
}}
|
|
986
|
-
rowSelection={rowSelection}
|
|
987
|
-
onDeleteSelected={secureHandlers.onDeleteSelected ? async (selectedRows: Record<string, boolean>) => {
|
|
988
|
-
const selectedCount = Object.values(selectedRows).filter(Boolean).length;
|
|
989
|
-
if (selectedCount === 0) {
|
|
990
|
-
toast({
|
|
991
|
-
title: "No Selection",
|
|
992
|
-
description: "Please select at least one row to delete",
|
|
993
|
-
variant: "default"
|
|
994
|
-
});
|
|
995
|
-
return;
|
|
996
|
-
}
|
|
997
|
-
try {
|
|
998
|
-
const result = secureHandlers.onDeleteSelected!(selectedRows) as any;
|
|
999
|
-
// Handle async operations
|
|
1000
|
-
if (result !== undefined && result !== null && typeof result === 'object' && typeof result.then === 'function') {
|
|
1001
|
-
await result;
|
|
1002
|
-
}
|
|
1003
|
-
toast({
|
|
1004
|
-
title: "Delete Successful",
|
|
1005
|
-
description: `Successfully deleted ${selectedCount} ${selectedCount === 1 ? 'row' : 'rows'}`,
|
|
1006
|
-
variant: "default"
|
|
1007
|
-
});
|
|
1008
|
-
} catch (error) {
|
|
1009
|
-
logger.error('Bulk delete error:', error);
|
|
1010
|
-
toast({
|
|
1011
|
-
title: "Delete Failed",
|
|
1012
|
-
description: error instanceof Error ? error.message : 'Failed to delete selected rows',
|
|
1013
|
-
variant: "destructive"
|
|
1014
|
-
});
|
|
1015
|
-
}
|
|
1016
|
-
} : undefined}
|
|
1017
|
-
onToggleFilterRow={() => stateActions.setFilterRow(!state.showFilterRow)}
|
|
1018
|
-
showFilterRow={state.showFilterRow}
|
|
1019
|
-
rbac={rbac}
|
|
1020
|
-
permissions={permissions}
|
|
1021
|
-
/>
|
|
1022
|
-
</>
|
|
1023
|
-
</caption>
|
|
1024
|
-
|
|
1025
|
-
{/* Column groups */}
|
|
1026
|
-
<colgroup>
|
|
1027
|
-
{hasSelectColumn && <col span={1} data-col-type="select"/>}
|
|
1028
|
-
<col span={dataColumns} data-col-type="data" />
|
|
1029
|
-
{hasActionsColumn && <col span={1} data-col-type="actions"/>}
|
|
1030
|
-
</colgroup>
|
|
1031
|
-
|
|
1032
|
-
{/* Table header */}
|
|
1033
|
-
<thead>
|
|
1034
|
-
{table?.getHeaderGroups().map((headerGroup) => {
|
|
1035
|
-
// Filter visible headers once to determine first and last
|
|
1036
|
-
const visibleHeaders = headerGroup.headers.filter(header => {
|
|
1037
|
-
return typeof header.column.getIsVisible === 'function'
|
|
1038
|
-
? header.column.getIsVisible()
|
|
1039
|
-
: true;
|
|
1040
|
-
});
|
|
1041
|
-
|
|
1042
|
-
return (
|
|
1043
|
-
<tr key={headerGroup.id}>
|
|
1044
|
-
{visibleHeaders.map((header, index) => {
|
|
1045
|
-
const isFirst = index === 0;
|
|
1046
|
-
const isLast = index === visibleHeaders.length - 1;
|
|
1047
|
-
const isSortable = header.column.getCanSort();
|
|
1048
|
-
const ariaSort = isSortable
|
|
1049
|
-
? (header.column.getIsSorted() === 'asc'
|
|
1050
|
-
? 'ascending'
|
|
1051
|
-
: header.column.getIsSorted() === 'desc'
|
|
1052
|
-
? 'descending'
|
|
1053
|
-
: 'none')
|
|
1054
|
-
: undefined;
|
|
1055
|
-
const isRightAligned = header.column.columnDef.meta?.align === 'right';
|
|
1056
|
-
|
|
1057
|
-
// Create custom sort handler with accessibility announcement
|
|
1058
|
-
const handleSortClick = (event: React.MouseEvent) => {
|
|
1059
|
-
const originalHandler = header.column.getToggleSortingHandler();
|
|
1060
|
-
if (originalHandler) {
|
|
1061
|
-
originalHandler(event);
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
// Announce the sort change
|
|
1065
|
-
const columnName = typeof header.column.columnDef.header === 'string'
|
|
1066
|
-
? header.column.columnDef.header
|
|
1067
|
-
: 'column';
|
|
1068
|
-
const currentSort = header.column.getIsSorted();
|
|
1069
|
-
const newSort = currentSort === 'asc' ? 'desc' : currentSort === 'desc' ? null : 'asc';
|
|
1070
|
-
announceSortChange(columnName, newSort);
|
|
1071
|
-
};
|
|
1072
|
-
|
|
1073
|
-
// Get keyboard navigation handlers for this header
|
|
1074
|
-
const headerKeyboardHandlers = keyboardNavigation.getHeaderKeyboardHandlers(
|
|
1075
|
-
header.index,
|
|
1076
|
-
() => {
|
|
1077
|
-
// Sort handler for keyboard navigation
|
|
1078
|
-
const originalHandler = header.column.getToggleSortingHandler();
|
|
1079
|
-
if (originalHandler) {
|
|
1080
|
-
originalHandler({} as any);
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
// Announce the sort change
|
|
1084
|
-
const columnName = typeof header.column.columnDef.header === 'string'
|
|
1085
|
-
? header.column.columnDef.header
|
|
1086
|
-
: 'column';
|
|
1087
|
-
const currentSort = header.column.getIsSorted();
|
|
1088
|
-
const newSort = currentSort === 'asc' ? 'desc' : currentSort === 'desc' ? null : 'asc';
|
|
1089
|
-
announceSortChange(columnName, newSort);
|
|
1090
|
-
}
|
|
1091
|
-
);
|
|
1092
|
-
|
|
1093
|
-
return (
|
|
1094
|
-
<th
|
|
1095
|
-
key={header.id}
|
|
1096
|
-
className={cn(
|
|
1097
|
-
'px-3 py-2 bg-main-200',
|
|
1098
|
-
isRightAligned ? 'text-right' : 'text-left',
|
|
1099
|
-
isFirst && 'rounded-l-md',
|
|
1100
|
-
isLast && 'rounded-r-md'
|
|
1101
|
-
)}
|
|
1102
|
-
scope="col"
|
|
1103
|
-
role="columnheader"
|
|
1104
|
-
{...(isSortable ? { 'aria-sort': ariaSort } : {})}
|
|
1105
|
-
{...(isSortable ? headerKeyboardHandlers : {})}
|
|
1106
|
-
>
|
|
1107
|
-
{header.isPlaceholder ? null : (
|
|
1108
|
-
isSortable ? (
|
|
1109
|
-
<Button
|
|
1110
|
-
variant="ghost"
|
|
1111
|
-
className={`h-auto p-0 font-bold hover:bg-transparent ${isRightAligned ? 'justify-end' : 'justify-start'}`}
|
|
1112
|
-
onClick={handleSortClick}
|
|
1113
|
-
{...headerKeyboardHandlers}
|
|
1114
|
-
aria-label={`Sort by ${typeof header.column.columnDef.header === 'string' ? header.column.columnDef.header : 'column'}`}
|
|
1115
|
-
tabIndex={0}
|
|
1116
|
-
>
|
|
1117
|
-
{typeof header.column.columnDef.header === 'function'
|
|
1118
|
-
? header.column.columnDef.header(header.getContext())
|
|
1119
|
-
: header.column.columnDef.header}
|
|
1120
|
-
{header.column.getIsSorted() === 'asc' ? (
|
|
1121
|
-
<ChevronUp className="size-4" />
|
|
1122
|
-
) : header.column.getIsSorted() === 'desc' ? (
|
|
1123
|
-
<ChevronDown className="size-4" />
|
|
1124
|
-
) : (
|
|
1125
|
-
<ChevronsUpDown className="size-4" />
|
|
1126
|
-
)}
|
|
1127
|
-
</Button>
|
|
1128
|
-
) : (
|
|
1129
|
-
typeof header.column.columnDef.header === 'function'
|
|
1130
|
-
? header.column.columnDef.header(header.getContext())
|
|
1131
|
-
: header.column.columnDef.header
|
|
1132
|
-
)
|
|
1133
|
-
)}
|
|
1134
|
-
</th>
|
|
1135
|
-
);
|
|
1136
|
-
})}
|
|
1137
|
-
</tr>
|
|
1138
|
-
);
|
|
1139
|
-
})}
|
|
1140
|
-
</thead>
|
|
1141
|
-
|
|
1142
|
-
{/* Table body */}
|
|
1143
|
-
<UnifiedTableBody
|
|
1144
|
-
table={table}
|
|
1145
|
-
isCreating={isCreating}
|
|
1146
|
-
creationData={creationData}
|
|
1147
|
-
onCreationDataChange={stateActions.setCreationData}
|
|
1148
|
-
onSaveCreation={() => {
|
|
1149
|
-
if (onCreateRow) {
|
|
1150
|
-
onCreateRow(creationData as Partial<TData>);
|
|
1151
|
-
stateActions.clearCreationData();
|
|
1152
|
-
stateActions.setCreating(false);
|
|
1153
|
-
}
|
|
1154
|
-
}}
|
|
1155
|
-
onCancelCreation={() => {
|
|
1156
|
-
stateActions.clearCreationData();
|
|
1157
|
-
stateActions.setCreating(false);
|
|
1158
|
-
}}
|
|
1159
|
-
editingRowId={editingRowId}
|
|
1160
|
-
editingData={editingData}
|
|
1161
|
-
onEditingDataChange={(data) => {
|
|
1162
|
-
// Update the editing data in the centralized state
|
|
1163
|
-
if (editingRowId) {
|
|
1164
|
-
stateActions.setEditingRow(editingRowId, data);
|
|
1165
|
-
}
|
|
1166
|
-
}}
|
|
1167
|
-
onSaveEditing={() => {
|
|
1168
|
-
if (onEditRow && editingRowId) {
|
|
1169
|
-
// Find the original row data
|
|
1170
|
-
const originalRow = data.find(row => {
|
|
1171
|
-
try {
|
|
1172
|
-
const rowId = resolvedGetRowId(row, 0);
|
|
1173
|
-
return rowId === editingRowId;
|
|
1174
|
-
} catch {
|
|
1175
|
-
return false;
|
|
1176
|
-
}
|
|
1177
|
-
});
|
|
1178
|
-
if (originalRow) {
|
|
1179
|
-
onEditRow(originalRow, editingData as Partial<TData>);
|
|
1180
|
-
}
|
|
1181
|
-
}
|
|
1182
|
-
stateActions.clearEditing();
|
|
1183
|
-
}}
|
|
1184
|
-
onCancelEditing={() => {
|
|
1185
|
-
stateActions.clearEditing();
|
|
1186
|
-
}}
|
|
1187
|
-
grouping={state.grouping}
|
|
1188
|
-
aggregates={aggregates}
|
|
1189
|
-
getRowId={resolvedGetRowId}
|
|
1190
|
-
emptyState={React.isValidElement(emptyState) ? undefined : emptyState as any}
|
|
1191
|
-
isFiltered={searchQuery !== '' || state.columnFilters.length > 0}
|
|
1192
|
-
onClearFilters={() => {
|
|
1193
|
-
// Clear both search query states to keep them in sync
|
|
1194
|
-
stateActions.setSearchQuery('');
|
|
1195
|
-
setSearchQuery('');
|
|
1196
|
-
stateActions.setColumnFilters([]);
|
|
1197
|
-
}}
|
|
1198
|
-
enableFiltering={secureFeatures.filtering}
|
|
1199
|
-
showFilterRow={showFilterRow}
|
|
1200
|
-
dataLength={finalTableData?.length || 0}
|
|
1201
|
-
virtualHeight={virtualHeight}
|
|
1202
|
-
forceVirtualization={false}
|
|
1203
|
-
hierarchical={secureFeatures.hierarchical && hierarchical?.enabled && hierarchicalState ? {
|
|
1204
|
-
...hierarchical,
|
|
1205
|
-
state: hierarchicalState,
|
|
1206
|
-
expandAll: hierarchicalState.expandAll,
|
|
1207
|
-
collapseAll: hierarchicalState.collapseAll,
|
|
1208
|
-
isAllExpanded: hierarchicalState.getExpandedIds().length > 0 &&
|
|
1209
|
-
hierarchicalState.getExpandedIds().length === (finalTableData as any[]).filter(row => row.isParent).length,
|
|
1210
|
-
hasAnyChildren: (finalTableData as any[]).some(row => row.isParent),
|
|
1211
|
-
} : undefined}
|
|
1212
|
-
actions={effectiveActions}
|
|
1213
|
-
rbac={rbac}
|
|
1214
|
-
permissions={permissions}
|
|
1215
|
-
/>
|
|
1216
|
-
|
|
1217
|
-
{/* Table footer with pagination */}
|
|
1218
|
-
{secureFeatures.pagination && (
|
|
1219
|
-
<tfoot>
|
|
1220
|
-
<tr>
|
|
1221
|
-
<td colSpan={visibleColumns.length}>
|
|
1222
|
-
<PaginationComponent
|
|
1223
|
-
table={table}
|
|
1224
|
-
pageSizeOptions={finalPageSizeOptions}
|
|
1225
|
-
paginationMode={finalPaginationMode}
|
|
1226
|
-
totalCount={finalDataCount}
|
|
1227
|
-
isLoading={isLoading}
|
|
1228
|
-
/>
|
|
1229
|
-
</td>
|
|
1230
|
-
</tr>
|
|
1231
|
-
</tfoot>
|
|
1232
|
-
)}
|
|
1233
|
-
|
|
1234
|
-
</table>
|
|
1235
|
-
{/* Modal Dialogs */}
|
|
1236
|
-
<DataTableModals
|
|
1237
|
-
showImportModal={state.showImportModal}
|
|
1238
|
-
onCloseImportModal={() => stateActions.setImportModal(false)}
|
|
1239
|
-
onImport={async (data: TData[]) => {
|
|
1240
|
-
if (onImport) {
|
|
1241
|
-
try {
|
|
1242
|
-
const result = onImport(data);
|
|
1243
|
-
if (result && typeof result.then === 'function') {
|
|
1244
|
-
await result;
|
|
1245
|
-
}
|
|
1246
|
-
|
|
1247
|
-
// Show success toast
|
|
1248
|
-
// NOTE: Toast notifications use default timeout (5 seconds) - do not set duration property
|
|
1249
|
-
toast({
|
|
1250
|
-
title: "Import Successful",
|
|
1251
|
-
description: `Successfully imported ${data.length} ${data.length === 1 ? 'row' : 'rows'}`,
|
|
1252
|
-
variant: "default"
|
|
1253
|
-
});
|
|
1254
|
-
} catch (error) {
|
|
1255
|
-
logger.error('Import error:', error);
|
|
1256
|
-
toast({
|
|
1257
|
-
title: "Import Failed",
|
|
1258
|
-
description: error instanceof Error ? error.message : 'Failed to import data',
|
|
1259
|
-
variant: "destructive"
|
|
1260
|
-
});
|
|
1261
|
-
// Don't close modal on error so user can see the error
|
|
1262
|
-
return;
|
|
1263
|
-
}
|
|
1264
|
-
} else {
|
|
1265
|
-
logger.error('onImport handler not provided');
|
|
1266
|
-
toast({
|
|
1267
|
-
title: "Import Not Configured",
|
|
1268
|
-
description: "Import functionality requires an onImport handler to be provided.",
|
|
1269
|
-
variant: "destructive"
|
|
1270
|
-
});
|
|
1271
|
-
// Don't close modal so user can see the error
|
|
1272
|
-
return;
|
|
1273
|
-
}
|
|
1274
|
-
stateActions.setImportModal(false);
|
|
1275
|
-
}}
|
|
1276
|
-
importModalConfig={importModalConfig}
|
|
1277
|
-
columns={columns.map(col => ({
|
|
1278
|
-
id: col.id,
|
|
1279
|
-
accessorKey: col.accessorKey,
|
|
1280
|
-
header: typeof col.header === 'string' ? col.header : undefined,
|
|
1281
|
-
editAccessorKey: col.editAccessorKey,
|
|
1282
|
-
}))}
|
|
1283
|
-
/>
|
|
1284
|
-
</>
|
|
878
|
+
<DataTableLayout
|
|
879
|
+
table={table}
|
|
880
|
+
title={title}
|
|
881
|
+
description={description}
|
|
882
|
+
variant={variant}
|
|
883
|
+
className={className}
|
|
884
|
+
columns={columns}
|
|
885
|
+
secureFeatures={secureFeatures}
|
|
886
|
+
enhancedPagination={enhancedPagination}
|
|
887
|
+
searchQuery={searchQuery}
|
|
888
|
+
onSearch={handleSearch}
|
|
889
|
+
state={state}
|
|
890
|
+
stateActions={stateActions}
|
|
891
|
+
rowSelection={rowSelection}
|
|
892
|
+
onCreateRow={secureHandlers.onCreateRow}
|
|
893
|
+
onEditRow={secureHandlers.onEditRow}
|
|
894
|
+
onImport={secureHandlers.onImport}
|
|
895
|
+
onExport={secureHandlers.onExport}
|
|
896
|
+
onDeleteSelected={secureHandlers.onDeleteSelected}
|
|
897
|
+
rbac={rbac}
|
|
898
|
+
permissions={permissions}
|
|
899
|
+
effectiveActions={effectiveActions}
|
|
900
|
+
finalPageSizeOptions={finalPageSizeOptions}
|
|
901
|
+
finalPaginationMode={finalPaginationMode}
|
|
902
|
+
finalDataCount={finalDataCount}
|
|
903
|
+
isLoading={isLoading}
|
|
904
|
+
finalTableData={finalTableData}
|
|
905
|
+
aggregates={aggregates}
|
|
906
|
+
resolvedGetRowId={resolvedGetRowId}
|
|
907
|
+
data={data}
|
|
908
|
+
emptyState={emptyState}
|
|
909
|
+
virtualHeight={virtualHeight}
|
|
910
|
+
hierarchical={hierarchical}
|
|
911
|
+
hierarchicalState={hierarchicalState}
|
|
912
|
+
logger={logger}
|
|
913
|
+
secureHandlers={secureHandlers}
|
|
914
|
+
importModalConfig={importModalConfig}
|
|
915
|
+
keyboardNavigation={keyboardNavigation}
|
|
916
|
+
lastFocusedElementRef={lastFocusedElementRef}
|
|
917
|
+
/>
|
|
1285
918
|
);
|
|
1286
919
|
}
|
|
1287
920
|
|
|
@@ -1289,6 +922,15 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
1289
922
|
// MAIN COMPONENT
|
|
1290
923
|
// ============================================================================
|
|
1291
924
|
|
|
925
|
+
/**
|
|
926
|
+
* Core DataTable component implementation.
|
|
927
|
+
* This is the internal component that handles all DataTable functionality including
|
|
928
|
+
* state management, RBAC, performance optimizations, and feature rendering.
|
|
929
|
+
*
|
|
930
|
+
* @template TData - The type of data records in the table
|
|
931
|
+
* @param props - DataTable configuration
|
|
932
|
+
* @returns The rendered DataTable with error boundary
|
|
933
|
+
*/
|
|
1292
934
|
export function DataTableCore<TData extends DataRecord>(props: DataTableCoreProps<TData>) {
|
|
1293
935
|
return (
|
|
1294
936
|
<DataTableErrorBoundary>
|