@jmruthers/pace-core 0.6.1 → 0.6.3

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