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