@jmruthers/pace-core 0.5.193 → 0.6.2
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 +62 -0
- package/README.md +7 -1
- package/cursor-rules/00-pace-core-compliance.mdc +299 -0
- package/cursor-rules/01-standards-compliance.mdc +244 -0
- package/cursor-rules/02-project-structure.mdc +200 -0
- package/cursor-rules/03-solid-principles.mdc +222 -0
- package/cursor-rules/04-testing-standards.mdc +268 -0
- package/cursor-rules/05-bug-reports-and-features.mdc +246 -0
- package/cursor-rules/06-code-quality.mdc +309 -0
- package/cursor-rules/07-tech-stack-compliance.mdc +214 -0
- package/cursor-rules/08-markup-quality.mdc +452 -0
- package/cursor-rules/CHANGELOG.md +119 -0
- package/cursor-rules/README.md +192 -0
- package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-BPvc3Ka0.d.ts} +54 -0
- package/dist/{DataTable-Be6dH_dR.d.ts → DataTable-BMRU8a1j.d.ts} +34 -2
- package/dist/{DataTable-5FU7IESH.js → DataTable-TPTKCX4D.js} +10 -9
- package/dist/{PublicPageProvider-C0Sm_e5k.d.ts → PublicPageProvider-DC6kCaqf.d.ts} +385 -261
- package/dist/{UnifiedAuthProvider-RGJTDE2C.js → UnifiedAuthProvider-CH6Z342H.js} +3 -3
- package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CVcTjx-d.d.ts} +29 -0
- package/dist/{api-N774RPUA.js → api-MVVQZLJI.js} +2 -2
- package/dist/{chunk-KNC55RTG.js → chunk-24UVZUZG.js} +90 -54
- package/dist/chunk-24UVZUZG.js.map +1 -0
- package/dist/{chunk-HWIIPPNI.js → chunk-2UOI2FG5.js} +20 -20
- package/dist/chunk-2UOI2FG5.js.map +1 -0
- package/dist/{chunk-E3SPN4VZ 5.js → chunk-3XC4CPTD.js} +4345 -3986
- package/dist/chunk-3XC4CPTD.js.map +1 -0
- package/dist/{chunk-7EQTDTTJ.js → chunk-6J4GEEJR.js} +172 -45
- package/dist/chunk-6J4GEEJR.js.map +1 -0
- package/dist/{chunk-6C4YBBJM 5.js → chunk-6SOIHG6Z.js} +1 -1
- package/dist/chunk-6SOIHG6Z.js.map +1 -0
- package/dist/{chunk-7FLMSG37.js → chunk-EHMR7VYL.js} +25 -25
- package/dist/chunk-EHMR7VYL.js.map +1 -0
- package/dist/{chunk-I7PSE6JW.js → chunk-F2IMUDXZ.js} +2 -75
- package/dist/chunk-F2IMUDXZ.js.map +1 -0
- package/dist/{chunk-QWWZ5CAQ.js → chunk-FFQEQTNW.js} +7 -9
- 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-HW3OVDUF.js → chunk-J36DSWQK.js} +1 -1
- package/dist/{chunk-HW3OVDUF.js.map → chunk-J36DSWQK.js.map} +1 -1
- package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
- package/dist/chunk-L4OXEN46.js.map +1 -0
- package/dist/{chunk-R77UEZ4E 3.js → chunk-M43Y4SSO.js} +1 -1
- package/dist/chunk-M43Y4SSO.js.map +1 -0
- package/dist/{chunk-IIELH4DL.js → chunk-MMZ7JXPU.js} +60 -223
- package/dist/chunk-MMZ7JXPU.js.map +1 -0
- package/dist/{chunk-NOAYCWCX 5.js → chunk-NECFR5MM.js} +394 -312
- package/dist/chunk-NECFR5MM.js.map +1 -0
- package/dist/{chunk-BC4IJKSL.js → chunk-SFZUDBL5.js} +40 -4
- package/dist/chunk-SFZUDBL5.js.map +1 -0
- package/dist/{chunk-XNXXZ43G.js → chunk-XWQCNGTQ.js} +748 -364
- package/dist/chunk-XWQCNGTQ.js.map +1 -0
- package/dist/components.d.ts +6 -6
- package/dist/components.js +15 -12
- package/dist/components.js.map +1 -1
- package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
- package/dist/hooks.d.ts +59 -126
- package/dist/hooks.js +19 -28
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +63 -16
- package/dist/index.js +23 -24
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +21 -3
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +146 -115
- package/dist/rbac/index.js +8 -11
- package/dist/theming/runtime.d.ts +1 -13
- package/dist/theming/runtime.js +1 -1
- 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/{usePublicRouteParams-TZe0gy-4.d.ts → usePublicRouteParams-1oMokgLF.d.ts} +34 -4
- package/dist/{useToast-C8gR5ir4.d.ts → useToast-AyaT-x7p.d.ts} +2 -2
- package/dist/utils.d.ts +4 -5
- package/dist/utils.js +15 -15
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +7 -1
- package/docs/api/classes/ColumnFactory.md +8 -8
- package/docs/api/classes/InvalidScopeError.md +4 -4
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +4 -4
- package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
- package/docs/api/classes/PermissionDeniedError.md +4 -4
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +4 -4
- package/docs/api/classes/RBACNotInitializedError.md +4 -4
- package/docs/api/classes/SecureSupabaseClient.md +18 -15
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AddressFieldProps.md +1 -1
- package/docs/api/interfaces/AddressFieldRef.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +4 -4
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +9 -2
- package/docs/api/interfaces/ButtonProps.md +7 -4
- package/docs/api/interfaces/CalendarProps.md +8 -5
- package/docs/api/interfaces/CardProps.md +8 -5
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/ComplianceResult.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +9 -9
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +24 -21
- package/docs/api/interfaces/DataTableColumn.md +31 -31
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
- package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +5 -5
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/ErrorBoundaryProps.md +147 -0
- package/docs/api/interfaces/ErrorBoundaryProviderProps.md +36 -0
- package/docs/api/interfaces/ErrorBoundaryState.md +75 -0
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +8 -8
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +26 -23
- package/docs/api/interfaces/FooterProps.md +10 -8
- package/docs/api/interfaces/FormFieldProps.md +10 -10
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +7 -4
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +14 -11
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +11 -11
- package/docs/api/interfaces/NavigationMenuProps.md +15 -15
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +30 -27
- package/docs/api/interfaces/PaceLoginPageProps.md +6 -4
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/ParsedAddress.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +7 -26
- package/docs/api/interfaces/PublicPageFooterProps.md +9 -9
- package/docs/api/interfaces/PublicPageHeaderProps.md +10 -10
- package/docs/api/interfaces/PublicPageLayoutProps.md +7 -20
- package/docs/api/interfaces/QuickFix.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +9 -9
- package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +3 -3
- package/docs/api/interfaces/SetupIssue.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +3 -3
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +4 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +58 -55
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +15 -13
- package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +11 -9
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +8 -8
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +6 -6
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +9 -6
- package/docs/api/interfaces/UsePublicEventOptions.md +3 -3
- package/docs/api/interfaces/UsePublicEventReturn.md +8 -5
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +4 -4
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +12 -9
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +10 -7
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +14 -11
- package/docs/api/interfaces/UserMenuProps.md +8 -6
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +575 -634
- package/docs/architecture/database-schema-requirements.md +161 -0
- package/docs/core-concepts/rbac-system.md +3 -3
- package/docs/documentation-index.md +2 -4
- package/docs/getting-started/cursor-rules.md +263 -0
- package/docs/getting-started/installation-guide.md +6 -1
- package/docs/getting-started/quick-start.md +6 -1
- package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
- package/docs/migration/MIGRATION_GUIDE.md +6 -28
- package/docs/migration/README.md +52 -6
- package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
- package/docs/migration/V0.6.0_REACT_19_MIGRATION.md +227 -0
- package/docs/migration/database-changes-december-2025.md +3 -3
- 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/standards/README.md +40 -0
- package/docs/troubleshooting/migration.md +4 -4
- package/examples/PublicPages/PublicEventPage.tsx +1 -1
- package/package.json +12 -6
- 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} +737 -691
- 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 +454 -0
- package/scripts/audit/core/checks/documentation.cjs +203 -0
- package/scripts/audit/core/checks/environment.cjs +128 -0
- package/scripts/audit/core/checks/error-handling.cjs +299 -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 +86 -0
- 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/install-cursor-rules.cjs +236 -0
- 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__/helpers/test-providers.tsx +1 -1
- package/src/__tests__/helpers/test-utils.tsx +1 -1
- 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 +16 -4
- package/src/components/Button/Button.tsx +27 -4
- package/src/components/Calendar/Calendar.tsx +9 -3
- 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/DataTable/DataTable.test.tsx +57 -93
- package/src/components/DataTable/DataTable.tsx +40 -6
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +29 -7
- package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +12 -12
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
- package/src/components/DataTable/components/AccessDeniedPage.tsx +17 -26
- package/src/components/DataTable/components/ActionButtons.tsx +10 -7
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
- 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 +200 -561
- 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 +9 -1
- 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 +9 -1
- 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 +62 -852
- 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/__tests__/DataTableModals.test.tsx +23 -23
- package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +11 -11
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +36 -36
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +27 -27
- package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +39 -39
- package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +33 -33
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +29 -29
- 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 +14 -2
- 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 +124 -32
- package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
- package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
- package/src/components/DataTable/hooks/useKeyboardNavigation.ts +2 -2
- 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/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -14
- package/src/components/Dialog/Dialog.tsx +8 -7
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +46 -6
- package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
- package/src/components/ErrorBoundary/index.ts +27 -2
- package/src/components/EventSelector/EventSelector.tsx +4 -1
- package/src/components/FileDisplay/FileDisplay.test.tsx +2 -2
- package/src/components/FileDisplay/FileDisplay.tsx +32 -18
- package/src/components/FileUpload/FileUpload.tsx +22 -2
- package/src/components/Footer/Footer.test.tsx +16 -16
- package/src/components/Footer/Footer.tsx +15 -12
- package/src/components/Form/Form.test.tsx +36 -15
- package/src/components/Form/Form.tsx +31 -26
- package/src/components/Header/Header.tsx +22 -11
- package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +40 -40
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +1 -1
- package/src/components/Input/Input.test.tsx +2 -2
- package/src/components/Input/Input.tsx +36 -34
- package/src/components/Label/Label.tsx +1 -1
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
- package/src/components/LoadingSpinner/LoadingSpinner.tsx +1 -1
- package/src/components/LoginForm/LoginForm.test.tsx +42 -42
- package/src/components/LoginForm/LoginForm.tsx +12 -8
- package/src/components/NavigationMenu/NavigationMenu.tsx +15 -514
- package/src/components/NavigationMenu/types.ts +56 -0
- package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +1 -1
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +54 -52
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +33 -12
- package/src/components/PaceAppLayout/README.md +1 -1
- package/src/components/PaceAppLayout/test-setup.tsx +1 -2
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +4 -1
- package/src/components/PasswordChange/PasswordChangeForm.test.tsx +33 -33
- package/src/components/PasswordChange/PasswordChangeForm.tsx +10 -1
- package/src/components/Progress/Progress.tsx +1 -1
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
- package/src/components/PublicLayout/PublicPageLayout.tsx +3 -6
- package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
- package/src/components/Select/Select.tsx +95 -438
- 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 +5 -6
- package/src/components/Switch/Switch.tsx +4 -4
- package/src/components/Table/Table.tsx +1 -1
- package/src/components/Tabs/Tabs.tsx +1 -1
- package/src/components/Textarea/Textarea.tsx +27 -29
- package/src/components/Toast/Toast.tsx +5 -1
- package/src/components/Tooltip/Tooltip.tsx +3 -3
- package/src/components/UserMenu/UserMenu.test.tsx +24 -11
- package/src/components/UserMenu/UserMenu.tsx +22 -19
- package/src/components/index.ts +2 -2
- package/src/hooks/__tests__/hooks.integration.test.tsx +80 -55
- package/src/hooks/__tests__/index.unit.test.ts +2 -5
- package/src/hooks/__tests__/useStorage.unit.test.ts +36 -36
- package/src/hooks/index.ts +1 -2
- package/src/hooks/public/usePublicEvent.ts +5 -1
- package/src/hooks/public/usePublicEventLogo.ts +5 -1
- package/src/hooks/public/usePublicFileDisplay.ts +4 -0
- package/src/hooks/public/usePublicRouteParams.ts +5 -1
- 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/useDataTableState.ts +8 -18
- package/src/hooks/useDebounce.ts +9 -0
- package/src/hooks/useEventTheme.ts +6 -0
- package/src/hooks/useFileDisplay.ts +4 -0
- package/src/hooks/useFileReference.ts +25 -7
- package/src/hooks/useFileUrl.ts +11 -1
- package/src/hooks/useFocusManagement.ts +16 -2
- package/src/hooks/useFocusTrap.ts +7 -4
- package/src/hooks/useFormDialog.ts +8 -7
- package/src/hooks/useInactivityTracker.ts +4 -1
- 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 +8 -1
- 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 +3 -3
- package/src/index.ts +2 -1
- package/src/providers/__tests__/OrganisationProvider.test.tsx +115 -49
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +21 -6
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +10 -10
- package/src/providers/services/AuthServiceProvider.tsx +18 -0
- package/src/providers/services/EventServiceProvider.tsx +18 -0
- package/src/providers/services/InactivityServiceProvider.tsx +18 -0
- package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
- package/src/providers/services/UnifiedAuthProvider.tsx +58 -22
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +33 -7
- package/src/rbac/README.md +1 -1
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +26 -26
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
- package/src/rbac/adapters.tsx +14 -5
- package/src/rbac/api.ts +100 -67
- package/src/rbac/components/EnhancedNavigationMenu.tsx +1 -1
- package/src/rbac/components/NavigationGuard.tsx +1 -1
- package/src/rbac/components/NavigationProvider.tsx +5 -2
- package/src/rbac/components/PagePermissionGuard.tsx +158 -18
- package/src/rbac/components/PagePermissionProvider.tsx +1 -1
- package/src/rbac/components/PermissionEnforcer.tsx +1 -1
- package/src/rbac/components/RoleBasedRouter.tsx +6 -2
- package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
- package/src/rbac/components/SecureDataProvider.tsx +21 -6
- 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/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 +347 -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 +71 -64
- package/src/rbac/hooks/usePermissions.ts +14 -995
- package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
- package/src/rbac/hooks/useResourcePermissions.ts +14 -4
- package/src/rbac/hooks/useSecureSupabase.ts +33 -13
- package/src/rbac/permissions.ts +0 -30
- package/src/rbac/secureClient.ts +212 -61
- package/src/rbac/types.ts +8 -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/file-reference/index.ts +53 -1
- package/src/utils/formatting/formatting.ts +8 -18
- package/src/utils/index.ts +0 -1
- package/src/utils/security/secureDataAccess.test.ts +31 -20
- package/src/utils/security/secureDataAccess.ts +4 -3
- package/dist/chunk-6C4YBBJM.js +0 -628
- package/dist/chunk-6C4YBBJM.js.map +0 -1
- package/dist/chunk-7D4SUZUM.js 2.map +0 -1
- package/dist/chunk-7EQTDTTJ.js 2.map +0 -1
- package/dist/chunk-7EQTDTTJ.js.map +0 -1
- package/dist/chunk-7FLMSG37.js 2.map +0 -1
- package/dist/chunk-7FLMSG37.js.map +0 -1
- package/dist/chunk-BC4IJKSL.js.map +0 -1
- package/dist/chunk-E3SPN4VZ.js +0 -12917
- package/dist/chunk-E3SPN4VZ.js.map +0 -1
- package/dist/chunk-E66EQZE6 5.js +0 -37
- package/dist/chunk-E66EQZE6.js 2.map +0 -1
- package/dist/chunk-HWIIPPNI.js.map +0 -1
- package/dist/chunk-I7PSE6JW 5.js +0 -191
- package/dist/chunk-I7PSE6JW.js 2.map +0 -1
- package/dist/chunk-I7PSE6JW.js.map +0 -1
- package/dist/chunk-IIELH4DL.js.map +0 -1
- package/dist/chunk-KNC55RTG.js 5.map +0 -1
- package/dist/chunk-KNC55RTG.js.map +0 -1
- package/dist/chunk-KQCRWDSA.js 5.map +0 -1
- package/dist/chunk-LFNCN2SP.js +0 -412
- package/dist/chunk-LFNCN2SP.js 2.map +0 -1
- package/dist/chunk-LFNCN2SP.js.map +0 -1
- package/dist/chunk-LMC26NLJ 2.js +0 -84
- package/dist/chunk-NOAYCWCX.js +0 -4993
- package/dist/chunk-NOAYCWCX.js.map +0 -1
- package/dist/chunk-QWWZ5CAQ.js 3.map +0 -1
- package/dist/chunk-QWWZ5CAQ.js.map +0 -1
- package/dist/chunk-QXHPKYJV 3.js +0 -113
- package/dist/chunk-R77UEZ4E.js +0 -68
- package/dist/chunk-R77UEZ4E.js.map +0 -1
- package/dist/chunk-SQGMNID3.js.map +0 -1
- package/dist/chunk-VBXEHIUJ.js 6.map +0 -1
- package/dist/chunk-XNXXZ43G.js.map +0 -1
- package/dist/chunk-ZSAAAMVR 6.js +0 -25
- package/dist/components.js 5.map +0 -1
- package/dist/styles/index 2.js +0 -12
- package/dist/styles/index.js 5.map +0 -1
- package/dist/theming/runtime 5.js +0 -19
- package/dist/theming/runtime.js 5.map +0 -1
- package/docs/api/classes/ErrorBoundary.md +0 -144
- package/docs/migration/quick-migration-guide.md +0 -356
- package/docs/migration/service-architecture.md +0 -281
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
- package/src/hooks/useSecureDataAccess.test.ts +0 -559
- package/src/hooks/useSecureDataAccess.ts +0 -666
- /package/dist/{DataTable-5FU7IESH.js.map → DataTable-TPTKCX4D.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-RGJTDE2C.js.map → UnifiedAuthProvider-CH6Z342H.js.map} +0 -0
- /package/dist/{api-N774RPUA.js.map → api-MVVQZLJI.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/examples/{rbac → RBAC}/CompleteRBACExample.tsx +0 -0
- /package/examples/{rbac → RBAC}/EventBasedApp.tsx +0 -0
- /package/examples/{rbac → RBAC}/PermissionExample.tsx +0 -0
- /package/examples/{rbac → RBAC}/index.ts +0 -0
|
@@ -8,27 +8,15 @@
|
|
|
8
8
|
* This is the main component that consumers will use.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import React, { useMemo, useCallback, useEffect,
|
|
12
|
-
import { useReactTable
|
|
11
|
+
import React, { useMemo, useCallback, useEffect, useRef } from 'react';
|
|
12
|
+
import { useReactTable } from '@tanstack/react-table';
|
|
13
13
|
import type {
|
|
14
14
|
SortingState,
|
|
15
|
-
ColumnFiltersState,
|
|
16
|
-
VisibilityState,
|
|
17
|
-
GroupingState,
|
|
18
|
-
ExpandedState,
|
|
19
|
-
PaginationState,
|
|
20
15
|
} from '@tanstack/react-table';
|
|
21
|
-
import { Edit, Trash
|
|
22
|
-
import { cn } from '../../../utils/core/cn';
|
|
23
|
-
import { Checkbox } from '../../Checkbox/Checkbox';
|
|
24
|
-
import { Button } from '../../Button/Button';
|
|
25
|
-
import { getTableClasses, getMainContainerClasses } from '../styles';
|
|
16
|
+
import { Edit, Trash } from 'lucide-react';
|
|
26
17
|
import { useDataTablePerformance } from '../../../hooks/useDataTablePerformance';
|
|
27
|
-
import { DataTableToolbar } from './DataTableToolbar';
|
|
28
|
-
import { UnifiedTableBody } from './UnifiedTableBody';
|
|
29
|
-
import { PaginationControls, EnhancedPaginationControls } from './PaginationControls';
|
|
30
18
|
import { LoadingState } from './LoadingState';
|
|
31
|
-
import {
|
|
19
|
+
import { DataTableLayout } from './DataTableLayout';
|
|
32
20
|
import { DataTableErrorBoundary } from './DataTableErrorBoundary';
|
|
33
21
|
import { useColumnOrderPersistence } from '../hooks/useColumnOrderPersistence';
|
|
34
22
|
import { useColumnVisibilityPersistence } from '../hooks/useColumnVisibilityPersistence';
|
|
@@ -41,20 +29,19 @@ import { useDataTableConfiguration } from '../hooks/useDataTableConfiguration';
|
|
|
41
29
|
import type { TableStateSnapshot } from '../hooks/useTableHandlers';
|
|
42
30
|
import { ColumnFactory } from '../core/ColumnFactory';
|
|
43
31
|
import { AccessDeniedPage } from './AccessDeniedPage';
|
|
44
|
-
import { useCan, useResolvedScope } from '../../../rbac/hooks';
|
|
45
32
|
// NOTE: All toast() calls in this component use the default timeout (5 seconds).
|
|
46
33
|
// Do NOT set duration or timeout properties - let the toast system use its default.
|
|
47
34
|
import { toast } from '../../../hooks/useToast';
|
|
48
|
-
import { exportToCSV, exportToCSVWithTableRows } from '../utils/exportUtils';
|
|
49
|
-
import type { ExportOptions } from '../types';
|
|
50
35
|
import { useUnifiedAuth } from '../../../providers/services/UnifiedAuthProvider';
|
|
51
|
-
import { Scope } from '../../../rbac/types';
|
|
52
36
|
import { useDataTablePermissions } from '../hooks/useDataTablePermissions';
|
|
53
37
|
import { useTableColumns } from '../hooks/useTableColumns';
|
|
54
|
-
import { initializeLiveRegion
|
|
38
|
+
import { initializeLiveRegion } from '../utils/a11yUtils';
|
|
55
39
|
import { useKeyboardNavigation } from '../hooks/useKeyboardNavigation';
|
|
56
40
|
import { getRowIdSafe } from '../utils/rowUtils';
|
|
57
41
|
import { createLogger } from '../../../utils/core/logger';
|
|
42
|
+
import { usePermissionTracking } from './hooks/usePermissionTracking';
|
|
43
|
+
import { useImportModalFocus } from './hooks/useImportModalFocus';
|
|
44
|
+
import { toCellValueRecord } from './cellValueUtils';
|
|
58
45
|
|
|
59
46
|
import { normalizeDataTableFeatures } from '../types';
|
|
60
47
|
import type {
|
|
@@ -69,50 +56,23 @@ import type {
|
|
|
69
56
|
DataTableFeatureConfig,
|
|
70
57
|
NormalizedDataTableFeatureConfig,
|
|
71
58
|
DataTableColumn,
|
|
72
|
-
SimpleColumn,
|
|
73
59
|
AggregateConfig,
|
|
74
60
|
DataTableAction,
|
|
75
61
|
HierarchicalConfig,
|
|
76
62
|
DataTableRBACConfig,
|
|
77
|
-
CellValue
|
|
78
63
|
} from '../types';
|
|
79
64
|
import type { ImportModalConfig } from './ImportModal';
|
|
80
65
|
|
|
81
|
-
const isCellValue = (value: unknown): value is CellValue => {
|
|
82
|
-
if (value === null || value === undefined) {
|
|
83
|
-
return true;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (value instanceof Date) {
|
|
87
|
-
return true;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const valueType = typeof value;
|
|
91
|
-
return valueType === 'string' || valueType === 'number' || valueType === 'boolean';
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const toCellValueRecord = <TData extends DataRecord>(row: TData): Record<string, CellValue> => {
|
|
95
|
-
if (typeof row !== 'object' || row === null) {
|
|
96
|
-
return {};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return Object.entries(row).reduce<Record<string, CellValue>>((accumulator, [key, entryValue]) => {
|
|
100
|
-
if (isCellValue(entryValue)) {
|
|
101
|
-
accumulator[key] = entryValue;
|
|
102
|
-
} else if (entryValue && typeof entryValue === 'object' && 'toString' in entryValue) {
|
|
103
|
-
accumulator[key] = String(entryValue) as CellValue;
|
|
104
|
-
} else {
|
|
105
|
-
accumulator[key] = entryValue as CellValue;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return accumulator;
|
|
109
|
-
}, {});
|
|
110
|
-
};
|
|
111
|
-
|
|
112
66
|
// ============================================================================
|
|
113
67
|
// CORE COMPONENT PROPS
|
|
114
68
|
// ============================================================================
|
|
115
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
|
+
*/
|
|
116
76
|
export interface DataTableCoreProps<TData extends DataRecord> {
|
|
117
77
|
// Core data
|
|
118
78
|
data: TData[];
|
|
@@ -159,7 +119,7 @@ export interface DataTableCoreProps<TData extends DataRecord> {
|
|
|
159
119
|
// Utilities
|
|
160
120
|
getRowId?: GetRowId<TData>;
|
|
161
121
|
isLoading?: boolean;
|
|
162
|
-
emptyState?: EmptyStateConfig | React.ReactElement
|
|
122
|
+
emptyState?: EmptyStateConfig | React.ReactElement<any>;
|
|
163
123
|
aggregates?: AggregateConfig[];
|
|
164
124
|
importModalConfig?: ImportModalConfig;
|
|
165
125
|
actions?: DataTableAction<TData>[];
|
|
@@ -220,7 +180,7 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
220
180
|
onLayoutChange,
|
|
221
181
|
} = props;
|
|
222
182
|
|
|
223
|
-
const logger =
|
|
183
|
+
const logger = createLogger('DataTableCore');
|
|
224
184
|
|
|
225
185
|
// ============================================================================
|
|
226
186
|
// ALL HOOKS MUST BE CALLED IN THE SAME ORDER EVERY RENDER
|
|
@@ -236,6 +196,16 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
236
196
|
|
|
237
197
|
// MANDATORY: Get permissions and secure features
|
|
238
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
|
+
});
|
|
239
209
|
|
|
240
210
|
|
|
241
211
|
// ============================================================================
|
|
@@ -362,43 +332,7 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
362
332
|
}
|
|
363
333
|
);
|
|
364
334
|
|
|
365
|
-
const lastFocusedElementRef =
|
|
366
|
-
const wasImportModalOpenRef = useRef(false);
|
|
367
|
-
|
|
368
|
-
// Store focus when modals open, restore when they close
|
|
369
|
-
useEffect(() => {
|
|
370
|
-
if (state.showImportModal) {
|
|
371
|
-
wasImportModalOpenRef.current = true;
|
|
372
|
-
keyboardNavigation.storeFocus();
|
|
373
|
-
if (document.activeElement instanceof HTMLElement) {
|
|
374
|
-
lastFocusedElementRef.current = document.activeElement;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
}, [state.showImportModal, keyboardNavigation]);
|
|
378
|
-
|
|
379
|
-
useEffect(() => {
|
|
380
|
-
if (!state.showImportModal) {
|
|
381
|
-
if (!wasImportModalOpenRef.current) {
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
wasImportModalOpenRef.current = false;
|
|
385
|
-
// Restore focus after modal closes
|
|
386
|
-
setTimeout(() => {
|
|
387
|
-
const storedElement = lastFocusedElementRef.current;
|
|
388
|
-
lastFocusedElementRef.current = null;
|
|
389
|
-
|
|
390
|
-
const elementToRestore = storedElement?.isConnected
|
|
391
|
-
? storedElement
|
|
392
|
-
: document.querySelector<HTMLElement>('[data-restore-target="datatable-import-button"]');
|
|
393
|
-
|
|
394
|
-
if (elementToRestore && typeof elementToRestore.focus === 'function') {
|
|
395
|
-
elementToRestore.focus();
|
|
396
|
-
} else {
|
|
397
|
-
keyboardNavigation.restoreFocus();
|
|
398
|
-
}
|
|
399
|
-
}, 100); // Small delay to ensure modal is fully closed
|
|
400
|
-
}
|
|
401
|
-
}, [state.showImportModal, keyboardNavigation]);
|
|
335
|
+
const { lastFocusedElementRef } = useImportModalFocus(state.showImportModal, keyboardNavigation);
|
|
402
336
|
|
|
403
337
|
// ============================================================================
|
|
404
338
|
// HIERARCHICAL DATA VALIDATION AND PROCESSING - ALWAYS call these hooks
|
|
@@ -540,7 +474,13 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
540
474
|
finalDataCount
|
|
541
475
|
]);
|
|
542
476
|
|
|
543
|
-
|
|
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
|
+
);
|
|
544
484
|
|
|
545
485
|
// ============================================================================
|
|
546
486
|
// DATA PROCESSING - ALWAYS call these hooks
|
|
@@ -773,15 +713,31 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
773
713
|
onLayoutChange,
|
|
774
714
|
});
|
|
775
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
|
+
|
|
776
732
|
const tableConfig = useDataTableConfiguration({
|
|
777
|
-
data:
|
|
733
|
+
data: safeTableData,
|
|
778
734
|
columns: enhancedColumns,
|
|
779
735
|
stateSnapshot: tableStateSnapshot,
|
|
780
736
|
handlers: tableHandlers,
|
|
781
737
|
features: secureFeatures,
|
|
782
738
|
getRowId: resolvedGetRowId,
|
|
783
739
|
finalPaginationMode,
|
|
784
|
-
finalDataCount,
|
|
740
|
+
finalDataCount: safeTableData.length > 0 ? finalDataCount : 0,
|
|
785
741
|
pageSize: effectivePageSize,
|
|
786
742
|
hasServerSideConfig: !!serverSide,
|
|
787
743
|
});
|
|
@@ -793,498 +749,172 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
793
749
|
// RBAC VALIDATION AND EARLY RETURNS - AFTER ALL HOOKS
|
|
794
750
|
// ============================================================================
|
|
795
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
|
+
|
|
796
774
|
// MANDATORY: Every DataTable must have a user
|
|
797
775
|
if (!user) {
|
|
776
|
+
logger.error('DataTable render blocked: No user', renderDiagnostics);
|
|
798
777
|
throw new Error('DataTable requires authenticated user for RBAC');
|
|
799
778
|
}
|
|
800
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
|
|
801
784
|
// Wait for permission check to complete before making access decisions
|
|
802
|
-
|
|
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
|
+
}
|
|
803
809
|
return <LoadingComponent />;
|
|
804
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
|
+
}
|
|
805
821
|
|
|
806
|
-
// MANDATORY: No data access without read permission
|
|
807
|
-
|
|
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) {
|
|
808
826
|
logger.warn('Access denied - no read permission:', {
|
|
809
827
|
canRead: permissions.canRead,
|
|
810
828
|
pageId: effectivePageId,
|
|
811
829
|
isLoading: permissions.canRead.isLoading,
|
|
830
|
+
diagnostics: renderDiagnostics,
|
|
812
831
|
});
|
|
813
832
|
return <AccessDeniedPage resource={effectivePageId || 'unknown-page'} operation="read" />;
|
|
814
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
|
+
}
|
|
815
845
|
|
|
816
846
|
// ============================================================================
|
|
817
847
|
// RENDER
|
|
818
848
|
// ============================================================================
|
|
819
849
|
|
|
820
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
|
+
}
|
|
821
861
|
return <LoadingComponent />;
|
|
822
862
|
}
|
|
823
863
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
:
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
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
|
+
}
|
|
835
876
|
|
|
836
877
|
return (
|
|
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
|
-
|
|
876
|
-
|
|
877
|
-
stateActions.setImportModal(true);
|
|
878
|
-
}}
|
|
879
|
-
onExport={async () => {
|
|
880
|
-
try {
|
|
881
|
-
// Prepare export options with all available data
|
|
882
|
-
// Get the table rows (which have getValue() that properly evaluates accessorFn)
|
|
883
|
-
const tableRows = table.getFilteredRowModel().rows;
|
|
884
|
-
|
|
885
|
-
// Get only visible columns by checking the actual table columns
|
|
886
|
-
// This approach is more reliable because it uses the table's actual column registry
|
|
887
|
-
const tableColumns = table.getAllColumns();
|
|
888
|
-
const visibleTableColumns = tableColumns.filter(col => {
|
|
889
|
-
// Exclude system columns (selection, actions) and only include data columns
|
|
890
|
-
const isSystemColumn = col.id === 'select' || col.id === 'actions';
|
|
891
|
-
return !isSystemColumn && col.getIsVisible();
|
|
892
|
-
});
|
|
893
|
-
|
|
894
|
-
// Map table columns to visible columns
|
|
895
|
-
const visibleColumns: DataTableColumn<TData>[] = [];
|
|
896
|
-
|
|
897
|
-
// Store mapping of column IDs to table column instances for getValue() calls
|
|
898
|
-
const columnIdToTableColumn = new Map<string, typeof visibleTableColumns[0]>();
|
|
899
|
-
|
|
900
|
-
visibleTableColumns.forEach(tableCol => {
|
|
901
|
-
// Find the original column definition that matches this table column
|
|
902
|
-
const originalCol = columns.find(col => {
|
|
903
|
-
const colId = col.id || col.accessorKey;
|
|
904
|
-
return colId && String(colId) === tableCol.id;
|
|
905
|
-
});
|
|
906
|
-
|
|
907
|
-
if (!originalCol) return;
|
|
908
|
-
|
|
909
|
-
// Store the table column for getValue() calls
|
|
910
|
-
columnIdToTableColumn.set(tableCol.id, tableCol);
|
|
911
|
-
|
|
912
|
-
// Add the display column (what's shown in the table)
|
|
913
|
-
visibleColumns.push(originalCol);
|
|
914
|
-
});
|
|
915
|
-
|
|
916
|
-
// Generate filename with timestamp
|
|
917
|
-
const timestamp = new Date().toISOString().split('T')[0];
|
|
918
|
-
const filename = title ? `${title.replace(/[^a-z0-9]/gi, '_').toLowerCase()}_${timestamp}.csv` : `data_export_${timestamp}.csv`;
|
|
919
|
-
|
|
920
|
-
// Create export options
|
|
921
|
-
const exportOptions: ExportOptions<TData> = {
|
|
922
|
-
tableRows,
|
|
923
|
-
allColumns: columns,
|
|
924
|
-
visibleColumns,
|
|
925
|
-
columnIdToTableColumn,
|
|
926
|
-
data,
|
|
927
|
-
filename,
|
|
928
|
-
table
|
|
929
|
-
};
|
|
930
|
-
|
|
931
|
-
// If custom handler provided, call it with options
|
|
932
|
-
if (secureHandlers.onExport) {
|
|
933
|
-
await secureHandlers.onExport(exportOptions);
|
|
934
|
-
return;
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
// Default export: exports exactly what's shown in the table
|
|
938
|
-
// Convert visible columns to ExportColumn format
|
|
939
|
-
const exportColumns: Array<{
|
|
940
|
-
header?: string;
|
|
941
|
-
id?: string;
|
|
942
|
-
accessorKey?: string;
|
|
943
|
-
accessorFn?: (row: any) => any;
|
|
944
|
-
editAccessorKey?: string;
|
|
945
|
-
isIdColumn?: boolean;
|
|
946
|
-
}> = exportOptions.visibleColumns.map(col => {
|
|
947
|
-
const colId = col.id || col.accessorKey;
|
|
948
|
-
const hasAccessorFn = 'accessorFn' in col && (col as any).accessorFn;
|
|
949
|
-
|
|
950
|
-
return {
|
|
951
|
-
...col,
|
|
952
|
-
header: typeof col.header === 'string'
|
|
953
|
-
? col.header
|
|
954
|
-
: col.accessorKey || colId || 'Column',
|
|
955
|
-
id: colId ? String(colId) : undefined,
|
|
956
|
-
accessorFn: hasAccessorFn ? (col as any).accessorFn : undefined,
|
|
957
|
-
};
|
|
958
|
-
});
|
|
959
|
-
|
|
960
|
-
// Export using table rows with getValue() for proper accessorFn evaluation
|
|
961
|
-
// This ensures we get the same values that are displayed in the table
|
|
962
|
-
await exportToCSVWithTableRows(
|
|
963
|
-
exportOptions.tableRows,
|
|
964
|
-
exportColumns,
|
|
965
|
-
exportOptions.columnIdToTableColumn,
|
|
966
|
-
exportOptions.filename
|
|
967
|
-
);
|
|
968
|
-
|
|
969
|
-
// Show success toast notification
|
|
970
|
-
// NOTE: Toast notifications use default timeout (5 seconds) - do not set duration property
|
|
971
|
-
toast({
|
|
972
|
-
title: "Export Successful",
|
|
973
|
-
description: `Data exported to ${exportOptions.filename}`,
|
|
974
|
-
variant: "default"
|
|
975
|
-
});
|
|
976
|
-
} catch (error) {
|
|
977
|
-
logger.error('Failed to export data:', error);
|
|
978
|
-
|
|
979
|
-
// Show error toast notification to user
|
|
980
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
|
981
|
-
toast({
|
|
982
|
-
title: "Export Failed",
|
|
983
|
-
description: `Failed to export data: ${errorMessage}`,
|
|
984
|
-
variant: "destructive"
|
|
985
|
-
});
|
|
986
|
-
}
|
|
987
|
-
}}
|
|
988
|
-
rowSelection={rowSelection}
|
|
989
|
-
onDeleteSelected={secureHandlers.onDeleteSelected ? async (selectedRows: Record<string, boolean>) => {
|
|
990
|
-
const selectedCount = Object.values(selectedRows).filter(Boolean).length;
|
|
991
|
-
if (selectedCount === 0) {
|
|
992
|
-
toast({
|
|
993
|
-
title: "No Selection",
|
|
994
|
-
description: "Please select at least one row to delete",
|
|
995
|
-
variant: "default"
|
|
996
|
-
});
|
|
997
|
-
return;
|
|
998
|
-
}
|
|
999
|
-
try {
|
|
1000
|
-
const result = secureHandlers.onDeleteSelected!(selectedRows) as any;
|
|
1001
|
-
// Handle async operations
|
|
1002
|
-
if (result !== undefined && result !== null && typeof result === 'object' && typeof result.then === 'function') {
|
|
1003
|
-
await result;
|
|
1004
|
-
}
|
|
1005
|
-
toast({
|
|
1006
|
-
title: "Delete Successful",
|
|
1007
|
-
description: `Successfully deleted ${selectedCount} ${selectedCount === 1 ? 'row' : 'rows'}`,
|
|
1008
|
-
variant: "default"
|
|
1009
|
-
});
|
|
1010
|
-
} catch (error) {
|
|
1011
|
-
logger.error('Bulk delete error:', error);
|
|
1012
|
-
toast({
|
|
1013
|
-
title: "Delete Failed",
|
|
1014
|
-
description: error instanceof Error ? error.message : 'Failed to delete selected rows',
|
|
1015
|
-
variant: "destructive"
|
|
1016
|
-
});
|
|
1017
|
-
}
|
|
1018
|
-
} : undefined}
|
|
1019
|
-
onToggleFilterRow={() => stateActions.setFilterRow(!state.showFilterRow)}
|
|
1020
|
-
showFilterRow={state.showFilterRow}
|
|
1021
|
-
rbac={rbac}
|
|
1022
|
-
permissions={permissions}
|
|
1023
|
-
/>
|
|
1024
|
-
</>
|
|
1025
|
-
</caption>
|
|
1026
|
-
|
|
1027
|
-
{/* Column groups */}
|
|
1028
|
-
<colgroup>
|
|
1029
|
-
{hasSelectColumn && <col span={1} data-col-type="select"/>}
|
|
1030
|
-
<col span={dataColumns} data-col-type="data" />
|
|
1031
|
-
{hasActionsColumn && <col span={1} data-col-type="actions"/>}
|
|
1032
|
-
</colgroup>
|
|
1033
|
-
|
|
1034
|
-
{/* Table header */}
|
|
1035
|
-
<thead>
|
|
1036
|
-
{table?.getHeaderGroups().map((headerGroup) => {
|
|
1037
|
-
// Filter visible headers once to determine first and last
|
|
1038
|
-
const visibleHeaders = headerGroup.headers.filter(header => {
|
|
1039
|
-
return typeof header.column.getIsVisible === 'function'
|
|
1040
|
-
? header.column.getIsVisible()
|
|
1041
|
-
: true;
|
|
1042
|
-
});
|
|
1043
|
-
|
|
1044
|
-
return (
|
|
1045
|
-
<tr key={headerGroup.id}>
|
|
1046
|
-
{visibleHeaders.map((header, index) => {
|
|
1047
|
-
const isFirst = index === 0;
|
|
1048
|
-
const isLast = index === visibleHeaders.length - 1;
|
|
1049
|
-
const isSortable = header.column.getCanSort();
|
|
1050
|
-
const ariaSort = isSortable
|
|
1051
|
-
? (header.column.getIsSorted() === 'asc'
|
|
1052
|
-
? 'ascending'
|
|
1053
|
-
: header.column.getIsSorted() === 'desc'
|
|
1054
|
-
? 'descending'
|
|
1055
|
-
: 'none')
|
|
1056
|
-
: undefined;
|
|
1057
|
-
const isRightAligned = header.column.columnDef.meta?.align === 'right';
|
|
1058
|
-
|
|
1059
|
-
// Create custom sort handler with accessibility announcement
|
|
1060
|
-
const handleSortClick = (event: React.MouseEvent) => {
|
|
1061
|
-
const originalHandler = header.column.getToggleSortingHandler();
|
|
1062
|
-
if (originalHandler) {
|
|
1063
|
-
originalHandler(event);
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
// Announce the sort change
|
|
1067
|
-
const columnName = typeof header.column.columnDef.header === 'string'
|
|
1068
|
-
? header.column.columnDef.header
|
|
1069
|
-
: 'column';
|
|
1070
|
-
const currentSort = header.column.getIsSorted();
|
|
1071
|
-
const newSort = currentSort === 'asc' ? 'desc' : currentSort === 'desc' ? null : 'asc';
|
|
1072
|
-
announceSortChange(columnName, newSort);
|
|
1073
|
-
};
|
|
1074
|
-
|
|
1075
|
-
// Get keyboard navigation handlers for this header
|
|
1076
|
-
const headerKeyboardHandlers = keyboardNavigation.getHeaderKeyboardHandlers(
|
|
1077
|
-
header.index,
|
|
1078
|
-
() => {
|
|
1079
|
-
// Sort handler for keyboard navigation
|
|
1080
|
-
const originalHandler = header.column.getToggleSortingHandler();
|
|
1081
|
-
if (originalHandler) {
|
|
1082
|
-
originalHandler({} as any);
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
// Announce the sort change
|
|
1086
|
-
const columnName = typeof header.column.columnDef.header === 'string'
|
|
1087
|
-
? header.column.columnDef.header
|
|
1088
|
-
: 'column';
|
|
1089
|
-
const currentSort = header.column.getIsSorted();
|
|
1090
|
-
const newSort = currentSort === 'asc' ? 'desc' : currentSort === 'desc' ? null : 'asc';
|
|
1091
|
-
announceSortChange(columnName, newSort);
|
|
1092
|
-
}
|
|
1093
|
-
);
|
|
1094
|
-
|
|
1095
|
-
return (
|
|
1096
|
-
<th
|
|
1097
|
-
key={header.id}
|
|
1098
|
-
className={cn(
|
|
1099
|
-
'px-3 py-2 bg-main-200',
|
|
1100
|
-
isRightAligned ? 'text-right' : 'text-left',
|
|
1101
|
-
isFirst && 'rounded-l-md',
|
|
1102
|
-
isLast && 'rounded-r-md'
|
|
1103
|
-
)}
|
|
1104
|
-
scope="col"
|
|
1105
|
-
role="columnheader"
|
|
1106
|
-
{...(isSortable ? { 'aria-sort': ariaSort } : {})}
|
|
1107
|
-
{...(isSortable ? headerKeyboardHandlers : {})}
|
|
1108
|
-
>
|
|
1109
|
-
{header.isPlaceholder ? null : (
|
|
1110
|
-
isSortable ? (
|
|
1111
|
-
<Button
|
|
1112
|
-
variant="ghost"
|
|
1113
|
-
className={`h-auto p-0 font-bold hover:bg-transparent ${isRightAligned ? 'justify-end' : 'justify-start'}`}
|
|
1114
|
-
onClick={handleSortClick}
|
|
1115
|
-
{...headerKeyboardHandlers}
|
|
1116
|
-
aria-label={`Sort by ${typeof header.column.columnDef.header === 'string' ? header.column.columnDef.header : 'column'}`}
|
|
1117
|
-
tabIndex={0}
|
|
1118
|
-
>
|
|
1119
|
-
{typeof header.column.columnDef.header === 'function'
|
|
1120
|
-
? header.column.columnDef.header(header.getContext())
|
|
1121
|
-
: header.column.columnDef.header}
|
|
1122
|
-
{header.column.getIsSorted() === 'asc' ? (
|
|
1123
|
-
<ChevronUp className="size-4" />
|
|
1124
|
-
) : header.column.getIsSorted() === 'desc' ? (
|
|
1125
|
-
<ChevronDown className="size-4" />
|
|
1126
|
-
) : (
|
|
1127
|
-
<ChevronsUpDown className="size-4" />
|
|
1128
|
-
)}
|
|
1129
|
-
</Button>
|
|
1130
|
-
) : (
|
|
1131
|
-
typeof header.column.columnDef.header === 'function'
|
|
1132
|
-
? header.column.columnDef.header(header.getContext())
|
|
1133
|
-
: header.column.columnDef.header
|
|
1134
|
-
)
|
|
1135
|
-
)}
|
|
1136
|
-
</th>
|
|
1137
|
-
);
|
|
1138
|
-
})}
|
|
1139
|
-
</tr>
|
|
1140
|
-
);
|
|
1141
|
-
})}
|
|
1142
|
-
</thead>
|
|
1143
|
-
|
|
1144
|
-
{/* Table body */}
|
|
1145
|
-
<UnifiedTableBody
|
|
1146
|
-
table={table}
|
|
1147
|
-
isCreating={isCreating}
|
|
1148
|
-
creationData={creationData}
|
|
1149
|
-
onCreationDataChange={stateActions.setCreationData}
|
|
1150
|
-
onSaveCreation={() => {
|
|
1151
|
-
if (onCreateRow) {
|
|
1152
|
-
onCreateRow(creationData as Partial<TData>);
|
|
1153
|
-
stateActions.clearCreationData();
|
|
1154
|
-
stateActions.setCreating(false);
|
|
1155
|
-
}
|
|
1156
|
-
}}
|
|
1157
|
-
onCancelCreation={() => {
|
|
1158
|
-
stateActions.clearCreationData();
|
|
1159
|
-
stateActions.setCreating(false);
|
|
1160
|
-
}}
|
|
1161
|
-
editingRowId={editingRowId}
|
|
1162
|
-
editingData={editingData}
|
|
1163
|
-
onEditingDataChange={(data) => {
|
|
1164
|
-
// Update the editing data in the centralized state
|
|
1165
|
-
if (editingRowId) {
|
|
1166
|
-
stateActions.setEditingRow(editingRowId, data);
|
|
1167
|
-
}
|
|
1168
|
-
}}
|
|
1169
|
-
onSaveEditing={() => {
|
|
1170
|
-
if (onEditRow && editingRowId) {
|
|
1171
|
-
// Find the original row data
|
|
1172
|
-
const originalRow = data.find(row => {
|
|
1173
|
-
try {
|
|
1174
|
-
const rowId = resolvedGetRowId(row, 0);
|
|
1175
|
-
return rowId === editingRowId;
|
|
1176
|
-
} catch {
|
|
1177
|
-
return false;
|
|
1178
|
-
}
|
|
1179
|
-
});
|
|
1180
|
-
if (originalRow) {
|
|
1181
|
-
onEditRow(originalRow, editingData as Partial<TData>);
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
stateActions.clearEditing();
|
|
1185
|
-
}}
|
|
1186
|
-
onCancelEditing={() => {
|
|
1187
|
-
stateActions.clearEditing();
|
|
1188
|
-
}}
|
|
1189
|
-
grouping={state.grouping}
|
|
1190
|
-
aggregates={aggregates}
|
|
1191
|
-
getRowId={resolvedGetRowId}
|
|
1192
|
-
emptyState={React.isValidElement(emptyState) ? undefined : emptyState as any}
|
|
1193
|
-
isFiltered={searchQuery !== '' || state.columnFilters.length > 0}
|
|
1194
|
-
onClearFilters={() => {
|
|
1195
|
-
// Clear both search query states to keep them in sync
|
|
1196
|
-
stateActions.setSearchQuery('');
|
|
1197
|
-
setSearchQuery('');
|
|
1198
|
-
stateActions.setColumnFilters([]);
|
|
1199
|
-
}}
|
|
1200
|
-
enableFiltering={secureFeatures.filtering}
|
|
1201
|
-
showFilterRow={showFilterRow}
|
|
1202
|
-
dataLength={finalTableData?.length || 0}
|
|
1203
|
-
virtualHeight={virtualHeight}
|
|
1204
|
-
forceVirtualization={false}
|
|
1205
|
-
hierarchical={secureFeatures.hierarchical && hierarchical?.enabled && hierarchicalState ? {
|
|
1206
|
-
...hierarchical,
|
|
1207
|
-
state: hierarchicalState,
|
|
1208
|
-
expandAll: hierarchicalState.expandAll,
|
|
1209
|
-
collapseAll: hierarchicalState.collapseAll,
|
|
1210
|
-
isAllExpanded: hierarchicalState.getExpandedIds().length > 0 &&
|
|
1211
|
-
hierarchicalState.getExpandedIds().length === (finalTableData as any[]).filter(row => row.isParent).length,
|
|
1212
|
-
hasAnyChildren: (finalTableData as any[]).some(row => row.isParent),
|
|
1213
|
-
} : undefined}
|
|
1214
|
-
actions={effectiveActions}
|
|
1215
|
-
rbac={rbac}
|
|
1216
|
-
permissions={permissions}
|
|
1217
|
-
/>
|
|
1218
|
-
|
|
1219
|
-
{/* Table footer with pagination */}
|
|
1220
|
-
{secureFeatures.pagination && (
|
|
1221
|
-
<tfoot>
|
|
1222
|
-
<tr>
|
|
1223
|
-
<td colSpan={visibleColumns.length}>
|
|
1224
|
-
<PaginationComponent
|
|
1225
|
-
table={table}
|
|
1226
|
-
pageSizeOptions={finalPageSizeOptions}
|
|
1227
|
-
paginationMode={finalPaginationMode}
|
|
1228
|
-
totalCount={finalDataCount}
|
|
1229
|
-
isLoading={isLoading}
|
|
1230
|
-
/>
|
|
1231
|
-
</td>
|
|
1232
|
-
</tr>
|
|
1233
|
-
</tfoot>
|
|
1234
|
-
)}
|
|
1235
|
-
|
|
1236
|
-
</table>
|
|
1237
|
-
|
|
1238
|
-
{/* Modal Dialogs */}
|
|
1239
|
-
<DataTableModals
|
|
1240
|
-
showImportModal={state.showImportModal}
|
|
1241
|
-
onCloseImportModal={() => stateActions.setImportModal(false)}
|
|
1242
|
-
onImport={async (data: TData[]) => {
|
|
1243
|
-
if (onImport) {
|
|
1244
|
-
try {
|
|
1245
|
-
const result = onImport(data);
|
|
1246
|
-
if (result && typeof result.then === 'function') {
|
|
1247
|
-
await result;
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
// Show success toast
|
|
1251
|
-
// NOTE: Toast notifications use default timeout (5 seconds) - do not set duration property
|
|
1252
|
-
toast({
|
|
1253
|
-
title: "Import Successful",
|
|
1254
|
-
description: `Successfully imported ${data.length} ${data.length === 1 ? 'row' : 'rows'}`,
|
|
1255
|
-
variant: "default"
|
|
1256
|
-
});
|
|
1257
|
-
} catch (error) {
|
|
1258
|
-
logger.error('Import error:', error);
|
|
1259
|
-
toast({
|
|
1260
|
-
title: "Import Failed",
|
|
1261
|
-
description: error instanceof Error ? error.message : 'Failed to import data',
|
|
1262
|
-
variant: "destructive"
|
|
1263
|
-
});
|
|
1264
|
-
// Don't close modal on error so user can see the error
|
|
1265
|
-
return;
|
|
1266
|
-
}
|
|
1267
|
-
} else {
|
|
1268
|
-
logger.error('onImport handler not provided');
|
|
1269
|
-
toast({
|
|
1270
|
-
title: "Import Not Configured",
|
|
1271
|
-
description: "Import functionality requires an onImport handler to be provided.",
|
|
1272
|
-
variant: "destructive"
|
|
1273
|
-
});
|
|
1274
|
-
// Don't close modal so user can see the error
|
|
1275
|
-
return;
|
|
1276
|
-
}
|
|
1277
|
-
stateActions.setImportModal(false);
|
|
1278
|
-
}}
|
|
1279
|
-
importModalConfig={importModalConfig}
|
|
1280
|
-
columns={columns.map(col => ({
|
|
1281
|
-
id: col.id,
|
|
1282
|
-
accessorKey: col.accessorKey,
|
|
1283
|
-
header: typeof col.header === 'string' ? col.header : undefined,
|
|
1284
|
-
editAccessorKey: col.editAccessorKey,
|
|
1285
|
-
}))}
|
|
1286
|
-
/>
|
|
1287
|
-
</>
|
|
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
|
+
/>
|
|
1288
918
|
);
|
|
1289
919
|
}
|
|
1290
920
|
|
|
@@ -1292,6 +922,15 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
1292
922
|
// MAIN COMPONENT
|
|
1293
923
|
// ============================================================================
|
|
1294
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
|
+
*/
|
|
1295
934
|
export function DataTableCore<TData extends DataRecord>(props: DataTableCoreProps<TData>) {
|
|
1296
935
|
return (
|
|
1297
936
|
<DataTableErrorBoundary>
|