@jmruthers/pace-core 0.5.193 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (577) hide show
  1. package/CHANGELOG.md +62 -0
  2. package/README.md +7 -1
  3. package/cursor-rules/00-pace-core-compliance.mdc +299 -0
  4. package/cursor-rules/01-standards-compliance.mdc +244 -0
  5. package/cursor-rules/02-project-structure.mdc +200 -0
  6. package/cursor-rules/03-solid-principles.mdc +222 -0
  7. package/cursor-rules/04-testing-standards.mdc +268 -0
  8. package/cursor-rules/05-bug-reports-and-features.mdc +246 -0
  9. package/cursor-rules/06-code-quality.mdc +309 -0
  10. package/cursor-rules/07-tech-stack-compliance.mdc +214 -0
  11. package/cursor-rules/08-markup-quality.mdc +452 -0
  12. package/cursor-rules/CHANGELOG.md +119 -0
  13. package/cursor-rules/README.md +192 -0
  14. package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-BPvc3Ka0.d.ts} +54 -0
  15. package/dist/{DataTable-Be6dH_dR.d.ts → DataTable-BMRU8a1j.d.ts} +34 -2
  16. package/dist/{DataTable-5FU7IESH.js → DataTable-TPTKCX4D.js} +10 -9
  17. package/dist/{PublicPageProvider-C0Sm_e5k.d.ts → PublicPageProvider-DC6kCaqf.d.ts} +385 -261
  18. package/dist/{UnifiedAuthProvider-RGJTDE2C.js → UnifiedAuthProvider-CH6Z342H.js} +3 -3
  19. package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CVcTjx-d.d.ts} +29 -0
  20. package/dist/{api-N774RPUA.js → api-MVVQZLJI.js} +2 -2
  21. package/dist/{chunk-KNC55RTG.js → chunk-24UVZUZG.js} +90 -54
  22. package/dist/chunk-24UVZUZG.js.map +1 -0
  23. package/dist/{chunk-HWIIPPNI.js → chunk-2UOI2FG5.js} +20 -20
  24. package/dist/chunk-2UOI2FG5.js.map +1 -0
  25. package/dist/{chunk-E3SPN4VZ 5.js → chunk-3XC4CPTD.js} +4345 -3986
  26. package/dist/chunk-3XC4CPTD.js.map +1 -0
  27. package/dist/{chunk-7EQTDTTJ.js → chunk-6J4GEEJR.js} +172 -45
  28. package/dist/chunk-6J4GEEJR.js.map +1 -0
  29. package/dist/{chunk-6C4YBBJM 5.js → chunk-6SOIHG6Z.js} +1 -1
  30. package/dist/chunk-6SOIHG6Z.js.map +1 -0
  31. package/dist/{chunk-7FLMSG37.js → chunk-EHMR7VYL.js} +25 -25
  32. package/dist/chunk-EHMR7VYL.js.map +1 -0
  33. package/dist/{chunk-I7PSE6JW.js → chunk-F2IMUDXZ.js} +2 -75
  34. package/dist/chunk-F2IMUDXZ.js.map +1 -0
  35. package/dist/{chunk-QWWZ5CAQ.js → chunk-FFQEQTNW.js} +7 -9
  36. package/dist/chunk-FFQEQTNW.js.map +1 -0
  37. package/dist/chunk-FMUCXFII.js +76 -0
  38. package/dist/chunk-FMUCXFII.js.map +1 -0
  39. package/dist/{chunk-HW3OVDUF.js → chunk-J36DSWQK.js} +1 -1
  40. package/dist/{chunk-HW3OVDUF.js.map → chunk-J36DSWQK.js.map} +1 -1
  41. package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
  42. package/dist/chunk-L4OXEN46.js.map +1 -0
  43. package/dist/{chunk-R77UEZ4E 3.js → chunk-M43Y4SSO.js} +1 -1
  44. package/dist/chunk-M43Y4SSO.js.map +1 -0
  45. package/dist/{chunk-IIELH4DL.js → chunk-MMZ7JXPU.js} +60 -223
  46. package/dist/chunk-MMZ7JXPU.js.map +1 -0
  47. package/dist/{chunk-NOAYCWCX 5.js → chunk-NECFR5MM.js} +394 -312
  48. package/dist/chunk-NECFR5MM.js.map +1 -0
  49. package/dist/{chunk-BC4IJKSL.js → chunk-SFZUDBL5.js} +40 -4
  50. package/dist/chunk-SFZUDBL5.js.map +1 -0
  51. package/dist/{chunk-XNXXZ43G.js → chunk-XWQCNGTQ.js} +748 -364
  52. package/dist/chunk-XWQCNGTQ.js.map +1 -0
  53. package/dist/components.d.ts +6 -6
  54. package/dist/components.js +15 -12
  55. package/dist/components.js.map +1 -1
  56. package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
  57. package/dist/hooks.d.ts +59 -126
  58. package/dist/hooks.js +19 -28
  59. package/dist/hooks.js.map +1 -1
  60. package/dist/index.d.ts +63 -16
  61. package/dist/index.js +23 -24
  62. package/dist/index.js.map +1 -1
  63. package/dist/providers.d.ts +21 -3
  64. package/dist/providers.js +2 -2
  65. package/dist/rbac/index.d.ts +146 -115
  66. package/dist/rbac/index.js +8 -11
  67. package/dist/theming/runtime.d.ts +1 -13
  68. package/dist/theming/runtime.js +1 -1
  69. package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
  70. package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
  71. package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
  72. package/dist/types.d.ts +2 -2
  73. package/dist/{usePublicRouteParams-TZe0gy-4.d.ts → usePublicRouteParams-1oMokgLF.d.ts} +34 -4
  74. package/dist/{useToast-C8gR5ir4.d.ts → useToast-AyaT-x7p.d.ts} +2 -2
  75. package/dist/utils.d.ts +4 -5
  76. package/dist/utils.js +15 -15
  77. package/dist/utils.js.map +1 -1
  78. package/docs/api/README.md +7 -1
  79. package/docs/api/classes/ColumnFactory.md +8 -8
  80. package/docs/api/classes/InvalidScopeError.md +4 -4
  81. package/docs/api/classes/Logger.md +1 -1
  82. package/docs/api/classes/MissingUserContextError.md +4 -4
  83. package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
  84. package/docs/api/classes/PermissionDeniedError.md +4 -4
  85. package/docs/api/classes/RBACAuditManager.md +1 -1
  86. package/docs/api/classes/RBACCache.md +1 -1
  87. package/docs/api/classes/RBACEngine.md +1 -1
  88. package/docs/api/classes/RBACError.md +4 -4
  89. package/docs/api/classes/RBACNotInitializedError.md +4 -4
  90. package/docs/api/classes/SecureSupabaseClient.md +18 -15
  91. package/docs/api/classes/StorageUtils.md +1 -1
  92. package/docs/api/enums/FileCategory.md +1 -1
  93. package/docs/api/enums/LogLevel.md +1 -1
  94. package/docs/api/enums/RBACErrorCode.md +1 -1
  95. package/docs/api/enums/RPCFunction.md +1 -1
  96. package/docs/api/interfaces/AddressFieldProps.md +1 -1
  97. package/docs/api/interfaces/AddressFieldRef.md +1 -1
  98. package/docs/api/interfaces/AggregateConfig.md +4 -4
  99. package/docs/api/interfaces/AutocompleteOptions.md +1 -1
  100. package/docs/api/interfaces/AvatarProps.md +1 -1
  101. package/docs/api/interfaces/BadgeProps.md +9 -2
  102. package/docs/api/interfaces/ButtonProps.md +7 -4
  103. package/docs/api/interfaces/CalendarProps.md +8 -5
  104. package/docs/api/interfaces/CardProps.md +8 -5
  105. package/docs/api/interfaces/ColorPalette.md +1 -1
  106. package/docs/api/interfaces/ColorShade.md +1 -1
  107. package/docs/api/interfaces/ComplianceResult.md +1 -1
  108. package/docs/api/interfaces/DataAccessRecord.md +9 -9
  109. package/docs/api/interfaces/DataRecord.md +1 -1
  110. package/docs/api/interfaces/DataTableAction.md +24 -21
  111. package/docs/api/interfaces/DataTableColumn.md +31 -31
  112. package/docs/api/interfaces/DataTableProps.md +1 -1
  113. package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
  114. package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
  115. package/docs/api/interfaces/DatabaseIssue.md +1 -1
  116. package/docs/api/interfaces/EmptyStateConfig.md +5 -5
  117. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  118. package/docs/api/interfaces/ErrorBoundaryProps.md +147 -0
  119. package/docs/api/interfaces/ErrorBoundaryProviderProps.md +36 -0
  120. package/docs/api/interfaces/ErrorBoundaryState.md +75 -0
  121. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  122. package/docs/api/interfaces/ExportColumn.md +1 -1
  123. package/docs/api/interfaces/ExportOptions.md +8 -8
  124. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  125. package/docs/api/interfaces/FileMetadata.md +1 -1
  126. package/docs/api/interfaces/FileReference.md +1 -1
  127. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  128. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  129. package/docs/api/interfaces/FileUploadProps.md +26 -23
  130. package/docs/api/interfaces/FooterProps.md +10 -8
  131. package/docs/api/interfaces/FormFieldProps.md +10 -10
  132. package/docs/api/interfaces/FormProps.md +1 -1
  133. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  134. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  135. package/docs/api/interfaces/InputProps.md +7 -4
  136. package/docs/api/interfaces/LabelProps.md +1 -1
  137. package/docs/api/interfaces/LoggerConfig.md +1 -1
  138. package/docs/api/interfaces/LoginFormProps.md +14 -11
  139. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  140. package/docs/api/interfaces/NavigationContextType.md +1 -1
  141. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  142. package/docs/api/interfaces/NavigationItem.md +11 -11
  143. package/docs/api/interfaces/NavigationMenuProps.md +15 -15
  144. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  145. package/docs/api/interfaces/Organisation.md +1 -1
  146. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  147. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  148. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  149. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  150. package/docs/api/interfaces/PaceAppLayoutProps.md +30 -27
  151. package/docs/api/interfaces/PaceLoginPageProps.md +6 -4
  152. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  153. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  154. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  155. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  156. package/docs/api/interfaces/PaletteData.md +1 -1
  157. package/docs/api/interfaces/ParsedAddress.md +1 -1
  158. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  159. package/docs/api/interfaces/ProgressProps.md +1 -1
  160. package/docs/api/interfaces/ProtectedRouteProps.md +7 -26
  161. package/docs/api/interfaces/PublicPageFooterProps.md +9 -9
  162. package/docs/api/interfaces/PublicPageHeaderProps.md +10 -10
  163. package/docs/api/interfaces/PublicPageLayoutProps.md +7 -20
  164. package/docs/api/interfaces/QuickFix.md +1 -1
  165. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  166. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  167. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  168. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  169. package/docs/api/interfaces/RBACConfig.md +1 -1
  170. package/docs/api/interfaces/RBACContext.md +1 -1
  171. package/docs/api/interfaces/RBACLogger.md +1 -1
  172. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  173. package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
  174. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  175. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  176. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  177. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  178. package/docs/api/interfaces/RBACResult.md +1 -1
  179. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  180. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  181. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  182. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  183. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  184. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  185. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  186. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  187. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  188. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  189. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  190. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  191. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  192. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  193. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  194. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  195. package/docs/api/interfaces/RouteConfig.md +1 -1
  196. package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
  197. package/docs/api/interfaces/SecureDataContextType.md +9 -9
  198. package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
  199. package/docs/api/interfaces/SessionRestorationLoaderProps.md +3 -3
  200. package/docs/api/interfaces/SetupIssue.md +1 -1
  201. package/docs/api/interfaces/StorageConfig.md +1 -1
  202. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  203. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  204. package/docs/api/interfaces/StorageListOptions.md +1 -1
  205. package/docs/api/interfaces/StorageListResult.md +1 -1
  206. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  207. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  208. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  209. package/docs/api/interfaces/StyleImport.md +1 -1
  210. package/docs/api/interfaces/SwitchProps.md +1 -1
  211. package/docs/api/interfaces/TabsContentProps.md +1 -1
  212. package/docs/api/interfaces/TabsListProps.md +1 -1
  213. package/docs/api/interfaces/TabsProps.md +1 -1
  214. package/docs/api/interfaces/TabsTriggerProps.md +3 -3
  215. package/docs/api/interfaces/TextareaProps.md +1 -1
  216. package/docs/api/interfaces/ToastActionElement.md +4 -1
  217. package/docs/api/interfaces/ToastProps.md +1 -1
  218. package/docs/api/interfaces/UnifiedAuthContextType.md +58 -55
  219. package/docs/api/interfaces/UnifiedAuthProviderProps.md +15 -13
  220. package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
  221. package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
  222. package/docs/api/interfaces/UseInactivityTrackerOptions.md +11 -9
  223. package/docs/api/interfaces/UseInactivityTrackerReturn.md +8 -8
  224. package/docs/api/interfaces/UsePublicEventLogoOptions.md +6 -6
  225. package/docs/api/interfaces/UsePublicEventLogoReturn.md +9 -6
  226. package/docs/api/interfaces/UsePublicEventOptions.md +3 -3
  227. package/docs/api/interfaces/UsePublicEventReturn.md +8 -5
  228. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +4 -4
  229. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +12 -9
  230. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +10 -7
  231. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  232. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  233. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  234. package/docs/api/interfaces/UserEventAccess.md +14 -11
  235. package/docs/api/interfaces/UserMenuProps.md +8 -6
  236. package/docs/api/interfaces/UserProfile.md +1 -1
  237. package/docs/api/modules.md +575 -634
  238. package/docs/architecture/database-schema-requirements.md +161 -0
  239. package/docs/core-concepts/rbac-system.md +3 -3
  240. package/docs/documentation-index.md +2 -4
  241. package/docs/getting-started/cursor-rules.md +263 -0
  242. package/docs/getting-started/installation-guide.md +6 -1
  243. package/docs/getting-started/quick-start.md +6 -1
  244. package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
  245. package/docs/migration/MIGRATION_GUIDE.md +6 -28
  246. package/docs/migration/README.md +52 -6
  247. package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
  248. package/docs/migration/V0.6.0_REACT_19_MIGRATION.md +227 -0
  249. package/docs/migration/database-changes-december-2025.md +3 -3
  250. package/docs/rbac/event-based-apps.md +1 -1
  251. package/docs/rbac/getting-started.md +1 -1
  252. package/docs/rbac/quick-start.md +1 -1
  253. package/docs/standards/README.md +40 -0
  254. package/docs/troubleshooting/migration.md +4 -4
  255. package/examples/PublicPages/PublicEventPage.tsx +1 -1
  256. package/package.json +12 -6
  257. package/scripts/audit/core/checks/accessibility.cjs +197 -0
  258. package/scripts/audit/core/checks/api-usage.cjs +191 -0
  259. package/scripts/audit/core/checks/bundle.cjs +142 -0
  260. package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +737 -691
  261. package/scripts/audit/core/checks/config.cjs +54 -0
  262. package/scripts/audit/core/checks/coverage.cjs +84 -0
  263. package/scripts/audit/core/checks/dependencies.cjs +454 -0
  264. package/scripts/audit/core/checks/documentation.cjs +203 -0
  265. package/scripts/audit/core/checks/environment.cjs +128 -0
  266. package/scripts/audit/core/checks/error-handling.cjs +299 -0
  267. package/scripts/audit/core/checks/forms.cjs +172 -0
  268. package/scripts/audit/core/checks/heuristics.cjs +68 -0
  269. package/scripts/audit/core/checks/hooks.cjs +334 -0
  270. package/scripts/audit/core/checks/imports.cjs +244 -0
  271. package/scripts/audit/core/checks/performance.cjs +325 -0
  272. package/scripts/audit/core/checks/routes.cjs +117 -0
  273. package/scripts/audit/core/checks/state.cjs +130 -0
  274. package/scripts/audit/core/checks/structure.cjs +65 -0
  275. package/scripts/audit/core/checks/style.cjs +584 -0
  276. package/scripts/audit/core/checks/testing.cjs +122 -0
  277. package/scripts/audit/core/checks/typescript.cjs +61 -0
  278. package/scripts/audit/core/scanner.cjs +199 -0
  279. package/scripts/audit/core/utils.cjs +137 -0
  280. package/scripts/audit/index.cjs +223 -0
  281. package/scripts/audit/reporters/console.cjs +151 -0
  282. package/scripts/audit/reporters/json.cjs +54 -0
  283. package/scripts/audit/reporters/markdown.cjs +124 -0
  284. package/scripts/audit-consuming-app.cjs +86 -0
  285. package/scripts/build-docs/build-decision.js +240 -0
  286. package/scripts/build-docs/cache-utils.js +105 -0
  287. package/scripts/build-docs/content-normalization.js +150 -0
  288. package/scripts/build-docs/file-utils.js +105 -0
  289. package/scripts/build-docs/git-utils.js +86 -0
  290. package/scripts/build-docs/hash-utils.js +116 -0
  291. package/scripts/build-docs/typedoc-runner.js +220 -0
  292. package/scripts/build-docs-incremental.js +77 -913
  293. package/scripts/install-cursor-rules.cjs +236 -0
  294. package/scripts/utils/command-runner.js +16 -11
  295. package/scripts/validate-formats.js +61 -56
  296. package/scripts/validate-master.js +74 -69
  297. package/scripts/validate-pre-publish.js +70 -65
  298. package/src/__tests__/helpers/test-providers.tsx +1 -1
  299. package/src/__tests__/helpers/test-utils.tsx +1 -1
  300. package/src/__tests__/hooks/usePermissions.test.ts +2 -2
  301. package/src/components/Alert/Alert.test.tsx +12 -18
  302. package/src/components/Alert/Alert.tsx +5 -7
  303. package/src/components/Avatar/Avatar.test.tsx +4 -4
  304. package/src/components/Badge/Badge.tsx +16 -4
  305. package/src/components/Button/Button.tsx +27 -4
  306. package/src/components/Calendar/Calendar.tsx +9 -3
  307. package/src/components/Card/Card.tsx +4 -0
  308. package/src/components/Checkbox/Checkbox.test.tsx +12 -12
  309. package/src/components/Checkbox/Checkbox.tsx +2 -2
  310. package/src/components/DataTable/DataTable.test.tsx +57 -93
  311. package/src/components/DataTable/DataTable.tsx +40 -6
  312. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
  313. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +29 -7
  314. package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +12 -12
  315. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
  316. package/src/components/DataTable/components/AccessDeniedPage.tsx +17 -26
  317. package/src/components/DataTable/components/ActionButtons.tsx +10 -7
  318. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
  319. package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
  320. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
  321. package/src/components/DataTable/components/DataTableBody.tsx +8 -0
  322. package/src/components/DataTable/components/DataTableCore.tsx +200 -561
  323. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
  324. package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
  325. package/src/components/DataTable/components/DataTableModals.tsx +9 -1
  326. package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
  327. package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
  328. package/src/components/DataTable/components/EditFields.tsx +307 -0
  329. package/src/components/DataTable/components/EditableRow.tsx +9 -1
  330. package/src/components/DataTable/components/EmptyState.tsx +10 -0
  331. package/src/components/DataTable/components/FilterRow.tsx +12 -0
  332. package/src/components/DataTable/components/GroupHeader.tsx +12 -0
  333. package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
  334. package/src/components/DataTable/components/ImportModal.tsx +7 -0
  335. package/src/components/DataTable/components/LoadingState.tsx +6 -0
  336. package/src/components/DataTable/components/PaginationControls.tsx +16 -1
  337. package/src/components/DataTable/components/RowComponent.tsx +391 -0
  338. package/src/components/DataTable/components/UnifiedTableBody.tsx +62 -852
  339. package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
  340. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
  341. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +23 -23
  342. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +11 -11
  343. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +36 -36
  344. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +27 -27
  345. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +39 -39
  346. package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +33 -33
  347. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +29 -29
  348. package/src/components/DataTable/components/cellValueUtils.ts +40 -0
  349. package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
  350. package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
  351. package/src/components/DataTable/context/DataTableContext.tsx +50 -0
  352. package/src/components/DataTable/core/ColumnFactory.ts +31 -0
  353. package/src/components/DataTable/core/DataTableContext.tsx +32 -1
  354. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
  355. package/src/components/DataTable/hooks/useColumnReordering.ts +14 -2
  356. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
  357. package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
  358. package/src/components/DataTable/hooks/useDataTablePermissions.ts +124 -32
  359. package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
  360. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
  361. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +2 -2
  362. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
  363. package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
  364. package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
  365. package/src/components/DataTable/styles.ts +6 -6
  366. package/src/components/DataTable/types.ts +6 -10
  367. package/src/components/DataTable/utils/a11yUtils.ts +7 -0
  368. package/src/components/DataTable/utils/debugTools.ts +18 -113
  369. package/src/components/DataTable/utils/errorHandling.ts +12 -0
  370. package/src/components/DataTable/utils/exportUtils.ts +9 -0
  371. package/src/components/DataTable/utils/flexibleImport.ts +12 -48
  372. package/src/components/DataTable/utils/paginationUtils.ts +8 -0
  373. package/src/components/DataTable/utils/performanceUtils.ts +5 -1
  374. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -14
  375. package/src/components/Dialog/Dialog.tsx +8 -7
  376. package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
  377. package/src/components/ErrorBoundary/ErrorBoundary.tsx +46 -6
  378. package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
  379. package/src/components/ErrorBoundary/index.ts +27 -2
  380. package/src/components/EventSelector/EventSelector.tsx +4 -1
  381. package/src/components/FileDisplay/FileDisplay.test.tsx +2 -2
  382. package/src/components/FileDisplay/FileDisplay.tsx +32 -18
  383. package/src/components/FileUpload/FileUpload.tsx +22 -2
  384. package/src/components/Footer/Footer.test.tsx +16 -16
  385. package/src/components/Footer/Footer.tsx +15 -12
  386. package/src/components/Form/Form.test.tsx +36 -15
  387. package/src/components/Form/Form.tsx +31 -26
  388. package/src/components/Header/Header.tsx +22 -11
  389. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +40 -40
  390. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +1 -1
  391. package/src/components/Input/Input.test.tsx +2 -2
  392. package/src/components/Input/Input.tsx +36 -34
  393. package/src/components/Label/Label.tsx +1 -1
  394. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
  395. package/src/components/LoadingSpinner/LoadingSpinner.tsx +1 -1
  396. package/src/components/LoginForm/LoginForm.test.tsx +42 -42
  397. package/src/components/LoginForm/LoginForm.tsx +12 -8
  398. package/src/components/NavigationMenu/NavigationMenu.tsx +15 -514
  399. package/src/components/NavigationMenu/types.ts +56 -0
  400. package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
  401. package/src/components/OrganisationSelector/OrganisationSelector.tsx +3 -0
  402. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +1 -1
  403. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +54 -52
  404. package/src/components/PaceAppLayout/PaceAppLayout.tsx +33 -12
  405. package/src/components/PaceAppLayout/README.md +1 -1
  406. package/src/components/PaceAppLayout/test-setup.tsx +1 -2
  407. package/src/components/PaceLoginPage/PaceLoginPage.tsx +4 -1
  408. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +33 -33
  409. package/src/components/PasswordChange/PasswordChangeForm.tsx +10 -1
  410. package/src/components/Progress/Progress.tsx +1 -1
  411. package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
  412. package/src/components/PublicLayout/PublicPageLayout.tsx +3 -6
  413. package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
  414. package/src/components/Select/Select.tsx +95 -438
  415. package/src/components/Select/context.ts +23 -0
  416. package/src/components/Select/hooks/useSelectEvents.ts +87 -0
  417. package/src/components/Select/hooks/useSelectSearch.ts +91 -0
  418. package/src/components/Select/hooks/useSelectState.ts +104 -0
  419. package/src/components/Select/index.ts +9 -1
  420. package/src/components/Select/types.ts +123 -0
  421. package/src/components/Select/utils/text.ts +26 -0
  422. package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +5 -6
  423. package/src/components/Switch/Switch.tsx +4 -4
  424. package/src/components/Table/Table.tsx +1 -1
  425. package/src/components/Tabs/Tabs.tsx +1 -1
  426. package/src/components/Textarea/Textarea.tsx +27 -29
  427. package/src/components/Toast/Toast.tsx +5 -1
  428. package/src/components/Tooltip/Tooltip.tsx +3 -3
  429. package/src/components/UserMenu/UserMenu.test.tsx +24 -11
  430. package/src/components/UserMenu/UserMenu.tsx +22 -19
  431. package/src/components/index.ts +2 -2
  432. package/src/hooks/__tests__/hooks.integration.test.tsx +80 -55
  433. package/src/hooks/__tests__/index.unit.test.ts +2 -5
  434. package/src/hooks/__tests__/useStorage.unit.test.ts +36 -36
  435. package/src/hooks/index.ts +1 -2
  436. package/src/hooks/public/usePublicEvent.ts +5 -1
  437. package/src/hooks/public/usePublicEventLogo.ts +5 -1
  438. package/src/hooks/public/usePublicFileDisplay.ts +4 -0
  439. package/src/hooks/public/usePublicRouteParams.ts +5 -1
  440. package/src/hooks/services/useAuth.ts +32 -0
  441. package/src/hooks/services/useCurrentEvent.ts +6 -0
  442. package/src/hooks/services/useCurrentOrganisation.ts +6 -0
  443. package/src/hooks/useDataTableState.ts +8 -18
  444. package/src/hooks/useDebounce.ts +9 -0
  445. package/src/hooks/useEventTheme.ts +6 -0
  446. package/src/hooks/useFileDisplay.ts +4 -0
  447. package/src/hooks/useFileReference.ts +25 -7
  448. package/src/hooks/useFileUrl.ts +11 -1
  449. package/src/hooks/useFocusManagement.ts +16 -2
  450. package/src/hooks/useFocusTrap.ts +7 -4
  451. package/src/hooks/useFormDialog.ts +8 -7
  452. package/src/hooks/useInactivityTracker.ts +4 -1
  453. package/src/hooks/useKeyboardShortcuts.ts +4 -0
  454. package/src/hooks/useOrganisationPermissions.ts +4 -0
  455. package/src/hooks/useOrganisationSecurity.ts +4 -0
  456. package/src/hooks/usePerformanceMonitor.ts +4 -0
  457. package/src/hooks/usePermissionCache.ts +8 -1
  458. package/src/hooks/useQueryCache.ts +12 -1
  459. package/src/hooks/useSessionRestoration.ts +4 -0
  460. package/src/hooks/useStorage.ts +4 -0
  461. package/src/hooks/useToast.ts +3 -3
  462. package/src/index.ts +2 -1
  463. package/src/providers/__tests__/OrganisationProvider.test.tsx +115 -49
  464. package/src/providers/__tests__/ProviderLifecycle.test.tsx +21 -6
  465. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +10 -10
  466. package/src/providers/services/AuthServiceProvider.tsx +18 -0
  467. package/src/providers/services/EventServiceProvider.tsx +18 -0
  468. package/src/providers/services/InactivityServiceProvider.tsx +18 -0
  469. package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
  470. package/src/providers/services/UnifiedAuthProvider.tsx +58 -22
  471. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +33 -7
  472. package/src/rbac/README.md +1 -1
  473. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +26 -26
  474. package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
  475. package/src/rbac/adapters.tsx +14 -5
  476. package/src/rbac/api.ts +100 -67
  477. package/src/rbac/components/EnhancedNavigationMenu.tsx +1 -1
  478. package/src/rbac/components/NavigationGuard.tsx +1 -1
  479. package/src/rbac/components/NavigationProvider.tsx +5 -2
  480. package/src/rbac/components/PagePermissionGuard.tsx +158 -18
  481. package/src/rbac/components/PagePermissionProvider.tsx +1 -1
  482. package/src/rbac/components/PermissionEnforcer.tsx +1 -1
  483. package/src/rbac/components/RoleBasedRouter.tsx +6 -2
  484. package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
  485. package/src/rbac/components/SecureDataProvider.tsx +21 -6
  486. package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
  487. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
  488. package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
  489. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
  490. package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
  491. package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
  492. package/src/rbac/engine.ts +38 -14
  493. package/src/rbac/hooks/permissions/index.ts +7 -0
  494. package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
  495. package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
  496. package/src/rbac/hooks/permissions/useCan.ts +347 -0
  497. package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
  498. package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
  499. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
  500. package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
  501. package/src/rbac/hooks/useCan.test.ts +71 -64
  502. package/src/rbac/hooks/usePermissions.ts +14 -995
  503. package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
  504. package/src/rbac/hooks/useResourcePermissions.ts +14 -4
  505. package/src/rbac/hooks/useSecureSupabase.ts +33 -13
  506. package/src/rbac/permissions.ts +0 -30
  507. package/src/rbac/secureClient.ts +212 -61
  508. package/src/rbac/types.ts +8 -0
  509. package/src/theming/__tests__/parseEventColours.test.ts +6 -9
  510. package/src/theming/parseEventColours.ts +5 -19
  511. package/src/types/vitest-globals.d.ts +51 -26
  512. package/src/utils/__mocks__/supabaseMock.ts +1 -3
  513. package/src/utils/__tests__/formatting.unit.test.ts +4 -4
  514. package/src/utils/__tests__/index.unit.test.ts +2 -2
  515. package/src/utils/audit/audit.ts +0 -3
  516. package/src/utils/core/cn.ts +1 -1
  517. package/src/utils/file-reference/index.ts +53 -1
  518. package/src/utils/formatting/formatting.ts +8 -18
  519. package/src/utils/index.ts +0 -1
  520. package/src/utils/security/secureDataAccess.test.ts +31 -20
  521. package/src/utils/security/secureDataAccess.ts +4 -3
  522. package/dist/chunk-6C4YBBJM.js +0 -628
  523. package/dist/chunk-6C4YBBJM.js.map +0 -1
  524. package/dist/chunk-7D4SUZUM.js 2.map +0 -1
  525. package/dist/chunk-7EQTDTTJ.js 2.map +0 -1
  526. package/dist/chunk-7EQTDTTJ.js.map +0 -1
  527. package/dist/chunk-7FLMSG37.js 2.map +0 -1
  528. package/dist/chunk-7FLMSG37.js.map +0 -1
  529. package/dist/chunk-BC4IJKSL.js.map +0 -1
  530. package/dist/chunk-E3SPN4VZ.js +0 -12917
  531. package/dist/chunk-E3SPN4VZ.js.map +0 -1
  532. package/dist/chunk-E66EQZE6 5.js +0 -37
  533. package/dist/chunk-E66EQZE6.js 2.map +0 -1
  534. package/dist/chunk-HWIIPPNI.js.map +0 -1
  535. package/dist/chunk-I7PSE6JW 5.js +0 -191
  536. package/dist/chunk-I7PSE6JW.js 2.map +0 -1
  537. package/dist/chunk-I7PSE6JW.js.map +0 -1
  538. package/dist/chunk-IIELH4DL.js.map +0 -1
  539. package/dist/chunk-KNC55RTG.js 5.map +0 -1
  540. package/dist/chunk-KNC55RTG.js.map +0 -1
  541. package/dist/chunk-KQCRWDSA.js 5.map +0 -1
  542. package/dist/chunk-LFNCN2SP.js +0 -412
  543. package/dist/chunk-LFNCN2SP.js 2.map +0 -1
  544. package/dist/chunk-LFNCN2SP.js.map +0 -1
  545. package/dist/chunk-LMC26NLJ 2.js +0 -84
  546. package/dist/chunk-NOAYCWCX.js +0 -4993
  547. package/dist/chunk-NOAYCWCX.js.map +0 -1
  548. package/dist/chunk-QWWZ5CAQ.js 3.map +0 -1
  549. package/dist/chunk-QWWZ5CAQ.js.map +0 -1
  550. package/dist/chunk-QXHPKYJV 3.js +0 -113
  551. package/dist/chunk-R77UEZ4E.js +0 -68
  552. package/dist/chunk-R77UEZ4E.js.map +0 -1
  553. package/dist/chunk-SQGMNID3.js.map +0 -1
  554. package/dist/chunk-VBXEHIUJ.js 6.map +0 -1
  555. package/dist/chunk-XNXXZ43G.js.map +0 -1
  556. package/dist/chunk-ZSAAAMVR 6.js +0 -25
  557. package/dist/components.js 5.map +0 -1
  558. package/dist/styles/index 2.js +0 -12
  559. package/dist/styles/index.js 5.map +0 -1
  560. package/dist/theming/runtime 5.js +0 -19
  561. package/dist/theming/runtime.js 5.map +0 -1
  562. package/docs/api/classes/ErrorBoundary.md +0 -144
  563. package/docs/migration/quick-migration-guide.md +0 -356
  564. package/docs/migration/service-architecture.md +0 -281
  565. package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
  566. package/src/hooks/useSecureDataAccess.test.ts +0 -559
  567. package/src/hooks/useSecureDataAccess.ts +0 -666
  568. /package/dist/{DataTable-5FU7IESH.js.map → DataTable-TPTKCX4D.js.map} +0 -0
  569. /package/dist/{UnifiedAuthProvider-RGJTDE2C.js.map → UnifiedAuthProvider-CH6Z342H.js.map} +0 -0
  570. /package/dist/{api-N774RPUA.js.map → api-MVVQZLJI.js.map} +0 -0
  571. /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
  572. /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
  573. /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
  574. /package/examples/{rbac → RBAC}/CompleteRBACExample.tsx +0 -0
  575. /package/examples/{rbac → RBAC}/EventBasedApp.tsx +0 -0
  576. /package/examples/{rbac → RBAC}/PermissionExample.tsx +0 -0
  577. /package/examples/{rbac → RBAC}/index.ts +0 -0
