@jmruthers/pace-core 0.6.4 → 0.6.6
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 +104 -0
- package/README.md +5 -403
- package/core-usage-manifest.json +93 -0
- package/cursor-rules/00-pace-core-compliance.mdc +128 -26
- package/cursor-rules/01-standards-compliance.mdc +49 -8
- package/cursor-rules/02-project-structure.mdc +6 -0
- package/cursor-rules/03-solid-principles.mdc +2 -0
- package/cursor-rules/04-testing-standards.mdc +2 -0
- package/cursor-rules/05-bug-reports-and-features.mdc +2 -0
- package/cursor-rules/06-code-quality.mdc +2 -0
- package/cursor-rules/07-tech-stack-compliance.mdc +2 -0
- package/cursor-rules/08-markup-quality.mdc +52 -27
- package/cursor-rules/09-rbac-compliance.mdc +462 -0
- package/cursor-rules/10-error-handling-patterns.mdc +179 -0
- package/cursor-rules/11-performance-optimization.mdc +169 -0
- package/cursor-rules/12-ci-cd-integration.mdc +150 -0
- package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
- package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-2N_tqbfq.d.ts} +1 -1
- package/dist/DataTable-LRJL4IRV.js +15 -0
- package/dist/{PublicPageProvider-DEMpysFR.d.ts → PublicPageProvider-BBH6Vqg7.d.ts} +72 -139
- package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
- package/dist/api-Y4MQWOFW.js +4 -0
- package/dist/audit-MYQXYZFU.js +3 -0
- package/dist/{chunk-J36DSWQK.js → chunk-2HGJFNAH.js} +8 -28
- package/dist/{chunk-OEWDTMG7.js → chunk-3O3WHILE.js} +38 -121
- package/dist/{chunk-M43Y4SSO.js → chunk-3QC3KRHK.js} +1 -14
- package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
- package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
- package/dist/chunk-4T7OBVTU.js +62 -0
- package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
- package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
- package/dist/{chunk-NN6WWZ5U.js → chunk-7TYHROIV.js} +579 -563
- package/dist/{chunk-M7MPQISP.js → chunk-A55DK444.js} +9 -16
- package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
- package/dist/{chunk-L4OXEN46.js → chunk-BVP2BCJF.js} +2 -16
- package/dist/chunk-C7NSAPTL.js +1 -0
- package/dist/{chunk-YKRAFF5K.js → chunk-FENMYN2U.js} +73 -149
- package/dist/{chunk-AVMLPIM7.js → chunk-FTCRZOG2.js} +284 -432
- package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
- package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
- package/dist/{chunk-I6DAQMWX.js → chunk-LAZMKTTF.js} +930 -891
- package/dist/{chunk-5EC5MEWX.js → chunk-MAGBIDNS.js} +77 -222
- package/dist/chunk-MBADTM7L.js +64 -0
- package/dist/chunk-OHIK3MIO.js +994 -0
- package/dist/{chunk-6SOIHG6Z.js → chunk-S7DKJPLT.js} +115 -44
- package/dist/{chunk-FMUCXFII.js → chunk-SD6WQY43.js} +1 -5
- package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
- package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
- package/dist/{chunk-FFQEQTNW.js → chunk-UIYSCEV7.js} +134 -45
- package/dist/{chunk-3LPHPB62.js → chunk-ZFYPMX46.js} +271 -87
- package/dist/{chunk-7JPAB3T5.js → chunk-ZS5VO5JB.js} +1989 -1283
- package/dist/components.d.ts +6 -6
- package/dist/components.js +57 -267
- package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
- package/dist/eslint-rules/index.cjs +22 -0
- package/dist/eslint-rules/rules/compliance.cjs +348 -0
- package/dist/eslint-rules/rules/components.cjs +113 -0
- package/dist/eslint-rules/rules/imports.cjs +102 -0
- package/dist/eslint-rules/rules/rbac.cjs +790 -0
- package/dist/eslint-rules/utils/helpers.cjs +42 -0
- package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
- package/dist/hooks.d.ts +5 -5
- package/dist/hooks.js +62 -270
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.d.ts +36 -26
- package/dist/index.js +87 -690
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +8 -35
- package/dist/rbac/eslint-rules.d.ts +46 -44
- package/dist/rbac/eslint-rules.js +7 -4
- package/dist/rbac/index.d.ts +124 -594
- package/dist/rbac/index.js +14 -207
- package/dist/styles/index.js +2 -12
- package/dist/theming/runtime.js +3 -19
- package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
- package/dist/{types-CkbwOr4Y.d.ts → types-B-K_5VnO.d.ts} +4 -0
- package/dist/types-t9H8qKRw.d.ts +55 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +7 -94
- package/dist/{usePublicRouteParams-i3qtoBgg.d.ts → usePublicRouteParams-COZ28Mvq.d.ts} +9 -9
- package/dist/utils.d.ts +24 -117
- package/dist/utils.js +54 -392
- package/docs/README.md +16 -6
- package/docs/api/README.md +4 -402
- package/docs/api/modules.md +454 -930
- package/docs/api-reference/components.md +3 -1
- package/docs/api-reference/deprecated.md +31 -6
- package/docs/api-reference/rpc-functions.md +78 -3
- package/docs/best-practices/accessibility.md +6 -3
- package/docs/getting-started/cursor-rules.md +3 -23
- package/docs/getting-started/dependencies.md +650 -0
- package/docs/getting-started/installation-guide.md +20 -7
- package/docs/getting-started/quick-start.md +23 -12
- package/docs/implementation-guides/permission-enforcement.md +4 -0
- package/docs/rbac/MIGRATION_GUIDE.md +819 -0
- package/docs/rbac/RBAC_CONTRACT.md +724 -0
- package/docs/rbac/README.md +12 -3
- package/docs/rbac/edge-functions-guide.md +376 -0
- package/docs/rbac/secure-client-protection.md +0 -34
- package/docs/standards/00-pace-core-compliance.md +967 -0
- package/docs/standards/01-standards-compliance.md +188 -0
- package/docs/standards/02-project-structure.md +985 -0
- package/docs/standards/03-solid-principles.md +39 -0
- package/docs/standards/04-testing-standards.md +36 -0
- package/docs/standards/05-bug-reports-and-features.md +27 -0
- package/docs/standards/{04-code-style-standard.md → 06-code-quality.md} +2 -0
- package/docs/standards/07-tech-stack-compliance.md +30 -0
- package/docs/standards/08-markup-quality.md +345 -0
- package/docs/standards/{07-rbac-and-rls-standard.md → 09-rbac-compliance.md} +149 -54
- package/docs/standards/10-error-handling-patterns.md +401 -0
- package/docs/standards/11-performance-optimization.md +348 -0
- package/docs/standards/12-ci-cd-integration.md +370 -0
- package/docs/standards/ALIGNMENT_REVIEW_SUMMARY.md +192 -0
- package/docs/standards/README.md +62 -33
- package/docs/troubleshooting/organisation-context-setup.md +42 -19
- package/eslint-config-pace-core.cjs +20 -4
- package/package.json +31 -21
- package/scripts/audit/audit-compliance.cjs +1295 -0
- package/scripts/audit/audit-components.cjs +260 -0
- package/scripts/audit/audit-dependencies.cjs +395 -0
- package/scripts/audit/audit-rbac.cjs +954 -0
- package/scripts/audit/audit-standards.cjs +1268 -0
- package/scripts/audit/index.cjs +1898 -194
- package/scripts/install-cursor-rules.cjs +259 -8
- package/scripts/validate-master.js +1 -1
- package/src/__tests__/fixtures/supabase.ts +1 -1
- package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +1 -1
- package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/test-utils.test.tsx +3 -3
- package/src/__tests__/helpers/component-test-utils.tsx +1 -1
- package/src/__tests__/helpers/supabaseMock.ts +2 -2
- package/src/__tests__/public-recipe-view.test.ts +38 -9
- package/src/components/Button/Button.tsx +5 -1
- package/src/components/ContextSelector/ContextSelector.tsx +42 -39
- package/src/components/DataTable/__tests__/keyboard.test.tsx +15 -2
- package/src/components/DataTable/components/DataTableBody.tsx +55 -31
- package/src/components/DataTable/components/DataTableCore.tsx +186 -13
- package/src/components/DataTable/components/DataTableLayout.tsx +30 -5
- package/src/components/DataTable/components/EditFields.tsx +23 -3
- package/src/components/DataTable/components/EditableRow.tsx +7 -2
- package/src/components/DataTable/components/ImportModal.tsx +4 -6
- package/src/components/DataTable/components/RowComponent.tsx +12 -0
- package/src/components/DataTable/components/ViewRowModal.tsx +4 -4
- package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +455 -96
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +122 -58
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
- package/src/components/DataTable/core/DataTableContext.tsx +1 -1
- package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
- package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
- package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
- package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
- package/src/components/DataTable/types.ts +5 -0
- package/src/components/DateTimeField/DateTimeField.tsx +20 -20
- package/src/components/DateTimeField/README.md +5 -2
- package/src/components/Dialog/Dialog.test.tsx +361 -318
- package/src/components/Dialog/Dialog.tsx +1154 -323
- package/src/components/Dialog/index.ts +3 -3
- package/src/components/FileDisplay/FileDisplay.test.tsx +45 -2
- package/src/components/FileDisplay/FileDisplay.tsx +28 -22
- package/src/components/Form/Form.test.tsx +9 -10
- package/src/components/Form/Form.tsx +369 -9
- package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +28 -28
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +40 -54
- package/src/components/LoginForm/LoginForm.tsx +2 -2
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
- package/src/components/NavigationMenu/NavigationMenu.tsx +2 -2
- package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +30 -41
- package/src/components/PaceAppLayout/README.md +10 -9
- package/src/components/PaceAppLayout/test-setup.tsx +40 -31
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
- package/src/components/PasswordChange/PasswordChangeForm.test.tsx +61 -0
- package/src/components/PasswordChange/PasswordChangeForm.tsx +20 -13
- package/src/components/PublicLayout/PublicLayout.test.tsx +7 -3
- package/src/components/PublicLayout/PublicPageLayout.tsx +5 -8
- package/src/components/Select/Select.tsx +23 -21
- package/src/components/Select/types.ts +1 -1
- package/src/components/UserMenu/UserMenu.test.tsx +38 -6
- package/src/components/UserMenu/UserMenu.tsx +39 -34
- package/src/components/index.ts +3 -4
- package/src/eslint-rules/index.cjs +22 -0
- package/src/eslint-rules/rules/compliance.cjs +348 -0
- package/src/eslint-rules/rules/components.cjs +113 -0
- package/src/eslint-rules/rules/imports.cjs +102 -0
- package/src/eslint-rules/rules/rbac.cjs +790 -0
- package/src/eslint-rules/utils/helpers.cjs +42 -0
- package/src/eslint-rules/utils/manifest-loader.cjs +75 -0
- package/src/hooks/__tests__/hooks.integration.test.tsx +6 -8
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
- package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
- package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
- package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
- package/src/hooks/public/usePublicEvent.ts +62 -190
- package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
- package/src/hooks/public/usePublicEventLogo.ts +19 -9
- package/src/hooks/useAppConfig.ts +26 -24
- package/src/hooks/useEventTheme.test.ts +211 -233
- package/src/hooks/useEventTheme.ts +19 -28
- package/src/hooks/useEvents.ts +11 -7
- package/src/hooks/useKeyboardShortcuts.ts +1 -1
- package/src/hooks/useOrganisationPermissions.ts +9 -11
- package/src/hooks/useOrganisations.ts +13 -7
- package/src/hooks/useQueryCache.ts +0 -1
- package/src/hooks/useSessionDraft.ts +380 -0
- package/src/hooks/useSessionRestoration.ts +3 -1
- package/src/icons/index.ts +27 -0
- package/src/index.ts +16 -1
- package/src/providers/OrganisationProvider.tsx +23 -14
- package/src/providers/services/EventServiceProvider.tsx +1 -24
- package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -0
- package/src/rbac/README.md +20 -20
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
- package/src/rbac/adapters.tsx +7 -295
- package/src/rbac/api.test.ts +44 -56
- package/src/rbac/api.ts +10 -17
- package/src/rbac/cache-invalidation.ts +0 -1
- package/src/rbac/compliance/index.ts +10 -0
- package/src/rbac/compliance/pattern-detector.ts +553 -0
- package/src/rbac/compliance/runtime-compliance.ts +22 -0
- package/src/rbac/components/AccessDenied.tsx +150 -0
- package/src/rbac/components/NavigationGuard.tsx +12 -20
- package/src/rbac/components/PagePermissionGuard.tsx +4 -24
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
- package/src/rbac/components/index.ts +3 -41
- package/src/rbac/eslint-rules.js +1 -1
- package/src/rbac/hooks/index.ts +0 -3
- package/src/rbac/hooks/permissions/index.ts +0 -3
- package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
- package/src/rbac/hooks/usePermissions.ts +0 -3
- package/src/rbac/hooks/useRBAC.test.ts +21 -3
- package/src/rbac/hooks/useRBAC.ts +4 -3
- package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
- package/src/rbac/hooks/useResolvedScope.ts +58 -140
- package/src/rbac/hooks/useResourcePermissions.test.ts +241 -60
- package/src/rbac/hooks/useResourcePermissions.ts +182 -63
- package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
- package/src/rbac/hooks/useRoleManagement.ts +147 -19
- package/src/rbac/hooks/useSecureSupabase.ts +4 -8
- package/src/rbac/index.ts +7 -9
- package/src/rbac/permissions.ts +17 -17
- package/src/rbac/utils/contextValidator.ts +45 -7
- package/src/services/AuthService.ts +132 -23
- package/src/services/EventService.ts +4 -97
- package/src/services/InactivityService.ts +155 -58
- package/src/services/OrganisationService.ts +7 -44
- package/src/services/__tests__/OrganisationService.test.ts +26 -8
- package/src/services/base/BaseService.ts +0 -3
- package/src/styles/core.css +4 -0
- package/src/types/database.generated.ts +4733 -3809
- package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
- package/src/utils/context/organisationContext.test.ts +13 -28
- package/src/utils/context/organisationContext.ts +21 -52
- package/src/utils/dynamic/dynamicUtils.ts +1 -1
- package/src/utils/file-reference/index.ts +39 -15
- package/src/utils/formatting/formatDateTime.test.ts +3 -2
- package/src/utils/formatting/formatTime.test.ts +3 -2
- package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
- package/src/utils/index.ts +4 -1
- package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
- package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
- package/src/utils/persistence/keyDerivation.ts +304 -0
- package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
- package/src/utils/security/secureStorage.ts +5 -5
- package/src/utils/storage/helpers.ts +3 -3
- package/src/utils/supabase/createBaseClient.ts +147 -0
- package/src/utils/timezone/timezone.test.ts +1 -2
- package/src/utils/timezone/timezone.ts +1 -1
- package/src/utils/validation/csrf.ts +4 -4
- package/cursor-rules/CHANGELOG.md +0 -119
- package/cursor-rules/README.md +0 -192
- package/dist/DataTable-E7YQZD7D.js +0 -175
- package/dist/DataTable-E7YQZD7D.js.map +0 -1
- package/dist/UnifiedAuthProvider-QPXO24B4.js +0 -18
- package/dist/UnifiedAuthProvider-QPXO24B4.js.map +0 -1
- package/dist/api-6LVZTHDS.js +0 -52
- package/dist/api-6LVZTHDS.js.map +0 -1
- package/dist/audit-V53FV5AG.js +0 -17
- package/dist/audit-V53FV5AG.js.map +0 -1
- package/dist/chunk-36LVWXB2.js +0 -227
- package/dist/chunk-36LVWXB2.js.map +0 -1
- package/dist/chunk-3LPHPB62.js.map +0 -1
- package/dist/chunk-5DRSZLL2.js.map +0 -1
- package/dist/chunk-5EC5MEWX.js.map +0 -1
- package/dist/chunk-63FOKYGO.js.map +0 -1
- package/dist/chunk-6SOIHG6Z.js.map +0 -1
- package/dist/chunk-7JPAB3T5.js.map +0 -1
- package/dist/chunk-ATKZM7RX.js +0 -2053
- package/dist/chunk-ATKZM7RX.js.map +0 -1
- package/dist/chunk-AVMLPIM7.js.map +0 -1
- package/dist/chunk-DGUM43GV.js.map +0 -1
- package/dist/chunk-E66EQZE6.js.map +0 -1
- package/dist/chunk-FFQEQTNW.js.map +0 -1
- package/dist/chunk-FMUCXFII.js.map +0 -1
- package/dist/chunk-G37KK66H.js.map +0 -1
- package/dist/chunk-I6DAQMWX.js.map +0 -1
- package/dist/chunk-J36DSWQK.js.map +0 -1
- package/dist/chunk-KQCRWDSA.js +0 -1
- package/dist/chunk-KQCRWDSA.js.map +0 -1
- package/dist/chunk-L4OXEN46.js.map +0 -1
- package/dist/chunk-LMC26NLJ.js +0 -84
- package/dist/chunk-LMC26NLJ.js.map +0 -1
- package/dist/chunk-M43Y4SSO.js.map +0 -1
- package/dist/chunk-M7MPQISP.js.map +0 -1
- package/dist/chunk-NN6WWZ5U.js.map +0 -1
- package/dist/chunk-OEWDTMG7.js.map +0 -1
- package/dist/chunk-PWLANIRT.js.map +0 -1
- package/dist/chunk-QXHPKYJV.js.map +0 -1
- package/dist/chunk-VBXEHIUJ.js.map +0 -1
- package/dist/chunk-YKRAFF5K.js.map +0 -1
- package/dist/chunk-ZSAAAMVR.js.map +0 -1
- package/dist/components.js.map +0 -1
- package/dist/contextValidator-OOPCLPZW.js +0 -9
- package/dist/contextValidator-OOPCLPZW.js.map +0 -1
- package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
- package/dist/hooks.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/providers.js.map +0 -1
- package/dist/rbac/eslint-rules.js.map +0 -1
- package/dist/rbac/index.js.map +0 -1
- package/dist/styles/index.js.map +0 -1
- package/dist/theming/runtime.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/docs/standards/01-architecture-standard.md +0 -44
- package/docs/standards/02-api-and-rpc-standard.md +0 -39
- package/docs/standards/03-component-standard.md +0 -32
- package/docs/standards/05-security-standard.md +0 -44
- package/docs/standards/06-testing-and-docs-standard.md +0 -29
- package/docs/standards/pace-core-compliance.md +0 -432
- package/scripts/audit/core/checks/accessibility.cjs +0 -197
- package/scripts/audit/core/checks/api-usage.cjs +0 -191
- package/scripts/audit/core/checks/bundle.cjs +0 -142
- package/scripts/audit/core/checks/compliance.cjs +0 -2706
- package/scripts/audit/core/checks/config.cjs +0 -54
- package/scripts/audit/core/checks/coverage.cjs +0 -84
- package/scripts/audit/core/checks/dependencies.cjs +0 -994
- package/scripts/audit/core/checks/documentation.cjs +0 -268
- package/scripts/audit/core/checks/environment.cjs +0 -116
- package/scripts/audit/core/checks/error-handling.cjs +0 -340
- package/scripts/audit/core/checks/forms.cjs +0 -172
- package/scripts/audit/core/checks/heuristics.cjs +0 -68
- package/scripts/audit/core/checks/hooks.cjs +0 -334
- package/scripts/audit/core/checks/imports.cjs +0 -244
- package/scripts/audit/core/checks/performance.cjs +0 -325
- package/scripts/audit/core/checks/routes.cjs +0 -117
- package/scripts/audit/core/checks/state.cjs +0 -130
- package/scripts/audit/core/checks/structure.cjs +0 -65
- package/scripts/audit/core/checks/style.cjs +0 -584
- package/scripts/audit/core/checks/testing.cjs +0 -122
- package/scripts/audit/core/checks/typescript.cjs +0 -61
- package/scripts/audit/core/scanner.cjs +0 -199
- package/scripts/audit/core/utils.cjs +0 -137
- package/scripts/audit/reporters/console.cjs +0 -151
- package/scripts/audit/reporters/json.cjs +0 -54
- package/scripts/audit/reporters/markdown.cjs +0 -124
- package/scripts/audit-consuming-app.cjs +0 -86
- package/src/eslint-rules/pace-core-compliance.cjs +0 -510
- package/src/eslint-rules/pace-core-compliance.js +0 -638
- package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
- package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
- package/src/rbac/components/NavigationProvider.test.tsx +0 -481
- package/src/rbac/components/NavigationProvider.tsx +0 -345
- package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
- package/src/rbac/components/PagePermissionProvider.tsx +0 -279
- package/src/rbac/components/PermissionEnforcer.tsx +0 -312
- package/src/rbac/components/RoleBasedRouter.tsx +0 -440
- package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
- package/src/rbac/components/SecureDataProvider.tsx +0 -339
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
- package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
- package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
|
@@ -301,15 +301,23 @@ export function DataTableBody<TData extends DataRecord>({
|
|
|
301
301
|
<tr>
|
|
302
302
|
{table.getVisibleFlatColumns().map((column) => (
|
|
303
303
|
<td key={column.id}>
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
304
|
+
{(() => {
|
|
305
|
+
// CRITICAL FIX: Use the correct accessor key (editAccessorKey or column.id) to get the value
|
|
306
|
+
// This ensures that when editAccessorKey is different from column.id, we use the correct key
|
|
307
|
+
const columnDef = column.columnDef;
|
|
308
|
+
const accessorKey = columnDef.editAccessorKey || column.id;
|
|
309
|
+
const currentValue = creationData[accessorKey] ?? creationData[column.id] ?? '';
|
|
310
|
+
|
|
311
|
+
return renderEditField(column, currentValue, (value) => {
|
|
312
|
+
if (typeof value === 'object' && value !== null) {
|
|
313
|
+
// Handle editAccessorKey case
|
|
314
|
+
onCreationDataChange({ ...creationData, ...value });
|
|
315
|
+
} else {
|
|
316
|
+
// Handle simple value case
|
|
317
|
+
onCreationDataChange({ ...creationData, [column.id]: value });
|
|
318
|
+
}
|
|
319
|
+
}, creationData);
|
|
320
|
+
})()}
|
|
313
321
|
</td>
|
|
314
322
|
))}
|
|
315
323
|
<td className="flex gap-1">
|
|
@@ -397,19 +405,27 @@ export function DataTableBody<TData extends DataRecord>({
|
|
|
397
405
|
<tr key={subRow.id} className="border-l-2 border-l-blue-200">
|
|
398
406
|
{subRow.getVisibleCells().map((cell) => (
|
|
399
407
|
<td key={cell.id} className="pl-8">
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
408
|
+
{isSubRowEditing && cell.column.id !== 'actions' ? (
|
|
409
|
+
(() => {
|
|
410
|
+
// CRITICAL FIX: Use the correct accessor key (editAccessorKey or column.id) to get the value
|
|
411
|
+
// This ensures that when editAccessorKey is different from column.id, we use the correct key
|
|
412
|
+
const columnDef = cell.column.columnDef;
|
|
413
|
+
const accessorKey = columnDef.editAccessorKey || cell.column.id;
|
|
414
|
+
const currentValue = editingData[accessorKey] ?? editingData[cell.column.id] ?? cell.getValue();
|
|
415
|
+
|
|
416
|
+
return renderEditField(cell.column, currentValue, (value) => {
|
|
417
|
+
if (typeof value === 'object' && value !== null) {
|
|
418
|
+
// Handle editAccessorKey case
|
|
419
|
+
onEditingDataChange({ ...editingData, ...value });
|
|
420
|
+
} else {
|
|
421
|
+
// Handle simple value case
|
|
422
|
+
onEditingDataChange({ ...editingData, [cell.column.id]: value });
|
|
423
|
+
}
|
|
424
|
+
}, editingData);
|
|
425
|
+
})()
|
|
426
|
+
) : (
|
|
427
|
+
flexRender(cell.column.columnDef.cell, cell.getContext())
|
|
428
|
+
)}
|
|
413
429
|
</td>
|
|
414
430
|
))}
|
|
415
431
|
</tr>
|
|
@@ -431,15 +447,23 @@ export function DataTableBody<TData extends DataRecord>({
|
|
|
431
447
|
{row.getVisibleCells().map((cell) => (
|
|
432
448
|
<td key={cell.id}>
|
|
433
449
|
{isEditing && cell.column.id !== 'actions' ? (
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
450
|
+
(() => {
|
|
451
|
+
// CRITICAL FIX: Use the correct accessor key (editAccessorKey or column.id) to get the value
|
|
452
|
+
// This ensures that when editAccessorKey is different from column.id, we use the correct key
|
|
453
|
+
const columnDef = cell.column.columnDef;
|
|
454
|
+
const accessorKey = columnDef.editAccessorKey || cell.column.id;
|
|
455
|
+
const currentValue = editingData[accessorKey] ?? editingData[cell.column.id] ?? cell.getValue();
|
|
456
|
+
|
|
457
|
+
return renderEditField(cell.column, currentValue, (value) => {
|
|
458
|
+
if (typeof value === 'object' && value !== null) {
|
|
459
|
+
// Handle editAccessorKey case
|
|
460
|
+
onEditingDataChange({ ...editingData, ...value });
|
|
461
|
+
} else {
|
|
462
|
+
// Handle simple value case
|
|
463
|
+
onEditingDataChange({ ...editingData, [cell.column.id]: value });
|
|
464
|
+
}
|
|
465
|
+
}, editingData);
|
|
466
|
+
})()
|
|
443
467
|
) : (
|
|
444
468
|
flexRender(cell.column.columnDef.cell, cell.getContext())
|
|
445
469
|
)}
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* This is the main component that consumers will use.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import React, { useMemo, useCallback, useEffect, useRef } from 'react';
|
|
11
|
+
import React, { useMemo, useCallback, useEffect, useLayoutEffect, useRef, useState, startTransition, useDeferredValue } from 'react';
|
|
12
12
|
import { useReactTable } from '@tanstack/react-table';
|
|
13
|
+
import { useLocation } from 'react-router-dom';
|
|
13
14
|
import type {
|
|
14
15
|
SortingState,
|
|
15
16
|
} from '@tanstack/react-table';
|
|
@@ -182,6 +183,81 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
182
183
|
|
|
183
184
|
const logger = createLogger('DataTableCore');
|
|
184
185
|
|
|
186
|
+
// Track deletion state to batch updates and prevent flashing
|
|
187
|
+
const [isDeleting, setIsDeleting] = useState(false);
|
|
188
|
+
const deletionTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
189
|
+
const pendingDeletionsRef = useRef<Set<string>>(new Set());
|
|
190
|
+
const dataSnapshotRef = useRef<TData[]>(data);
|
|
191
|
+
const dataChangeCountRef = useRef(0);
|
|
192
|
+
const previousDataLengthRef = useRef(data.length);
|
|
193
|
+
|
|
194
|
+
// CRITICAL: Use useLayoutEffect to capture previous length synchronously
|
|
195
|
+
// This runs before paint, so we can detect changes before React commits
|
|
196
|
+
useLayoutEffect(() => {
|
|
197
|
+
const currentLength = data.length;
|
|
198
|
+
const previousLength = previousDataLengthRef.current;
|
|
199
|
+
|
|
200
|
+
// Check if length decreased - this is the key detection
|
|
201
|
+
const lengthDecreased = currentLength < previousLength;
|
|
202
|
+
|
|
203
|
+
if (lengthDecreased && !isDeleting) {
|
|
204
|
+
// Data length decreased - start deletion batching
|
|
205
|
+
const snapshotLength = dataSnapshotRef.current.length;
|
|
206
|
+
|
|
207
|
+
// Data decrease detected (logging removed for performance)
|
|
208
|
+
|
|
209
|
+
// If snapshot is larger, it's valid (has pre-deletion state)
|
|
210
|
+
// Otherwise, update it to preserve current state
|
|
211
|
+
if (snapshotLength <= currentLength) {
|
|
212
|
+
dataSnapshotRef.current = [...data];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
setIsDeleting(true);
|
|
216
|
+
|
|
217
|
+
// Set timeout to reset after batching
|
|
218
|
+
if (deletionTimeoutRef.current) {
|
|
219
|
+
clearTimeout(deletionTimeoutRef.current);
|
|
220
|
+
}
|
|
221
|
+
deletionTimeoutRef.current = setTimeout(() => {
|
|
222
|
+
setIsDeleting(false);
|
|
223
|
+
dataSnapshotRef.current = data;
|
|
224
|
+
previousDataLengthRef.current = data.length;
|
|
225
|
+
// Batching complete (logging removed for performance)
|
|
226
|
+
}, 150);
|
|
227
|
+
|
|
228
|
+
// Update previous length AFTER setting up batching
|
|
229
|
+
previousDataLengthRef.current = currentLength;
|
|
230
|
+
} else if (!lengthDecreased && !isDeleting) {
|
|
231
|
+
// No deletion - update snapshot and previous length normally
|
|
232
|
+
dataSnapshotRef.current = data;
|
|
233
|
+
previousDataLengthRef.current = currentLength;
|
|
234
|
+
} else if (isDeleting) {
|
|
235
|
+
// Already deleting - just update previous length for next comparison
|
|
236
|
+
previousDataLengthRef.current = currentLength;
|
|
237
|
+
}
|
|
238
|
+
}, [data, isDeleting]);
|
|
239
|
+
|
|
240
|
+
// Keep data snapshot stable during deletions to prevent flashing
|
|
241
|
+
// Logging effect - runs after layout to show what happened
|
|
242
|
+
useEffect(() => {
|
|
243
|
+
dataChangeCountRef.current += 1;
|
|
244
|
+
|
|
245
|
+
const previousLength = previousDataLengthRef.current;
|
|
246
|
+
const currentLength = data.length;
|
|
247
|
+
const snapshotLength = dataSnapshotRef.current.length;
|
|
248
|
+
const lengthDecreased = currentLength < previousLength;
|
|
249
|
+
|
|
250
|
+
// Data prop changed (logging removed for performance)
|
|
251
|
+
}, [data, isDeleting]);
|
|
252
|
+
|
|
253
|
+
// Use snapshot data during deletions to prevent flashing
|
|
254
|
+
// CRITICAL: If we're deleting and data length decreased, use snapshot to prevent flashing
|
|
255
|
+
const dataLengthChanged = data.length !== dataSnapshotRef.current.length;
|
|
256
|
+
const isDataDecreasing = data.length < dataSnapshotRef.current.length;
|
|
257
|
+
const effectiveData = (isDeleting && isDataDecreasing) ? dataSnapshotRef.current : data;
|
|
258
|
+
|
|
259
|
+
// Effective data tracking (logging removed for performance)
|
|
260
|
+
|
|
185
261
|
// ============================================================================
|
|
186
262
|
// ALL HOOKS MUST BE CALLED IN THE SAME ORDER EVERY RENDER
|
|
187
263
|
// ============================================================================
|
|
@@ -241,15 +317,38 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
241
317
|
return {};
|
|
242
318
|
}, [secureFeatures.columnVisibility, savedColumnVisibility]);
|
|
243
319
|
|
|
320
|
+
// Get location for route-based key derivation
|
|
321
|
+
let location: { pathname: string } | null = null;
|
|
322
|
+
try {
|
|
323
|
+
// useLocation may not be available if React Router is not set up
|
|
324
|
+
const routerLocation = useLocation();
|
|
325
|
+
location = routerLocation;
|
|
326
|
+
} catch {
|
|
327
|
+
// Router not available, location will be null
|
|
328
|
+
location = null;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Extract column field names for sensitive field filtering
|
|
332
|
+
const columnFieldNames = useMemo(() => {
|
|
333
|
+
return columns
|
|
334
|
+
.map((col) => col.accessorKey || col.id)
|
|
335
|
+
.filter((key): key is string => Boolean(key));
|
|
336
|
+
}, [columns]);
|
|
337
|
+
|
|
244
338
|
// Use the centralized state management hook for ALL table state
|
|
245
339
|
// Note: 'actions' prop parameter is shadowed by destructuring, so we rename to stateActions
|
|
246
|
-
const { state, actions: stateActions } = useDataTableState<TData>({
|
|
340
|
+
const { state, actions: stateActions, clearDraft } = useDataTableState<TData>({
|
|
247
341
|
initialPageSize,
|
|
248
342
|
columnIds: effectiveColumnOrder,
|
|
249
343
|
initialRowSelection: selection || {},
|
|
250
344
|
onRowSelectionChange,
|
|
251
345
|
defaultSorting: defaultSorting || [],
|
|
252
|
-
defaultGrouping: defaultGrouping || []
|
|
346
|
+
defaultGrouping: defaultGrouping || [],
|
|
347
|
+
// Persistence props
|
|
348
|
+
rbacPageId: rbac.pageId,
|
|
349
|
+
title,
|
|
350
|
+
location,
|
|
351
|
+
columnFieldNames,
|
|
253
352
|
});
|
|
254
353
|
|
|
255
354
|
// Apply saved visibility to state if available
|
|
@@ -344,7 +443,7 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
344
443
|
hierarchicalState,
|
|
345
444
|
hierarchicalValidation,
|
|
346
445
|
} = useDataTableDataPipeline<TData>({
|
|
347
|
-
data,
|
|
446
|
+
data: effectiveData,
|
|
348
447
|
features: secureFeatures,
|
|
349
448
|
hierarchical,
|
|
350
449
|
sorting: state.sorting,
|
|
@@ -357,6 +456,34 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
357
456
|
logger.error('Hierarchical data validation failed:', hierarchicalValidation.errors);
|
|
358
457
|
}
|
|
359
458
|
}, [hierarchicalValidation, logger]);
|
|
459
|
+
|
|
460
|
+
// Final table data tracking (logging removed for performance)
|
|
461
|
+
|
|
462
|
+
// Cleanup deletion timeout on unmount
|
|
463
|
+
useEffect(() => {
|
|
464
|
+
return () => {
|
|
465
|
+
if (deletionTimeoutRef.current) {
|
|
466
|
+
clearTimeout(deletionTimeoutRef.current);
|
|
467
|
+
}
|
|
468
|
+
pendingDeletionsRef.current.clear();
|
|
469
|
+
};
|
|
470
|
+
}, []);
|
|
471
|
+
|
|
472
|
+
// Update data snapshot when data changes and we're not deleting
|
|
473
|
+
useEffect(() => {
|
|
474
|
+
if (!isDeleting && data !== dataSnapshotRef.current) {
|
|
475
|
+
dataSnapshotRef.current = data;
|
|
476
|
+
}
|
|
477
|
+
}, [data, isDeleting]);
|
|
478
|
+
|
|
479
|
+
// Cleanup deletion timeout on unmount
|
|
480
|
+
useEffect(() => {
|
|
481
|
+
return () => {
|
|
482
|
+
if (deletionTimeoutRef.current) {
|
|
483
|
+
clearTimeout(deletionTimeoutRef.current);
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
}, []);
|
|
360
487
|
|
|
361
488
|
const {
|
|
362
489
|
columnOrder: savedColumnOrder,
|
|
@@ -563,19 +690,24 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
563
690
|
// RBAC VALIDATION AND SECURE CONFIGURATION - ALWAYS call these hooks
|
|
564
691
|
// ============================================================================
|
|
565
692
|
|
|
693
|
+
// Wrap handlers - persistence is handled automatically via useDataTableState
|
|
694
|
+
// The state actions (clearCreationData, clearEditing) will automatically update persisted state
|
|
695
|
+
const wrappedOnCreateRow = onCreateRow;
|
|
696
|
+
const wrappedOnEditRow = onEditRow;
|
|
697
|
+
|
|
566
698
|
// MANDATORY: Handlers are automatically secured
|
|
567
699
|
const secureHandlers = useMemo(() => {
|
|
568
700
|
const handlers = {
|
|
569
|
-
onEditRow: permissions.canUpdate.can ?
|
|
701
|
+
onEditRow: permissions.canUpdate.can ? wrappedOnEditRow : undefined,
|
|
570
702
|
onDeleteRow: permissions.canDelete.can ? onDeleteRow : undefined,
|
|
571
|
-
onCreateRow: permissions.canCreate.can ?
|
|
703
|
+
onCreateRow: permissions.canCreate.can ? wrappedOnCreateRow : undefined,
|
|
572
704
|
onImport: permissions.canImport.can ? onImport : undefined,
|
|
573
705
|
onExport: permissions.canExport.can ? onExport : undefined,
|
|
574
706
|
onDeleteSelected: permissions.canDelete.can ? onDeleteSelected : undefined,
|
|
575
707
|
};
|
|
576
708
|
|
|
577
709
|
return handlers;
|
|
578
|
-
}, [permissions.canUpdate.can, permissions.canDelete.can, permissions.canCreate.can, permissions.canImport.can, permissions.canExport.can,
|
|
710
|
+
}, [permissions.canUpdate.can, permissions.canDelete.can, permissions.canCreate.can, permissions.canImport.can, permissions.canExport.can, wrappedOnEditRow, onDeleteRow, wrappedOnCreateRow, onImport, onExport, onDeleteSelected, secureFeatures.creation]);
|
|
579
711
|
|
|
580
712
|
// MANDATORY: Process actions with RBAC checks
|
|
581
713
|
const effectiveActions = useMemo(() => {
|
|
@@ -617,19 +749,60 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
617
749
|
});
|
|
618
750
|
return;
|
|
619
751
|
}
|
|
752
|
+
|
|
753
|
+
// Get row ID for tracking - use current data, not effectiveData
|
|
754
|
+
const rowIndex = data.findIndex(r => r === row);
|
|
755
|
+
const rowId = resolvedGetRowId(row, rowIndex >= 0 ? rowIndex : 0);
|
|
756
|
+
|
|
757
|
+
// Mark deletion as in progress and track this deletion
|
|
758
|
+
// CRITICAL: Set isDeleting and snapshot BEFORE calling the handler
|
|
759
|
+
// This prevents the parent's data update from causing a flash
|
|
760
|
+
if (!isDeleting) {
|
|
761
|
+
// Snapshot current data BEFORE deletion
|
|
762
|
+
dataSnapshotRef.current = [...data]; // Create a copy to prevent reference issues
|
|
763
|
+
setIsDeleting(true);
|
|
764
|
+
}
|
|
765
|
+
pendingDeletionsRef.current.add(rowId);
|
|
766
|
+
|
|
767
|
+
// Clear any existing timeout
|
|
768
|
+
if (deletionTimeoutRef.current) {
|
|
769
|
+
clearTimeout(deletionTimeoutRef.current);
|
|
770
|
+
}
|
|
771
|
+
|
|
620
772
|
try {
|
|
773
|
+
// Call the delete handler - this may update the parent's data prop
|
|
774
|
+
const deleteStartTime = Date.now();
|
|
621
775
|
const result = secureHandlers.onDeleteRow!(row) as any;
|
|
622
776
|
// Handle async operations
|
|
623
777
|
if (result !== undefined && result !== null && typeof result === 'object' && typeof result.then === 'function') {
|
|
624
778
|
await result;
|
|
625
779
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
780
|
+
// Remove from pending deletions
|
|
781
|
+
pendingDeletionsRef.current.delete(rowId);
|
|
782
|
+
|
|
783
|
+
// Reset deletion state after a delay to allow batching
|
|
784
|
+
// This prevents the table from refreshing after each individual deletion
|
|
785
|
+
deletionTimeoutRef.current = setTimeout(() => {
|
|
786
|
+
// Only reset if all deletions are complete
|
|
787
|
+
if (pendingDeletionsRef.current.size === 0) {
|
|
788
|
+
setIsDeleting(false);
|
|
789
|
+
// Update snapshot to latest data
|
|
790
|
+
dataSnapshotRef.current = data;
|
|
791
|
+
toast({
|
|
792
|
+
title: "Delete Successful",
|
|
793
|
+
description: "Row deleted successfully",
|
|
794
|
+
variant: "default"
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
}, 150); // Delay to batch rapid deletions
|
|
798
|
+
|
|
631
799
|
} catch (error) {
|
|
632
800
|
logger.error('Delete error:', error);
|
|
801
|
+
pendingDeletionsRef.current.delete(rowId);
|
|
802
|
+
if (pendingDeletionsRef.current.size === 0) {
|
|
803
|
+
setIsDeleting(false);
|
|
804
|
+
dataSnapshotRef.current = data;
|
|
805
|
+
}
|
|
633
806
|
toast({
|
|
634
807
|
title: "Delete Failed",
|
|
635
808
|
description: error instanceof Error ? error.message : 'Failed to delete row',
|
|
@@ -645,7 +818,7 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
|
|
|
645
818
|
}
|
|
646
819
|
|
|
647
820
|
return result;
|
|
648
|
-
}, [actions, secureFeatures, permissions, secureHandlers, resolvedGetRowId, stateActions,
|
|
821
|
+
}, [actions, secureFeatures, permissions, secureHandlers, resolvedGetRowId, stateActions, effectiveData, isDeleting, pendingDeletionsRef, deletionTimeoutRef, dataSnapshotRef]);
|
|
649
822
|
|
|
650
823
|
// Normalize columnOrder for useTableColumns: ensure 'select' is always first
|
|
651
824
|
const normalizedColumnOrderForColumns = useMemo(() => {
|
|
@@ -13,6 +13,7 @@ import { getTableClasses } from '../styles';
|
|
|
13
13
|
import { toast } from '../../../hooks/useToast';
|
|
14
14
|
import type {
|
|
15
15
|
AggregateConfig,
|
|
16
|
+
CellValue,
|
|
16
17
|
DataRecord,
|
|
17
18
|
DataTableAction,
|
|
18
19
|
DataTableColumn,
|
|
@@ -130,6 +131,12 @@ export function DataTableLayout<TData extends DataRecord>({
|
|
|
130
131
|
keyboardNavigation,
|
|
131
132
|
lastFocusedElementRef,
|
|
132
133
|
}: DataTableLayoutProps<TData>) {
|
|
134
|
+
// CRITICAL FIX: Use a ref to track the latest editingData to avoid stale closure issues
|
|
135
|
+
// The ref is always kept in sync with state.editingData via useEffect
|
|
136
|
+
const editingDataRef = React.useRef(state.editingData);
|
|
137
|
+
React.useEffect(() => {
|
|
138
|
+
editingDataRef.current = state.editingData;
|
|
139
|
+
}, [state.editingData]);
|
|
133
140
|
const handleExport = async () => {
|
|
134
141
|
try {
|
|
135
142
|
const tableRows = table.getFilteredRowModel().rows;
|
|
@@ -282,6 +289,8 @@ export function DataTableLayout<TData extends DataRecord>({
|
|
|
282
289
|
if (result !== undefined && result !== null && typeof result === 'object' && typeof result.then === 'function') {
|
|
283
290
|
await result;
|
|
284
291
|
}
|
|
292
|
+
// Clear selection after successful deletion
|
|
293
|
+
stateActions.clearRowSelection();
|
|
285
294
|
toast({
|
|
286
295
|
title: 'Delete Successful',
|
|
287
296
|
description: `Successfully deleted ${selectedCount} ${selectedCount === 1 ? 'row' : 'rows'}`,
|
|
@@ -424,8 +433,18 @@ export function DataTableLayout<TData extends DataRecord>({
|
|
|
424
433
|
creationData={state.creationData}
|
|
425
434
|
onCreationDataChange={stateActions.setCreationData}
|
|
426
435
|
onSaveCreation={() => {
|
|
436
|
+
const creationDataToUse = state.creationData;
|
|
437
|
+
// CRITICAL FIX: Ensure status field is included with default value "active" if not already set
|
|
438
|
+
// This ensures new rows are created with the correct status
|
|
439
|
+
const finalCreationData = {
|
|
440
|
+
...creationDataToUse,
|
|
441
|
+
// Only set status to "active" if it's not already set and if status is a column in the table
|
|
442
|
+
...(creationDataToUse.status === undefined && table.getAllColumns().some(col => col.id === 'status')
|
|
443
|
+
? { status: 'active' as CellValue }
|
|
444
|
+
: {}),
|
|
445
|
+
};
|
|
427
446
|
if (onCreateRow) {
|
|
428
|
-
onCreateRow(
|
|
447
|
+
onCreateRow(finalCreationData as Partial<TData>);
|
|
429
448
|
stateActions.clearCreationData();
|
|
430
449
|
stateActions.setCreating(false);
|
|
431
450
|
}
|
|
@@ -442,17 +461,23 @@ export function DataTableLayout<TData extends DataRecord>({
|
|
|
442
461
|
}
|
|
443
462
|
}}
|
|
444
463
|
onSaveEditing={() => {
|
|
464
|
+
// CRITICAL FIX: Use ref to get the latest editingData to avoid stale closure issues
|
|
465
|
+
// The ref is always kept in sync with state.editingData, so it should always have the correct value
|
|
466
|
+
const latestEditingData = editingDataRef.current;
|
|
445
467
|
if (onEditRow && state.editingRowId) {
|
|
446
|
-
|
|
468
|
+
// CRITICAL FIX: Use findIndex to get the actual index, then use that index for getRowId
|
|
469
|
+
const originalRowIndex = data.findIndex((row, index) => {
|
|
447
470
|
try {
|
|
448
|
-
const rowId = resolvedGetRowId(row,
|
|
471
|
+
const rowId = resolvedGetRowId(row, index);
|
|
449
472
|
return rowId === state.editingRowId;
|
|
450
473
|
} catch {
|
|
451
474
|
return false;
|
|
452
475
|
}
|
|
453
476
|
});
|
|
454
|
-
|
|
455
|
-
|
|
477
|
+
|
|
478
|
+
if (originalRowIndex >= 0) {
|
|
479
|
+
const originalRow = data[originalRowIndex];
|
|
480
|
+
onEditRow(originalRow, latestEditingData as Partial<TData>);
|
|
456
481
|
}
|
|
457
482
|
}
|
|
458
483
|
stateActions.clearEditing();
|
|
@@ -33,7 +33,7 @@ export function SelectEditField<TData extends DataRecord>({
|
|
|
33
33
|
const logger = createLogger('SelectEditField');
|
|
34
34
|
const isSearchable = columnDef.selectSearchable !== false;
|
|
35
35
|
const isCreatable = columnDef.creatable === true;
|
|
36
|
-
const selectRef = React.useRef<
|
|
36
|
+
const selectRef = React.useRef<HTMLFieldSetElement>(null);
|
|
37
37
|
const [searchTerm, setSearchTerm] = React.useState('');
|
|
38
38
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
39
39
|
const [showCreateOption, setShowCreateOption] = React.useState(false);
|
|
@@ -258,7 +258,27 @@ export const renderEditField = <TData extends DataRecord>(
|
|
|
258
258
|
|
|
259
259
|
if (columnDef.fieldType === 'select' && columnDef.fieldOptions) {
|
|
260
260
|
const accessorKey = columnDef.editAccessorKey || column.id;
|
|
261
|
-
|
|
261
|
+
// CRITICAL FIX: Ensure we use the value from editingData first, then fall back to the passed value
|
|
262
|
+
// Convert to string to match Select component's string-based value system
|
|
263
|
+
// Always prioritize editingData[accessorKey] to ensure we use the most recent value
|
|
264
|
+
const rawValue = editingData[accessorKey] !== undefined && editingData[accessorKey] !== null
|
|
265
|
+
? editingData[accessorKey]
|
|
266
|
+
: (value !== undefined && value !== null ? value : '');
|
|
267
|
+
const currentValue = rawValue !== null && rawValue !== undefined ? String(rawValue) : '';
|
|
268
|
+
|
|
269
|
+
// Create onChange handler that immediately updates state
|
|
270
|
+
const handleValueChange = (newValue: CellValue) => {
|
|
271
|
+
// Store the new value - preserve original type if option values are numbers
|
|
272
|
+
// Check if any option has a numeric value to determine if we should convert
|
|
273
|
+
const hasNumericValues = columnDef.fieldOptions?.some((opt: any) =>
|
|
274
|
+
'value' in opt && !('type' in opt) && typeof opt.value === 'number'
|
|
275
|
+
);
|
|
276
|
+
const finalValue = hasNumericValues && !isNaN(Number(newValue)) && newValue !== ''
|
|
277
|
+
? Number(newValue)
|
|
278
|
+
: newValue;
|
|
279
|
+
// Immediately update state - this happens synchronously
|
|
280
|
+
onChange({ [accessorKey]: finalValue });
|
|
281
|
+
};
|
|
262
282
|
|
|
263
283
|
return (
|
|
264
284
|
<SelectEditField
|
|
@@ -266,7 +286,7 @@ export const renderEditField = <TData extends DataRecord>(
|
|
|
266
286
|
accessorKey={accessorKey}
|
|
267
287
|
currentValue={currentValue}
|
|
268
288
|
placeholder={placeholder}
|
|
269
|
-
onChange={
|
|
289
|
+
onChange={handleValueChange}
|
|
270
290
|
/>
|
|
271
291
|
);
|
|
272
292
|
}
|
|
@@ -51,7 +51,7 @@ function SelectEditField<TData extends DataRecord>({
|
|
|
51
51
|
// When selectSearchable is false, hide the search input (type-to-search still works via SelectContent internals)
|
|
52
52
|
const isSearchable = columnDef.selectSearchable !== false;
|
|
53
53
|
const isCreatable = columnDef.creatable === true;
|
|
54
|
-
const selectRef = React.useRef<
|
|
54
|
+
const selectRef = React.useRef<HTMLFieldSetElement>(null);
|
|
55
55
|
const [searchTerm, setSearchTerm] = React.useState('');
|
|
56
56
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
57
57
|
const [showCreateOption, setShowCreateOption] = React.useState(false);
|
|
@@ -434,9 +434,14 @@ export function EditableRow<TData extends DataRecord>({
|
|
|
434
434
|
hasAssignedRef.current = true;
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
+
// CRITICAL FIX: Use the correct accessor key (editAccessorKey or column.id) to get the value
|
|
438
|
+
// This ensures that when editAccessorKey is different from column.id, we use the correct key
|
|
439
|
+
const accessorKey = columnDef.editAccessorKey || cell.column.id;
|
|
440
|
+
const currentValue = editingData[accessorKey] ?? (cell.getValue() as CellValue);
|
|
441
|
+
|
|
437
442
|
return renderEditField(
|
|
438
443
|
cell.column,
|
|
439
|
-
|
|
444
|
+
currentValue,
|
|
440
445
|
(value) => {
|
|
441
446
|
if (typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date)) {
|
|
442
447
|
onEditingDataChange({ ...editingData, ...(value as Record<string, CellValue>) });
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
* - Customizable text content
|
|
37
37
|
*/
|
|
38
38
|
import React, { useState, useRef, useEffect } from 'react';
|
|
39
|
-
import { Dialog, DialogContent,
|
|
39
|
+
import { Dialog, DialogContent, DialogHeader } from '../../Dialog';
|
|
40
40
|
import { Button } from '../../Button/Button';
|
|
41
41
|
import { Input } from '../../Input/Input';
|
|
42
42
|
import { Progress } from '../../Progress';
|
|
@@ -372,12 +372,10 @@ export function ImportModal({ isOpen, onClose, onImport, config = {} }: ImportMo
|
|
|
372
372
|
|
|
373
373
|
return (
|
|
374
374
|
<Dialog open={isOpen} onOpenChange={handleClose}>
|
|
375
|
-
<DialogContent className="sm:max-w-2xl bg-main-50">
|
|
375
|
+
<DialogContent className="sm:max-w-2xl bg-main-50" title={title} description={description}>
|
|
376
376
|
<DialogHeader>
|
|
377
|
-
<
|
|
378
|
-
<
|
|
379
|
-
{description}
|
|
380
|
-
</DialogDescription>
|
|
377
|
+
<h2>{title}</h2>
|
|
378
|
+
<p>{description}</p>
|
|
381
379
|
</DialogHeader>
|
|
382
380
|
|
|
383
381
|
<div className="space-y-4">
|
|
@@ -381,6 +381,18 @@ const areRowPropsEqual = (prevProps: RowProps, nextProps: RowProps): boolean =>
|
|
|
381
381
|
return false;
|
|
382
382
|
}
|
|
383
383
|
|
|
384
|
+
// CRITICAL FIX: Check if editingData has changed
|
|
385
|
+
// This ensures EditableRow re-renders when editingData updates (e.g., when dropdown value changes)
|
|
386
|
+
// For React 19: Manual memoization is still beneficial for table rows to prevent unnecessary re-renders
|
|
387
|
+
// of hundreds/thousands of rows when only one row's editingData changes
|
|
388
|
+
if (prevProps.isEditing && nextProps.isEditing) {
|
|
389
|
+
// Simple reference equality check - if editingData object reference changed, it's different
|
|
390
|
+
// This works because setEditingRow creates a new object, so reference equality is sufficient
|
|
391
|
+
if (prevProps.editingData !== nextProps.editingData) {
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
384
396
|
return true;
|
|
385
397
|
};
|
|
386
398
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import React from 'react';
|
|
9
|
-
import { Dialog, DialogContent, DialogHeader
|
|
9
|
+
import { Dialog, DialogContent, DialogHeader } from '../../Dialog/Dialog';
|
|
10
10
|
import { Button } from '../../Button/Button';
|
|
11
11
|
import { X } from 'lucide-react';
|
|
12
12
|
|
|
@@ -27,9 +27,9 @@ export function ViewRowModal<TData extends Record<string, any>>({
|
|
|
27
27
|
|
|
28
28
|
return (
|
|
29
29
|
<Dialog open={isOpen} onOpenChange={onClose}>
|
|
30
|
-
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
|
|
30
|
+
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto" title={title}>
|
|
31
31
|
<DialogHeader>
|
|
32
|
-
<
|
|
32
|
+
<h2 className="flex items-center justify-between">
|
|
33
33
|
{title}
|
|
34
34
|
<Button
|
|
35
35
|
variant="ghost"
|
|
@@ -39,7 +39,7 @@ export function ViewRowModal<TData extends Record<string, any>>({
|
|
|
39
39
|
>
|
|
40
40
|
<X className="size-4" />
|
|
41
41
|
</Button>
|
|
42
|
-
</
|
|
42
|
+
</h2>
|
|
43
43
|
</DialogHeader>
|
|
44
44
|
|
|
45
45
|
<div className="mt-4">
|