@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
|
@@ -3,45 +3,35 @@
|
|
|
3
3
|
* @package @jmruthers/pace-core
|
|
4
4
|
* @module Components/DataTable/Components
|
|
5
5
|
* @since 0.3.0
|
|
6
|
-
*
|
|
6
|
+
*
|
|
7
7
|
* A unified table body component that handles both standard and virtualized rendering
|
|
8
8
|
* based on data size. This eliminates the need for separate virtualized components
|
|
9
9
|
* while maintaining all features consistently.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import React, {
|
|
13
|
-
import { type Table
|
|
12
|
+
import React, { useEffect, useRef } from 'react';
|
|
13
|
+
import { type Table } from '@tanstack/react-table';
|
|
14
14
|
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
15
|
-
// Removed Table component imports - using native HTML elements
|
|
16
|
-
import { Button } from '../../Button/Button';
|
|
17
|
-
import { ChevronUp, ChevronDown, ChevronRight } from 'lucide-react';
|
|
18
15
|
import { EmptyState } from './EmptyState';
|
|
19
16
|
import { FilterRow } from './FilterRow';
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import { getTableCellClasses
|
|
17
|
+
import { MemoizedRow } from './RowComponent';
|
|
18
|
+
import { renderEditField } from './EditFields';
|
|
19
|
+
import { getTableCellClasses } from '../styles';
|
|
23
20
|
import { Input } from '../../Input/Input';
|
|
24
|
-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, SelectGroup, SelectLabel, SelectSeparator } from '../../Select/Select';
|
|
25
21
|
import { createLogger } from '../../../utils/core/logger';
|
|
26
|
-
import { cn } from '../../../utils/core/cn';
|
|
27
22
|
import type {
|
|
28
23
|
AggregateConfig,
|
|
24
|
+
CellValue,
|
|
29
25
|
DataRecord,
|
|
30
26
|
DataTableAction,
|
|
31
|
-
DataTableColumn,
|
|
32
27
|
EditableColumnDef,
|
|
33
28
|
EmptyStateConfig,
|
|
34
29
|
HierarchicalConfig,
|
|
35
|
-
HierarchicalDataRow,
|
|
36
|
-
CellValue
|
|
37
30
|
} from '../types';
|
|
38
|
-
import { calculateIndentation } from '../utils/hierarchicalUtils';
|
|
39
31
|
import { getRowIdSafe } from '../utils/rowUtils';
|
|
40
32
|
|
|
41
33
|
// Performance thresholds
|
|
42
34
|
const VIRTUALIZATION_THRESHOLD = 1000;
|
|
43
|
-
const CHUNKING_THRESHOLD = 10000;
|
|
44
|
-
const MEMORY_OPTIMIZATION_THRESHOLD = 100000;
|
|
45
35
|
|
|
46
36
|
/**
|
|
47
37
|
* Props for the unified table body component
|
|
@@ -123,754 +113,6 @@ interface UnifiedTableBodyProps<TData extends DataRecord> {
|
|
|
123
113
|
};
|
|
124
114
|
}
|
|
125
115
|
|
|
126
|
-
// Component for select fields with searchable and creatable support
|
|
127
|
-
function SelectEditField<TData extends DataRecord>({
|
|
128
|
-
columnDef,
|
|
129
|
-
accessorKey,
|
|
130
|
-
currentValue,
|
|
131
|
-
placeholder,
|
|
132
|
-
onChange,
|
|
133
|
-
}: {
|
|
134
|
-
columnDef: EditableColumnDef<TData>;
|
|
135
|
-
accessorKey: string;
|
|
136
|
-
currentValue: CellValue;
|
|
137
|
-
placeholder?: string;
|
|
138
|
-
onChange: (value: CellValue) => void;
|
|
139
|
-
}) {
|
|
140
|
-
const logger = React.useMemo(() => createLogger('SelectEditField'), []);
|
|
141
|
-
// Determine if searchable - explicitly check for true to ensure visible search input appears
|
|
142
|
-
// When selectSearchable is true or undefined, show the visible search input box
|
|
143
|
-
// When selectSearchable is false, hide the search input (type-to-search still works via SelectContent internals)
|
|
144
|
-
const isSearchable = columnDef.selectSearchable !== false;
|
|
145
|
-
const isCreatable = columnDef.creatable === true;
|
|
146
|
-
const selectRef = React.useRef<HTMLFormElement>(null);
|
|
147
|
-
const [searchTerm, setSearchTerm] = React.useState('');
|
|
148
|
-
const [isOpen, setIsOpen] = React.useState(false);
|
|
149
|
-
const [showCreateOption, setShowCreateOption] = React.useState(false);
|
|
150
|
-
|
|
151
|
-
// Monitor search input value via DOM events to detect when user types
|
|
152
|
-
React.useEffect(() => {
|
|
153
|
-
if (!isOpen || !isSearchable || !isCreatable || !columnDef.onCreateNew) {
|
|
154
|
-
// Reset showCreateOption when conditions aren't met
|
|
155
|
-
if (!isOpen || !isCreatable || !columnDef.onCreateNew) {
|
|
156
|
-
setShowCreateOption(false);
|
|
157
|
-
}
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Function to find and attach listener to search input
|
|
162
|
-
const findAndAttachSearchInput = (): (() => void) | null => {
|
|
163
|
-
// Try to find search input - check both within selectRef and document
|
|
164
|
-
// SelectContent might be rendered outside the form element
|
|
165
|
-
let searchInput: HTMLInputElement | null = null;
|
|
166
|
-
|
|
167
|
-
if (selectRef.current) {
|
|
168
|
-
searchInput = selectRef.current.querySelector<HTMLInputElement>('[data-testid="select-search-input"]');
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// If not found in selectRef, try document (in case SelectContent is in a portal)
|
|
172
|
-
if (!searchInput) {
|
|
173
|
-
// Find the most recently opened select's search input
|
|
174
|
-
const allSearchInputs = document.querySelectorAll<HTMLInputElement>('[data-testid="select-search-input"]');
|
|
175
|
-
// Get the one that's visible (not hidden)
|
|
176
|
-
for (const input of Array.from(allSearchInputs)) {
|
|
177
|
-
const content = input.closest('[data-testid="select-content"]');
|
|
178
|
-
if (content && content.getAttribute('aria-hidden') !== 'true') {
|
|
179
|
-
searchInput = input;
|
|
180
|
-
break;
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (!searchInput) return null;
|
|
186
|
-
|
|
187
|
-
const handleInput = (e: Event) => {
|
|
188
|
-
const target = e.target as HTMLInputElement;
|
|
189
|
-
const currentSearch = target.value;
|
|
190
|
-
setSearchTerm(currentSearch);
|
|
191
|
-
|
|
192
|
-
// Check if search doesn't match any option (including items in groups)
|
|
193
|
-
if (currentSearch.trim()) {
|
|
194
|
-
const searchLower = currentSearch.toLowerCase().trim();
|
|
195
|
-
|
|
196
|
-
// Helper to check if an option matches
|
|
197
|
-
// Use explicit union type instead of typeof to avoid Babel parsing issues
|
|
198
|
-
type FieldOption =
|
|
199
|
-
| { value: string | number; label: string }
|
|
200
|
-
| { type: 'group'; label: string; items: Array<{ value: string | number; label: string }> }
|
|
201
|
-
| { type: 'separator' };
|
|
202
|
-
|
|
203
|
-
const checkMatch = (opt: FieldOption): boolean => {
|
|
204
|
-
// Simple option
|
|
205
|
-
if ('value' in opt && !('type' in opt)) {
|
|
206
|
-
return opt.label.toLowerCase().includes(searchLower);
|
|
207
|
-
}
|
|
208
|
-
// Group - check items within the group
|
|
209
|
-
if ('type' in opt && opt.type === 'group') {
|
|
210
|
-
return (opt as { type: 'group'; label: string; items: Array<{ value: string | number; label: string }> }).items.some((item: { value: string | number; label: string }) => item.label.toLowerCase().includes(searchLower));
|
|
211
|
-
}
|
|
212
|
-
// Separator - doesn't match
|
|
213
|
-
return false;
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
const hasMatch = (columnDef.fieldOptions || []).some(checkMatch);
|
|
217
|
-
// Always show create option when there's a search term (user might want to create even if matches exist)
|
|
218
|
-
const shouldShow = isCreatable && !!columnDef.onCreateNew;
|
|
219
|
-
|
|
220
|
-
setShowCreateOption(shouldShow);
|
|
221
|
-
} else {
|
|
222
|
-
setShowCreateOption(false);
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
// Check initial value in case user has already typed
|
|
227
|
-
const initialValue = searchInput.value;
|
|
228
|
-
if (initialValue) {
|
|
229
|
-
const currentSearch = initialValue;
|
|
230
|
-
setSearchTerm(currentSearch);
|
|
231
|
-
|
|
232
|
-
// Check if search doesn't match any option (including items in groups)
|
|
233
|
-
if (currentSearch.trim()) {
|
|
234
|
-
const searchLower = currentSearch.toLowerCase().trim();
|
|
235
|
-
|
|
236
|
-
type FieldOption =
|
|
237
|
-
| { value: string | number; label: string }
|
|
238
|
-
| { type: 'group'; label: string; items: Array<{ value: string | number; label: string }> }
|
|
239
|
-
| { type: 'separator' };
|
|
240
|
-
|
|
241
|
-
const checkMatch = (opt: FieldOption): boolean => {
|
|
242
|
-
if ('value' in opt && !('type' in opt)) {
|
|
243
|
-
return opt.label.toLowerCase().includes(searchLower);
|
|
244
|
-
}
|
|
245
|
-
if ('type' in opt && opt.type === 'group') {
|
|
246
|
-
return (opt as { type: 'group'; label: string; items: Array<{ value: string | number; label: string }> }).items.some((item: { value: string | number; label: string }) => item.label.toLowerCase().includes(searchLower));
|
|
247
|
-
}
|
|
248
|
-
return false;
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
const hasMatch = (columnDef.fieldOptions || []).some(checkMatch);
|
|
252
|
-
// Always show create option when there's a search term (user might want to create even if matches exist)
|
|
253
|
-
const shouldShow = isCreatable && !!columnDef.onCreateNew;
|
|
254
|
-
setShowCreateOption(shouldShow);
|
|
255
|
-
} else {
|
|
256
|
-
setShowCreateOption(false);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
searchInput.addEventListener('input', handleInput);
|
|
261
|
-
|
|
262
|
-
return () => {
|
|
263
|
-
searchInput?.removeEventListener('input', handleInput);
|
|
264
|
-
};
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
// Try to find immediately
|
|
268
|
-
let cleanup: (() => void) | null = findAndAttachSearchInput();
|
|
269
|
-
|
|
270
|
-
// If not found, try again after a short delay (SelectContent might render asynchronously)
|
|
271
|
-
if (!cleanup) {
|
|
272
|
-
let timeoutCleanup: (() => void) | null = null;
|
|
273
|
-
const timeoutId = setTimeout(() => {
|
|
274
|
-
timeoutCleanup = findAndAttachSearchInput();
|
|
275
|
-
}, 50);
|
|
276
|
-
|
|
277
|
-
return () => {
|
|
278
|
-
clearTimeout(timeoutId);
|
|
279
|
-
timeoutCleanup?.();
|
|
280
|
-
};
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
return cleanup;
|
|
284
|
-
}, [isOpen, isSearchable, isCreatable, columnDef.fieldOptions, columnDef.onCreateNew]);
|
|
285
|
-
|
|
286
|
-
const handleCreateNew = React.useCallback(async () => {
|
|
287
|
-
if (!isCreatable || !columnDef.onCreateNew || !searchTerm.trim()) return;
|
|
288
|
-
|
|
289
|
-
try {
|
|
290
|
-
const newValue = await columnDef.onCreateNew(searchTerm.trim());
|
|
291
|
-
onChange(newValue);
|
|
292
|
-
setSearchTerm('');
|
|
293
|
-
setShowCreateOption(false);
|
|
294
|
-
} catch (error) {
|
|
295
|
-
logger.error('Error creating new item:', error);
|
|
296
|
-
}
|
|
297
|
-
}, [isCreatable, columnDef.onCreateNew, searchTerm, onChange, logger]);
|
|
298
|
-
|
|
299
|
-
return (
|
|
300
|
-
<Select
|
|
301
|
-
ref={selectRef}
|
|
302
|
-
value={String(currentValue)}
|
|
303
|
-
onValueChange={(newValue) => {
|
|
304
|
-
if (newValue.startsWith('__create_new__')) {
|
|
305
|
-
handleCreateNew();
|
|
306
|
-
} else {
|
|
307
|
-
onChange(newValue as CellValue);
|
|
308
|
-
}
|
|
309
|
-
}}
|
|
310
|
-
onOpenChange={(open) => {
|
|
311
|
-
setIsOpen(open);
|
|
312
|
-
if (!open) {
|
|
313
|
-
setSearchTerm('');
|
|
314
|
-
setShowCreateOption(false);
|
|
315
|
-
}
|
|
316
|
-
}}
|
|
317
|
-
>
|
|
318
|
-
<SelectTrigger className="h-8">
|
|
319
|
-
<SelectValue placeholder={placeholder || `Select ${columnDef.header || 'option'}...`} />
|
|
320
|
-
</SelectTrigger>
|
|
321
|
-
<SelectContent
|
|
322
|
-
searchable={Boolean(isSearchable)}
|
|
323
|
-
searchPlaceholder={`Search ${columnDef.header || 'options'}...`}
|
|
324
|
-
maxHeight={columnDef.selectMaxHeight}
|
|
325
|
-
className={columnDef.selectContentClassName}
|
|
326
|
-
style={columnDef.selectContentStyle}
|
|
327
|
-
>
|
|
328
|
-
{columnDef.fieldOptions?.map((option, index) => {
|
|
329
|
-
// Simple option item
|
|
330
|
-
if ('value' in option && !('type' in option)) {
|
|
331
|
-
return (
|
|
332
|
-
<SelectItem key={`${option.value}-${index}`} value={String(option.value)}>
|
|
333
|
-
{option.label}
|
|
334
|
-
</SelectItem>
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Separator
|
|
339
|
-
if ('type' in option && option.type === 'separator') {
|
|
340
|
-
return <SelectSeparator key={`separator-${index}`} />;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Group with label
|
|
344
|
-
if ('type' in option && option.type === 'group') {
|
|
345
|
-
const groupOption = option as { type: 'group'; label: string; items: Array<{ value: string | number; label: string }> };
|
|
346
|
-
return (
|
|
347
|
-
<SelectGroup key={`group-${groupOption.label}-${index}`}>
|
|
348
|
-
<SelectLabel>{groupOption.label}</SelectLabel>
|
|
349
|
-
{groupOption.items.map((item: { value: string | number; label: string }) => (
|
|
350
|
-
<SelectItem key={`${item.value}-${index}`} value={String(item.value)}>
|
|
351
|
-
{item.label}
|
|
352
|
-
</SelectItem>
|
|
353
|
-
))}
|
|
354
|
-
</SelectGroup>
|
|
355
|
-
);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
return null;
|
|
359
|
-
})}
|
|
360
|
-
{showCreateOption && isCreatable && searchTerm.trim() && columnDef.onCreateNew && (
|
|
361
|
-
<SelectItem
|
|
362
|
-
key="__create_new__"
|
|
363
|
-
value={`__create_new__${searchTerm}`}
|
|
364
|
-
className="bg-main-100 font-medium border-t border-main-200"
|
|
365
|
-
>
|
|
366
|
-
Create "{searchTerm}"
|
|
367
|
-
</SelectItem>
|
|
368
|
-
)}
|
|
369
|
-
</SelectContent>
|
|
370
|
-
</Select>
|
|
371
|
-
);
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// Helper function to render the appropriate input type based on column configuration
|
|
375
|
-
const renderEditField = <TData extends DataRecord>(
|
|
376
|
-
column: Column<TData, unknown>,
|
|
377
|
-
value: CellValue,
|
|
378
|
-
onChange: (value: CellValue | Record<string, CellValue>) => void,
|
|
379
|
-
editingData: Record<string, CellValue> = {},
|
|
380
|
-
placeholder?: string
|
|
381
|
-
) => {
|
|
382
|
-
const columnDef = column.columnDef as EditableColumnDef<TData>;
|
|
383
|
-
|
|
384
|
-
// Check if column is editable (default: true)
|
|
385
|
-
if (columnDef.editable === false) {
|
|
386
|
-
// Return the original value as text if column is not editable
|
|
387
|
-
return <span className="text-sm text-sec-600">{String(value ?? '')}</span>;
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// Check for custom field type
|
|
391
|
-
if (columnDef.fieldType === 'select' && columnDef.fieldOptions) {
|
|
392
|
-
// Use editAccessorKey if specified, otherwise use the column id
|
|
393
|
-
const accessorKey = columnDef.editAccessorKey || column.id;
|
|
394
|
-
const currentValue = editingData[accessorKey] ?? value ?? '';
|
|
395
|
-
|
|
396
|
-
return (
|
|
397
|
-
<SelectEditField
|
|
398
|
-
columnDef={columnDef}
|
|
399
|
-
accessorKey={accessorKey}
|
|
400
|
-
currentValue={currentValue}
|
|
401
|
-
placeholder={placeholder}
|
|
402
|
-
onChange={(newValue) => onChange({ [accessorKey]: newValue })}
|
|
403
|
-
/>
|
|
404
|
-
);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// Check for number type (applies to number, currency, and percentage fields)
|
|
408
|
-
if (columnDef.fieldType === 'number') {
|
|
409
|
-
// Hide spinner arrows by default for all number-related fields
|
|
410
|
-
// Currency and percentage columns use fieldType: 'number' with formatting in cell renderer
|
|
411
|
-
// Only show spinners if explicitly set to false
|
|
412
|
-
const hideSpinners = columnDef.hideNumberSpinners !== false; // Default to true
|
|
413
|
-
return (
|
|
414
|
-
<Input
|
|
415
|
-
type="number"
|
|
416
|
-
value={String(value ?? '')}
|
|
417
|
-
onChange={(e) => onChange(e.target.value as unknown as CellValue)}
|
|
418
|
-
placeholder={placeholder || `Enter ${columnDef.header || column.id}...`}
|
|
419
|
-
className={`h-8 ${hideSpinners ? 'datatable-number-no-spinners' : ''}`}
|
|
420
|
-
/>
|
|
421
|
-
);
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// Check for date type
|
|
425
|
-
if (columnDef.fieldType === 'date') {
|
|
426
|
-
return (
|
|
427
|
-
<Input
|
|
428
|
-
type="date"
|
|
429
|
-
value={String(value ?? '')}
|
|
430
|
-
onChange={(e) => onChange(e.target.value as unknown as CellValue)}
|
|
431
|
-
className="h-8"
|
|
432
|
-
/>
|
|
433
|
-
);
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// Default to text input
|
|
437
|
-
return (
|
|
438
|
-
<Input
|
|
439
|
-
type="text"
|
|
440
|
-
value={String(value ?? '')}
|
|
441
|
-
onChange={(e) => onChange(e.target.value as unknown as CellValue)}
|
|
442
|
-
placeholder={placeholder || `Enter ${columnDef.header || column.id}...`}
|
|
443
|
-
className="h-8"
|
|
444
|
-
/>
|
|
445
|
-
);
|
|
446
|
-
};
|
|
447
|
-
|
|
448
|
-
// Row component props interface
|
|
449
|
-
interface RowProps {
|
|
450
|
-
row: any;
|
|
451
|
-
style?: React.CSSProperties;
|
|
452
|
-
isEditing?: boolean;
|
|
453
|
-
editingData?: Record<string, any>;
|
|
454
|
-
onEditingDataChange?: (data: Record<string, any>) => void;
|
|
455
|
-
onSaveEditing?: () => void;
|
|
456
|
-
onCancelEditing?: () => void;
|
|
457
|
-
getRowId?: (row: any, index: number) => string;
|
|
458
|
-
grouping: string[];
|
|
459
|
-
editingRowId?: string | null;
|
|
460
|
-
hierarchical?: HierarchicalConfig & {
|
|
461
|
-
state?: {
|
|
462
|
-
isExpanded: (rowId: string) => boolean;
|
|
463
|
-
hasChildren: (rowId: string) => boolean;
|
|
464
|
-
getChildrenCount: (rowId: string) => number;
|
|
465
|
-
toggleRow: (rowId: string) => void;
|
|
466
|
-
};
|
|
467
|
-
};
|
|
468
|
-
actions?: Array<{
|
|
469
|
-
label: string;
|
|
470
|
-
onClick: (row: any) => void;
|
|
471
|
-
icon?: any;
|
|
472
|
-
variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost';
|
|
473
|
-
disabled?: boolean | ((row?: any) => boolean);
|
|
474
|
-
visible?: boolean | ((row?: any) => boolean);
|
|
475
|
-
testId?: string;
|
|
476
|
-
showInEditMode?: boolean;
|
|
477
|
-
hideInViewMode?: boolean;
|
|
478
|
-
showInViewMode?: boolean;
|
|
479
|
-
hidden?: boolean;
|
|
480
|
-
showForParent?: boolean;
|
|
481
|
-
showForChild?: boolean;
|
|
482
|
-
parentIcon?: any;
|
|
483
|
-
childIcon?: any;
|
|
484
|
-
parentLabel?: string;
|
|
485
|
-
childLabel?: string;
|
|
486
|
-
}>;
|
|
487
|
-
rbac?: {
|
|
488
|
-
pageId?: string;
|
|
489
|
-
pageName?: string;
|
|
490
|
-
};
|
|
491
|
-
permissions?: {
|
|
492
|
-
canRead: { can: boolean; isLoading: boolean };
|
|
493
|
-
canCreate: { can: boolean; isLoading: boolean };
|
|
494
|
-
canUpdate: { can: boolean; isLoading: boolean };
|
|
495
|
-
canDelete: { can: boolean; isLoading: boolean };
|
|
496
|
-
canExport: { can: boolean; isLoading: boolean };
|
|
497
|
-
canImport: { can: boolean; isLoading: boolean };
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
// Row component with proper memoization
|
|
502
|
-
const RowComponent = React.memo(({
|
|
503
|
-
row,
|
|
504
|
-
style,
|
|
505
|
-
isEditing,
|
|
506
|
-
editingData,
|
|
507
|
-
onEditingDataChange,
|
|
508
|
-
onSaveEditing,
|
|
509
|
-
onCancelEditing,
|
|
510
|
-
getRowId,
|
|
511
|
-
grouping,
|
|
512
|
-
editingRowId,
|
|
513
|
-
hierarchical,
|
|
514
|
-
actions,
|
|
515
|
-
rbac,
|
|
516
|
-
permissions
|
|
517
|
-
}: RowProps) => {
|
|
518
|
-
const rowRef = useRef<HTMLTableRowElement>(null);
|
|
519
|
-
const firstInputRef = useRef<HTMLInputElement>(null);
|
|
520
|
-
const logger = React.useMemo(() => createLogger('RowComponent'), []);
|
|
521
|
-
|
|
522
|
-
const rowId = getRowIdSafe(row.original, row.index, getRowId);
|
|
523
|
-
|
|
524
|
-
// Hierarchical row styling - moved to top to avoid hoisting issues
|
|
525
|
-
const hierarchicalRow = row.original as HierarchicalDataRow;
|
|
526
|
-
const isHierarchical = hierarchical?.enabled && hierarchicalRow?.isParent !== undefined;
|
|
527
|
-
const isParent = isHierarchical && hierarchicalRow.isParent;
|
|
528
|
-
const isChild = isHierarchical && !hierarchicalRow.isParent;
|
|
529
|
-
|
|
530
|
-
// Memoize visible cells to prevent unnecessary re-renders
|
|
531
|
-
const visibleCells = React.useMemo(() => row.getVisibleCells(), [row]);
|
|
532
|
-
const isSelected = React.useMemo(() => {
|
|
533
|
-
return typeof row.getIsSelected === 'function' ? row.getIsSelected() : false;
|
|
534
|
-
}, [row]);
|
|
535
|
-
|
|
536
|
-
// Auto-focus first editable field when entering edit mode
|
|
537
|
-
useEffect(() => {
|
|
538
|
-
if (isEditing && firstInputRef.current) {
|
|
539
|
-
firstInputRef.current.focus();
|
|
540
|
-
firstInputRef.current.select();
|
|
541
|
-
}
|
|
542
|
-
}, [isEditing]);
|
|
543
|
-
|
|
544
|
-
// Keyboard navigation (Enter to save, Escape to cancel)
|
|
545
|
-
useEffect(() => {
|
|
546
|
-
if (!isEditing) return;
|
|
547
|
-
|
|
548
|
-
const handleKeyDown = (event: KeyboardEvent) => {
|
|
549
|
-
const target = event.target as HTMLElement;
|
|
550
|
-
if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
|
|
551
|
-
if (event.key === 'Enter' && !event.shiftKey && target.tagName === 'INPUT') {
|
|
552
|
-
event.preventDefault();
|
|
553
|
-
onSaveEditing?.();
|
|
554
|
-
} else if (event.key === 'Escape') {
|
|
555
|
-
event.preventDefault();
|
|
556
|
-
onCancelEditing?.();
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
};
|
|
560
|
-
|
|
561
|
-
const currentRow = rowRef.current;
|
|
562
|
-
if (currentRow) {
|
|
563
|
-
currentRow.addEventListener('keydown', handleKeyDown);
|
|
564
|
-
return () => {
|
|
565
|
-
currentRow.removeEventListener('keydown', handleKeyDown);
|
|
566
|
-
};
|
|
567
|
-
}
|
|
568
|
-
}, [isEditing, onSaveEditing, onCancelEditing]);
|
|
569
|
-
|
|
570
|
-
// Handle grouped rows
|
|
571
|
-
if (row.getIsGrouped && row.getIsGrouped()) {
|
|
572
|
-
const groupValue = row.getValue(grouping[0]);
|
|
573
|
-
const subRowsCount = row.subRows?.length || 0;
|
|
574
|
-
const isExpanded = row.getIsExpanded();
|
|
575
|
-
|
|
576
|
-
// Get child rows for aggregation (convert TanStack Row to original data)
|
|
577
|
-
const childRows: DataRecord[] = row.subRows?.map((subRow: any) => subRow.original) || [];
|
|
578
|
-
|
|
579
|
-
// Render individual cells for each column with aggregation support
|
|
580
|
-
return (
|
|
581
|
-
<tr className="bg-sec-50 hover:bg-sec-100" style={style}>
|
|
582
|
-
{visibleCells.map((cell: any, cellIndex: number) => {
|
|
583
|
-
const columnDef = cell.column.columnDef as DataTableColumn<DataRecord>;
|
|
584
|
-
const isGroupingColumn = cellIndex === 0 || grouping.includes(cell.column.id || '');
|
|
585
|
-
|
|
586
|
-
// For the grouping column, show the group header with expand/collapse
|
|
587
|
-
if (isGroupingColumn && cellIndex === 0) {
|
|
588
|
-
return (
|
|
589
|
-
<td
|
|
590
|
-
key={cell.id}
|
|
591
|
-
className={getTableCellClasses({
|
|
592
|
-
isCompact: true,
|
|
593
|
-
className: "px-3 py-2 flex items-center font-medium"
|
|
594
|
-
})}
|
|
595
|
-
>
|
|
596
|
-
<Button
|
|
597
|
-
variant="ghost"
|
|
598
|
-
size="sm"
|
|
599
|
-
onClick={() => row.toggleExpanded()}
|
|
600
|
-
className="p-0 h-auto mr-2"
|
|
601
|
-
>
|
|
602
|
-
{isExpanded ? (
|
|
603
|
-
<ChevronDown className="size-4" />
|
|
604
|
-
) : (
|
|
605
|
-
<ChevronRight className="size-4" />
|
|
606
|
-
)}
|
|
607
|
-
</Button>
|
|
608
|
-
<span className="text-sm">
|
|
609
|
-
{String(groupValue)} ({subRowsCount} items)
|
|
610
|
-
</span>
|
|
611
|
-
</td>
|
|
612
|
-
);
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
// For other columns, check if they have aggregation functions
|
|
616
|
-
if (columnDef.aggregateFn && childRows.length > 0) {
|
|
617
|
-
try {
|
|
618
|
-
// Call the aggregation function with child rows
|
|
619
|
-
const aggregatedValue = columnDef.aggregateFn(childRows, columnDef);
|
|
620
|
-
|
|
621
|
-
// Use aggregateCell renderer if provided, otherwise use regular cell renderer
|
|
622
|
-
let cellContent: React.ReactNode;
|
|
623
|
-
if (columnDef.aggregateCell) {
|
|
624
|
-
cellContent = columnDef.aggregateCell(aggregatedValue, childRows, columnDef);
|
|
625
|
-
} else if (columnDef.cell) {
|
|
626
|
-
// Use regular cell renderer with a mock cell context
|
|
627
|
-
// Create a mock cell object for the aggregated value
|
|
628
|
-
const mockCell = {
|
|
629
|
-
...cell,
|
|
630
|
-
getValue: () => aggregatedValue,
|
|
631
|
-
renderValue: () => aggregatedValue,
|
|
632
|
-
};
|
|
633
|
-
cellContent = flexRender(columnDef.cell, {
|
|
634
|
-
...mockCell,
|
|
635
|
-
row: row,
|
|
636
|
-
column: cell.column,
|
|
637
|
-
cell: mockCell,
|
|
638
|
-
getValue: () => aggregatedValue,
|
|
639
|
-
renderValue: () => aggregatedValue,
|
|
640
|
-
});
|
|
641
|
-
} else {
|
|
642
|
-
// Fallback to displaying the raw value
|
|
643
|
-
cellContent = aggregatedValue != null ? String(aggregatedValue) : '';
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
return (
|
|
647
|
-
<td
|
|
648
|
-
key={cell.id}
|
|
649
|
-
className={getTableCellClasses({
|
|
650
|
-
isCompact: true,
|
|
651
|
-
className: `px-3 py-2 ${cell.column.columnDef.meta?.align === 'right' ? 'text-right' : ''}`
|
|
652
|
-
})}
|
|
653
|
-
>
|
|
654
|
-
{cellContent}
|
|
655
|
-
</td>
|
|
656
|
-
);
|
|
657
|
-
} catch (error) {
|
|
658
|
-
// If aggregation fails, show empty cell
|
|
659
|
-
logger.warn('Error in aggregation function:', error);
|
|
660
|
-
return (
|
|
661
|
-
<td
|
|
662
|
-
key={cell.id}
|
|
663
|
-
className={getTableCellClasses({
|
|
664
|
-
isCompact: true,
|
|
665
|
-
className: "px-3 py-2"
|
|
666
|
-
})}
|
|
667
|
-
/>
|
|
668
|
-
);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
// For columns without aggregation, show empty cell
|
|
673
|
-
return (
|
|
674
|
-
<td
|
|
675
|
-
key={cell.id}
|
|
676
|
-
className={getTableCellClasses({
|
|
677
|
-
isCompact: true,
|
|
678
|
-
className: "px-3 py-2"
|
|
679
|
-
})}
|
|
680
|
-
/>
|
|
681
|
-
);
|
|
682
|
-
})}
|
|
683
|
-
</tr>
|
|
684
|
-
);
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
// CRITICAL FIX: Removed the buggy filter that was hiding all rows when grouping is enabled
|
|
688
|
-
// TanStack Table's getRowModel() already correctly handles collapsing/expanding groups,
|
|
689
|
-
// returning only visible rows (group headers + children of expanded groups).
|
|
690
|
-
// The previous check was incorrectly filtering out all child rows regardless of parent expansion state,
|
|
691
|
-
// causing the bug where DataTable showed record count but no rows for large datasets.
|
|
692
|
-
//
|
|
693
|
-
// When grouping is enabled:
|
|
694
|
-
// - Group headers are rendered above (lines 317-426)
|
|
695
|
-
// - Child rows are ONLY in getRowModel().rows if their parent is expanded
|
|
696
|
-
// - Collapsed groups' children are NOT in getRowModel().rows (handled by TanStack Table)
|
|
697
|
-
//
|
|
698
|
-
// Therefore, we should trust getRowModel() and render all rows it returns.
|
|
699
|
-
|
|
700
|
-
// If we're in edit mode, use EditableRow for better UX (auto-focus, keyboard shortcuts)
|
|
701
|
-
if (isEditing && editingData && onEditingDataChange && onSaveEditing && onCancelEditing) {
|
|
702
|
-
return (
|
|
703
|
-
<EditableRow
|
|
704
|
-
row={row}
|
|
705
|
-
editingData={editingData}
|
|
706
|
-
onEditingDataChange={onEditingDataChange}
|
|
707
|
-
onSave={onSaveEditing}
|
|
708
|
-
onCancel={onCancelEditing}
|
|
709
|
-
actions={actions ?? []}
|
|
710
|
-
getRowId={getRowId}
|
|
711
|
-
isParent={isParent}
|
|
712
|
-
hierarchical={!!hierarchical}
|
|
713
|
-
/>
|
|
714
|
-
);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
// Regular row (not in edit mode)
|
|
718
|
-
// visibleCells is already memoized above
|
|
719
|
-
|
|
720
|
-
// Calculate indentation for child rows
|
|
721
|
-
const indentSize = hierarchical?.indentSize || 24;
|
|
722
|
-
const indentation = React.useMemo(() => {
|
|
723
|
-
return isChild && hierarchical?.state ?
|
|
724
|
-
calculateIndentation(hierarchicalRow, [], indentSize) : 0;
|
|
725
|
-
}, [isChild, hierarchical?.state, hierarchicalRow, indentSize]);
|
|
726
|
-
|
|
727
|
-
// Apply hierarchical row classes - combine with standard row classes
|
|
728
|
-
const rowClassName = React.useMemo(() => {
|
|
729
|
-
if (isHierarchical) {
|
|
730
|
-
const hierarchicalClass = isParent
|
|
731
|
-
? hierarchical?.parentRowClassName || 'bg-main-50 hover:bg-main-100 font-medium'
|
|
732
|
-
: hierarchical?.childRowClassName || 'bg-sec-25 hover:bg-sec-50';
|
|
733
|
-
// Combine with standard row classes for consistent hover behavior
|
|
734
|
-
return cn(
|
|
735
|
-
getTableRowClasses({ isSelected, isVirtualized: false }),
|
|
736
|
-
hierarchicalClass
|
|
737
|
-
);
|
|
738
|
-
}
|
|
739
|
-
// Use standard row classes when not hierarchical
|
|
740
|
-
return getTableRowClasses({ isSelected, isVirtualized: false });
|
|
741
|
-
}, [isHierarchical, isParent, isSelected, hierarchical]);
|
|
742
|
-
|
|
743
|
-
return (
|
|
744
|
-
<tr
|
|
745
|
-
ref={rowRef}
|
|
746
|
-
key={row.id}
|
|
747
|
-
role="row"
|
|
748
|
-
style={{
|
|
749
|
-
...style,
|
|
750
|
-
...(isChild && indentation > 0 ? { paddingLeft: `${indentation}px` } : {})
|
|
751
|
-
}}
|
|
752
|
-
className={rowClassName}
|
|
753
|
-
aria-selected={isSelected ? 'true' : 'false'}
|
|
754
|
-
>
|
|
755
|
-
{visibleCells.map((cell: any, cellIndex: number) => {
|
|
756
|
-
const isFirstCell = cellIndex === 0;
|
|
757
|
-
const shouldShowExpansionButton = isHierarchical && isParent && isFirstCell && hierarchical?.state;
|
|
758
|
-
const isExpanded = shouldShowExpansionButton ? hierarchical?.state?.isExpanded(rowId) : false;
|
|
759
|
-
const hasChildren = shouldShowExpansionButton ? hierarchical?.state?.hasChildren(rowId) : false;
|
|
760
|
-
|
|
761
|
-
return (
|
|
762
|
-
<td
|
|
763
|
-
key={cell.id}
|
|
764
|
-
className={getTableCellClasses({
|
|
765
|
-
isCompact: true,
|
|
766
|
-
className: `px-3 py-2 ${cell.column.id === 'actions' ? 'whitespace-nowrap' : 'whitespace-normal break-words'} ${cell.column.columnDef.meta?.align === 'right' ? 'text-right' : ''}`
|
|
767
|
-
})}
|
|
768
|
-
>
|
|
769
|
-
{shouldShowExpansionButton && hasChildren && (
|
|
770
|
-
<Button
|
|
771
|
-
variant="ghost"
|
|
772
|
-
size="sm"
|
|
773
|
-
onClick={() => hierarchical?.state?.toggleRow(rowId)}
|
|
774
|
-
className="size-6 p-0 flex-shrink-0"
|
|
775
|
-
aria-label={isExpanded ? 'Collapse row' : 'Expand row'}
|
|
776
|
-
title={isExpanded ? 'Collapse row' : 'Expand row'}
|
|
777
|
-
>
|
|
778
|
-
{isExpanded ? (
|
|
779
|
-
<ChevronDown className="size-4" />
|
|
780
|
-
) : (
|
|
781
|
-
<ChevronRight className="size-4" />
|
|
782
|
-
)}
|
|
783
|
-
</Button>
|
|
784
|
-
)}
|
|
785
|
-
{cell.column.id === 'actions' ? (
|
|
786
|
-
<ActionButtons
|
|
787
|
-
row={row}
|
|
788
|
-
actions={actions}
|
|
789
|
-
isEditing={isEditing}
|
|
790
|
-
isParent={isParent}
|
|
791
|
-
hierarchical={!!hierarchical}
|
|
792
|
-
rbac={rbac}
|
|
793
|
-
permissions={permissions}
|
|
794
|
-
/>
|
|
795
|
-
) : (
|
|
796
|
-
flexRender(cell.column.columnDef.cell, {
|
|
797
|
-
...cell.getContext(),
|
|
798
|
-
hierarchical: hierarchical,
|
|
799
|
-
isParent: isParent,
|
|
800
|
-
isChild: isChild,
|
|
801
|
-
isHierarchical: isHierarchical,
|
|
802
|
-
rowId: rowId,
|
|
803
|
-
isExpanded: isExpanded,
|
|
804
|
-
hasChildren: hasChildren,
|
|
805
|
-
})
|
|
806
|
-
)}
|
|
807
|
-
</td>
|
|
808
|
-
);
|
|
809
|
-
})}
|
|
810
|
-
</tr>
|
|
811
|
-
);
|
|
812
|
-
});
|
|
813
|
-
|
|
814
|
-
RowComponent.displayName = 'RowComponent';
|
|
815
|
-
|
|
816
|
-
// Custom comparison function for React.memo to prevent unnecessary re-renders
|
|
817
|
-
// This compares the actual row data and props, not just object references
|
|
818
|
-
const areRowPropsEqual = (prevProps: RowProps, nextProps: RowProps): boolean => {
|
|
819
|
-
// Compare row by ID and index (stable identifiers)
|
|
820
|
-
if (prevProps.row.id !== nextProps.row.id || prevProps.row.index !== nextProps.row.index) {
|
|
821
|
-
return false;
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
// Compare editing state
|
|
825
|
-
if (prevProps.isEditing !== nextProps.isEditing) {
|
|
826
|
-
return false;
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
if (prevProps.editingRowId !== nextProps.editingRowId) {
|
|
830
|
-
return false;
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
// Compare style object (shallow comparison)
|
|
834
|
-
if (prevProps.style !== nextProps.style) {
|
|
835
|
-
if (!prevProps.style || !nextProps.style) {
|
|
836
|
-
return false;
|
|
837
|
-
}
|
|
838
|
-
// Convert to records for comparison
|
|
839
|
-
const prevStyle = prevProps.style as Record<string, unknown>;
|
|
840
|
-
const nextStyle = nextProps.style as Record<string, unknown>;
|
|
841
|
-
const styleKeys = new Set([...Object.keys(prevStyle), ...Object.keys(nextStyle)]);
|
|
842
|
-
for (const key of styleKeys) {
|
|
843
|
-
if (prevStyle[key] !== nextStyle[key]) {
|
|
844
|
-
return false;
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
// Compare other primitive props
|
|
850
|
-
if (prevProps.grouping.length !== nextProps.grouping.length ||
|
|
851
|
-
prevProps.grouping.some((id, i) => id !== nextProps.grouping[i])) {
|
|
852
|
-
return false;
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
// For hierarchical state, compare the enabled flag and state functions
|
|
856
|
-
if (prevProps.hierarchical?.enabled !== nextProps.hierarchical?.enabled) {
|
|
857
|
-
return false;
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
// Compare row selection state by checking the function result
|
|
861
|
-
const prevSelected = typeof prevProps.row.getIsSelected === 'function' ? prevProps.row.getIsSelected() : false;
|
|
862
|
-
const nextSelected = typeof nextProps.row.getIsSelected === 'function' ? nextProps.row.getIsSelected() : false;
|
|
863
|
-
if (prevSelected !== nextSelected) {
|
|
864
|
-
return false;
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
// If all checks pass, props are equal
|
|
868
|
-
return true;
|
|
869
|
-
};
|
|
870
|
-
|
|
871
|
-
// Use the memoized RowComponent with custom comparison
|
|
872
|
-
const MemoizedRow = React.memo(RowComponent, areRowPropsEqual);
|
|
873
|
-
|
|
874
116
|
/**
|
|
875
117
|
* Unified table body component with intelligent virtualization
|
|
876
118
|
*/
|
|
@@ -887,7 +129,7 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
|
|
|
887
129
|
onSaveEditing,
|
|
888
130
|
onCancelEditing,
|
|
889
131
|
grouping,
|
|
890
|
-
aggregates,
|
|
132
|
+
aggregates: _aggregates,
|
|
891
133
|
getRowId,
|
|
892
134
|
emptyState,
|
|
893
135
|
isFiltered,
|
|
@@ -895,33 +137,26 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
|
|
|
895
137
|
enableFiltering = false,
|
|
896
138
|
showFilterRow = false,
|
|
897
139
|
dataLength,
|
|
898
|
-
virtualHeight = 600,
|
|
140
|
+
virtualHeight: _virtualHeight = 600,
|
|
899
141
|
forceVirtualization = false,
|
|
900
142
|
hierarchical,
|
|
901
143
|
actions = [],
|
|
902
144
|
rbac,
|
|
903
|
-
permissions
|
|
145
|
+
permissions,
|
|
904
146
|
}: UnifiedTableBodyProps<TData>) {
|
|
905
|
-
const logger =
|
|
906
|
-
|
|
907
|
-
const headerRef = useRef<HTMLTableSectionElement>(null);
|
|
147
|
+
const logger = createLogger('UnifiedTableBody');
|
|
148
|
+
|
|
908
149
|
const bodyRef = useRef<HTMLTableSectionElement>(null);
|
|
909
150
|
const parentRef = useRef<HTMLDivElement>(null);
|
|
910
151
|
|
|
911
|
-
// Determine if virtualization should be used
|
|
912
152
|
const shouldVirtualize = forceVirtualization || dataLength > VIRTUALIZATION_THRESHOLD;
|
|
913
|
-
|
|
914
|
-
// Get table data
|
|
153
|
+
|
|
915
154
|
const rows = table.getRowModel().rows;
|
|
916
155
|
const headerGroups = table.getHeaderGroups();
|
|
917
156
|
|
|
918
|
-
// CRITICAL FIX: Virtual scrolling requires a scroll container (parentRef).
|
|
919
|
-
// If virtualization is enabled but no scroll container exists, fall back to standard rendering.
|
|
920
|
-
// This fixes the bug where rows exist but nothing renders when virtualization is enabled.
|
|
921
157
|
const hasScrollContainer = !!parentRef.current;
|
|
922
158
|
const effectiveShouldVirtualize = shouldVirtualize && hasScrollContainer;
|
|
923
|
-
|
|
924
|
-
// Virtual scrolling setup - only create virtualizer when we have a scroll container
|
|
159
|
+
|
|
925
160
|
const virtualizer = useVirtualizer({
|
|
926
161
|
count: effectiveShouldVirtualize ? rows.length : 0,
|
|
927
162
|
getScrollElement: () => parentRef.current || null,
|
|
@@ -930,9 +165,7 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
|
|
|
930
165
|
});
|
|
931
166
|
|
|
932
167
|
const virtualRows = effectiveShouldVirtualize ? virtualizer.getVirtualItems() : [];
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
// Warning if virtualization is expected but no container exists
|
|
168
|
+
|
|
936
169
|
useEffect(() => {
|
|
937
170
|
if (shouldVirtualize && !hasScrollContainer) {
|
|
938
171
|
logger.warn('Virtualization enabled but no scroll container found. Falling back to standard rendering.', {
|
|
@@ -942,17 +175,11 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
|
|
|
942
175
|
}
|
|
943
176
|
}, [shouldVirtualize, hasScrollContainer, rows.length, dataLength, logger]);
|
|
944
177
|
|
|
945
|
-
|
|
946
|
-
// Render table content
|
|
947
178
|
const renderTableContent = () => {
|
|
948
179
|
if (rows.length === 0) {
|
|
949
180
|
return (
|
|
950
181
|
<tr>
|
|
951
|
-
<td
|
|
952
|
-
colSpan={table.getVisibleFlatColumns().length}
|
|
953
|
-
className="px-3 py-2"
|
|
954
|
-
role="status"
|
|
955
|
-
>
|
|
182
|
+
<td colSpan={table.getVisibleFlatColumns().length} className="px-3 py-2" role="status">
|
|
956
183
|
<EmptyState
|
|
957
184
|
title={emptyState?.title}
|
|
958
185
|
description={emptyState?.description}
|
|
@@ -967,14 +194,13 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
|
|
|
967
194
|
}
|
|
968
195
|
|
|
969
196
|
if (effectiveShouldVirtualize && virtualRows.length > 0) {
|
|
970
|
-
// Virtualized rendering
|
|
971
197
|
return virtualRows.map((virtualRow) => {
|
|
972
198
|
const row = rows[virtualRow.index];
|
|
973
199
|
if (!row) return null;
|
|
974
|
-
|
|
200
|
+
|
|
975
201
|
const rowId = getRowIdSafe(row.original, row.index, getRowId);
|
|
976
202
|
const isEditing = editingRowId === rowId;
|
|
977
|
-
|
|
203
|
+
|
|
978
204
|
return (
|
|
979
205
|
<MemoizedRow
|
|
980
206
|
key={row.id}
|
|
@@ -1002,32 +228,31 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
|
|
|
1002
228
|
/>
|
|
1003
229
|
);
|
|
1004
230
|
});
|
|
1005
|
-
} else {
|
|
1006
|
-
// Standard rendering
|
|
1007
|
-
return rows.map((row) => {
|
|
1008
|
-
const rowId = getRowIdSafe(row.original, row.index, getRowId);
|
|
1009
|
-
const isEditing = editingRowId === rowId;
|
|
1010
|
-
|
|
1011
|
-
return (
|
|
1012
|
-
<MemoizedRow
|
|
1013
|
-
key={row.id}
|
|
1014
|
-
row={row}
|
|
1015
|
-
isEditing={isEditing}
|
|
1016
|
-
editingData={editingData}
|
|
1017
|
-
onEditingDataChange={onEditingDataChange}
|
|
1018
|
-
onSaveEditing={onSaveEditing}
|
|
1019
|
-
onCancelEditing={onCancelEditing}
|
|
1020
|
-
getRowId={getRowId}
|
|
1021
|
-
grouping={grouping}
|
|
1022
|
-
editingRowId={editingRowId}
|
|
1023
|
-
hierarchical={hierarchical}
|
|
1024
|
-
actions={actions}
|
|
1025
|
-
rbac={rbac}
|
|
1026
|
-
permissions={permissions}
|
|
1027
|
-
/>
|
|
1028
|
-
);
|
|
1029
|
-
});
|
|
1030
231
|
}
|
|
232
|
+
|
|
233
|
+
return rows.map((row) => {
|
|
234
|
+
const rowId = getRowIdSafe(row.original, row.index, getRowId);
|
|
235
|
+
const isEditing = editingRowId === rowId;
|
|
236
|
+
|
|
237
|
+
return (
|
|
238
|
+
<MemoizedRow
|
|
239
|
+
key={row.id}
|
|
240
|
+
row={row}
|
|
241
|
+
isEditing={isEditing}
|
|
242
|
+
editingData={editingData}
|
|
243
|
+
onEditingDataChange={onEditingDataChange}
|
|
244
|
+
onSaveEditing={onSaveEditing}
|
|
245
|
+
onCancelEditing={onCancelEditing}
|
|
246
|
+
getRowId={getRowId}
|
|
247
|
+
grouping={grouping}
|
|
248
|
+
editingRowId={editingRowId}
|
|
249
|
+
hierarchical={hierarchical}
|
|
250
|
+
actions={actions}
|
|
251
|
+
rbac={rbac}
|
|
252
|
+
permissions={permissions}
|
|
253
|
+
/>
|
|
254
|
+
);
|
|
255
|
+
});
|
|
1031
256
|
};
|
|
1032
257
|
|
|
1033
258
|
return (
|
|
@@ -1037,60 +262,50 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
|
|
|
1037
262
|
{isCreating && (
|
|
1038
263
|
<tr>
|
|
1039
264
|
{headerGroups[0]?.headers
|
|
1040
|
-
?.filter(header => {
|
|
1041
|
-
|
|
1042
|
-
return typeof header.column.getIsVisible === 'function'
|
|
1043
|
-
? header.column.getIsVisible()
|
|
1044
|
-
: true;
|
|
265
|
+
?.filter((header) => {
|
|
266
|
+
return typeof header.column.getIsVisible === 'function' ? header.column.getIsVisible() : true;
|
|
1045
267
|
})
|
|
1046
|
-
?.filter(header => header.column.id !== 'actions')
|
|
268
|
+
?.filter((header) => header.column.id !== 'actions')
|
|
1047
269
|
?.map((header) => {
|
|
1048
|
-
// Handle select column separately - render empty checkbox cell for alignment
|
|
1049
270
|
if (header.column.id === 'select') {
|
|
1050
271
|
return (
|
|
1051
|
-
<td
|
|
1052
|
-
key={header.column.id}
|
|
272
|
+
<td
|
|
273
|
+
key={header.column.id}
|
|
1053
274
|
className={getTableCellClasses({
|
|
1054
275
|
isCompact: true,
|
|
1055
|
-
className:
|
|
276
|
+
className: 'px-3 py-2',
|
|
1056
277
|
})}
|
|
1057
278
|
>
|
|
1058
279
|
{/* Empty cell for selection checkbox to maintain alignment */}
|
|
1059
280
|
</td>
|
|
1060
281
|
);
|
|
1061
282
|
}
|
|
1062
|
-
|
|
1063
|
-
// Render edit fields for data columns
|
|
1064
|
-
// Determine the correct key to use for creationData
|
|
1065
|
-
// Priority: editAccessorKey > accessorKey > column.id
|
|
283
|
+
|
|
1066
284
|
const columnDef = header.column.columnDef as EditableColumnDef<TData>;
|
|
1067
285
|
const dataKey = columnDef.editAccessorKey || columnDef.accessorKey || header.column.id;
|
|
1068
|
-
|
|
1069
|
-
// Always render a cell to maintain alignment - renderEditField always returns something
|
|
286
|
+
|
|
1070
287
|
const editField = renderEditField(
|
|
1071
|
-
header.column,
|
|
1072
|
-
creationData[dataKey] ?? creationData[header.column.id] ?? '',
|
|
288
|
+
header.column,
|
|
289
|
+
creationData[dataKey] ?? creationData[header.column.id] ?? '',
|
|
1073
290
|
(value) => {
|
|
1074
291
|
if (typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date)) {
|
|
1075
292
|
onCreationDataChange({ ...creationData, ...(value as Record<string, CellValue>) });
|
|
1076
293
|
} else {
|
|
1077
|
-
// Use the determined dataKey for consistent data access
|
|
1078
294
|
onCreationDataChange({ ...creationData, [dataKey]: value as CellValue });
|
|
1079
295
|
}
|
|
1080
|
-
},
|
|
296
|
+
},
|
|
1081
297
|
creationData
|
|
1082
298
|
);
|
|
1083
|
-
|
|
299
|
+
|
|
1084
300
|
return (
|
|
1085
|
-
<td
|
|
1086
|
-
key={header.column.id}
|
|
301
|
+
<td
|
|
302
|
+
key={header.column.id}
|
|
1087
303
|
className={getTableCellClasses({
|
|
1088
304
|
isCompact: true,
|
|
1089
|
-
className:
|
|
305
|
+
className: 'px-3 py-2',
|
|
1090
306
|
})}
|
|
1091
307
|
>
|
|
1092
308
|
{editField || (
|
|
1093
|
-
// Fallback: render a text input if renderEditField somehow returns nothing
|
|
1094
309
|
<Input
|
|
1095
310
|
type="text"
|
|
1096
311
|
value={String(creationData[dataKey] ?? creationData[header.column.id] ?? '')}
|
|
@@ -1102,10 +317,10 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
|
|
|
1102
317
|
</td>
|
|
1103
318
|
);
|
|
1104
319
|
})}
|
|
1105
|
-
<td
|
|
320
|
+
<td
|
|
1106
321
|
className={getTableCellClasses({
|
|
1107
322
|
isCompact: true,
|
|
1108
|
-
className:
|
|
323
|
+
className: 'px-3 py-2 flex gap-1',
|
|
1109
324
|
})}
|
|
1110
325
|
>
|
|
1111
326
|
<button
|
|
@@ -1129,15 +344,10 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
|
|
|
1129
344
|
</td>
|
|
1130
345
|
</tr>
|
|
1131
346
|
)}
|
|
1132
|
-
|
|
347
|
+
|
|
1133
348
|
{/* Filter Row */}
|
|
1134
|
-
{showFilterRow && enableFiltering && (
|
|
1135
|
-
|
|
1136
|
-
table={table}
|
|
1137
|
-
visibleColumns={table.getHeaderGroups()[0]?.headers || []}
|
|
1138
|
-
/>
|
|
1139
|
-
)}
|
|
1140
|
-
|
|
349
|
+
{showFilterRow && enableFiltering && <FilterRow table={table} visibleColumns={table.getHeaderGroups()[0]?.headers || []} />}
|
|
350
|
+
|
|
1141
351
|
{/* Table Content */}
|
|
1142
352
|
{renderTableContent()}
|
|
1143
353
|
</tbody>
|