@@ -3,45 +3,35 @@
3
3
  * @package @jmruthers/pace-core
4
4
  * @module Components/DataTable/Components
5
5
  * @since 0.3.0
6
- *
6
+ *
7
7
  * A unified table body component that handles both standard and virtualized rendering
8
8
  * based on data size. This eliminates the need for separate virtualized components
9
9
  * while maintaining all features consistently.
10
10
  */
11
11
 
12
- import React, { useLayoutEffect, useState, useRef, useMemo, useEffect } from 'react';
13
- import { type Table, flexRender, type Column, type Row } from '@tanstack/react-table';
12
+ import React, { useEffect, useRef } from 'react';
13
+ import { type Table } from '@tanstack/react-table';
14
14
  import { useVirtualizer } from '@tanstack/react-virtual';
15
- // Removed Table component imports - using native HTML elements
16
- import { Button } from '../../Button/Button';
17
- import { ChevronUp, ChevronDown, ChevronRight } from 'lucide-react';
18
15
  import { EmptyState } from './EmptyState';
19
16
  import { FilterRow } from './FilterRow';
20
- import { 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,754 +113,6 @@ interface UnifiedTableBodyProps<TData extends DataRecord> {
123
113
  };
124
114
  }
125
115
 
126
- // Component for select fields with searchable and creatable support
127
- function SelectEditField<TData extends DataRecord>({
128
- columnDef,
129
- accessorKey,
130
- currentValue,
131
- placeholder,
132
- onChange,
133
- }: {
134
- columnDef: EditableColumnDef<TData>;
135
- accessorKey: string;
136
- currentValue: CellValue;
137
- placeholder?: string;
138
- onChange: (value: CellValue) => void;
139
- }) {
140
- const logger = React.useMemo(() => createLogger('SelectEditField'), []);
141
- // Determine if searchable - explicitly check for true to ensure visible search input appears
142
- // When selectSearchable is true or undefined, show the visible search input box
143
- // When selectSearchable is false, hide the search input (type-to-search still works via SelectContent internals)
144
- const isSearchable = columnDef.selectSearchable !== false;
145
- const isCreatable = columnDef.creatable === true;
146
- const selectRef = React.useRef<HTMLFormElement>(null);
147
- const [searchTerm, setSearchTerm] = React.useState('');
148
- const [isOpen, setIsOpen] = React.useState(false);
149
- const [showCreateOption, setShowCreateOption] = React.useState(false);
150
-
151
- // Monitor search input value via DOM events to detect when user types
152
- React.useEffect(() => {
153
- if (!isOpen || !isSearchable || !isCreatable || !columnDef.onCreateNew) {
154
- // Reset showCreateOption when conditions aren't met
155
- if (!isOpen || !isCreatable || !columnDef.onCreateNew) {
156
- setShowCreateOption(false);
157
- }
158
- return;
159
- }
160
-
161
- // Function to find and attach listener to search input
162
- const findAndAttachSearchInput = (): (() => void) | null => {
163
- // Try to find search input - check both within selectRef and document
164
- // SelectContent might be rendered outside the form element
165
- let searchInput: HTMLInputElement | null = null;
166
-
167
- if (selectRef.current) {
168
- searchInput = selectRef.current.querySelector<HTMLInputElement>('[data-testid="select-search-input"]');
169
- }
170
-
171
- // If not found in selectRef, try document (in case SelectContent is in a portal)
172
- if (!searchInput) {
173
- // Find the most recently opened select's search input
174
- const allSearchInputs = document.querySelectorAll<HTMLInputElement>('[data-testid="select-search-input"]');
175
- // Get the one that's visible (not hidden)
176
- for (const input of Array.from(allSearchInputs)) {
177
- const content = input.closest('[data-testid="select-content"]');
178
- if (content && content.getAttribute('aria-hidden') !== 'true') {
179
- searchInput = input;
180
- break;
181
- }
182
- }
183
- }
184
-
185
- if (!searchInput) return null;
186
-
187
- const handleInput = (e: Event) => {
188
- const target = e.target as HTMLInputElement;
189
- const currentSearch = target.value;
190
- setSearchTerm(currentSearch);
191
-
192
- // Check if search doesn't match any option (including items in groups)
193
- if (currentSearch.trim()) {
194
- const searchLower = currentSearch.toLowerCase().trim();
195
-
196
- // Helper to check if an option matches
197
- // Use explicit union type instead of typeof to avoid Babel parsing issues
198
- type FieldOption =
199
- | { value: string | number; label: string }
200
- | { type: 'group'; label: string; items: Array<{ value: string | number; label: string }> }
201
- | { type: 'separator' };
202
-
203
- const checkMatch = (opt: FieldOption): boolean => {
204
- // Simple option
205
- if ('value' in opt && !('type' in opt)) {
206
- return opt.label.toLowerCase().includes(searchLower);
207
- }
208
- // Group - check items within the group
209
- if ('type' in opt && opt.type === 'group') {
210
- return (opt as { type: 'group'; label: string; items: Array<{ value: string | number; label: string }> }).items.some((item: { value: string | number; label: string }) => item.label.toLowerCase().includes(searchLower));
211
- }
212
- // Separator - doesn't match
213
- return false;
214
- };
215
-
216
- const hasMatch = (columnDef.fieldOptions || []).some(checkMatch);
217
- // Always show create option when there's a search term (user might want to create even if matches exist)
218
- const shouldShow = isCreatable && !!columnDef.onCreateNew;
219
-
220
- setShowCreateOption(shouldShow);
221
- } else {
222
- setShowCreateOption(false);
223
- }
224
- };
225
-
226
- // Check initial value in case user has already typed
227
- const initialValue = searchInput.value;
228
- if (initialValue) {
229
- const currentSearch = initialValue;
230
- setSearchTerm(currentSearch);
231
-
232
- // Check if search doesn't match any option (including items in groups)
233
- if (currentSearch.trim()) {
234
- const searchLower = currentSearch.toLowerCase().trim();
235
-
236
- type FieldOption =
237
- | { value: string | number; label: string }
238
- | { type: 'group'; label: string; items: Array<{ value: string | number; label: string }> }
239
- | { type: 'separator' };
240
-
241
- const checkMatch = (opt: FieldOption): boolean => {
242
- if ('value' in opt && !('type' in opt)) {
243
- return opt.label.toLowerCase().includes(searchLower);
244
- }
245
- if ('type' in opt && opt.type === 'group') {
246
- return (opt as { type: 'group'; label: string; items: Array<{ value: string | number; label: string }> }).items.some((item: { value: string | number; label: string }) => item.label.toLowerCase().includes(searchLower));
247
- }
248
- return false;
249
- };
250
-
251
- const hasMatch = (columnDef.fieldOptions || []).some(checkMatch);
252
- // Always show create option when there's a search term (user might want to create even if matches exist)
253
- const shouldShow = isCreatable && !!columnDef.onCreateNew;
254
- setShowCreateOption(shouldShow);
255
- } else {
256
- setShowCreateOption(false);
257
- }
258
- }
259
-
260
- searchInput.addEventListener('input', handleInput);
261
-
262
- return () => {
263
- searchInput?.removeEventListener('input', handleInput);
264
- };
265
- };
266
-
267
- // Try to find immediately
268
- let cleanup: (() => void) | null = findAndAttachSearchInput();
269
-
270
- // If not found, try again after a short delay (SelectContent might render asynchronously)
271
- if (!cleanup) {
272
- let timeoutCleanup: (() => void) | null = null;
273
- const timeoutId = setTimeout(() => {
274
- timeoutCleanup = findAndAttachSearchInput();
275
- }, 50);
276
-
277
- return () => {
278
- clearTimeout(timeoutId);
279
- timeoutCleanup?.();
280
- };
281
- }
282
-
283
- return cleanup;
284
- }, [isOpen, isSearchable, isCreatable, columnDef.fieldOptions, columnDef.onCreateNew]);
285
-
286
- const handleCreateNew = React.useCallback(async () => {
287
- if (!isCreatable || !columnDef.onCreateNew || !searchTerm.trim()) return;
288
-
289
- try {
290
- const newValue = await columnDef.onCreateNew(searchTerm.trim());
291
- onChange(newValue);
292
- setSearchTerm('');
293
- setShowCreateOption(false);
294
- } catch (error) {
295
- logger.error('Error creating new item:', error);
296
- }
297
- }, [isCreatable, columnDef.onCreateNew, searchTerm, onChange, logger]);
298
-
299
- return (
300
- <Select
301
- ref={selectRef}
302
- value={String(currentValue)}
303
- onValueChange={(newValue) => {
304
- if (newValue.startsWith('__create_new__')) {
305
- handleCreateNew();
306
- } else {
307
- onChange(newValue as CellValue);
308
- }
309
- }}
310
- onOpenChange={(open) => {
311
- setIsOpen(open);
312
- if (!open) {
313
- setSearchTerm('');
314
- setShowCreateOption(false);
315
- }
316
- }}
317
- >
318
- <SelectTrigger className="h-8">
319
- <SelectValue placeholder={placeholder || `Select ${columnDef.header || 'option'}...`} />
320
- </SelectTrigger>
321
- <SelectContent
322
- searchable={Boolean(isSearchable)}
323
- searchPlaceholder={`Search ${columnDef.header || 'options'}...`}
324
- maxHeight={columnDef.selectMaxHeight}
325
- className={columnDef.selectContentClassName}
326
- style={columnDef.selectContentStyle}
327
- >
328
- {columnDef.fieldOptions?.map((option, index) => {
329
- // Simple option item
330
- if ('value' in option && !('type' in option)) {
331
- return (
332
- <SelectItem key={`${option.value}-${index}`} value={String(option.value)}>
333
- {option.label}
334
- </SelectItem>
335
- );
336
- }
337
-
338
- // Separator
339
- if ('type' in option && option.type === 'separator') {
340
- return <SelectSeparator key={`separator-${index}`} />;
341
- }
342
-
343
- // Group with label
344
- if ('type' in option && option.type === 'group') {
345
- const groupOption = option as { type: 'group'; label: string; items: Array<{ value: string | number; label: string }> };
346
- return (
347
- <SelectGroup key={`group-${groupOption.label}-${index}`}>
348
- <SelectLabel>{groupOption.label}</SelectLabel>
349
- {groupOption.items.map((item: { value: string | number; label: string }) => (
350
- <SelectItem key={`${item.value}-${index}`} value={String(item.value)}>
351
- {item.label}
352
- </SelectItem>
353
- ))}
354
- </SelectGroup>
355
- );
356
- }
357
-
358
- return null;
359
- })}
360
- {showCreateOption && isCreatable && searchTerm.trim() && columnDef.onCreateNew && (
361
- <SelectItem
362
- key="__create_new__"
363
- value={`__create_new__${searchTerm}`}
364
- className="bg-main-100 font-medium border-t border-main-200"
365
- >
366
- Create "{searchTerm}"
367
- </SelectItem>
368
- )}
369
- </SelectContent>
370
- </Select>
371
- );
372
- }
373
-
374
- // Helper function to render the appropriate input type based on column configuration
375
- const renderEditField = <TData extends DataRecord>(
376
- column: Column<TData, unknown>,
377
- value: CellValue,
378
- onChange: (value: CellValue | Record<string, CellValue>) => void,
379
- editingData: Record<string, CellValue> = {},
380
- placeholder?: string
381
- ) => {
382
- const columnDef = column.columnDef as EditableColumnDef<TData>;
383
-
384
- // Check if column is editable (default: true)
385
- if (columnDef.editable === false) {
386
- // Return the original value as text if column is not editable
387
- return <span className="text-sm text-sec-600">{String(value ?? '')}</span>;
388
- }
389
-
390
- // Check for custom field type
391
- if (columnDef.fieldType === 'select' && columnDef.fieldOptions) {
392
- // Use editAccessorKey if specified, otherwise use the column id
393
- const accessorKey = columnDef.editAccessorKey || column.id;
394
- const currentValue = editingData[accessorKey] ?? value ?? '';
395
-
396
- return (
397
- <SelectEditField
398
- columnDef={columnDef}
399
- accessorKey={accessorKey}
400
- currentValue={currentValue}
401
- placeholder={placeholder}
402
- onChange={(newValue) => onChange({ [accessorKey]: newValue })}
403
- />
404
- );
405
- }
406
-
407
- // Check for number type (applies to number, currency, and percentage fields)
408
- if (columnDef.fieldType === 'number') {
409
- // Hide spinner arrows by default for all number-related fields
410
- // Currency and percentage columns use fieldType: 'number' with formatting in cell renderer
411
- // Only show spinners if explicitly set to false
412
- const hideSpinners = columnDef.hideNumberSpinners !== false; // Default to true
413
- return (
414
- <Input
415
- type="number"
416
- value={String(value ?? '')}
417
- onChange={(e) => onChange(e.target.value as unknown as CellValue)}
418
- placeholder={placeholder || `Enter ${columnDef.header || column.id}...`}
419
- className={`h-8 ${hideSpinners ? 'datatable-number-no-spinners' : ''}`}
420
- />
421
- );
422
- }
423
-
424
- // Check for date type
425
- if (columnDef.fieldType === 'date') {
426
- return (
427
- <Input
428
- type="date"
429
- value={String(value ?? '')}
430
- onChange={(e) => onChange(e.target.value as unknown as CellValue)}
431
- className="h-8"
432
- />
433
- );
434
- }
435
-
436
- // Default to text input
437
- return (
438
- <Input
439
- type="text"
440
- value={String(value ?? '')}
441
- onChange={(e) => onChange(e.target.value as unknown as CellValue)}
442
- placeholder={placeholder || `Enter ${columnDef.header || column.id}...`}
443
- className="h-8"
444
- />
445
- );
446
- };
447
-
448
- // Row component props interface
449
- interface RowProps {
450
- row: any;
451
- style?: React.CSSProperties;
452
- isEditing?: boolean;
453
- editingData?: Record<string, any>;
454
- onEditingDataChange?: (data: Record<string, any>) => void;
455
- onSaveEditing?: () => void;
456
- onCancelEditing?: () => void;
457
- getRowId?: (row: any, index: number) => string;
458
- grouping: string[];
459
- editingRowId?: string | null;
460
- hierarchical?: HierarchicalConfig & {
461
- state?: {
462
- isExpanded: (rowId: string) => boolean;
463
- hasChildren: (rowId: string) => boolean;
464
- getChildrenCount: (rowId: string) => number;
465
- toggleRow: (rowId: string) => void;
466
- };
467
- };
468
- actions?: Array<{
469
- label: string;
470
- onClick: (row: any) => void;
471
- icon?: any;
472
- variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost';
473
- disabled?: boolean | ((row?: any) => boolean);
474
- visible?: boolean | ((row?: any) => boolean);
475
- testId?: string;
476
- showInEditMode?: boolean;
477
- hideInViewMode?: boolean;
478
- showInViewMode?: boolean;
479
- hidden?: boolean;
480
- showForParent?: boolean;
481
- showForChild?: boolean;
482
- parentIcon?: any;
483
- childIcon?: any;
484
- parentLabel?: string;
485
- childLabel?: string;
486
- }>;
487
- rbac?: {
488
- pageId?: string;
489
- pageName?: string;
490
- };
491
- permissions?: {
492
- canRead: { can: boolean; isLoading: boolean };
493
- canCreate: { can: boolean; isLoading: boolean };
494
- canUpdate: { can: boolean; isLoading: boolean };
495
- canDelete: { can: boolean; isLoading: boolean };
496
- canExport: { can: boolean; isLoading: boolean };
497
- canImport: { can: boolean; isLoading: boolean };
498
- };
499
- }
500
-
501
- // Row component with proper memoization
502
- const RowComponent = React.memo(({
503
- row,
504
- style,
505
- isEditing,
506
- editingData,
507
- onEditingDataChange,
508
- onSaveEditing,
509
- onCancelEditing,
510
- getRowId,
511
- grouping,
512
- editingRowId,
513
- hierarchical,
514
- actions,
515
- rbac,
516
- permissions
517
- }: RowProps) => {
518
- const rowRef = useRef<HTMLTableRowElement>(null);
519
- const firstInputRef = useRef<HTMLInputElement>(null);
520
- const logger = React.useMemo(() => createLogger('RowComponent'), []);
521
-
522
- const rowId = getRowIdSafe(row.original, row.index, getRowId);
523
-
524
- // Hierarchical row styling - moved to top to avoid hoisting issues
525
- const hierarchicalRow = row.original as HierarchicalDataRow;
526
- const isHierarchical = hierarchical?.enabled && hierarchicalRow?.isParent !== undefined;
527
- const isParent = isHierarchical && hierarchicalRow.isParent;
528
- const isChild = isHierarchical && !hierarchicalRow.isParent;
529
-
530
- // Memoize visible cells to prevent unnecessary re-renders
531
- const visibleCells = React.useMemo(() => row.getVisibleCells(), [row]);
532
- const isSelected = React.useMemo(() => {
533
- return typeof row.getIsSelected === 'function' ? row.getIsSelected() : false;
534
- }, [row]);
535
-
536
- // Auto-focus first editable field when entering edit mode
537
- useEffect(() => {
538
- if (isEditing && firstInputRef.current) {
539
- firstInputRef.current.focus();
540
- firstInputRef.current.select();
541
- }
542
- }, [isEditing]);
543
-
544
- // Keyboard navigation (Enter to save, Escape to cancel)
545
- useEffect(() => {
546
- if (!isEditing) return;
547
-
548
- const handleKeyDown = (event: KeyboardEvent) => {
549
- const target = event.target as HTMLElement;
550
- if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') {
551
- if (event.key === 'Enter' && !event.shiftKey && target.tagName === 'INPUT') {
552
- event.preventDefault();
553
- onSaveEditing?.();
554
- } else if (event.key === 'Escape') {
555
- event.preventDefault();
556
- onCancelEditing?.();
557
- }
558
- }
559
- };
560
-
561
- const currentRow = rowRef.current;
562
- if (currentRow) {
563
- currentRow.addEventListener('keydown', handleKeyDown);
564
- return () => {
565
- currentRow.removeEventListener('keydown', handleKeyDown);
566
- };
567
- }
568
- }, [isEditing, onSaveEditing, onCancelEditing]);
569
-
570
- // Handle grouped rows
571
- if (row.getIsGrouped && row.getIsGrouped()) {
572
- const groupValue = row.getValue(grouping[0]);
573
- const subRowsCount = row.subRows?.length || 0;
574
- const isExpanded = row.getIsExpanded();
575
-
576
- // Get child rows for aggregation (convert TanStack Row to original data)
577
- const childRows: DataRecord[] = row.subRows?.map((subRow: any) => subRow.original) || [];
578
-
579
- // Render individual cells for each column with aggregation support
580
- return (
581
- <tr className="bg-sec-50 hover:bg-sec-100" style={style}>
582
- {visibleCells.map((cell: any, cellIndex: number) => {
583
- const columnDef = cell.column.columnDef as DataTableColumn<DataRecord>;
584
- const isGroupingColumn = cellIndex === 0 || grouping.includes(cell.column.id || '');
585
-
586
- // For the grouping column, show the group header with expand/collapse
587
- if (isGroupingColumn && cellIndex === 0) {
588
- return (
589
- <td
590
- key={cell.id}
591
- className={getTableCellClasses({
592
- isCompact: true,
593
- className: "px-3 py-2 flex items-center font-medium"
594
- })}
595
- >
596
- <Button
597
- variant="ghost"
598
- size="sm"
599
- onClick={() => row.toggleExpanded()}
600
- className="p-0 h-auto mr-2"
601
- >
602
- {isExpanded ? (
603
- <ChevronDown className="size-4" />
604
- ) : (
605
- <ChevronRight className="size-4" />
606
- )}
607
- </Button>
608
- <span className="text-sm">
609
- {String(groupValue)} ({subRowsCount} items)
610
- </span>
611
- </td>
612
- );
613
- }
614
-
615
- // For other columns, check if they have aggregation functions
616
- if (columnDef.aggregateFn && childRows.length > 0) {
617
- try {
618
- // Call the aggregation function with child rows
619
- const aggregatedValue = columnDef.aggregateFn(childRows, columnDef);
620
-
621
- // Use aggregateCell renderer if provided, otherwise use regular cell renderer
622
- let cellContent: React.ReactNode;
623
- if (columnDef.aggregateCell) {
624
- cellContent = columnDef.aggregateCell(aggregatedValue, childRows, columnDef);
625
- } else if (columnDef.cell) {
626
- // Use regular cell renderer with a mock cell context
627
- // Create a mock cell object for the aggregated value
628
- const mockCell = {
629
- ...cell,
630
- getValue: () => aggregatedValue,
631
- renderValue: () => aggregatedValue,
632
- };
633
- cellContent = flexRender(columnDef.cell, {
634
- ...mockCell,
635
- row: row,
636
- column: cell.column,
637
- cell: mockCell,
638
- getValue: () => aggregatedValue,
639
- renderValue: () => aggregatedValue,
640
- });
641
- } else {
642
- // Fallback to displaying the raw value
643
- cellContent = aggregatedValue != null ? String(aggregatedValue) : '';
644
- }
645
-
646
- return (
647
- <td
648
- key={cell.id}
649
- className={getTableCellClasses({
650
- isCompact: true,
651
- className: `px-3 py-2 ${cell.column.columnDef.meta?.align === 'right' ? 'text-right' : ''}`
652
- })}
653
- >
654
- {cellContent}
655
- </td>
656
- );
657
- } catch (error) {
658
- // If aggregation fails, show empty cell
659
- logger.warn('Error in aggregation function:', error);
660
- return (
661
- <td
662
- key={cell.id}
663
- className={getTableCellClasses({
664
- isCompact: true,
665
- className: "px-3 py-2"
666
- })}
667
- />
668
- );
669
- }
670
- }
671
-
672
- // For columns without aggregation, show empty cell
673
- return (
674
- <td
675
- key={cell.id}
676
- className={getTableCellClasses({
677
- isCompact: true,
678
- className: "px-3 py-2"
679
- })}
680
- />
681
- );
682
- })}
683
- </tr>
684
- );
685
- }
686
-
687
- // CRITICAL FIX: Removed the buggy filter that was hiding all rows when grouping is enabled
688
- // TanStack Table's getRowModel() already correctly handles collapsing/expanding groups,
689
- // returning only visible rows (group headers + children of expanded groups).
690
- // The previous check was incorrectly filtering out all child rows regardless of parent expansion state,
691
- // causing the bug where DataTable showed record count but no rows for large datasets.
692
- //
693
- // When grouping is enabled:
694
- // - Group headers are rendered above (lines 317-426)
695
- // - Child rows are ONLY in getRowModel().rows if their parent is expanded
696
- // - Collapsed groups' children are NOT in getRowModel().rows (handled by TanStack Table)
697
- //
698
- // Therefore, we should trust getRowModel() and render all rows it returns.
699
-
700
- // If we're in edit mode, use EditableRow for better UX (auto-focus, keyboard shortcuts)
701
- if (isEditing && editingData && onEditingDataChange && onSaveEditing && onCancelEditing) {
702
- return (
703
- <EditableRow
704
- row={row}
705
- editingData={editingData}
706
- onEditingDataChange={onEditingDataChange}
707
- onSave={onSaveEditing}
708
- onCancel={onCancelEditing}
709
- actions={actions ?? []}
710
- getRowId={getRowId}
711
- isParent={isParent}
712
- hierarchical={!!hierarchical}
713
- />
714
- );
715
- }
716
-
717
- // Regular row (not in edit mode)
718
- // visibleCells is already memoized above
719
-
720
- // Calculate indentation for child rows
721
- const indentSize = hierarchical?.indentSize || 24;
722
- const indentation = React.useMemo(() => {
723
- return isChild && hierarchical?.state ?
724
- calculateIndentation(hierarchicalRow, [], indentSize) : 0;
725
- }, [isChild, hierarchical?.state, hierarchicalRow, indentSize]);
726
-
727
- // Apply hierarchical row classes - combine with standard row classes
728
- const rowClassName = React.useMemo(() => {
729
- if (isHierarchical) {
730
- const hierarchicalClass = isParent
731
- ? hierarchical?.parentRowClassName || 'bg-main-50 hover:bg-main-100 font-medium'
732
- : hierarchical?.childRowClassName || 'bg-sec-25 hover:bg-sec-50';
733
- // Combine with standard row classes for consistent hover behavior
734
- return cn(
735
- getTableRowClasses({ isSelected, isVirtualized: false }),
736
- hierarchicalClass
737
- );
738
- }
739
- // Use standard row classes when not hierarchical
740
- return getTableRowClasses({ isSelected, isVirtualized: false });
741
- }, [isHierarchical, isParent, isSelected, hierarchical]);
742
-
743
- return (
744
- <tr
745
- ref={rowRef}
746
- key={row.id}
747
- role="row"
748
- style={{
749
- ...style,
750
- ...(isChild && indentation > 0 ? { paddingLeft: `${indentation}px` } : {})
751
- }}
752
- className={rowClassName}
753
- aria-selected={isSelected ? 'true' : 'false'}
754
- >
755
- {visibleCells.map((cell: any, cellIndex: number) => {
756
- const isFirstCell = cellIndex === 0;
757
- const shouldShowExpansionButton = isHierarchical && isParent && isFirstCell && hierarchical?.state;
758
- const isExpanded = shouldShowExpansionButton ? hierarchical?.state?.isExpanded(rowId) : false;
759
- const hasChildren = shouldShowExpansionButton ? hierarchical?.state?.hasChildren(rowId) : false;
760
-
761
- return (
762
- <td
763
- key={cell.id}
764
- className={getTableCellClasses({
765
- isCompact: true,
766
- className: `px-3 py-2 ${cell.column.id === 'actions' ? 'whitespace-nowrap' : 'whitespace-normal break-words'} ${cell.column.columnDef.meta?.align === 'right' ? 'text-right' : ''}`
767
- })}
768
- >
769
- {shouldShowExpansionButton && hasChildren && (
770
- <Button
771
- variant="ghost"
772
- size="sm"
773
- onClick={() => hierarchical?.state?.toggleRow(rowId)}
774
- className="size-6 p-0 flex-shrink-0"
775
- aria-label={isExpanded ? 'Collapse row' : 'Expand row'}
776
- title={isExpanded ? 'Collapse row' : 'Expand row'}
777
- >
778
- {isExpanded ? (
779
- <ChevronDown className="size-4" />
780
- ) : (
781
- <ChevronRight className="size-4" />
782
- )}
783
- </Button>
784
- )}
785
- {cell.column.id === 'actions' ? (
786
- <ActionButtons
787
- row={row}
788
- actions={actions}
789
- isEditing={isEditing}
790
- isParent={isParent}
791
- hierarchical={!!hierarchical}
792
- rbac={rbac}
793
- permissions={permissions}
794
- />
795
- ) : (
796
- flexRender(cell.column.columnDef.cell, {
797
- ...cell.getContext(),
798
- hierarchical: hierarchical,
799
- isParent: isParent,
800
- isChild: isChild,
801
- isHierarchical: isHierarchical,
802
- rowId: rowId,
803
- isExpanded: isExpanded,
804
- hasChildren: hasChildren,
805
- })
806
- )}
807
- </td>
808
- );
809
- })}
810
- </tr>
811
- );
812
- });
813
-
814
- RowComponent.displayName = 'RowComponent';
815
-
816
- // Custom comparison function for React.memo to prevent unnecessary re-renders
817
- // This compares the actual row data and props, not just object references
818
- const areRowPropsEqual = (prevProps: RowProps, nextProps: RowProps): boolean => {
819
- // Compare row by ID and index (stable identifiers)
820
- if (prevProps.row.id !== nextProps.row.id || prevProps.row.index !== nextProps.row.index) {
821
- return false;
822
- }
823
-
824
- // Compare editing state
825
- if (prevProps.isEditing !== nextProps.isEditing) {
826
- return false;
827
- }
828
-
829
- if (prevProps.editingRowId !== nextProps.editingRowId) {
830
- return false;
831
- }
832
-
833
- // Compare style object (shallow comparison)
834
- if (prevProps.style !== nextProps.style) {
835
- if (!prevProps.style || !nextProps.style) {
836
- return false;
837
- }
838
- // Convert to records for comparison
839
- const prevStyle = prevProps.style as Record<string, unknown>;
840
- const nextStyle = nextProps.style as Record<string, unknown>;
841
- const styleKeys = new Set([...Object.keys(prevStyle), ...Object.keys(nextStyle)]);
842
- for (const key of styleKeys) {
843
- if (prevStyle[key] !== nextStyle[key]) {
844
- return false;
845
- }
846
- }
847
- }
848
-
849
- // Compare other primitive props
850
- if (prevProps.grouping.length !== nextProps.grouping.length ||
851
- prevProps.grouping.some((id, i) => id !== nextProps.grouping[i])) {
852
- return false;
853
- }
854
-
855
- // For hierarchical state, compare the enabled flag and state functions
856
- if (prevProps.hierarchical?.enabled !== nextProps.hierarchical?.enabled) {
857
- return false;
858
- }
859
-
860
- // Compare row selection state by checking the function result
861
- const prevSelected = typeof prevProps.row.getIsSelected === 'function' ? prevProps.row.getIsSelected() : false;
862
- const nextSelected = typeof nextProps.row.getIsSelected === 'function' ? nextProps.row.getIsSelected() : false;
863
- if (prevSelected !== nextSelected) {
864
- return false;
865
- }
866
-
867
- // If all checks pass, props are equal
868
- return true;
869
- };
870
-
871
- // Use the memoized RowComponent with custom comparison
872
- const MemoizedRow = React.memo(RowComponent, areRowPropsEqual);
873
-
874
116
  /**
875
117
  * Unified table body component with intelligent virtualization
876
118
  */
@@ -887,7 +129,7 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
887
129
  onSaveEditing,
888
130
  onCancelEditing,
889
131
  grouping,
890
- aggregates,
132
+ aggregates: _aggregates,
891
133
  getRowId,
892
134
  emptyState,
893
135
  isFiltered,
@@ -895,33 +137,26 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
895
137
  enableFiltering = false,
896
138
  showFilterRow = false,
897
139
  dataLength,
898
- virtualHeight = 600,
140
+ virtualHeight: _virtualHeight = 600,
899
141
  forceVirtualization = false,
900
142
  hierarchical,
901
143
  actions = [],
902
144
  rbac,
903
- permissions
145
+ permissions,
904
146
  }: UnifiedTableBodyProps<TData>) {
905
- const logger = React.useMemo(() => createLogger('UnifiedTableBody'), []);
906
-
907
- const headerRef = useRef<HTMLTableSectionElement>(null);
147
+ const logger = createLogger('UnifiedTableBody');
148
+
908
149
  const bodyRef = useRef<HTMLTableSectionElement>(null);
909
150
  const parentRef = useRef<HTMLDivElement>(null);
910
151
 
911
- // Determine if virtualization should be used
912
152
  const shouldVirtualize = forceVirtualization || dataLength > VIRTUALIZATION_THRESHOLD;
913
-
914
- // Get table data
153
+
915
154
  const rows = table.getRowModel().rows;
916
155
  const headerGroups = table.getHeaderGroups();
917
156
 
918
- // CRITICAL FIX: Virtual scrolling requires a scroll container (parentRef).
919
- // If virtualization is enabled but no scroll container exists, fall back to standard rendering.
920
- // This fixes the bug where rows exist but nothing renders when virtualization is enabled.
921
157
  const hasScrollContainer = !!parentRef.current;
922
158
  const effectiveShouldVirtualize = shouldVirtualize && hasScrollContainer;
923
-
924
- // Virtual scrolling setup - only create virtualizer when we have a scroll container
159
+
925
160
  const virtualizer = useVirtualizer({
926
161
  count: effectiveShouldVirtualize ? rows.length : 0,
927
162
  getScrollElement: () => parentRef.current || null,
@@ -930,9 +165,7 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
930
165
  });
931
166
 
932
167
  const virtualRows = effectiveShouldVirtualize ? virtualizer.getVirtualItems() : [];
933
- const totalSize = effectiveShouldVirtualize ? virtualizer.getTotalSize() : 0;
934
-
935
- // Warning if virtualization is expected but no container exists
168
+
936
169
  useEffect(() => {
937
170
  if (shouldVirtualize && !hasScrollContainer) {
938
171
  logger.warn('Virtualization enabled but no scroll container found. Falling back to standard rendering.', {
@@ -942,17 +175,11 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
942
175
  }
943
176
  }, [shouldVirtualize, hasScrollContainer, rows.length, dataLength, logger]);
944
177
 
945
-
946
- // Render table content
947
178
  const renderTableContent = () => {
948
179
  if (rows.length === 0) {
949
180
  return (
950
181
  <tr>
951
- <td
952
- colSpan={table.getVisibleFlatColumns().length}
953
- className="px-3 py-2"
954
- role="status"
955
- >
182
+ <td colSpan={table.getVisibleFlatColumns().length} className="px-3 py-2" role="status">
956
183
  <EmptyState
957
184
  title={emptyState?.title}
958
185
  description={emptyState?.description}
@@ -967,14 +194,13 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
967
194
  }
968
195
 
969
196
  if (effectiveShouldVirtualize && virtualRows.length > 0) {
970
- // Virtualized rendering
971
197
  return virtualRows.map((virtualRow) => {
972
198
  const row = rows[virtualRow.index];
973
199
  if (!row) return null;
974
-
200
+
975
201
  const rowId = getRowIdSafe(row.original, row.index, getRowId);
976
202
  const isEditing = editingRowId === rowId;
977
-
203
+
978
204
  return (
979
205
  <MemoizedRow
980
206
  key={row.id}
@@ -1002,32 +228,31 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
1002
228
  />
1003
229
  );
1004
230
  });
1005
- } else {
1006
- // Standard rendering
1007
- return rows.map((row) => {
1008
- const rowId = getRowIdSafe(row.original, row.index, getRowId);
1009
- const isEditing = editingRowId === rowId;
1010
-
1011
- return (
1012
- <MemoizedRow
1013
- key={row.id}
1014
- row={row}
1015
- isEditing={isEditing}
1016
- editingData={editingData}
1017
- onEditingDataChange={onEditingDataChange}
1018
- onSaveEditing={onSaveEditing}
1019
- onCancelEditing={onCancelEditing}
1020
- getRowId={getRowId}
1021
- grouping={grouping}
1022
- editingRowId={editingRowId}
1023
- hierarchical={hierarchical}
1024
- actions={actions}
1025
- rbac={rbac}
1026
- permissions={permissions}
1027
- />
1028
- );
1029
- });
1030
231
  }
232
+
233
+ return rows.map((row) => {
234
+ const rowId = getRowIdSafe(row.original, row.index, getRowId);
235
+ const isEditing = editingRowId === rowId;
236
+
237
+ return (
238
+ <MemoizedRow
239
+ key={row.id}
240
+ row={row}
241
+ isEditing={isEditing}
242
+ editingData={editingData}
243
+ onEditingDataChange={onEditingDataChange}
244
+ onSaveEditing={onSaveEditing}
245
+ onCancelEditing={onCancelEditing}
246
+ getRowId={getRowId}
247
+ grouping={grouping}
248
+ editingRowId={editingRowId}
249
+ hierarchical={hierarchical}
250
+ actions={actions}
251
+ rbac={rbac}
252
+ permissions={permissions}
253
+ />
254
+ );
255
+ });
1031
256
  };
1032
257
 
1033
258
  return (
@@ -1037,60 +262,50 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
1037
262
  {isCreating && (
1038
263
  <tr>
1039
264
  {headerGroups[0]?.headers
1040
- ?.filter(header => {
1041
- // Check if getIsVisible method exists and call it, otherwise assume visible
1042
- return typeof header.column.getIsVisible === 'function'
1043
- ? header.column.getIsVisible()
1044
- : true;
265
+ ?.filter((header) => {
266
+ return typeof header.column.getIsVisible === 'function' ? header.column.getIsVisible() : true;
1045
267
  })
1046
- ?.filter(header => header.column.id !== 'actions') // Only exclude actions column
268
+ ?.filter((header) => header.column.id !== 'actions')
1047
269
  ?.map((header) => {
1048
- // Handle select column separately - render empty checkbox cell for alignment
1049
270
  if (header.column.id === 'select') {
1050
271
  return (
1051
- <td
1052
- key={header.column.id}
272
+ <td
273
+ key={header.column.id}
1053
274
  className={getTableCellClasses({
1054
275
  isCompact: true,
1055
- className: "px-3 py-2"
276
+ className: 'px-3 py-2',
1056
277
  })}
1057
278
  >
1058
279
  {/* Empty cell for selection checkbox to maintain alignment */}
1059
280
  </td>
1060
281
  );
1061
282
  }
1062
-
1063
- // Render edit fields for data columns
1064
- // Determine the correct key to use for creationData
1065
- // Priority: editAccessorKey > accessorKey > column.id
283
+
1066
284
  const columnDef = header.column.columnDef as EditableColumnDef<TData>;
1067
285
  const dataKey = columnDef.editAccessorKey || columnDef.accessorKey || header.column.id;
1068
-
1069
- // Always render a cell to maintain alignment - renderEditField always returns something
286
+
1070
287
  const editField = renderEditField(
1071
- header.column,
1072
- creationData[dataKey] ?? creationData[header.column.id] ?? '',
288
+ header.column,
289
+ creationData[dataKey] ?? creationData[header.column.id] ?? '',
1073
290
  (value) => {
1074
291
  if (typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date)) {
1075
292
  onCreationDataChange({ ...creationData, ...(value as Record<string, CellValue>) });
1076
293
  } else {
1077
- // Use the determined dataKey for consistent data access
1078
294
  onCreationDataChange({ ...creationData, [dataKey]: value as CellValue });
1079
295
  }
1080
- },
296
+ },
1081
297
  creationData
1082
298
  );
1083
-
299
+
1084
300
  return (
1085
- <td
1086
- key={header.column.id}
301
+ <td
302
+ key={header.column.id}
1087
303
  className={getTableCellClasses({
1088
304
  isCompact: true,
1089
- className: "px-3 py-2"
305
+ className: 'px-3 py-2',
1090
306
  })}
1091
307
  >
1092
308
  {editField || (
1093
- // Fallback: render a text input if renderEditField somehow returns nothing
1094
309
  <Input
1095
310
  type="text"
1096
311
  value={String(creationData[dataKey] ?? creationData[header.column.id] ?? '')}
@@ -1102,10 +317,10 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
1102
317
  </td>
1103
318
  );
1104
319
  })}
1105
- <td
320
+ <td
1106
321
  className={getTableCellClasses({
1107
322
  isCompact: true,
1108
- className: "px-3 py-2 flex gap-1"
323
+ className: 'px-3 py-2 flex gap-1',
1109
324
  })}
1110
325
  >
1111
326
  <button
@@ -1129,15 +344,10 @@ export function UnifiedTableBody<TData extends Record<string, any>>({
1129
344
  </td>
1130
345
  </tr>
1131
346
  )}
1132
-
347
+
1133
348
  {/* Filter Row */}
1134
- {showFilterRow && enableFiltering && (
1135
- <FilterRow
1136
- table={table}
1137
- visibleColumns={table.getHeaderGroups()[0]?.headers || []}
1138
- />
1139
- )}
1140
-
349
+ {showFilterRow && enableFiltering && <FilterRow table={table} visibleColumns={table.getHeaderGroups()[0]?.headers || []} />}
350
+
1141
351
  {/* Table Content */}
1142
352
  {renderTableContent()}
1143
353
  </tbody>