@jmruthers/pace-core 0.5.76 → 0.5.78

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 (447) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/{RBACService-C4udt_Zp.d.ts → AuthService-Df3IozMG.d.ts} +10 -118
  3. package/dist/{DataTable-ntgmhO2W.d.ts → DataTable-BE0OXZKQ.d.ts} +9 -2
  4. package/dist/{DataTable-4GAVPIEG.js → DataTable-ETGVF4Y5.js} +50 -13
  5. package/dist/{PublicLoadingSpinner-BiNER8F5.d.ts → PublicLoadingSpinner-CnUaz0vG.d.ts} +5 -2
  6. package/dist/{UnifiedAuthProvider-Bj6YCf7c.d.ts → UnifiedAuthProvider-B391Aqum.d.ts} +42 -45
  7. package/dist/{UnifiedAuthProvider-3NKDOSOK.js → UnifiedAuthProvider-P5SOJAQ6.js} +4 -5
  8. package/dist/{api-DDMUKIUD.js → api-KG4A2X7P.js} +9 -3
  9. package/dist/{audit-6TOCAMKO.js → audit-65VNHEV2.js} +2 -2
  10. package/dist/{chunk-K34IM5CT.js → chunk-2OGV6IRV.js} +196 -626
  11. package/dist/chunk-2OGV6IRV.js.map +1 -0
  12. package/dist/{chunk-NTNILOBC.js → chunk-5BO3MI5Y.js} +4 -4
  13. package/dist/{chunk-XLZ7U46Z.js → chunk-CVMVPYAL.js} +9 -60
  14. package/dist/chunk-CVMVPYAL.js.map +1 -0
  15. package/dist/{chunk-URUTVZ7N.js → chunk-FL4ZCQLD.js} +2 -2
  16. package/dist/{chunk-LW7MMEAQ.js → chunk-FT2M4R4F.js} +2 -2
  17. package/dist/{chunk-5BSLGBYI.js → chunk-JCQZ6LA7.js} +2 -8
  18. package/dist/{chunk-5BSLGBYI.js.map → chunk-JCQZ6LA7.js.map} +1 -1
  19. package/dist/{chunk-KHJS6VIA.js → chunk-LRQ6RBJC.js} +157 -112
  20. package/dist/chunk-LRQ6RBJC.js.map +1 -0
  21. package/dist/{chunk-WN6XJWOS.js → chunk-MNJXXD6C.js} +274 -743
  22. package/dist/chunk-MNJXXD6C.js.map +1 -0
  23. package/dist/{chunk-KK73ZB4E.js → chunk-PTR5PMPE.js} +153 -132
  24. package/dist/chunk-PTR5PMPE.js.map +1 -0
  25. package/dist/{chunk-B2WTCLCV.js → chunk-Q7APDV6H.js} +18 -8
  26. package/dist/chunk-Q7APDV6H.js.map +1 -0
  27. package/dist/{chunk-A4FUBC7B.js → chunk-QGVSOUJ2.js} +2 -4
  28. package/dist/{chunk-A4FUBC7B.js.map → chunk-QGVSOUJ2.js.map} +1 -1
  29. package/dist/{chunk-FGMFQSHX.js → chunk-S63MFSY6.js} +500 -551
  30. package/dist/chunk-S63MFSY6.js.map +1 -0
  31. package/dist/{chunk-AFGTSUAD.js → chunk-VSOKOFRF.js} +4 -4
  32. package/dist/chunk-WUXCWRL6.js +20 -0
  33. package/dist/chunk-WUXCWRL6.js.map +1 -0
  34. package/dist/{chunk-Y6TXWPJO.js → chunk-YVVGHRGI.js} +105 -31
  35. package/dist/chunk-YVVGHRGI.js.map +1 -0
  36. package/dist/{chunk-M5IWZRBT.js → chunk-ZMNXIJP4.js} +2187 -981
  37. package/dist/chunk-ZMNXIJP4.js.map +1 -0
  38. package/dist/components.d.ts +6 -6
  39. package/dist/components.js +14 -18
  40. package/dist/components.js.map +1 -1
  41. package/dist/{database-C3Szpi5J.d.ts → database-BXAfr2Y_.d.ts} +18 -0
  42. package/dist/hooks.d.ts +5 -5
  43. package/dist/hooks.js +8 -9
  44. package/dist/hooks.js.map +1 -1
  45. package/dist/index.d.ts +19 -27
  46. package/dist/index.js +21 -29
  47. package/dist/index.js.map +1 -1
  48. package/dist/{organisation-BtshODVF.d.ts → organisation-D6qRDtbF.d.ts} +1 -1
  49. package/dist/providers.d.ts +7 -21
  50. package/dist/providers.js +3 -10
  51. package/dist/rbac/index.d.ts +71 -221
  52. package/dist/rbac/index.js +15 -16
  53. package/dist/{types-CGX9Vyf5.d.ts → types-BDg1mAGG.d.ts} +36 -6
  54. package/dist/types.d.ts +3 -3
  55. package/dist/types.js +61 -18
  56. package/dist/types.js.map +1 -1
  57. package/dist/{unified-CM7T0aTK.d.ts → unified-DQ4VcT7H.d.ts} +1 -1
  58. package/dist/{usePublicRouteParams-B-CumWRc.d.ts → usePublicRouteParams-BlgwXweB.d.ts} +3 -3
  59. package/dist/utils.d.ts +2 -2
  60. package/dist/utils.js +52 -9
  61. package/dist/utils.js.map +1 -1
  62. package/docs/CONTENT_AUDIT_REPORT.md +253 -0
  63. package/docs/DOCUMENTATION_AUDIT.md +172 -0
  64. package/docs/README.md +142 -147
  65. package/docs/STYLE_GUIDE.md +37 -0
  66. package/docs/api/classes/ColumnFactory.md +17 -17
  67. package/docs/api/classes/ErrorBoundary.md +1 -1
  68. package/docs/api/classes/InvalidScopeError.md +4 -4
  69. package/docs/api/classes/MissingUserContextError.md +4 -4
  70. package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
  71. package/docs/api/classes/PermissionDeniedError.md +5 -5
  72. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  73. package/docs/api/classes/RBACAuditManager.md +8 -8
  74. package/docs/api/classes/RBACCache.md +35 -5
  75. package/docs/api/classes/RBACEngine.md +49 -20
  76. package/docs/api/classes/RBACError.md +4 -4
  77. package/docs/api/classes/RBACNotInitializedError.md +4 -4
  78. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  79. package/docs/api/classes/StorageUtils.md +1 -1
  80. package/docs/api/enums/FileCategory.md +1 -1
  81. package/docs/api/interfaces/AggregateConfig.md +4 -4
  82. package/docs/api/interfaces/ButtonProps.md +1 -1
  83. package/docs/api/interfaces/CardProps.md +1 -1
  84. package/docs/api/interfaces/ColorPalette.md +1 -1
  85. package/docs/api/interfaces/ColorShade.md +1 -1
  86. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  87. package/docs/api/interfaces/DataRecord.md +11 -0
  88. package/docs/api/interfaces/DataTableAction.md +65 -29
  89. package/docs/api/interfaces/DataTableColumn.md +36 -23
  90. package/docs/api/interfaces/DataTableProps.md +80 -38
  91. package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
  92. package/docs/api/interfaces/EmptyStateConfig.md +5 -5
  93. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  94. package/docs/api/interfaces/EventLogoProps.md +1 -1
  95. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  96. package/docs/api/interfaces/FileMetadata.md +1 -1
  97. package/docs/api/interfaces/FileReference.md +1 -1
  98. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  99. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  100. package/docs/api/interfaces/FileUploadProps.md +1 -1
  101. package/docs/api/interfaces/FooterProps.md +1 -1
  102. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  103. package/docs/api/interfaces/InputProps.md +1 -1
  104. package/docs/api/interfaces/LabelProps.md +1 -1
  105. package/docs/api/interfaces/LoginFormProps.md +1 -1
  106. package/docs/api/interfaces/NavigationAccessRecord.md +11 -11
  107. package/docs/api/interfaces/NavigationContextType.md +9 -9
  108. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  109. package/docs/api/interfaces/NavigationItem.md +1 -1
  110. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  111. package/docs/api/interfaces/NavigationProviderProps.md +7 -7
  112. package/docs/api/interfaces/Organisation.md +1 -1
  113. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  114. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  115. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  116. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  117. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  118. package/docs/api/interfaces/PaceLoginPageProps.md +16 -3
  119. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  120. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  121. package/docs/api/interfaces/PagePermissionGuardProps.md +2 -2
  122. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  123. package/docs/api/interfaces/PaletteData.md +1 -1
  124. package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
  125. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  126. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  127. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  128. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  129. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  130. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  131. package/docs/api/interfaces/RBACConfig.md +1 -1
  132. package/docs/api/interfaces/RBACLogger.md +1 -1
  133. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  134. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  135. package/docs/api/interfaces/RouteAccessRecord.md +2 -2
  136. package/docs/api/interfaces/RouteConfig.md +2 -2
  137. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  138. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  139. package/docs/api/interfaces/StorageConfig.md +1 -1
  140. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  141. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  142. package/docs/api/interfaces/StorageListOptions.md +1 -1
  143. package/docs/api/interfaces/StorageListResult.md +1 -1
  144. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  145. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  146. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  147. package/docs/api/interfaces/StyleImport.md +1 -1
  148. package/docs/api/interfaces/SwitchProps.md +1 -1
  149. package/docs/api/interfaces/ToastActionElement.md +1 -1
  150. package/docs/api/interfaces/ToastProps.md +1 -1
  151. package/docs/api/interfaces/UnifiedAuthContextType.md +94 -521
  152. package/docs/api/interfaces/UnifiedAuthProviderProps.md +16 -16
  153. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  154. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  155. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  156. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  157. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  158. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  159. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  160. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  161. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  162. package/docs/api/interfaces/UserEventAccess.md +11 -11
  163. package/docs/api/interfaces/UserMenuProps.md +1 -1
  164. package/docs/api/interfaces/UserProfile.md +1 -1
  165. package/docs/api/modules.md +251 -269
  166. package/docs/api-reference/components.md +193 -0
  167. package/docs/api-reference/hooks.md +265 -0
  168. package/docs/api-reference/providers.md +6 -0
  169. package/docs/api-reference/types.md +6 -0
  170. package/docs/api-reference/utilities.md +207 -0
  171. package/docs/architecture/README.md +6 -0
  172. package/docs/{database-schema-requirements.md → architecture/database-schema-requirements.md} +6 -0
  173. package/docs/architecture/rbac-security-architecture.md +258 -0
  174. package/docs/architecture/services.md +9 -1
  175. package/docs/best-practices/README.md +6 -0
  176. package/docs/best-practices/accessibility.md +6 -0
  177. package/docs/{common-patterns.md → best-practices/common-patterns.md} +6 -0
  178. package/docs/best-practices/deployment.md +6 -0
  179. package/docs/best-practices/performance.md +475 -2
  180. package/docs/best-practices/security.md +6 -0
  181. package/docs/best-practices/testing.md +6 -0
  182. package/docs/core-concepts/authentication.md +6 -0
  183. package/docs/core-concepts/events.md +6 -0
  184. package/docs/core-concepts/organisations.md +6 -0
  185. package/docs/core-concepts/permissions.md +6 -0
  186. package/docs/core-concepts/rbac-system.md +8 -0
  187. package/docs/documentation-index.md +121 -182
  188. package/docs/{consuming-app-vite-config.md → getting-started/consuming-app-vite-config.md} +6 -0
  189. package/docs/getting-started/documentation-index.md +40 -0
  190. package/docs/getting-started/examples/README.md +878 -35
  191. package/docs/{faq.md → getting-started/faq.md} +7 -1
  192. package/docs/getting-started/installation-guide.md +6 -0
  193. package/docs/{quick-reference.md → getting-started/quick-reference.md} +6 -0
  194. package/docs/implementation-guides/app-layout.md +6 -0
  195. package/docs/implementation-guides/authentication.md +1021 -0
  196. package/docs/implementation-guides/component-styling.md +6 -0
  197. package/docs/implementation-guides/data-tables.md +1264 -2076
  198. package/docs/implementation-guides/dynamic-colors.md +6 -0
  199. package/docs/implementation-guides/event-theming-summary.md +6 -0
  200. package/docs/{file-reference-system.md → implementation-guides/file-reference-system.md} +6 -0
  201. package/docs/implementation-guides/file-upload-storage.md +6 -0
  202. package/docs/implementation-guides/forms.md +6 -0
  203. package/docs/implementation-guides/inactivity-tracking.md +6 -0
  204. package/docs/implementation-guides/navigation.md +6 -0
  205. package/docs/implementation-guides/organisation-security.md +6 -0
  206. package/docs/implementation-guides/permission-enforcement.md +6 -0
  207. package/docs/implementation-guides/public-pages-advanced.md +6 -0
  208. package/docs/implementation-guides/public-pages.md +6 -0
  209. package/docs/migration/MIGRATION_GUIDE.md +827 -351
  210. package/docs/migration/README.md +7 -1
  211. package/docs/migration/organisation-context-timing-fix.md +6 -0
  212. package/docs/migration/rbac-migration.md +44 -1
  213. package/docs/migration/service-architecture.md +6 -0
  214. package/docs/migration/v0.4.15-tailwind-scanning.md +6 -0
  215. package/docs/migration/v0.4.16-css-first-approach.md +6 -0
  216. package/docs/migration/v0.4.17-source-path-fix.md +6 -0
  217. package/docs/rbac/README-rbac-rls-integration.md +6 -0
  218. package/docs/rbac/README.md +6 -0
  219. package/docs/rbac/advanced-patterns.md +6 -0
  220. package/docs/rbac/api-reference.md +7 -1
  221. package/docs/rbac/breaking-changes-v3.md +222 -0
  222. package/docs/rbac/examples/rbac-rls-integration-example.md +6 -0
  223. package/docs/rbac/examples.md +6 -0
  224. package/docs/rbac/getting-started.md +6 -0
  225. package/docs/rbac/migration-guide.md +260 -0
  226. package/docs/rbac/quick-start.md +70 -13
  227. package/docs/rbac/rbac-rls-integration.md +6 -0
  228. package/docs/rbac/super-admin-guide.md +6 -0
  229. package/docs/rbac/troubleshooting.md +6 -0
  230. package/docs/security/README.md +6 -0
  231. package/docs/security/checklist.md +6 -0
  232. package/docs/styles/README.md +7 -1
  233. package/docs/{usage.md → styles/usage.md} +6 -0
  234. package/docs/testing/README.md +6 -0
  235. package/docs/{visual-testing.md → testing/visual-testing.md} +6 -0
  236. package/docs/troubleshooting/README.md +387 -5
  237. package/docs/troubleshooting/cake-page-permission-guard-issue-summary.md +6 -0
  238. package/docs/troubleshooting/common-issues.md +6 -0
  239. package/docs/troubleshooting/database-view-compatibility.md +6 -0
  240. package/docs/troubleshooting/organisation-context-setup.md +6 -0
  241. package/docs/troubleshooting/react-hooks-issue-analysis.md +6 -0
  242. package/docs/troubleshooting/styling-issues.md +6 -0
  243. package/docs/troubleshooting/tailwind-content-scanning.md +6 -0
  244. package/package.json +1 -1
  245. package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -1
  246. package/src/__tests__/helpers/test-providers.tsx +3 -53
  247. package/src/components/DataTable/DataTable.test.tsx +319 -0
  248. package/src/components/DataTable/DataTable.tsx +32 -11
  249. package/src/components/DataTable/__tests__/{DataTable.comprehensive.test.tsx → DataTable.comprehensive.test.tsx.skip} +6 -4
  250. package/src/components/DataTable/__tests__/{DataTable.test.tsx → DataTable.test.tsx.skip} +6 -4
  251. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +31 -9
  252. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +601 -0
  253. package/src/components/DataTable/__tests__/keyboard.test.tsx +615 -0
  254. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +639 -0
  255. package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx.skip +330 -0
  256. package/src/components/DataTable/components/AccessDeniedPage.tsx +2 -2
  257. package/src/components/DataTable/components/ActionButtons.tsx +88 -104
  258. package/src/components/DataTable/components/DataTableCore.tsx +309 -337
  259. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +4 -2
  260. package/src/components/DataTable/components/DataTableModals.tsx +22 -1
  261. package/src/components/DataTable/components/EditableRow.tsx +69 -84
  262. package/src/components/DataTable/components/EmptyState.tsx +5 -1
  263. package/src/components/DataTable/components/ImportModal.tsx +65 -36
  264. package/src/components/DataTable/components/PaginationControls.tsx +40 -100
  265. package/src/components/DataTable/components/UnifiedTableBody.tsx +125 -148
  266. package/src/components/DataTable/context/DataTableContext.tsx +1 -1
  267. package/src/components/DataTable/core/ColumnFactory.ts +5 -0
  268. package/src/components/DataTable/examples/HierarchicalActionsExample.tsx +12 -10
  269. package/src/components/DataTable/examples/HierarchicalExample.tsx +1 -1
  270. package/src/components/DataTable/examples/InitialPageSizeExample.tsx +1 -0
  271. package/src/components/DataTable/examples/PerformanceExample.tsx +1 -0
  272. package/src/components/DataTable/hooks/__tests__/useColumnOrderPersistence.test.ts +1 -5
  273. package/src/components/DataTable/hooks/__tests__/useColumnVisibilityPersistence.test.ts +167 -0
  274. package/src/components/DataTable/hooks/index.ts +7 -0
  275. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +32 -15
  276. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +102 -0
  277. package/src/components/DataTable/hooks/useDataTableConfiguration.ts +89 -0
  278. package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +117 -0
  279. package/src/components/DataTable/hooks/useDataTablePermissions.ts +71 -27
  280. package/src/components/DataTable/hooks/useDataTableState.ts +39 -11
  281. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +33 -0
  282. package/src/components/DataTable/hooks/useHierarchicalState.ts +15 -1
  283. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +447 -0
  284. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +94 -0
  285. package/src/components/DataTable/hooks/useTableColumns.ts +10 -7
  286. package/src/components/DataTable/hooks/useTableHandlers.ts +174 -0
  287. package/src/components/DataTable/index.ts +12 -3
  288. package/src/components/DataTable/types.ts +129 -9
  289. package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +159 -22
  290. package/src/components/DataTable/utils/__tests__/flexibleImport.test.ts +111 -0
  291. package/src/components/DataTable/utils/__tests__/rowUtils.test.ts +15 -29
  292. package/src/components/DataTable/utils/a11yUtils.ts +244 -0
  293. package/src/components/DataTable/utils/debugTools.ts +609 -0
  294. package/src/components/DataTable/utils/exportUtils.ts +114 -16
  295. package/src/components/DataTable/utils/flexibleImport.ts +202 -32
  296. package/src/components/DataTable/utils/hierarchicalUtils.ts +1 -1
  297. package/src/components/DataTable/utils/index.ts +2 -0
  298. package/src/components/DataTable/utils/paginationUtils.ts +350 -0
  299. package/src/components/DataTable/utils/rowUtils.ts +6 -5
  300. package/src/components/NavigationMenu/NavigationMenu.test.tsx +19 -24
  301. package/src/components/NavigationMenu/NavigationMenu.tsx +19 -8
  302. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +1 -23
  303. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +56 -6
  304. package/src/components/PaceLoginPage/PaceLoginPage.tsx +137 -13
  305. package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +1 -1
  306. package/src/components/Select/Select.tsx +1 -0
  307. package/src/components/examples/PermissionExample.tsx +173 -0
  308. package/src/examples/CorrectPublicPageImplementation.tsx +301 -0
  309. package/src/examples/PublicEventPage.tsx +274 -0
  310. package/src/examples/PublicPageApp.tsx +308 -0
  311. package/src/examples/PublicPageUsageExample.tsx +216 -0
  312. package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +12 -1
  313. package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +129 -17
  314. package/src/hooks/__tests__/useRBAC.unit.test.ts +151 -846
  315. package/src/hooks/useOrganisationPermissions.test.ts +42 -18
  316. package/src/hooks/useOrganisationPermissions.ts +12 -6
  317. package/src/hooks/useOrganisationSecurity.test.ts +138 -85
  318. package/src/hooks/useOrganisationSecurity.ts +41 -10
  319. package/src/index.ts +0 -1
  320. package/src/providers/AuthProvider.simplified.tsx +880 -0
  321. package/src/providers/UnifiedAuthProvider.test.simple.tsx +8 -8
  322. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +29 -19
  323. package/src/providers/index.ts +0 -1
  324. package/src/providers/services/EventServiceProvider.tsx +19 -15
  325. package/src/providers/services/InactivityServiceProvider.tsx +19 -15
  326. package/src/providers/services/OrganisationServiceProvider.tsx +19 -15
  327. package/src/providers/services/UnifiedAuthProvider.tsx +156 -127
  328. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +1 -1
  329. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -3
  330. package/src/rbac/README.md +1 -1
  331. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +25 -27
  332. package/src/rbac/__tests__/auth-rbac-security.integration.test.tsx +313 -0
  333. package/src/rbac/__tests__/engine.comprehensive.test.ts +114 -348
  334. package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +28 -110
  335. package/src/rbac/__tests__/rbac-engine-simplified.test.ts +33 -85
  336. package/src/rbac/__tests__/scenarios.user-role.test.tsx +2 -2
  337. package/src/rbac/adapters.tsx +26 -69
  338. package/src/rbac/api.test.ts +90 -27
  339. package/src/rbac/api.ts +61 -10
  340. package/src/rbac/audit.test.ts +33 -38
  341. package/src/rbac/audit.ts +21 -6
  342. package/src/rbac/cache.ts +33 -1
  343. package/src/rbac/components/NavigationGuard.tsx +11 -11
  344. package/src/rbac/components/NavigationProvider.test.tsx +11 -5
  345. package/src/rbac/components/NavigationProvider.tsx +37 -13
  346. package/src/rbac/components/PagePermissionGuard.tsx +111 -50
  347. package/src/rbac/components/PagePermissionProvider.tsx +5 -5
  348. package/src/rbac/components/PermissionEnforcer.tsx +11 -11
  349. package/src/rbac/components/RoleBasedRouter.tsx +5 -5
  350. package/src/rbac/components/SecureDataProvider.tsx +5 -5
  351. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +8 -8
  352. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +14 -14
  353. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +12 -12
  354. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +6 -6
  355. package/src/rbac/engine.test.simple.ts +19 -13
  356. package/src/rbac/engine.test.ts +1 -0
  357. package/src/rbac/engine.ts +330 -766
  358. package/src/rbac/errors.ts +156 -0
  359. package/src/rbac/hooks/usePermissions.ts +32 -10
  360. package/src/rbac/hooks/useRBAC.test.ts +126 -512
  361. package/src/rbac/hooks/useRBAC.ts +147 -193
  362. package/src/rbac/hooks/useResolvedScope.ts +12 -0
  363. package/src/rbac/index.ts +7 -4
  364. package/src/rbac/security.ts +109 -18
  365. package/src/rbac/types.ts +12 -1
  366. package/src/services/AuthService.ts +2 -15
  367. package/src/services/EventService.ts +43 -46
  368. package/src/services/OrganisationService.ts +51 -31
  369. package/src/services/__tests__/AuthService.test.ts +1 -1
  370. package/src/services/__tests__/EventService.test.ts +1 -1
  371. package/src/services/__tests__/OrganisationService.test.ts +1 -1
  372. package/src/services/base/BaseService.ts +8 -0
  373. package/src/styles/base.css +208 -0
  374. package/src/styles/semantic.css +24 -0
  375. package/src/types/database.generated.ts +7347 -0
  376. package/src/types/database.ts +20 -0
  377. package/src/utils/logger.ts +179 -0
  378. package/src/utils/organisationContext.ts +11 -4
  379. package/src/utils/storage/__tests__/helpers.unit.test.ts +6 -2
  380. package/dist/appNameResolver-UURKN7NF.js +0 -22
  381. package/dist/audit-6TOCAMKO.js.map +0 -1
  382. package/dist/chunk-B2WTCLCV.js.map +0 -1
  383. package/dist/chunk-FGMFQSHX.js.map +0 -1
  384. package/dist/chunk-K34IM5CT.js.map +0 -1
  385. package/dist/chunk-KHJS6VIA.js.map +0 -1
  386. package/dist/chunk-KK73ZB4E.js.map +0 -1
  387. package/dist/chunk-M5IWZRBT.js.map +0 -1
  388. package/dist/chunk-ULBI5JGB.js +0 -109
  389. package/dist/chunk-ULBI5JGB.js.map +0 -1
  390. package/dist/chunk-WN6XJWOS.js.map +0 -1
  391. package/dist/chunk-XLZ7U46Z.js.map +0 -1
  392. package/dist/chunk-Y6TXWPJO.js.map +0 -1
  393. package/docs/DOCUMENTATION_CHECKLIST.md +0 -281
  394. package/docs/TERMINOLOGY.md +0 -231
  395. package/docs/api/interfaces/RBACContextType.md +0 -468
  396. package/docs/api/interfaces/RBACProviderProps.md +0 -107
  397. package/docs/best-practices/performance-expansion.md +0 -473
  398. package/docs/breaking-changes.md +0 -179
  399. package/docs/consuming-app-example.md +0 -290
  400. package/docs/documentation-templates.md +0 -539
  401. package/docs/examples/navigation-menu-auth-fix.md +0 -344
  402. package/docs/getting-started/examples/basic-auth-app.md +0 -520
  403. package/docs/getting-started/examples/full-featured-app.md +0 -616
  404. package/docs/getting-started/quick-start.md +0 -376
  405. package/docs/implementation-guides/datatable-filtering.md +0 -313
  406. package/docs/implementation-guides/datatable-rbac-usage.md +0 -317
  407. package/docs/implementation-guides/hierarchical-datatable.md +0 -850
  408. package/docs/implementation-guides/large-datasets.md +0 -281
  409. package/docs/implementation-guides/performance.md +0 -403
  410. package/docs/migration/quick-migration-guide.md +0 -320
  411. package/docs/migration-guide.md +0 -193
  412. package/docs/migration-guides/unified-auth-provider-mandatory-timeouts.md +0 -226
  413. package/docs/performance/README.md +0 -551
  414. package/docs/style-guide.md +0 -964
  415. package/docs/troubleshooting/authentication-issues.md +0 -334
  416. package/docs/troubleshooting/debugging.md +0 -1117
  417. package/docs/troubleshooting/migration.md +0 -918
  418. package/src/__tests__/hooks/usePermissions.test.ts +0 -261
  419. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +0 -574
  420. package/src/hooks/__tests__/ServiceHooks.test.tsx +0 -613
  421. package/src/hooks/services/__tests__/useServiceHooks.test.tsx +0 -137
  422. package/src/hooks/services/usePermissions.ts +0 -70
  423. package/src/hooks/services/useRBACService.ts +0 -30
  424. package/src/hooks/usePermissionCheck.ts +0 -150
  425. package/src/providers/__tests__/ServiceProviders.test.tsx +0 -477
  426. package/src/providers/services/RBACServiceProvider.tsx +0 -79
  427. package/src/rbac/__tests__/integration.authflow.test.tsx +0 -119
  428. package/src/rbac/__tests__/integration.navigation.test.tsx +0 -69
  429. package/src/rbac/__tests__/integration.securedata.test.tsx +0 -92
  430. package/src/rbac/__tests__/integration.smoke.test.tsx +0 -73
  431. package/src/rbac/providers/RBACProvider.tsx +0 -645
  432. package/src/rbac/providers/__tests__/RBACProvider.integration.test.tsx +0 -688
  433. package/src/rbac/providers/__tests__/RBACProvider.test.tsx +0 -1186
  434. package/src/rbac/providers/index.ts +0 -11
  435. package/src/services/RBACService.ts +0 -522
  436. package/src/services/__tests__/RBACService.test.ts +0 -492
  437. package/src/services/interfaces/IRBACService.ts +0 -62
  438. package/src/utils/appNameResolver.test 2.ts +0 -494
  439. /package/dist/{DataTable-4GAVPIEG.js.map → DataTable-ETGVF4Y5.js.map} +0 -0
  440. /package/dist/{UnifiedAuthProvider-3NKDOSOK.js.map → UnifiedAuthProvider-P5SOJAQ6.js.map} +0 -0
  441. /package/dist/{api-DDMUKIUD.js.map → api-KG4A2X7P.js.map} +0 -0
  442. /package/dist/{appNameResolver-UURKN7NF.js.map → audit-65VNHEV2.js.map} +0 -0
  443. /package/dist/{chunk-NTNILOBC.js.map → chunk-5BO3MI5Y.js.map} +0 -0
  444. /package/dist/{chunk-URUTVZ7N.js.map → chunk-FL4ZCQLD.js.map} +0 -0
  445. /package/dist/{chunk-LW7MMEAQ.js.map → chunk-FT2M4R4F.js.map} +0 -0
  446. /package/dist/{chunk-AFGTSUAD.js.map → chunk-VSOKOFRF.js.map} +0 -0
  447. /package/docs/{app.css.example → styles/app.css.example} +0 -0
@@ -1,8 +1,16 @@
1
- # Data Tables
1
+ ---
2
+ lastUpdated: 2025-10-29T22:43:00+11:00
3
+ version: 0.5.76
4
+ reviewedBy: content-audit
5
+ ---
2
6
 
3
- > **📚 Implementation Guide**: Data Management | [← Back](../README.md) | [Forms](./forms.md) | [RBAC Usage](./datatable-rbac-usage.md)
7
+ # DataTable Implementation Guide
4
8
 
5
- This guide covers implementing data tables with PACE Core's DataTable component, including basic usage, advanced features, and best practices.
9
+ > **📚 Complete Implementation Guide**: Data Management | [← Back](../README.md) | [Forms](./forms.md)
10
+
11
+ This comprehensive guide covers implementing data tables with PACE Core's DataTable component, including basic usage, advanced features, RBAC integration, hierarchical data, filtering, performance optimization, and best practices.
12
+
13
+ > **📚 API Reference**: See [DataTable Component API](../api-reference/components.md#datatable) for complete prop documentation.
6
14
 
7
15
  ## Overview
8
16
 
@@ -10,11 +18,12 @@ The PACE Core DataTable is an enterprise-grade data table component built on Tan
10
18
 
11
19
  - **🚀 Performance Optimized** - Virtual scrolling, intelligent chunking, and automatic mode detection
12
20
  - **📊 Data Management** - Sorting, filtering, pagination, search, **automatic export**/import
13
- - **🌳 Hierarchical Rows** - Parent/child row relationships with expand/collapse all and smart sorting ([Detailed Guide](./hierarchical-datatable.md))
21
+ - **🌳 Hierarchical Rows** - Parent/child row relationships with expand/collapse all and smart sorting
14
22
  - **✏️ Inline Editing** - Row editing, creation, and deletion
15
23
  - **🎯 Actions** - Custom row actions and toolbar buttons
16
24
  - **📈 Grouping** - Data grouping with aggregation functions
17
25
  - **📏 Auto Column Sizing** - Automatic column width adjustment based on content
26
+ - **🔐 RBAC Integration** - Page-based permission control
18
27
  - **🎨 Customizable** - Column visibility, responsive design, accessibility
19
28
  - **🔧 TypeScript** - Full TypeScript support with strict typing
20
29
 
@@ -89,2303 +98,1237 @@ function UserTable() {
89
98
  pagination: true,
90
99
  sorting: true,
91
100
  filtering: true,
101
+ creation: true,
102
+ editing: true,
103
+ deletion: true,
104
+ export: true,
105
+ import: true,
92
106
  }}
93
- columnOrder={['name', 'email', 'role', 'status']} // Control column order
94
- // Performance and other settings can be configured via performance prop
107
+ getRowId={(row) => row.id}
95
108
  />
96
109
  );
97
110
  }
98
111
  ```
99
112
 
100
- ### With Permission Enforcement
101
-
102
- ```tsx
103
- import { DataTable, PagePermissionGuard, PAGE_IDS } from '@jmruthers/pace-core';
104
-
105
- function UsersPage() {
106
- return (
107
- <PagePermissionGuard pageId={PAGE_IDS.USER_MANAGEMENT}>
108
- {(permissions) => (
109
- <DataTable
110
- data={users}
111
- columns={columns}
112
- rbac={{
113
- resource: 'users',
114
- pageId: 'user-management'
115
- }}
116
- title="User Management"
117
- description="Manage system users"
118
- features={{
119
- search: true,
120
- pagination: true,
121
- creation: permissions.canCreate,
122
- editing: permissions.canUpdate,
123
- deletion: permissions.canDelete,
124
- }}
125
- onEditRow={permissions.canUpdate ? (row) => navigate(`/users/${row.id}/edit`) : undefined}
126
- onDeleteRow={permissions.canDelete ? handleDeleteUser : undefined}
127
- // Custom actions can be added via the deprecated actions prop
128
- actions={
129
- permissions.canUpdate || permissions.canDelete ? [
130
- ...(permissions.canUpdate ? [{
131
- label: 'View Details',
132
- onClick: (row) => navigate(`/users/${row.id}`),
133
- }] : [])
134
- ] : []
135
- }
136
- />
137
- )}
138
- </PagePermissionGuard>
139
- );
140
- }
141
- ```
142
-
143
- ## DataTable Props Interface
144
-
145
- The DataTable component has a comprehensive prop interface. Here are the key properties:
146
-
147
- ### Core Props
148
- - `data: TData[]` - Array of data records to display
149
- - `columns: any[]` - Column definitions (supports TanStack Table format)
150
- - `features: DataTableFeatureConfig` - **Required** - Feature configuration object
151
-
152
- ### Display Props
153
- - `title?: string` - Table title
154
- - `description?: string` - Table description
155
- - `variant?: 'default' | 'compact' | 'spacious'` - Visual variant
156
- - `className?: string` - Additional CSS classes
157
- - `columnOrder?: string[]` - **Column ordering** - Array of column IDs in desired order
158
-
159
- ### Event Handlers
160
-
161
- ⚠️ **Critical**: DataTable is a controlled component. You must update your local state after CRUD operations to keep the UI in sync.
162
-
163
- - `onEditRow?: (row: TData, data: Partial<TData>) => void` - Row edit handler
164
- - `onDeleteRow?: (row: TData) => void` - Row deletion handler (also used as fallback for bulk delete)
165
- - `onCreateRow?: (data: Partial<TData>) => void` - Row creation handler
166
- - `onImport?: (data: TData[]) => void | Promise<void>` - Import handler
167
- - `onExport?: () => void` - **Optional**: Custom export handler. If not provided, exports automatically with current table filters and visible columns.
168
- - `onRowSelectionChange?: (selection: Record<string, boolean>) => void` - Row selection change handler
169
- - `onDeleteSelected?: (selectedRows: Record<string, boolean>) => void` - **Bulk delete handler** - Called when delete selected button is clicked
170
-
171
- ### Performance Props
172
- - `performance?: PerformanceConfig` - Performance optimization settings
173
- - `serverSide?: ServerSideConfig<TData>` - Server-side data fetching
174
- - `virtualHeight?: number` - Virtual scrolling height (default: 600)
175
- - `showPerformanceMetrics?: boolean` - Show performance metrics
176
- - `initialPageSize?: number` - Initial page size for pagination (default: 10)
113
+ ## RBAC Integration
177
114
 
178
- ### Feature-Specific Configuration
179
- - `bulkOperationsConfig?: BulkOperationsConfig` - Bulk operations config
115
+ ### Page-Based Permissions (Recommended)
180
116
 
181
- ### Row Actions
182
- - `actions?: any[]` - Custom row actions for each row
117
+ The DataTable uses page-based RBAC permissions instead of resource-based permissions. This ensures that DataTable permissions align with your application's page-level permission system.
183
118
 
184
- ## CRUD Operations Implementation
185
-
186
- ### Understanding Controlled Components
187
-
188
- The DataTable is a **controlled component** that displays whatever data you pass to it. When implementing CRUD operations, you must manage both the database operations AND the local state updates to keep the UI in sync.
189
-
190
- **Two-step process for all CRUD operations:**
191
- 1. **Perform the database operation** (create, update, delete)
192
- 2. **Update your local state** to reflect the changes
119
+ #### Basic Setup
193
120
 
194
- ### Complete CRUD Examples
195
-
196
- #### State Management Setup
197
-
198
- ```typescript
199
- import { useState, useEffect } from 'react';
121
+ ```tsx
200
122
  import { DataTable } from '@jmruthers/pace-core';
201
123
 
202
- function MealsPage() {
203
- const [meals, setMeals] = useState<Meal[]>([]);
204
- const [loading, setLoading] = useState(true);
124
+ function DishesPage() {
125
+ const dishes = [
126
+ { id: '1', name: 'Pasta', category: 'Main' },
127
+ { id: '2', name: 'Salad', category: 'Side' }
128
+ ];
205
129
 
206
- // Load initial data
207
- useEffect(() => {
208
- loadMeals().then(setMeals).finally(() => setLoading(false));
209
- }, []);
130
+ const columns = [
131
+ { accessorKey: 'id', header: 'ID' },
132
+ { accessorKey: 'name', header: 'Name' },
133
+ { accessorKey: 'category', header: 'Category' }
134
+ ];
210
135
 
211
- // ... rest of component
212
- }
213
- ```
214
-
215
- #### Create Operation
216
-
217
- ```typescript
218
- const handleCreateRow = async (data: Partial<Meal>) => {
219
- try {
220
- // 1. Create in database
221
- const newMeal = await supabase
222
- .from('meals')
223
- .insert(data)
224
- .select()
225
- .single();
226
-
227
- if (newMeal.error) throw newMeal.error;
228
-
229
- // 2. Update local state - ADD the new row
230
- setMeals(prevMeals => [...prevMeals, newMeal.data]);
231
-
232
- console.log('Meal created successfully');
233
- } catch (error) {
234
- console.error('Failed to create meal:', error);
235
- // Show error message to user
236
- }
237
- };
238
- ```
239
-
240
- #### Update Operation
241
-
242
- ```typescript
243
- const handleEditRow = async (row: Meal, data: Partial<Meal>) => {
244
- try {
245
- // 1. Update in database
246
- const { error } = await supabase
247
- .from('meals')
248
- .update(data)
249
- .eq('meal_id', row.meal_id);
250
-
251
- if (error) throw error;
252
-
253
- // 2. Update local state - REPLACE the updated row
254
- setMeals(prevMeals =>
255
- prevMeals.map(meal =>
256
- meal.meal_id === row.meal_id
257
- ? { ...meal, ...data }
258
- : meal
259
- )
260
- );
261
-
262
- console.log('Meal updated successfully');
263
- } catch (error) {
264
- console.error('Failed to update meal:', error);
265
- // Show error message to user
266
- }
267
- };
268
- ```
269
-
270
- #### Delete Operation
271
-
272
- ```typescript
273
- const handleDeleteRow = async (row: Meal) => {
274
- try {
275
- // 1. Delete from database
276
- const { error } = await supabase
277
- .from('meals')
278
- .delete()
279
- .eq('meal_id', row.meal_id);
280
-
281
- if (error) throw error;
282
-
283
- // 2. Update local state - REMOVE the deleted row
284
- setMeals(prevMeals =>
285
- prevMeals.filter(meal => meal.meal_id !== row.meal_id)
286
- );
287
-
288
- console.log('Meal deleted successfully');
289
- } catch (error) {
290
- console.error('Failed to delete meal:', error);
291
- // Show error message to user
292
- }
293
- };
294
- ```
295
-
296
- #### Bulk Delete Operation
297
-
298
- ```typescript
299
- const handleDeleteSelected = async (selectedRows: Record<string, boolean>) => {
300
- try {
301
- const selectedIds = Object.keys(selectedRows).filter(id => selectedRows[id]);
302
-
303
- if (selectedIds.length === 0) return;
304
-
305
- // 1. Delete from database
306
- const { error } = await supabase
307
- .from('meals')
308
- .delete()
309
- .in('meal_id', selectedIds);
310
-
311
- if (error) throw error;
312
-
313
- // 2. Update local state - REMOVE all selected rows
314
- setMeals(prevMeals =>
315
- prevMeals.filter(meal => !selectedIds.includes(meal.meal_id))
316
- );
317
-
318
- // 3. Clear selection
319
- setSelectedRows({});
320
-
321
- console.log(`${selectedIds.length} meals deleted successfully`);
322
- } catch (error) {
323
- console.error('Failed to delete selected meals:', error);
324
- // Show error message to user
325
- }
326
- };
327
- ```
328
-
329
- ### Common Patterns
330
-
331
- #### Optimistic Updates
332
-
333
- For better UX, update the UI immediately and rollback on error:
334
-
335
- ```typescript
336
- const handleDeleteRow = async (row: Meal) => {
337
- // 1. Optimistically update UI first
338
- const originalMeals = meals;
339
- setMeals(prevMeals =>
340
- prevMeals.filter(meal => meal.meal_id !== row.meal_id)
136
+ return (
137
+ <DataTable
138
+ data={dishes}
139
+ columns={columns}
140
+ rbac={{
141
+ pageName: 'dishes' // Page name for permissions (recommended)
142
+ }}
143
+ features={{
144
+ search: true,
145
+ pagination: true,
146
+ sorting: true,
147
+ filtering: true,
148
+ creation: true, // Will be controlled by create:page.dishes permission
149
+ editing: true, // Will be controlled by update:page.dishes permission
150
+ deletion: true, // Will be controlled by delete:page.dishes permission
151
+ export: true, // Will be controlled by read:page.dishes permission
152
+ import: true // Will be controlled by create:page.dishes permission
153
+ }}
154
+ getRowId={(row) => row.id}
155
+ />
341
156
  );
342
-
343
- try {
344
- // 2. Delete from database
345
- const { error } = await supabase
346
- .from('meals')
347
- .delete()
348
- .eq('meal_id', row.meal_id);
349
-
350
- if (error) throw error;
351
-
352
- console.log('Meal deleted successfully');
353
- } catch (error) {
354
- // 3. Rollback on error
355
- setMeals(originalMeals);
356
- console.error('Failed to delete meal:', error);
357
- // Show error message to user
358
- }
359
- };
157
+ }
360
158
  ```
361
159
 
362
- ## Feature Configuration
363
-
364
- The DataTable component uses a unified `features` prop to control all table functionality. This provides a consistent and intuitive way to enable or disable features.
365
-
366
- ### DataTableFeatureConfig Properties
367
-
368
- | Property | Type | Default | Description |
369
- |----------|------|---------|-------------|
370
- | **Core Features** |
371
- | `search` | `boolean` | `false` | Enable global search functionality |
372
- | `pagination` | `boolean` | `false` | Enable pagination controls |
373
- | `sorting` | `boolean` | `false` | Enable column sorting |
374
- | `filtering` | `boolean` | `false` | Enable column filtering |
375
- | **Data Management** |
376
- | `import` | `boolean` | `false` | Enable data import functionality |
377
- | `export` | `boolean` | `false` | Enable data export functionality |
378
- | **Row Operations** |
379
- | `selection` | `boolean` | `false` | Enable row selection for bulk operations |
380
- | `creation` | `boolean` | `false` | Enable row creation |
381
- | `editing` | `boolean` | `false` | Enable row editing |
382
- | `deletion` | `boolean` | `false` | Enable row deletion |
383
- | `deleteSelected` | `boolean` | `false` | Enable bulk deletion of selected rows |
384
- | **Table Customization** |
385
- | `grouping` | `boolean` | `false` | Enable data grouping |
386
- | `columnVisibility` | `boolean` | `false` | Enable column visibility controls |
387
- | `columnReordering` | `boolean` | `false` | Enable column reordering |
388
- | `autoColumnSizing` | `boolean` | `false` | Enable automatic column width adjustment based on content |
389
-
390
- ### Basic Feature Configuration
160
+ #### Page Identification Options
391
161
 
162
+ **Option 1: Page Name (Recommended)**
392
163
  ```tsx
393
164
  <DataTable
394
165
  data={data}
395
166
  columns={columns}
396
167
  rbac={{
397
- resource: 'users',
398
- pageId: 'user-management'
399
- }}
400
- features={{
401
- // Core Features
402
- search: true,
403
- pagination: true,
404
- sorting: true,
405
- filtering: true,
406
-
407
- // Data Management
408
- import: true,
409
- export: true,
410
-
411
- // Row Operations
412
- selection: true, // Required for row selection
413
- creation: true,
414
- editing: true,
415
- deletion: true, // Required for delete functionality
416
- deleteSelected: true, // Required for delete selected button
417
-
418
- // Table Customization
419
- grouping: true,
420
- columnVisibility: true,
421
- columnReordering: true,
422
- autoColumnSizing: true, // Enable automatic column width adjustment
168
+ pageName: 'dishes' // Will be resolved to page ID by RBAC engine
423
169
  }}
170
+ features={features}
424
171
  />
425
172
  ```
426
173
 
427
- ### Minimal Configuration
428
-
429
- For a simple read-only table:
430
-
174
+ **Option 2: Page ID (Direct)**
431
175
  ```tsx
432
176
  <DataTable
433
177
  data={data}
434
178
  columns={columns}
435
179
  rbac={{
436
- resource: 'users',
437
- pageId: 'user-management'
438
- }}
439
- features={{
440
- search: true,
441
- pagination: true,
442
- sorting: true,
180
+ pageId: 'uuid-here' // Use page ID directly
443
181
  }}
182
+ features={features}
444
183
  />
445
184
  ```
446
185
 
447
- ### Advanced Configuration
186
+ **Note:** You must provide either `pageName` or `pageId`, but not both. If both are provided, `pageId` takes precedence.
187
+
188
+ #### Permission Mapping
189
+
190
+ The DataTable checks the following page-based permissions:
448
191
 
449
- For a full-featured data management table:
192
+ | DataTable Feature | Permission Checked | Description |
193
+ |------------------|-------------------|-------------|
194
+ | **Data Display** | `read:page.{pageId}` | Controls whether data is visible |
195
+ | **Create Button** | `create:page.{pageId}` | Controls create functionality |
196
+ | **Edit Actions** | `update:page.{pageId}` | Controls edit/update functionality |
197
+ | **Delete Actions** | `delete:page.{pageId}` | Controls delete functionality |
198
+ | **Export** | `read:page.{pageId}` | Controls export functionality |
199
+ | **Import** | `create:page.{pageId}` | Controls import functionality |
450
200
 
201
+ #### Role-Based Examples
202
+
203
+ **Planner Role:**
451
204
  ```tsx
452
205
  <DataTable
453
- data={data}
206
+ data={dishes}
454
207
  columns={columns}
208
+ rbac={{
209
+ pageId: 'dishes'
210
+ }}
455
211
  features={{
456
- search: true,
457
- pagination: true,
458
- sorting: true,
459
- filtering: true,
460
- selection: true,
461
- creation: true,
462
- editing: true,
463
- deletion: true,
464
- deleteSelected: true, // Enable delete selected functionality
465
- export: true,
466
- import: true,
467
- grouping: true,
468
- columnVisibility: true,
469
- bulkOperations: true,
212
+ creation: true, // ✅ Allowed if planner has create:page.dishes
213
+ editing: true, // ✅ Allowed if planner has update:page.dishes
214
+ deletion: false, // ❌ Disabled if planner lacks delete:page.dishes
470
215
  }}
471
- columnOrder={['select', 'name', 'email', 'role', 'status', 'actions']} // Custom column order
472
- onEditRow={handleEdit}
473
- onDeleteRow={handleDelete}
474
- onCreateRow={handleCreate}
475
- onImport={handleImport}
476
- onRowSelectionChange={handleSelectionChange}
477
- onDeleteSelected={handleBulkDelete}
478
- getRowId={(row) => row.id} // Important: Required for selection and delete operations
479
216
  />
480
217
  ```
481
218
 
482
- ## Auto Column Sizing
483
-
484
- The DataTable component includes automatic column width adjustment based on content, which helps optimize space usage and improve readability.
485
-
486
- ### How Auto-Sizing Works
487
-
488
- When `autoColumnSizing: true` is enabled:
489
-
490
- 1. **Content Analysis**: Analyzes both header text and data content to determine optimal widths
491
- 2. **Smart Calculation**: Uses character count estimation (8px per character + padding)
492
- 3. **Constraints**: Respects min-width (80px) and max-width (400px) limits
493
- 4. **Performance**: Only samples first 100 rows for large datasets
494
- 5. **Table Layout**: Switches from `tableLayout: 'fixed'` to `tableLayout: 'auto'`
495
-
496
- ### Benefits
497
-
498
- - **Better Space Utilization**: Columns automatically adjust to content width
499
- - **Improved Readability**: Long text gets more space, short text takes less space
500
- - **No Text Wrapping**: Prevents unnecessary text wrapping in narrow columns
501
- - **Consistent Layout**: Maintains proper spacing and alignment
502
-
503
- ### Enabling Auto-Sizing
504
-
219
+ **Viewer Role:**
505
220
  ```tsx
506
221
  <DataTable
507
- data={data}
222
+ data={dishes}
508
223
  columns={columns}
224
+ rbac={{
225
+ pageId: 'dishes'
226
+ }}
509
227
  features={{
510
- search: true,
511
- pagination: true,
512
- sorting: true,
513
- autoColumnSizing: true, // Enable automatic column width adjustment
228
+ creation: false, // ❌ Disabled - viewer typically lacks create:page.dishes
229
+ editing: false, // ❌ Disabled - viewer typically lacks update:page.dishes
230
+ deletion: false, // ❌ Disabled - viewer typically lacks delete:page.dishes
514
231
  }}
515
232
  />
516
233
  ```
517
234
 
518
- ### Auto-Sizing vs Fixed Widths
235
+ #### Database Setup
519
236
 
520
- **With Auto-Sizing (Recommended):**
521
- - Columns automatically adjust to content width
522
- - Long text gets more space, short text takes less space
523
- - Better space utilization
524
- - Improved readability
237
+ Ensure your `rbac_page_permissions` table has entries for each page and role:
525
238
 
526
- **Without Auto-Sizing:**
527
- - All columns have equal width
528
- - Long text may wrap unnecessarily
529
- - Short columns have wasted space
530
- - Consistent but potentially inefficient layout
239
+ ```sql
240
+ -- Example: Planner role permissions for dishes page
241
+ INSERT INTO rbac_page_permissions (app_page_id, operation, role_name, allowed) VALUES
242
+ ((SELECT id FROM rbac_app_pages WHERE page_name = 'dishes'), 'read', 'planner', true),
243
+ ((SELECT id FROM rbac_app_pages WHERE page_name = 'dishes'), 'create', 'planner', true),
244
+ ((SELECT id FROM rbac_app_pages WHERE page_name = 'dishes'), 'update', 'planner', true),
245
+ ((SELECT id FROM rbac_app_pages WHERE page_name = 'dishes'), 'delete', 'planner', false);
246
+ ```
531
247
 
532
- ### Example: Content-Based Sizing
248
+ ### Permission-Based Actions (Legacy)
533
249
 
534
250
  ```tsx
535
- const data = [
536
- { id: '1', name: 'John', description: 'A short description' },
537
- { id: '2', name: 'Jane', description: 'A very long description that should make this column wider' },
538
- { id: '3', name: 'Bob', description: 'Medium length description' }
539
- ];
540
-
541
- const columns = [
542
- { accessorKey: 'name', header: 'Name' },
543
- { accessorKey: 'description', header: 'Description' }
544
- ];
545
-
546
- // With auto-sizing: Description column will be wider than Name column
547
251
  <DataTable
548
252
  data={data}
549
253
  columns={columns}
550
- features={{ autoColumnSizing: true }}
254
+ rbac={{
255
+ resource: 'users',
256
+ actions: [
257
+ {
258
+ name: 'edit',
259
+ permission: 'update:users',
260
+ scope: { userId: 'user-123' }
261
+ },
262
+ {
263
+ name: 'delete',
264
+ permission: 'delete:users',
265
+ scope: { userId: 'user-123' }
266
+ }
267
+ ]
268
+ }}
269
+ features={{
270
+ rowActions: true,
271
+ creation: true,
272
+ editing: true,
273
+ deletion: true
274
+ }}
551
275
  />
552
276
  ```
553
277
 
554
- ### Feature-Based Conditional Rendering
278
+ ## Hierarchical Data Implementation
279
+
280
+ ### Overview
281
+
282
+ The hierarchical DataTable feature allows you to display parent/child relationships in your data with:
283
+ - **Expand/Collapse All**: Single button to expand/collapse all parent rows
284
+ - **Individual Controls**: Click arrows next to each parent row
285
+ - **Different Action Icons**: Context-aware actions for parent vs child rows
286
+ - **Smart Sorting**: Sorting by child fields sorts children within their parents
287
+ - **Column Rendering**: Different content display for parent vs child rows
288
+ - **Visual Distinction**: Clear visual hierarchy with indentation and styling
289
+
290
+ ### Quick Start
555
291
 
556
- When features are disabled, the corresponding UI elements are automatically hidden:
292
+ #### 1. Enable Hierarchical Features
557
293
 
558
294
  ```tsx
559
- // This will show only search and pagination controls
560
- // No edit, delete, or action buttons will appear
295
+ import { DataTable, type HierarchicalDataRow } from '@jmruthers/pace-core';
296
+
561
297
  <DataTable
562
- data={data}
298
+ data={hierarchicalData}
563
299
  columns={columns}
564
300
  features={{
565
- search: true,
566
- pagination: true,
567
- // All other features disabled by default
301
+ hierarchical: true, // Enable hierarchical functionality
302
+ }}
303
+ hierarchical={{
304
+ enabled: true,
305
+ defaultExpanded: false, // Start with all collapsed
568
306
  }}
569
307
  />
570
308
  ```
571
309
 
572
- ## Column Configuration
310
+ #### 2. Structure Your Data
573
311
 
574
- ### Basic Column Properties
312
+ ```typescript
313
+ interface Dish extends HierarchicalDataRow {
314
+ id: string;
315
+ isParent: boolean;
316
+ parentId?: string;
317
+ name: string;
318
+ type: string;
319
+ cost: number;
320
+ // ... other properties
321
+ }
575
322
 
576
- ```tsx
577
- const columns: DataTableColumn<User>[] = [
323
+ const hierarchicalData: Dish[] = [
324
+ // Parent rows (dishes)
578
325
  {
579
- accessorKey: 'name', // Data field to access
580
- header: 'Name', // Column header
581
- sortable: true, // Enable sorting
582
- features: { search: true }, // Enable search
583
- enableGrouping: true, // Enable grouping
584
- size: 200, // Column width
585
- minSize: 100, // Minimum width
586
- maxSize: 300, // Maximum width
326
+ id: 'dish-1',
327
+ isParent: true,
328
+ parentId: null,
329
+ name: 'Caesar Salad',
330
+ type: 'Salad',
331
+ cost: 12.99
587
332
  },
588
- ];
589
- ```
590
-
591
- ### Custom Cell Rendering
592
-
593
- ```tsx
594
- const columns: DataTableColumn<User>[] = [
595
333
  {
596
- accessorKey: 'avatar',
597
- header: 'Avatar',
598
- cell: ({ row }) => (
599
- <Avatar>
600
- <AvatarImage src={row.original.avatarUrl} />
601
- <AvatarFallback>
602
- {row.original.name.charAt(0).toUpperCase()}
603
- </AvatarFallback>
604
- </Avatar>
605
- ),
334
+ id: 'dish-2',
335
+ isParent: true,
336
+ parentId: null,
337
+ name: 'Beef Stir Fry',
338
+ type: 'Main Course',
339
+ cost: 18.99
340
+ },
341
+
342
+ // Child rows (ingredients) for Caesar Salad
343
+ {
344
+ id: 'ingredient-1',
345
+ isParent: false,
346
+ parentId: 'dish-1',
347
+ name: 'Romaine Lettuce',
348
+ type: 'Vegetable',
349
+ cost: 2.50
606
350
  },
607
351
  {
608
- accessorKey: 'status',
609
- header: 'Status',
610
- cell: ({ row }) => (
611
- <Badge variant={row.original.status === 'active' ? 'default' : 'secondary'}>
612
- {row.original.status}
613
- </Badge>
614
- ),
352
+ id: 'ingredient-2',
353
+ isParent: false,
354
+ parentId: 'dish-1',
355
+ name: 'Parmesan Cheese',
356
+ type: 'Dairy',
357
+ cost: 3.00
615
358
  },
359
+
360
+ // Child rows (ingredients) for Beef Stir Fry
616
361
  {
617
- accessorKey: 'lastLogin',
618
- header: 'Last Login',
619
- cell: ({ row }) => (
620
- <span className="text-sm text-muted-foreground">
621
- {formatDate(row.original.lastLogin)}
622
- </span>
623
- ),
362
+ id: 'ingredient-3',
363
+ isParent: false,
364
+ parentId: 'dish-2',
365
+ name: 'Beef Strips',
366
+ type: 'Protein',
367
+ cost: 8.50
624
368
  },
369
+ {
370
+ id: 'ingredient-4',
371
+ isParent: false,
372
+ parentId: 'dish-2',
373
+ name: 'Bell Peppers',
374
+ type: 'Vegetable',
375
+ cost: 2.00
376
+ }
625
377
  ];
626
378
  ```
627
379
 
628
- ### Advanced Column Features
380
+ #### 3. Configure Columns for Hierarchical Display
629
381
 
630
- ```tsx
631
- const columns: DataTableColumn<User>[] = [
382
+ ```typescript
383
+ const columns: DataTableColumn<Dish>[] = [
632
384
  {
633
385
  accessorKey: 'name',
634
386
  header: 'Name',
635
- sortable: true,
636
- features: { search: true },
637
- enableColumnFilter: true,
638
- filterFn: 'includesString',
387
+ cell: ({ row }) => {
388
+ const isParent = row.original.isParent;
389
+ const indentLevel = isParent ? 0 : 1;
390
+
391
+ return (
392
+ <div className={`flex items-center ${isParent ? 'font-semibold' : ''}`}>
393
+ <div style={{ marginLeft: `${indentLevel * 20}px` }}>
394
+ {isParent ? '🍽️' : '🥬'} {row.original.name}
395
+ </div>
396
+ </div>
397
+ );
398
+ }
639
399
  },
640
400
  {
641
- accessorKey: 'role',
642
- header: 'Role',
643
- enableGrouping: true,
644
- enableColumnFilter: true,
645
- filterFn: 'equals',
646
- cell: ({ row }) => (
647
- <Select value={row.original.role} onValueChange={(value) => handleRoleChange(row.original.id, value)}>
648
- <SelectTrigger>
649
- <SelectValue />
650
- </SelectTrigger>
651
- <SelectContent>
652
- <SelectItem value="admin">Admin</SelectItem>
653
- <SelectItem value="user">User</SelectItem>
654
- <SelectItem value="guest">Guest</SelectItem>
655
- </SelectContent>
656
- </Select>
657
- ),
401
+ accessorKey: 'type',
402
+ header: 'Type',
403
+ cell: ({ row }) => {
404
+ const isParent = row.original.isParent;
405
+ return (
406
+ <span className={isParent ? 'text-main-600 font-medium' : 'text-sec-600'}>
407
+ {row.original.type}
408
+ </span>
409
+ );
410
+ }
658
411
  },
412
+ {
413
+ accessorKey: 'cost',
414
+ header: 'Cost',
415
+ cell: ({ row }) => {
416
+ const isParent = row.original.isParent;
417
+ return (
418
+ <span className={isParent ? 'font-bold text-main-700' : 'text-sec-600'}>
419
+ ${row.original.cost.toFixed(2)}
420
+ </span>
421
+ );
422
+ }
423
+ }
659
424
  ];
660
425
  ```
661
426
 
662
- ## Inline Editing Configuration
427
+ ### Advanced Hierarchical Features
663
428
 
664
- The DataTable supports inline row editing, where columns can be configured to become input fields when the user clicks the "Edit" button on a row.
665
-
666
- ### Enabling Inline Editing
667
-
668
- To enable inline editing, set `editing: true` in the features configuration:
429
+ #### Expand/Collapse All Control
669
430
 
670
431
  ```tsx
671
432
  <DataTable
672
- data={data}
433
+ data={hierarchicalData}
673
434
  columns={columns}
674
435
  features={{
675
- editing: true, // Enable inline editing
676
- // ... other features
436
+ hierarchical: true,
437
+ }}
438
+ hierarchical={{
439
+ enabled: true,
440
+ defaultExpanded: false,
441
+ showExpandAll: true, // Shows expand/collapse all button
677
442
  }}
678
- onEditRow={handleEdit} // Required for saving changes
443
+ title="Dishes & Ingredients"
444
+ description="Hierarchical view of dishes and their ingredients"
679
445
  />
680
446
  ```
681
447
 
682
- ### Configuring Editable Columns
448
+ #### Custom Action Icons for Parent/Child Rows
449
+
450
+ ```typescript
451
+ const columns: DataTableColumn<Dish>[] = [
452
+ // ... other columns
453
+ {
454
+ id: 'actions',
455
+ header: 'Actions',
456
+ cell: ({ row }) => {
457
+ const isParent = row.original.isParent;
458
+
459
+ if (isParent) {
460
+ // Parent row actions
461
+ return (
462
+ <div className="flex gap-2">
463
+ <Button size="sm" variant="outline">
464
+ Edit Dish
465
+ </Button>
466
+ <Button size="sm" variant="outline">
467
+ Add Ingredient
468
+ </Button>
469
+ </div>
470
+ );
471
+ } else {
472
+ // Child row actions
473
+ return (
474
+ <div className="flex gap-2">
475
+ <Button size="sm" variant="ghost">
476
+ Edit Ingredient
477
+ </Button>
478
+ <Button size="sm" variant="ghost">
479
+ Remove
480
+ </Button>
481
+ </div>
482
+ );
483
+ }
484
+ }
485
+ }
486
+ ];
487
+ ```
488
+
489
+ #### Smart Sorting
683
490
 
684
- By default, all columns are editable in edit mode. To control which columns can be edited, use the `editable` and `fieldType` properties:
491
+ When sorting by child fields, children are sorted within their parent groups:
685
492
 
686
- ```tsx
687
- const columns: DataTableColumn<User>[] = [
493
+ ```typescript
494
+ const columns: DataTableColumn<Dish>[] = [
495
+ {
496
+ accessorKey: 'cost',
497
+ header: 'Cost',
498
+ sortable: true,
499
+ cell: ({ row }) => {
500
+ const isParent = row.original.isParent;
501
+ return (
502
+ <span className={isParent ? 'font-bold text-main-700' : 'text-sec-600'}>
503
+ ${row.original.cost.toFixed(2)}
504
+ </span>
505
+ );
506
+ }
507
+ }
508
+ ];
509
+
510
+ // When sorting by cost:
511
+ // 1. Parent rows are sorted by their cost
512
+ // 2. Child rows are sorted by their cost within each parent group
513
+ // 3. Parent-child relationships are maintained
514
+ ```
515
+
516
+ ## Advanced Filtering
517
+
518
+ ### Filter Types
519
+
520
+ #### 1. Text Filters (Default)
521
+ Text filters allow users to type and search for values in a column.
522
+
523
+ ```typescript
524
+ const columns = [
688
525
  {
526
+ id: 'name',
689
527
  accessorKey: 'name',
690
528
  header: 'Name',
691
- editable: true, // Column becomes input in edit mode
692
- fieldType: 'text', // Input type: text, number, date, boolean, select
693
- },
694
- {
695
- accessorKey: 'email',
696
- header: 'Email',
697
- editable: true,
698
- fieldType: 'text',
699
- },
700
- {
701
- accessorKey: 'age',
702
- header: 'Age',
703
- editable: true,
704
- fieldType: 'number', // Numeric input field
705
- },
529
+ enableColumnFilter: true,
530
+ // No filterType specified - defaults to 'text'
531
+ }
532
+ ];
533
+ ```
534
+
535
+ #### 2. Select Filters
536
+ Select filters provide dropdown menus with predefined options, perfect for categorical data.
537
+
538
+ **Method 1: Using `filterSelectOptions` (Recommended)**
539
+ ```typescript
540
+ const columns = [
706
541
  {
707
- accessorKey: 'role',
708
- header: 'Role',
709
- editable: true,
710
- fieldType: 'select', // Select dropdown
711
- fieldOptions: [ // Options for select fields
712
- { value: 'admin', label: 'Administrator' },
713
- { value: 'user', label: 'User' },
714
- { value: 'guest', label: 'Guest' },
542
+ id: 'meal_type',
543
+ accessorFn: (row) => row.mealtype?.mealtype_name || 'N/A',
544
+ header: 'Type',
545
+ enableColumnFilter: true,
546
+ filterType: 'select',
547
+ filterSelectOptions: [
548
+ { value: 'breakfast', label: 'Breakfast' },
549
+ { value: 'lunch', label: 'Lunch' },
550
+ { value: 'dinner', label: 'Dinner' },
551
+ { value: 'morning_tea', label: 'Morning Tea' },
552
+ { value: 'afternoon_tea', label: 'Afternoon Tea' },
553
+ { value: 'pantry', label: 'Pantry' }
715
554
  ],
716
- },
555
+ filterFn: (row, id, value) => {
556
+ return value.includes(row.original.mealtype?.mealtype_id);
557
+ }
558
+ }
559
+ ];
560
+ ```
561
+
562
+ **Method 2: Using `fieldOptions`**
563
+ ```typescript
564
+ const columns = [
717
565
  {
566
+ id: 'status',
718
567
  accessorKey: 'status',
719
568
  header: 'Status',
720
- editable: false, // Read-only column (won't become input in edit mode)
721
- },
569
+ enableColumnFilter: true,
570
+ filterType: 'select',
571
+ fieldOptions: [
572
+ { value: 'active', label: 'Active' },
573
+ { value: 'inactive', label: 'Inactive' },
574
+ { value: 'pending', label: 'Pending' }
575
+ ]
576
+ }
722
577
  ];
723
578
  ```
724
579
 
725
- ### Available Field Types
580
+ **Method 3: Auto-detection**
581
+ The DataTable automatically detects select filters when a column has limited unique values (≤10):
726
582
 
727
- - **`text`**: Standard text input
728
- - **`number`**: Numeric input with validation
729
- - **`select`**: Dropdown select (requires `fieldOptions`)
730
- - **`date`**: Date picker
731
- - **`boolean`**: Checkbox
732
-
733
- ### Complete Inline Editing Example
734
-
735
- ```tsx
736
- interface User {
737
- id: string;
738
- name: string;
739
- email: string;
740
- age: number;
741
- role: 'admin' | 'user' | 'guest';
742
- active: boolean;
743
- }
744
-
745
- const columns: DataTableColumn<User>[] = [
746
- {
747
- accessorKey: 'name',
748
- header: 'Name',
749
- sortable: true,
750
- editable: true,
751
- fieldType: 'text',
752
- },
753
- {
754
- accessorKey: 'email',
755
- header: 'Email',
756
- sortable: true,
757
- editable: true,
758
- fieldType: 'text',
759
- },
760
- {
761
- accessorKey: 'age',
762
- header: 'Age',
763
- sortable: true,
764
- editable: true,
765
- fieldType: 'number',
766
- },
767
- {
768
- accessorKey: 'role',
769
- header: 'Role',
770
- editable: true,
771
- fieldType: 'select',
772
- fieldOptions: [
773
- { value: 'admin', label: 'Admin' },
774
- { value: 'user', label: 'User' },
775
- { value: 'guest', label: 'Guest' },
776
- ],
777
- },
583
+ ```typescript
584
+ const columns = [
778
585
  {
779
- accessorKey: 'active',
780
- header: 'Active',
781
- editable: true,
782
- fieldType: 'boolean',
783
- },
586
+ id: 'priority',
587
+ accessorKey: 'priority',
588
+ header: 'Priority',
589
+ enableColumnFilter: true,
590
+ // No filterType specified - will auto-detect as 'select' if ≤10 unique values
591
+ }
784
592
  ];
785
-
786
- function UserManagement() {
787
- const [users, setUsers] = useState<User[]>([]);
788
-
789
- const handleEdit = async (row: User, data: Partial<User>) => {
790
- // Update in database
791
- const { error } = await supabase
792
- .from('users')
793
- .update(data)
794
- .eq('id', row.id);
795
-
796
- if (error) throw error;
797
-
798
- // Update local state
799
- setUsers(prev => prev.map(u => u.id === row.id ? { ...u, ...data } : u));
800
- };
801
-
802
- return (
803
- <DataTable
804
- data={users}
805
- columns={columns}
806
- features={{
807
- editing: true,
808
- // ... other features
809
- }}
810
- onEditRow={handleEdit}
811
- />
812
- );
813
- }
814
593
  ```
815
594
 
816
- ### How Inline Editing Works
817
-
818
- 1. User clicks "Edit" button on a row
819
- 2. Editable columns (those with `editable: true`) become input fields
820
- 3. Non-editable columns (those with `editable: false`) remain as text
821
- 4. Save/Cancel buttons replace the Edit button
822
- 5. On Save: `onEditRow(originalRow, editedData)` is called
823
- 6. On Cancel: Row returns to view mode without changes
824
-
825
- ### Mixing Editable and Non-Editable Columns
595
+ #### 3. Number Filters
596
+ Number filters provide numeric input with comparison operators.
826
597
 
827
- You can mix editable and non-editable columns in the same table:
828
-
829
- ```tsx
598
+ ```typescript
830
599
  const columns = [
831
600
  {
832
- accessorKey: 'id',
833
- header: 'ID',
834
- editable: false, // Read-only ID field
835
- },
836
- {
837
- accessorKey: 'name',
838
- header: 'Name',
839
- editable: true, // Can be edited
840
- fieldType: 'text',
841
- },
842
- {
843
- accessorKey: 'createdAt',
844
- header: 'Created',
845
- editable: false, // Read-only timestamp
846
- cell: ({ row }) => formatDate(row.original.createdAt),
847
- },
601
+ id: 'price',
602
+ accessorKey: 'price',
603
+ header: 'Price',
604
+ enableColumnFilter: true,
605
+ filterType: 'number'
606
+ }
848
607
  ];
849
608
  ```
850
609
 
851
- When in edit mode, only columns with `editable: true` will become input fields.
852
-
853
- ## Automatic Export
854
-
855
- The DataTable includes automatic CSV export functionality that works out of the box. When the export button is clicked, it automatically exports exactly what's shown in the table.
856
-
857
- ### How Automatic Export Works
858
-
859
- By default, the export feature:
860
- - ✅ Exports **exactly** what's visible in the table (respects filters, search, sorting)
861
- - ✅ Includes **only visible columns** (respects column visibility settings)
862
- - ✅ Uses proper column headers from your column definitions
863
- - ✅ Generates a filename with timestamp (e.g., `users_2025-01-15.csv`)
864
- - ✅ Requires **no configuration** - just enable the feature
865
-
866
- ### Basic Usage (Zero Configuration)
867
-
868
- ```tsx
869
- <DataTable
870
- data={data}
871
- columns={columns}
872
- features={{
873
- export: true, // ✅ That's all you need!
874
- }}
875
- />
876
- ```
877
-
878
- That's it! The export button appears automatically and exports the current table state when clicked.
879
-
880
- ### What Gets Exported
881
-
882
- The automatic export includes:
883
- - **Filtered rows**: Only data matching current search/filter criteria
884
- - **Visible columns**: Only columns that are currently visible
885
- - **Current sorting**: Preserved in the exported data
886
- - **Column headers**: Proper names from your column definitions
887
-
888
- ### Export Behavior
889
-
890
- - **Filename**: `{table-title}_{date}.csv` (e.g., `users_2025-01-15.csv`)
891
- - **Format**: CSV with proper escaping for special characters
892
- - **Data**: All filtered rows (not just current page)
893
- - **Columns**: Only currently visible columns
894
-
895
- ### Custom Export Handler
896
-
897
- If you need custom export behavior, you can provide your own handler:
898
-
899
- ```tsx
900
- <DataTable
901
- data={data}
902
- columns={columns}
903
- features={{
904
- export: true,
905
- }}
906
- onExport={() => {
907
- // Your custom export logic
908
- // This overrides the default automatic export
909
- exportToCustomFormat(data);
910
- }}
911
- />
912
- ```
913
-
914
- ### Export Permissions (RBAC)
915
-
916
- Export is controlled by the `read:page.{pageId}` permission:
917
-
918
- ```tsx
919
- <DataTable
920
- data={data}
921
- columns={columns}
922
- rbac={{
923
- pageId: 'user-management'
924
- }}
925
- features={{
926
- export: true, // Requires read:page.user-management permission
927
- }}
928
- />
929
- ```
930
-
931
- Users without export permission won't see the export button.
932
-
933
- ## Column Ordering
934
-
935
- The DataTable component supports custom column ordering through the `columnOrder` prop, allowing you to control the exact position of columns including selection and actions columns.
936
-
937
- ### Basic Column Ordering
938
-
939
- ```tsx
940
- <DataTable
941
- data={data}
942
- columns={columns}
943
- features={{
944
- selection: true,
945
- sorting: true,
946
- filtering: true,
947
- }}
948
- columnOrder={['select', 'name', 'email', 'role', 'status']}
949
- />
950
- ```
951
-
952
- ### Column Order Behavior
953
-
954
- #### When `columnOrder` is provided:
955
- - **Exact positioning**: Columns appear in the exact order specified
956
- - **Selection column**: Include `'select'` in the array to position the selection column
957
- - **Actions column**: Include `'actions'` in the array to position the actions column
958
- - **Data columns**: Use the column's `accessorKey` or `id` to reference data columns
959
-
960
- #### When `columnOrder` is not provided:
961
- - **Default behavior**: Selection column first (if enabled), then data columns, then actions column
962
- - **Automatic ordering**: Columns appear in the order they're defined in the `columns` array
963
-
964
- ### Selection Column Positioning
965
-
966
- The selection column can be positioned anywhere in the column order:
967
-
968
- ```tsx
969
- // Selection column first (default when not in columnOrder)
970
- <DataTable
971
- data={data}
972
- columns={columns}
973
- features={{ selection: true }}
974
- columnOrder={['select', 'name', 'email', 'role']}
975
- />
976
-
977
- // Selection column in the middle
978
- <DataTable
979
- data={data}
980
- columns={columns}
981
- features={{ selection: true }}
982
- columnOrder={['name', 'select', 'email', 'role']}
983
- />
984
-
985
- // Selection column last
986
- <DataTable
987
- data={data}
988
- columns={columns}
989
- features={{ selection: true }}
990
- columnOrder={['name', 'email', 'role', 'select']}
991
- />
992
-
993
- // Selection column not in columnOrder - defaults to first position
994
- <DataTable
995
- data={data}
996
- columns={columns}
997
- features={{ selection: true }}
998
- columnOrder={['name', 'email', 'role']} // selection will be first
999
- />
1000
- ```
1001
-
1002
- ### Actions Column Positioning
1003
-
1004
- The actions column can also be positioned anywhere:
1005
-
1006
- ```tsx
1007
- // Actions column last (default when not in columnOrder)
1008
- <DataTable
1009
- data={data}
1010
- columns={columns}
1011
- features={{ editing: true, deletion: true }}
1012
- columnOrder={['name', 'email', 'role', 'actions']}
1013
- />
1014
-
1015
- // Actions column first
1016
- <DataTable
1017
- data={data}
1018
- columns={columns}
1019
- features={{ editing: true, deletion: true }}
1020
- columnOrder={['actions', 'name', 'email', 'role']}
1021
- />
610
+ #### 4. Date Filters
611
+ Date filters provide date picker inputs.
1022
612
 
1023
- // Actions column in the middle
1024
- <DataTable
1025
- data={data}
1026
- columns={columns}
1027
- features={{ editing: true, deletion: true }}
1028
- columnOrder={['name', 'actions', 'email', 'role']}
1029
- />
613
+ ```typescript
614
+ const columns = [
615
+ {
616
+ id: 'created_date',
617
+ accessorKey: 'created_date',
618
+ header: 'Created Date',
619
+ enableColumnFilter: true,
620
+ filterType: 'date'
621
+ }
622
+ ];
1030
623
  ```
1031
624
 
1032
- ### Complete Column Ordering Example
625
+ ### Complete Filtering Example
1033
626
 
1034
- ```tsx
1035
- interface User {
1036
- id: string;
1037
- name: string;
1038
- email: string;
1039
- role: string;
1040
- status: 'active' | 'inactive';
1041
- createdAt: Date;
1042
- }
627
+ ```typescript
628
+ import { DataTable } from '@jmruthers/pace-core';
1043
629
 
1044
- const columns: DataTableColumn<User>[] = [
1045
- {
1046
- accessorKey: 'name',
1047
- header: 'Name',
1048
- sortable: true,
1049
- },
1050
- {
1051
- accessorKey: 'email',
1052
- header: 'Email',
1053
- sortable: true,
1054
- },
1055
- {
1056
- accessorKey: 'role',
1057
- header: 'Role',
1058
- sortable: true,
1059
- enableGrouping: true,
1060
- },
1061
- {
1062
- accessorKey: 'status',
1063
- header: 'Status',
1064
- sortable: true,
1065
- },
1066
- {
1067
- accessorKey: 'createdAt',
1068
- header: 'Created',
1069
- sortable: true,
1070
- },
1071
- ];
630
+ function MealsTable() {
631
+ const meals = [
632
+ {
633
+ id: '1',
634
+ meal_code: '28py',
635
+ mealtype: { mealtype_id: 'breakfast', mealtype_name: 'Breakfast' },
636
+ meal_date: '2024-12-28',
637
+ price: 15.50
638
+ },
639
+ // ... more data
640
+ ];
641
+
642
+ const mealTypes = [
643
+ { mealtype_id: 'breakfast', mealtype_name: 'Breakfast' },
644
+ { mealtype_id: 'lunch', mealtype_name: 'Lunch' },
645
+ { mealtype_id: 'dinner', mealtype_name: 'Dinner' },
646
+ { mealtype_id: 'morning_tea', mealtype_name: 'Morning Tea' },
647
+ { mealtype_id: 'afternoon_tea', mealtype_name: 'Afternoon Tea' },
648
+ { mealtype_id: 'pantry', mealtype_name: 'Pantry' }
649
+ ];
650
+
651
+ const columns = [
652
+ {
653
+ id: 'meal_code',
654
+ accessorKey: 'meal_code',
655
+ header: 'Meal Code',
656
+ enableColumnFilter: true,
657
+ // Text filter (default)
658
+ },
659
+ {
660
+ id: 'meal_type',
661
+ accessorFn: (row) => row.mealtype?.mealtype_name || 'N/A',
662
+ header: 'Type',
663
+ enableColumnFilter: true,
664
+ filterType: 'select',
665
+ filterSelectOptions: mealTypes.map(mt => ({
666
+ value: mt.mealtype_id,
667
+ label: mt.mealtype_name
668
+ })),
669
+ filterFn: (row, id, value) => {
670
+ return value.includes(row.original.mealtype?.mealtype_id);
671
+ }
672
+ },
673
+ {
674
+ id: 'meal_date',
675
+ accessorKey: 'meal_date',
676
+ header: 'Date',
677
+ enableColumnFilter: true,
678
+ filterType: 'date'
679
+ },
680
+ {
681
+ id: 'price',
682
+ accessorKey: 'price',
683
+ header: 'Price',
684
+ enableColumnFilter: true,
685
+ filterType: 'number'
686
+ }
687
+ ];
1072
688
 
1073
- function UserTable() {
1074
689
  return (
1075
690
  <DataTable
1076
- data={users}
691
+ data={meals}
1077
692
  columns={columns}
1078
- title="User Management"
1079
- features={{
1080
- selection: true,
1081
- sorting: true,
1082
- filtering: true,
1083
- editing: true,
1084
- deletion: true,
1085
- }}
1086
- // Custom column order: selection first, then name, email, role, status, actions, created last
1087
- columnOrder={['select', 'name', 'email', 'role', 'status', 'actions', 'createdAt']}
1088
- onEditRow={handleEdit}
1089
- onDeleteRow={handleDelete}
1090
- />
1091
- );
1092
- }
1093
- ```
1094
-
1095
- ### Column Ordering with Hierarchical Data
1096
-
1097
- When using hierarchical data, column ordering works the same way:
1098
-
1099
- ```tsx
1100
- <DataTable
1101
- data={hierarchicalData}
1102
- columns={columns}
1103
- features={{
1104
- hierarchical: true,
1105
- selection: true,
1106
- editing: true,
1107
- deletion: true,
1108
- }}
1109
- hierarchical={{
1110
- enabled: true,
1111
- defaultExpanded: false,
1112
- }}
1113
- // Custom order for hierarchical table
1114
- columnOrder={['select', 'name', 'ingredient', 'quantity', 'actions']}
1115
- />
1116
- ```
1117
-
1118
- ### Dynamic Column Ordering
1119
-
1120
- You can dynamically change column order based on user preferences or application state:
1121
-
1122
- ```tsx
1123
- function DynamicUserTable() {
1124
- const [columnOrder, setColumnOrder] = useState(['select', 'name', 'email', 'role', 'status']);
1125
-
1126
- const handleColumnReorder = (newOrder: string[]) => {
1127
- setColumnOrder(newOrder);
1128
- // Save to localStorage or user preferences
1129
- localStorage.setItem('userTableColumnOrder', JSON.stringify(newOrder));
1130
- };
1131
-
1132
- return (
1133
- <DataTable
1134
- data={users}
1135
- columns={columns}
1136
- features={{
1137
- selection: true,
1138
- sorting: true,
1139
- columnReordering: true, // Enable drag-and-drop reordering
1140
- }}
1141
- columnOrder={columnOrder}
1142
- onColumnOrderChange={handleColumnReorder}
1143
- />
1144
- );
1145
- }
1146
- ```
1147
-
1148
- ### Column Ordering Best Practices
1149
-
1150
- #### 1. **Consistent Ordering**
1151
- ```tsx
1152
- // ✅ Good - Consistent ordering across similar tables
1153
- const userTableOrder = ['select', 'name', 'email', 'role', 'status', 'actions'];
1154
- const productTableOrder = ['select', 'name', 'sku', 'price', 'status', 'actions'];
1155
-
1156
- // ❌ Avoid - Inconsistent ordering
1157
- const userTableOrder = ['select', 'name', 'email', 'role', 'status', 'actions'];
1158
- const productTableOrder = ['name', 'select', 'sku', 'actions', 'price', 'status'];
1159
- ```
1160
-
1161
- #### 2. **Logical Grouping**
1162
- ```tsx
1163
- // ✅ Good - Logical grouping of related columns
1164
- columnOrder={[
1165
- 'select', // Selection controls
1166
- 'name', 'email', // Identity information
1167
- 'role', 'status', // Access and state
1168
- 'createdAt', // Metadata
1169
- 'actions' // Actions
1170
- ]}
1171
-
1172
- // ❌ Avoid - Random column order
1173
- columnOrder={['email', 'actions', 'name', 'select', 'status', 'role', 'createdAt']}
1174
- ```
1175
-
1176
- #### 3. **User Experience Considerations**
1177
- ```tsx
1178
- // ✅ Good - Most important columns first
1179
- columnOrder={['select', 'name', 'email', 'role', 'status', 'actions']}
1180
-
1181
- // ✅ Good - Actions column last (standard pattern)
1182
- columnOrder={['select', 'name', 'email', 'role', 'status', 'actions']}
1183
-
1184
- // ❌ Avoid - Actions column in the middle (confusing)
1185
- columnOrder={['select', 'name', 'actions', 'email', 'role', 'status']}
1186
- ```
1187
-
1188
- #### 4. **Responsive Considerations**
1189
- ```tsx
1190
- // For mobile-first design, put most important columns first
1191
- const mobileColumnOrder = ['select', 'name', 'status', 'actions'];
1192
- const desktopColumnOrder = ['select', 'name', 'email', 'role', 'status', 'createdAt', 'actions'];
1193
-
1194
- <DataTable
1195
- data={users}
1196
- columns={columns}
1197
- features={{ selection: true, sorting: true }}
1198
- columnOrder={isMobile ? mobileColumnOrder : desktopColumnOrder}
1199
- />
1200
- ```
1201
-
1202
- ## Data Management
1203
-
1204
- ### Sorting
1205
-
1206
- ```tsx
1207
- <DataTable
1208
- data={data}
1209
- columns={columns}
1210
- features={{ sorting: true }}
1211
- defaultSorting={[
1212
- { id: 'name', desc: false },
1213
- { id: 'createdAt', desc: true }
1214
- ]}
1215
- />
1216
- ```
1217
-
1218
- #### Default Sorting
1219
-
1220
- You can configure your DataTable to load with pre-applied sorting:
1221
-
1222
- ```tsx
1223
- <DataTable
1224
- data={recipes}
1225
- columns={columns}
1226
- features={{ sorting: true }}
1227
- defaultSorting={[
1228
- { id: 'diettype_name', desc: false },
1229
- { id: 'item_name', desc: false }
1230
- ]}
1231
- rbac={{ pageId: 'recipes' }}
1232
- />
1233
- ```
1234
-
1235
- **Examples:**
1236
- - **Sort by Date (descending):** `defaultSorting={[{ id: 'created_at', desc: true }]}`
1237
- - **Multi-column sort:** `defaultSorting={[{ id: 'category', desc: false }, { id: 'name', desc: false }]}`
1238
-
1239
- ### Filtering
1240
-
1241
- ```tsx
1242
- <DataTable
1243
- data={data}
1244
- columns={columns}
1245
- features={{ filtering: true }}
1246
- globalFilterFn="includesString"
1247
- columnFilters={[
1248
- { id: 'status', value: 'active' },
1249
- { id: 'role', value: 'admin' }
1250
- ]}
1251
- />
1252
- ```
1253
-
1254
- ### Pagination
1255
-
1256
- ```tsx
1257
- <DataTable
1258
- data={data}
1259
- columns={columns}
1260
- features={{ pagination: true }}
1261
- />
1262
- ```
1263
-
1264
- #### Custom Initial Page Size
1265
-
1266
- ```tsx
1267
- <DataTable
1268
- data={data}
1269
- columns={columns}
1270
- features={{ pagination: true }}
1271
- initialPageSize={25} // Start with 25 items per page instead of default 10
1272
- />
1273
- ```
1274
-
1275
- #### Page Size Validation
1276
-
1277
- The DataTable automatically validates the `initialPageSize` against available page size options:
1278
-
1279
- ```tsx
1280
- <DataTable
1281
- data={data}
1282
- columns={columns}
1283
- features={{ pagination: true }}
1284
- initialPageSize={15} // Will fallback to closest valid option (10) with console warning
1285
- />
1286
- ```
1287
-
1288
- **Available page size options by mode:**
1289
- - **Client mode**: `[10, 25, 50]` or `[10, 25, 50, 100]` (depending on data size)
1290
- - **Hybrid mode**: `[50, 100, 250, 500]`
1291
- - **Server mode**: `[25, 50, 100, 250]`
1292
-
1293
- ### Search
1294
-
1295
- ```tsx
1296
- <DataTable
1297
- data={data}
1298
- columns={columns}
1299
- features={{ search: true }}
1300
- searchKey="name" // Default search field
1301
- searchPlaceholder="Search users..."
1302
- searchDebounceMs={300}
1303
- />
1304
- ```
1305
-
1306
- ## Advanced Features
1307
-
1308
- ### Row Actions
1309
-
1310
- Row actions are handled through the `actions` prop, which provides full flexibility for custom action buttons:
1311
-
1312
- ```tsx
1313
- <DataTable
1314
- data={data}
1315
- columns={columns}
1316
- features={{
1317
- editing: true,
1318
- deletion: true,
1319
- }}
1320
- onEditRow={handleEdit}
1321
- onDeleteRow={handleDelete}
1322
- // Custom actions using the actions prop
1323
- actions={[
1324
- {
1325
- label: 'View Details',
1326
- onClick: (row) => navigate(`/users/${row.original.id}`),
1327
- icon: EyeIcon,
1328
- variant: 'default',
1329
- },
1330
- {
1331
- label: 'Edit',
1332
- onClick: (row) => editUser(row.original.id),
1333
- icon: EditIcon,
1334
- variant: 'outline',
1335
- },
1336
- {
1337
- label: 'Share',
1338
- onClick: (row) => shareUser(row.original.id),
1339
- icon: ShareIcon,
1340
- variant: 'secondary',
1341
- },
1342
- {
1343
- label: 'More Options',
1344
- onClick: (row) => showMoreOptions(row.original.id),
1345
- icon: MoreHorizontalIcon,
1346
- variant: 'ghost',
1347
- },
1348
- {
1349
- label: 'Archive',
1350
- onClick: (row) => archiveUser(row.original.id),
1351
- icon: ArchiveIcon,
1352
- variant: 'destructive',
1353
- disabled: (row) => row.original.status === 'archived',
1354
- },
1355
- ]}
1356
- />
1357
- ```
1358
-
1359
- #### Action Configuration
1360
-
1361
- Each action supports the following properties:
1362
-
1363
- - `label: string` - Display label for the action
1364
- - `onClick: (row: TData) => void` - Action handler function
1365
- - `icon?: ComponentType` - Optional icon component
1366
- - `variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost'` - Button variant
1367
- - `disabled?: (row: TData) => boolean` - Function to determine if action is disabled
1368
- - `testId?: string` - Test ID for testing
1369
- - `visible?: boolean | (row: TData) => boolean` - Control visibility
1370
- - `showInEditMode?: boolean` - Show in edit mode (default: true)
1371
- - `hideInViewMode?: boolean` - Hide in view mode (default: false)
1372
-
1373
- #### Action Variants Guide
1374
-
1375
- The DataTable supports 5 action variants, each designed for specific use cases:
1376
-
1377
- ```tsx
1378
- const actions = [
1379
- // DEFAULT - Primary actions (most important)
1380
- {
1381
- label: 'View Details',
1382
- onClick: (row) => viewDetails(row.original.id),
1383
- icon: EyeIcon,
1384
- variant: 'default', // Solid background, primary color
1385
- },
1386
-
1387
- // OUTLINE - Secondary actions (important but not primary)
1388
- {
1389
- label: 'Edit',
1390
- onClick: (row) => editItem(row.original.id),
1391
- icon: EditIcon,
1392
- variant: 'outline', // Bordered, transparent background
1393
- },
1394
-
1395
- // SECONDARY - Supporting actions (less prominent)
1396
- {
1397
- label: 'Share',
1398
- onClick: (row) => shareItem(row.original.id),
1399
- icon: ShareIcon,
1400
- variant: 'secondary', // Muted background, subtle styling
1401
- },
1402
-
1403
- // GHOST - Subtle actions (minimal visual impact)
1404
- {
1405
- label: 'More Options',
1406
- onClick: (row) => showMoreOptions(row.original.id),
1407
- icon: MoreHorizontalIcon,
1408
- variant: 'ghost', // No background, minimal styling
1409
- },
1410
-
1411
- // DESTRUCTIVE - Dangerous actions (require caution)
1412
- {
1413
- label: 'Delete',
1414
- onClick: (row) => deleteItem(row.original.id),
1415
- icon: TrashIcon,
1416
- variant: 'destructive', // Red styling to indicate danger
1417
- disabled: (row) => row.original.status === 'locked',
1418
- },
1419
- ];
1420
- ```
1421
-
1422
- **Variant Usage Guidelines:**
1423
-
1424
- - **`default`**: Use for the most important action (e.g., "View Details", "Open")
1425
- - **`outline`**: Use for important secondary actions (e.g., "Edit", "Download")
1426
- - **`secondary`**: Use for supporting actions (e.g., "Share", "Copy", "Export")
1427
- - **`ghost`**: Use for subtle actions (e.g., "More Options", "Settings")
1428
- - **`destructive`**: Use for dangerous actions (e.g., "Delete", "Archive", "Remove")
1429
-
1430
- #### Hierarchical Actions
1431
-
1432
- When using hierarchical rows, you can define different action icons and labels for parent vs child rows:
1433
-
1434
- ```tsx
1435
- <DataTable
1436
- data={hierarchicalData}
1437
- columns={columns}
1438
- features={{
1439
- hierarchical: true,
1440
- editing: true,
1441
- deletion: true,
1442
- }}
1443
- hierarchical={{
1444
- enabled: true,
1445
- defaultExpanded: false,
1446
- }}
1447
- actions={[
1448
- {
1449
- label: 'View Details',
1450
- icon: EyeIcon,
1451
- // Different icons for parent vs child rows
1452
- parentIcon: EyeIcon,
1453
- childIcon: InfoIcon,
1454
- // Different labels for parent vs child rows
1455
- parentLabel: 'View Recipe',
1456
- childLabel: 'View Ingredient',
1457
- onClick: (row) => {
1458
- console.log('Viewing:', row.isParent ? 'Recipe' : 'Ingredient', row.name);
1459
- },
1460
- variant: 'default',
1461
- },
1462
- {
1463
- label: 'Edit',
1464
- icon: PencilIcon,
1465
- parentIcon: SettingsIcon,
1466
- childIcon: PencilIcon,
1467
- parentLabel: 'Edit Recipe',
1468
- childLabel: 'Edit Ingredient',
1469
- onClick: (row) => {
1470
- console.log('Editing:', row.isParent ? 'Recipe' : 'Ingredient', row.name);
1471
- },
1472
- variant: 'default',
1473
- },
1474
- {
1475
- label: 'Copy Recipe',
1476
- icon: CopyIcon,
1477
- parentIcon: CopyIcon,
1478
- parentLabel: 'Duplicate Recipe',
1479
- onClick: (row) => {
1480
- console.log('Copying recipe:', row.name);
1481
- },
1482
- variant: 'outline',
1483
- // Only show for parent rows
1484
- showForParent: true,
1485
- },
1486
- {
1487
- label: 'Export',
1488
- icon: DownloadIcon,
1489
- childIcon: DownloadIcon,
1490
- childLabel: 'Export Ingredient',
1491
- onClick: (row) => {
1492
- console.log('Exporting ingredient:', row.item);
1493
- },
1494
- variant: 'ghost',
1495
- // Only show for child rows
1496
- showForChild: true,
1497
- },
1498
- ]}
1499
- />
1500
- ```
1501
-
1502
- ##### Hierarchical Action Properties
1503
-
1504
- When using hierarchical rows, actions support additional properties:
1505
-
1506
- - `showForParent?: boolean` - Only show this action for parent rows
1507
- - `showForChild?: boolean` - Only show this action for child rows
1508
- - `parentIcon?: ComponentType` - Icon to use for parent rows (overrides `icon`)
1509
- - `childIcon?: ComponentType` - Icon to use for child rows (overrides `icon`)
1510
- - `parentLabel?: string` - Label to use for parent rows (overrides `label`)
1511
- - `childLabel?: string` - Label to use for child rows (overrides `label`)
1512
-
1513
- ##### Action Visibility Logic
1514
-
1515
- The action visibility is determined by the following logic:
1516
-
1517
- 1. **Hierarchical filtering**: If `showForParent` is true, action only shows for parent rows
1518
- 2. **Hierarchical filtering**: If `showForChild` is true, action only shows for child rows
1519
- 3. **Standard filtering**: Uses `visible`, `showInEditMode`, `hideInViewMode` properties
1520
- 4. **Disabled state**: Uses `disabled` function to determine if action is disabled
1521
-
1522
- ### Toolbar Actions
1523
-
1524
- Toolbar actions are automatically generated based on the enabled features. Custom toolbar buttons are not directly supported in the current interface, but you can implement them outside the DataTable component:
1525
-
1526
- ```tsx
1527
- // Toolbar actions are handled through feature configuration
1528
- <div>
1529
- {/* Custom toolbar */}
1530
- <div className="flex gap-2 mb-4">
1531
- <Button onClick={() => navigate('/users/create')}>
1532
- <PlusIcon className="h-4 w-4 mr-2" />
1533
- Add User
1534
- </Button>
1535
- </div>
1536
-
1537
- <DataTable
1538
- data={data}
1539
- columns={columns}
1540
- features={{
1541
- search: true,
1542
- pagination: true,
1543
- export: true,
1544
- import: true,
1545
- creation: true, // Enables built-in create functionality if onCreateRow is provided
1546
- }}
1547
- onCreateRow={handleCreate}
1548
- onImport={handleImport}
1549
- exportFilename="users"
1550
- />
1551
- </div>
1552
- ```
1553
-
1554
- ### Hierarchical Parent/Child Rows
1555
-
1556
- The DataTable supports hierarchical parent/child row relationships with built-in expand/collapse functionality, expand/collapse all controls, and proper column rendering for different row types.
1557
-
1558
- #### Data Structure
1559
-
1560
- Your data must conform to the `HierarchicalDataRow` interface:
1561
-
1562
- ```typescript
1563
- interface HierarchicalDataRow extends DataRecord {
1564
- /** Whether this row is a parent row */
1565
- isParent: boolean;
1566
- /** For child rows, references the parent row ID */
1567
- parentId?: string;
1568
- /** Unique identifier for this row */
1569
- id: string;
1570
- // ... your actual data properties
1571
- }
1572
- ```
1573
-
1574
- #### Configuration
1575
-
1576
- Enable hierarchical functionality through the `features` prop and configure it with the `hierarchical` prop:
1577
-
1578
- ```tsx
1579
- <DataTable
1580
- data={hierarchicalData}
1581
- columns={columns}
1582
- features={{
1583
- // ... other features
1584
- hierarchical: true, // Enable hierarchical functionality
1585
- }}
1586
- hierarchical={{
1587
- enabled: true,
1588
- defaultExpanded: false, // true = all expanded, false = all collapsed, array = specific IDs
1589
- onExpandedChange: (expandedIds) => console.log('Expanded:', expandedIds),
1590
- expandButton: CustomExpandButton, // Optional custom expand button
1591
- indentSize: 24, // Indentation for child rows in pixels
1592
- parentRowClassName: 'bg-main-50 font-medium', // Custom styling for parent rows
1593
- childRowClassName: 'bg-sec-25', // Custom styling for child rows
1594
- }}
1595
- ```
1596
-
1597
- **Default Collapsed State:**
1598
- To make parent rows collapsed by default (recommended for better UX), set `defaultExpanded: false`. This ensures users see only parent rows initially and must click to expand and view child rows.
1599
-
1600
- #### Column Configuration
1601
-
1602
- Configure how columns render for different row types:
1603
-
1604
- ```tsx
1605
- const columns: DataTableColumn<YourDataType>[] = [
1606
- {
1607
- accessorKey: 'code',
1608
- header: 'Code',
1609
- // Render differently for parent vs child rows
1610
- renderForParent: (row) => (
1611
- <div className="flex items-center gap-2">
1612
- <strong>{row.code}</strong>
1613
- </div>
1614
- ),
1615
- renderForChild: () => '', // Blank for child rows
1616
- hideForChild: true, // Hide this column for child rows
1617
- },
1618
- {
1619
- accessorKey: 'name',
1620
- header: 'Name',
1621
- renderForParent: (row) => <strong>{row.name}</strong>,
1622
- renderForChild: () => '', // Blank for child rows
1623
- hideForChild: true,
1624
- },
1625
- {
1626
- accessorKey: 'ingredient',
1627
- header: 'Ingredient',
1628
- renderForParent: () => '', // Blank for parent rows
1629
- renderForChild: (row) => <span className="ml-4">{row.ingredient}</span>,
1630
- hideForParent: true, // Hide this column for parent rows
1631
- },
1632
- ];
1633
- ```
1634
-
1635
- #### Column Properties
1636
-
1637
- - `renderForParent?: (row: TData) => React.ReactNode` - Custom renderer for parent rows
1638
- - `renderForChild?: (row: TData) => React.ReactNode` - Custom renderer for child rows
1639
- - `hideForParent?: boolean` - Hide this column for parent rows
1640
- - `hideForChild?: boolean` - Hide this column for child rows
1641
-
1642
- #### Expand/Collapse All Controls
1643
-
1644
- The DataTable automatically adds an expand/collapse all button in the header when hierarchical mode is enabled:
1645
-
1646
- **Features:**
1647
- - **Expand All Button (▶️)**: Expands all parent rows to show their children
1648
- - **Collapse All Button (🔽)**: Collapses all parent rows to hide their children
1649
- - **Smart State Detection**: Button automatically updates based on current expansion state
1650
- - **Accessibility**: Proper ARIA labels and keyboard navigation support
1651
-
1652
- **Usage:**
1653
- ```tsx
1654
- <DataTable
1655
- data={hierarchicalData}
1656
- columns={columns}
1657
- features={{ hierarchical: true }}
1658
- hierarchical={{
1659
- enabled: true,
1660
- defaultExpanded: false, // Start with all collapsed
1661
- onExpandedChange: (expandedIds) => {
1662
- console.log('Expanded rows:', expandedIds);
1663
- },
1664
- }}
1665
- />
1666
- ```
1667
-
1668
- **Button Behavior:**
1669
- - Shows ▶️ when some or all parent rows are collapsed
1670
- - Shows 🔽 when all parent rows are expanded
1671
- - Only appears when there are parent rows with children
1672
- - Positioned in the first column of the table header
1673
-
1674
- #### Hierarchical Sorting
1675
-
1676
- When hierarchical mode is enabled, sorting behavior is optimized to preserve the parent-child relationship structure:
1677
-
1678
- **Parent Row Sorting:**
1679
- - Parent rows maintain their original order and are not affected by sorting
1680
- - Only child rows within each parent group are sorted
1681
- - This prevents parent rows from being moved to unexpected positions
1682
-
1683
- **Child Row Sorting:**
1684
- - Child rows are sorted within their parent group only
1685
- - Sorting by a child-specific column (e.g., "Diet", "Ingredient") will sort children within each parent
1686
- - The parent row remains at the top of its group
1687
-
1688
- **Example:**
1689
- ```tsx
1690
- // When sorting by "Diet" column:
1691
- // ✅ Correct behavior:
1692
- // Parent: Caesar Salad
1693
- // ├─ Child: Dairy Free (Sour cream)
1694
- // ├─ Child: Egg Free (Mayonnaise)
1695
- // └─ Child: GF & Vegan (Lettuce)
1696
- // Parent: Beef Stir Fry
1697
- // ├─ Child: GF & Vegan (Vegetables)
1698
- // └─ Child: Halal (Beef)
1699
-
1700
- // ❌ Incorrect behavior (what we prevent):
1701
- // Child: Dairy Free (Sour cream)
1702
- // Child: Egg Free (Mayonnaise)
1703
- // Child: GF & Vegan (Lettuce)
1704
- // Child: GF & Vegan (Vegetables)
1705
- // Child: Halal (Beef)
1706
- // Parent: Caesar Salad (moved to end)
1707
- // Parent: Beef Stir Fry (moved to end)
1708
- ```
1709
-
1710
- **Sorting Configuration:**
1711
- - All existing sorting features work with hierarchical data
1712
- - Multi-column sorting is supported
1713
- - Server-side sorting is compatible
1714
- - Column-specific sorting behavior is preserved
1715
-
1716
- #### Example Use Case
1717
-
1718
- ```tsx
1719
- // Dishes and ingredients example
1720
- const dishData = [
1721
- // Parent rows (dishes)
1722
- { id: 'dish-1', isParent: true, code: 'D001', name: 'Caesar Salad', type: 'Salad' },
1723
- { id: 'dish-2', isParent: true, code: 'D002', name: 'Beef Stir Fry', type: 'Main Course' },
1724
-
1725
- // Child rows (ingredients) for Caesar Salad
1726
- { id: 'ing-1-1', isParent: false, parentId: 'dish-1', ingredient: 'Lettuce', quantity: '200g' },
1727
- { id: 'ing-1-2', isParent: false, parentId: 'dish-1', ingredient: 'Chicken', quantity: '150g' },
1728
-
1729
- // Child rows (ingredients) for Beef Stir Fry
1730
- { id: 'ing-2-1', isParent: false, parentId: 'dish-2', ingredient: 'Beef Strips', quantity: '250g' },
1731
- { id: 'ing-2-2', isParent: false, parentId: 'dish-2', ingredient: 'Mixed Vegetables', quantity: '200g' },
1732
- ];
1733
- ```
1734
-
1735
- ### Row Selection and Bulk Operations
1736
-
1737
- ```tsx
1738
- function SelectableUserTable() {
1739
- const [selectedRows, setSelectedRows] = useState<Record<string, boolean>>({});
1740
- const [data, setData] = useState<User[]>(initialData);
1741
-
1742
- const handleBulkDelete = (selectedRows: Record<string, boolean>) => {
1743
- // Get the selected row IDs
1744
- const selectedRowIds = Object.keys(selectedRows).filter(id => selectedRows[id]);
1745
-
1746
- // Get the actual data for selected rows
1747
- const selectedData = data.filter(row => {
1748
- const rowId = getRowId ? getRowId(row, data.indexOf(row)) : row.id;
1749
- return selectedRowIds.includes(rowId);
1750
- });
1751
-
1752
- // Perform your deletion logic
1753
- console.log('Deleting selected rows:', selectedData);
1754
-
1755
- // Update your data state
1756
- setData(prevData =>
1757
- prevData.filter(row => {
1758
- const rowId = getRowId ? getRowId(row, prevData.indexOf(row)) : row.id;
1759
- return !selectedRowIds.includes(rowId);
1760
- })
1761
- );
1762
-
1763
- // Clear selection after deletion
1764
- setSelectedRows({});
1765
- };
1766
-
1767
- return (
1768
- <DataTable
1769
- data={data}
1770
- columns={columns}
1771
- features={{
1772
- selection: true,
1773
- deletion: true, // Required for delete functionality
1774
- deleteSelected: true, // Enables the delete selected button
1775
- bulkOperations: true,
1776
- }}
1777
- onRowSelectionChange={setSelectedRows}
1778
- onDeleteSelected={handleBulkDelete}
1779
- getRowId={(row) => row.id} // Important: provide getRowId for proper row identification
1780
- />
1781
- );
1782
- }
1783
- ```
1784
-
1785
- #### Alternative: Using Individual onDeleteRow (Fallback)
1786
-
1787
- If you don't provide `onDeleteSelected`, the DataTable will automatically fall back to calling `onDeleteRow` for each selected row:
1788
-
1789
- ```tsx
1790
- function SelectableUserTable() {
1791
- const [data, setData] = useState<User[]>(initialData);
1792
-
1793
- const handleDeleteRow = (row: User) => {
1794
- console.log('Deleting row:', row);
1795
-
1796
- // Update your data state
1797
- setData(prevData => prevData.filter(item => item.id !== row.id));
1798
- };
1799
-
1800
- return (
1801
- <DataTable
1802
- data={data}
1803
- columns={columns}
1804
- features={{
1805
- selection: true,
1806
- deletion: true,
1807
- deleteSelected: true,
1808
- }}
1809
- onDeleteRow={handleDeleteRow} // This will be called for each selected row
1810
- getRowId={(row) => row.id}
1811
- />
1812
- );
1813
- }
1814
- ```
1815
-
1816
- ### Column Visibility
1817
-
1818
- ```tsx
1819
- <DataTable
1820
- data={data}
1821
- columns={columns}
1822
- features={{ columnVisibility: true }}
1823
- defaultColumnVisibility={{
1824
- id: false,
1825
- createdAt: false,
1826
- }}
1827
- />
1828
- ```
1829
-
1830
- ### Data Grouping
1831
-
1832
- ```tsx
1833
- <DataTable
1834
- data={data}
1835
- columns={columns}
1836
- features={{ grouping: true }}
1837
- grouping={['role', 'status']}
1838
- />
1839
- ```
1840
-
1841
- #### Default Grouping
1842
-
1843
- You can configure your DataTable to load with pre-applied grouping:
1844
-
1845
- ```tsx
1846
- <DataTable
1847
- data={recipes}
1848
- columns={columns}
1849
- features={{ grouping: true }}
1850
- defaultGrouping={['diettype_name']}
1851
- rbac={{ pageId: 'recipes' }}
1852
- />
1853
- ```
1854
-
1855
- **Examples:**
1856
- - **Group by Status:** `defaultGrouping={['status']}`
1857
- - **Multi-level grouping:** `defaultGrouping={['category', 'status']}`
1858
-
1859
- #### Combined Default Grouping and Sorting
1860
-
1861
- You can combine both default grouping and sorting for a fully pre-organized view:
1862
-
1863
- ```tsx
1864
- <DataTable
1865
- data={recipes}
1866
- columns={columns}
1867
- features={{
1868
- grouping: true,
1869
- sorting: true,
1870
- }}
1871
- defaultGrouping={['diettype_name']}
1872
- defaultSorting={[
1873
- { id: 'diettype_name', desc: false },
1874
- { id: 'item_name', desc: false }
1875
- ]}
1876
- rbac={{ pageId: 'recipes' }}
1877
- />
1878
- ```
1879
-
1880
- **Behavior:**
1881
- - **defaultGrouping**: Array of column IDs to group by on initial load
1882
- - **defaultSorting**: Array of sort configurations (column ID + direction)
1883
- - Both props are optional and work independently or together
1884
- - Users can still modify grouping/sorting via UI controls
1885
- - Works with all DataTable features (pagination, filtering, etc.)
1886
-
1887
- ## Performance Optimization
1888
-
1889
- ### Virtual Scrolling
1890
-
1891
- ```tsx
1892
- <DataTable
1893
- data={largeDataset}
1894
- columns={columns}
1895
- features={{ virtualization: true }}
1896
- virtualHeight={600} // Height of the virtual scrolling container
1897
- performance={{
1898
- virtualScrolling: true,
1899
- memoryOptimization: true,
1900
- }}
1901
- />
1902
- ```
1903
-
1904
- ### Lazy Loading
1905
-
1906
- ```tsx
1907
- function LazyLoadingTable() {
1908
- const [data, setData] = useState<User[]>([]);
1909
- const [loading, setLoading] = useState(false);
1910
- const [hasMore, setHasMore] = useState(true);
1911
-
1912
- const loadMoreData = async () => {
1913
- setLoading(true);
1914
- const newData = await fetchUsers(data.length, 50);
1915
- setData(prev => [...prev, ...newData]);
1916
- setHasMore(newData.length === 50);
1917
- setLoading(false);
1918
- };
1919
-
1920
- return (
1921
- <DataTable
1922
- data={data}
1923
- columns={columns}
1924
- loading={loading}
1925
- onLoadMore={hasMore ? loadMoreData : undefined}
1926
- hasMore={hasMore}
1927
- />
1928
- );
1929
- }
1930
- ```
1931
-
1932
- ### Optimized Rendering
1933
-
1934
- ```tsx
1935
- <DataTable
1936
- data={data}
1937
- columns={columns}
1938
- features={{
1939
- virtualization: true,
1940
- performanceMetrics: true,
1941
- }}
1942
- performance={{
1943
- virtualScrolling: true,
1944
- memoryOptimization: true,
1945
- renderOptimization: true,
1946
- }}
1947
- showPerformanceMetrics={true}
1948
- virtualHeight={600}
1949
- />
1950
- ```
1951
-
1952
- ## Integration with RBAC
1953
-
1954
- ### Permission-Based Actions
1955
-
1956
- ```tsx
1957
- function SecureUserTable() {
1958
- const { checkPermission } = usePermissionCache();
1959
-
1960
- const [permissions, setPermissions] = useState({
1961
- canCreate: false,
1962
- canUpdate: false,
1963
- canDelete: false,
1964
- });
1965
-
1966
- useEffect(() => {
1967
- const loadPermissions = async () => {
1968
- const results = await Promise.all([
1969
- checkPermission('create', 'user-management'),
1970
- checkPermission('update', 'user-management'),
1971
- checkPermission('delete', 'user-management'),
1972
- ]);
1973
-
1974
- setPermissions({
1975
- canCreate: results[0],
1976
- canUpdate: results[1],
1977
- canDelete: results[2],
1978
- });
1979
- };
1980
-
1981
- loadPermissions();
1982
- }, [checkPermission]);
1983
-
1984
- return (
1985
- <DataTable
1986
- data={users}
1987
- columns={columns}
1988
- rbac={{
1989
- resource: 'users',
1990
- pageId: 'user-management'
1991
- }}
1992
693
  features={{
694
+ filtering: true, // Enable filtering
1993
695
  search: true,
1994
696
  pagination: true,
1995
- creation: permissions.canCreate,
1996
- editing: permissions.canUpdate,
1997
- deletion: permissions.canDelete,
1998
- rowActions: permissions.canUpdate || permissions.canDelete,
1999
- }}
2000
- onEditRow={permissions.canUpdate ? (row) => navigate(`/users/${row.original.id}/edit`) : undefined}
2001
- onDeleteRow={permissions.canDelete ? (row) => handleDelete(row.original.id) : undefined}
2002
- // Custom actions using deprecated actions prop (still supported)
2003
- actions={
2004
- permissions.canUpdate || permissions.canDelete ? [
2005
- {
2006
- label: 'View Details',
2007
- onClick: (row) => navigate(`/users/${row.original.id}`),
2008
- }
2009
- ] : []
2010
- }
2011
- />
2012
- );
2013
- }
2014
- ```
2015
-
2016
- ## Practical Examples
2017
-
2018
- ### Custom Page Size Configuration
2019
-
2020
- Here's a complete example showing how to configure a DataTable with custom initial page size:
2021
-
2022
- ```tsx
2023
- import { DataTable, type DataTableColumn } from '@jmruthers/pace-core';
2024
-
2025
- interface User {
2026
- id: string;
2027
- name: string;
2028
- email: string;
2029
- role: string;
2030
- status: 'active' | 'inactive';
2031
- }
2032
-
2033
- const columns: DataTableColumn<User>[] = [
2034
- {
2035
- accessorKey: 'name',
2036
- header: 'Name',
2037
- sortable: true,
2038
- searchable: true,
2039
- },
2040
- {
2041
- accessorKey: 'email',
2042
- header: 'Email',
2043
- sortable: true,
2044
- searchable: true,
2045
- },
2046
- {
2047
- accessorKey: 'role',
2048
- header: 'Role',
2049
- sortable: true,
2050
- enableGrouping: true,
2051
- },
2052
- {
2053
- accessorKey: 'status',
2054
- header: 'Status',
2055
- sortable: true,
2056
- cell: ({ row }) => (
2057
- <span className={`px-2 py-1 rounded text-xs ${
2058
- row.original.status === 'active'
2059
- ? 'bg-main-100 text-main-800'
2060
- : 'bg-acc-100 text-acc-800'
2061
- }`}>
2062
- {row.original.status}
2063
- </span>
2064
- ),
2065
- },
2066
- ];
2067
-
2068
- function UserManagementTable() {
2069
- const [users, setUsers] = useState<User[]>([]);
2070
- const [loading, setLoading] = useState(true);
2071
-
2072
- useEffect(() => {
2073
- // Load users data
2074
- loadUsers().then(setUsers).finally(() => setLoading(false));
2075
- }, []);
2076
-
2077
- return (
2078
- <DataTable
2079
- data={users}
2080
- columns={columns}
2081
- title="User Management"
2082
- description="Manage system users with custom page size"
2083
- features={{
2084
- search: true,
2085
- pagination: true,
2086
- sorting: true,
2087
- filtering: true,
2088
- selection: true,
2089
- creation: true,
2090
- editing: true,
2091
- deletion: true,
2092
- deleteSelected: true,
2093
- export: true,
2094
- import: true,
2095
- grouping: true,
2096
- columnVisibility: true,
2097
- columnReordering: true,
2098
- autoColumnSizing: true,
2099
- }}
2100
- initialPageSize={25} // Start with 25 users per page
2101
- isLoading={loading}
2102
- onEditRow={(row, data) => {
2103
- console.log('Editing user:', row.id, data);
2104
- // Handle user edit
2105
- }}
2106
- onDeleteRow={(row) => {
2107
- console.log('Deleting user:', row.id);
2108
- // Handle user deletion
2109
- }}
2110
- onCreateRow={(data) => {
2111
- console.log('Creating user:', data);
2112
- // Handle user creation
2113
- }}
2114
- onImport={(data) => {
2115
- console.log('Importing users:', data);
2116
- // Handle user import
2117
- }}
2118
- onRowSelectionChange={(selection) => {
2119
- console.log('Selection changed:', selection);
2120
- // Handle selection changes
697
+ sorting: true
2121
698
  }}
2122
- onDeleteSelected={(selectedRows) => {
2123
- console.log('Bulk deleting users:', selectedRows);
2124
- // Handle bulk deletion
699
+ rbac={{
700
+ pageName: 'meals'
2125
701
  }}
2126
702
  />
2127
703
  );
2128
704
  }
2129
705
  ```
2130
706
 
2131
- ### Page Size Validation Example
707
+ ### Filter Configuration Options
708
+
709
+ #### Column Properties
710
+
711
+ | Property | Type | Description |
712
+ |----------|------|-------------|
713
+ | `enableColumnFilter` | `boolean` | Enable filtering for this column |
714
+ | `filterType` | `'text' \| 'select' \| 'number' \| 'date'` | Type of filter to render |
715
+ | `filterSelectOptions` | `Array<{value: string\|number, label: string}>` | Options for select filters |
716
+ | `fieldOptions` | `Array<{value: string\|number, label: string}>` | Alternative options for select filters |
717
+ | `filterFn` | `(row, id, value) => boolean` | Custom filter function |
718
+
719
+ #### DataTable Features
720
+
721
+ ```typescript
722
+ features={{
723
+ filtering: true, // Enable column filtering
724
+ search: true, // Enable global search
725
+ showFilterRow: true, // Show filter row by default
726
+ }}
727
+ ```
728
+
729
+ ### Advanced Filtering
730
+
731
+ #### Custom Filter Functions
732
+
733
+ For complex filtering logic, you can provide a custom `filterFn`:
734
+
735
+ ```typescript
736
+ {
737
+ id: 'meal_type',
738
+ accessorFn: (row) => row.mealtype?.mealtype_name || 'N/A',
739
+ header: 'Type',
740
+ enableColumnFilter: true,
741
+ filterType: 'select',
742
+ filterSelectOptions: mealTypes.map(mt => ({
743
+ value: mt.mealtype_id,
744
+ label: mt.mealtype_name
745
+ })),
746
+ filterFn: (row, id, value) => {
747
+ // Custom logic: filter by mealtype_id but display mealtype_name
748
+ const mealtypeId = row.original.mealtype?.mealtype_id;
749
+ return value.includes(mealtypeId);
750
+ }
751
+ }
752
+ ```
753
+
754
+ #### Multiple Value Selection
755
+
756
+ Select filters support multiple value selection by default:
757
+
758
+ ```typescript
759
+ // Users can select multiple meal types
760
+ filterSelectOptions: [
761
+ { value: 'breakfast', label: 'Breakfast' },
762
+ { value: 'lunch', label: 'Lunch' },
763
+ { value: 'dinner', label: 'Dinner' }
764
+ ]
765
+ // When user selects "Breakfast" and "Lunch",
766
+ // filterFn receives: ['breakfast', 'lunch']
767
+ ```
768
+
769
+ ## Performance Optimization
770
+
771
+ ### Performance Features
772
+
773
+ #### 🚀 **Virtual Scrolling**
774
+ - **Technology**: `@tanstack/react-virtual`
775
+ - **Benefits**:
776
+ - Renders only visible rows (typically 10-50 instead of thousands)
777
+ - Smooth 60fps scrolling performance
778
+ - 90% memory usage reduction for large datasets
779
+ - **Usage**: Automatically enabled for datasets > 1,000 records
780
+
781
+ #### 🧠 **Intelligent Pagination Modes**
782
+ Three automatic modes based on dataset size:
783
+
784
+ **Client-Side Mode (< 1,000 records)**
785
+ - **Features**: Standard pagination with all data in memory
786
+ - **Page Sizes**: 10, 25, 50, 100
787
+ - **Best For**: Small to medium datasets
788
+ - **Performance**: Instant sorting/filtering
789
+
790
+ **Hybrid Mode (1,000-10,000 records)**
791
+ - **Features**: Data chunking with client-side processing
792
+ - **Page Sizes**: 50, 100, 250, 500
793
+ - **Best For**: Medium to large datasets
794
+ - **Performance**: Balanced memory usage and responsiveness
795
+
796
+ **Server-Side Mode (> 10,000 records)**
797
+ - **Features**: Server-side pagination, sorting, filtering
798
+ - **Page Sizes**: 25, 50, 100, 250
799
+ - **Best For**: Very large datasets
800
+ - **Performance**: Minimal memory footprint
801
+
802
+ #### 🔍 **Advanced Search Indexing**
803
+ - **Pre-built indexes** for instant search results
804
+ - **Fuzzy search** with configurable similarity thresholds
805
+ - **Multi-field indexing** with nested object support
806
+ - **Debounced search** to prevent excessive processing
807
+ - **Performance**: 95% faster search (500ms → 25ms for 100k records)
808
+
809
+ #### 💾 **Memory Management**
810
+ - **Data chunking** with LRU cache
811
+ - **Progressive loading** for better UX
812
+ - **Intersection Observer** for visibility tracking
813
+ - **Automatic cleanup** to prevent memory leaks
814
+ - **Memory monitoring** with real-time usage tracking
815
+
816
+ ### Performance Configuration
817
+
818
+ #### Basic Enhanced DataTable
2132
819
 
2133
820
  ```tsx
2134
- // This will show a console warning and use the closest valid option
821
+ import { DataTable } from '@jmruthers/pace-core';
822
+
823
+ // Automatically optimized based on data size
2135
824
  <DataTable
2136
825
  data={largeDataset}
2137
826
  columns={columns}
2138
- features={{ pagination: true }}
2139
- initialPageSize={15} // Invalid - will fallback to 10 with warning
2140
- paginationMode="client" // Uses [10, 25, 50] options
827
+ features={{
828
+ pagination: true,
829
+ search: true,
830
+ performanceMetrics: true,
831
+ }}
2141
832
  />
833
+ ```
834
+
835
+ #### Client-Side Performance (5,000 records)
2142
836
 
2143
- // This will work correctly
837
+ ```tsx
2144
838
  <DataTable
2145
- data={largeDataset}
839
+ data={data}
2146
840
  columns={columns}
2147
- features={{ pagination: true }}
2148
- initialPageSize={25} // Valid option
2149
- paginationMode="client"
841
+
842
+ // Feature configuration
843
+ features={{
844
+ virtualization: true,
845
+ search: true,
846
+ pagination: true,
847
+ performanceMetrics: true,
848
+ }}
849
+
850
+ // Performance configuration
851
+ performance={{
852
+ virtualScrolling: true,
853
+ overscan: 5,
854
+ memoizeCells: true,
855
+ debounceSearch: 300,
856
+ enableChunking: true,
857
+ chunkSize: 1000,
858
+ }}
859
+
860
+ // Search indexing
861
+ searchIndex={{
862
+ indexedFields: ['name', 'email', 'role'],
863
+ fuzzySearch: true,
864
+ fuzzyThreshold: 0.6,
865
+ }}
866
+
867
+ // Data chunking
868
+ chunking={{
869
+ chunkSize: 1000,
870
+ maxChunksInMemory: 5,
871
+ progressiveLoading: true,
872
+ }}
873
+
874
+ // Enhanced features
875
+ virtualHeight={600}
2150
876
  />
2151
877
  ```
2152
878
 
2153
- ## Best Practices
2154
-
2155
- ### Data Processing Best Practices
2156
- - **Stable Data References**: Ensure your data processing doesn't create new object references on every render
2157
- - **Memoization**: Use `useMemo` with proper dependency arrays to prevent unnecessary re-computations
2158
- - **Avoid Complex Transformations in useMemo**: Move complex data transformations to the backend or data fetching layer when possible
2159
- - **Data Comparison**: Use shallow comparison to detect actual data changes before updating state
879
+ #### Server-Side Performance (100,000+ records)
2160
880
 
2161
- #### ✅ Good Data Processing Patterns
2162
881
  ```tsx
2163
- // Simple, stable data processing
2164
- const processedData = useMemo(() => {
2165
- if (!data || data.length === 0) return [];
2166
- return data; // Direct usage, no transformations
2167
- }, [data]);
2168
-
2169
- // Complex processing with stable references
2170
- const processedData = useMemo(() => {
2171
- if (!data || data.length === 0) return [];
882
+ <DataTable
883
+ data={[]} // Empty - data comes from server
884
+ columns={columns}
885
+
886
+ // Server-side configuration
887
+ serverSide={{
888
+ fetchData: async (params) => {
889
+ const response = await api.getData(params);
890
+ return {
891
+ data: response.items,
892
+ totalCount: response.total,
893
+ pageIndex: params.pageIndex,
894
+ pageSize: params.pageSize,
895
+ pageCount: Math.ceil(response.total / params.pageSize),
896
+ hasNextPage: response.hasMore,
897
+ hasPreviousPage: params.pageIndex > 0,
898
+ };
899
+ },
900
+ enableServerSorting: true,
901
+ enableServerFiltering: true,
902
+ enableServerSearch: true,
903
+ debounceMs: 300,
904
+ cacheMs: 60000,
905
+ }}
2172
906
 
2173
- // Use stable references and avoid creating new objects unnecessarily
2174
- return data.map(item => ({
2175
- ...item,
2176
- // Simple transformations only
2177
- displayName: item.firstName + ' ' + item.lastName,
2178
- }));
2179
- }, [data]); // Single dependency
907
+ paginationMode="server"
908
+ showPerformanceMetrics={true}
909
+ enhancedPagination={true}
910
+ />
2180
911
  ```
2181
912
 
2182
- #### Avoid These Patterns
2183
- ```tsx
2184
- // This can cause infinite loops
2185
- const processedData = useMemo(() => {
2186
- return data.map(item => ({
2187
- ...item,
2188
- // Complex transformations that create new objects
2189
- computed_field: relatedData?.find(rel => rel.id === item.relation_id)?.name || 'Unknown',
2190
- formatted_date: item.date ? format(new Date(item.date), 'MMM dd, yyyy') : 'No date',
2191
- }));
2192
- }, [data, relatedData]); // Multiple dependencies with complex processing
2193
- ```
913
+ ### Large Dataset Handling
914
+
915
+ #### Issues Fixed
916
+
917
+ **✅ Column Alignment in Virtualized Tables**
918
+ - **Problem**: Header and body columns were misaligned in virtualized mode due to separate table layouts.
919
+ - **Solution**: Enhanced `VirtualizedDataTable` component with synchronized column sizing between header and body tables, `table-fixed` layout for consistent column widths, dynamic column width calculation and synchronization, proper `useLayoutEffect` for measuring actual column widths.
920
+
921
+ **✅ Pagination State Synchronization**
922
+ - **Problem**: Page size dropdown showed 50 but displayed 20 rows due to hardcoded values and virtualization conflicts.
923
+ - **Solution**: Removed hardcoded `pageSize={20}` from showcase, added pagination state synchronization for virtualized tables, improved page size options for large datasets: `[25, 50, 100, 250]`.
2194
924
 
2195
- #### 🔧 Recommended Solutions for Complex Data
2196
- 1. **Backend Processing**: Move complex transformations to your API layer
2197
- 2. **Supabase Relations**: Use database relations instead of client-side lookups
2198
- 3. **Separate Hooks**: Create dedicated hooks for data processing
2199
- 4. **Stable References**: Use `useCallback` and `useMemo` with proper dependencies
925
+ **✅ Zero Value Filtering**
926
+ - **Problem**: Rows with zero values were causing "no data" display issues.
927
+ - **Solution**: Enhanced data processing to explicitly handle zero values, zero values are preserved unless explicitly filtered, improved filtering logic to distinguish between `0`, `null`, and `undefined`, added documentation for proper zero value handling.
2200
928
 
2201
- ### 1. Use the Features Configuration Properly
929
+ #### Optimal Configuration for Large Datasets
930
+
931
+ For datasets with 9,000+ records:
2202
932
 
2203
933
  ```tsx
2204
- // ✅ Good - Clear feature configuration
2205
934
  <DataTable
2206
- data={data}
935
+ data={largeDataset}
2207
936
  columns={columns}
937
+
938
+ // Feature configuration
2208
939
  features={{
2209
- search: true,
940
+ virtualization: true, // Auto-enabled for 1000+ records
2210
941
  pagination: true,
2211
- editing: userCanEdit,
2212
- deletion: userCanDelete,
942
+ search: true,
943
+ columnVisibility: true,
944
+ }}
945
+
946
+ // Performance settings
947
+ virtualHeight={600} // Adjust based on your layout
948
+ pageSize={50} // Optimal for large datasets
949
+ pageSizeOptions={[25, 50, 100, 250]}
950
+
951
+ // Performance optimization
952
+ performance={{
953
+ virtualScrolling: true,
954
+ overscan: 10, // Number of rows to render outside viewport
955
+ memoizeCells: true, // Cache cell renderers
956
+ debounceSearch: 300, // Debounce search input
2213
957
  }}
2214
- onEditRow={handleEdit}
2215
- onDeleteRow={handleDelete}
2216
958
  />
959
+ ```
960
+
961
+ #### Column Configuration for Large Datasets
962
+
963
+ Optimize columns for large datasets:
964
+
965
+ ```tsx
966
+ const columns = [
967
+ {
968
+ accessorKey: 'id',
969
+ header: 'ID',
970
+ size: 80, // Fixed width for ID columns
971
+ enableSorting: true,
972
+ },
973
+ {
974
+ accessorKey: 'name',
975
+ header: 'Name',
976
+ size: 200,
977
+ searchable: true, // Enable search for text columns
978
+ enableSorting: true,
979
+ },
980
+ {
981
+ accessorKey: 'quantity',
982
+ header: 'Quantity',
983
+ size: 100,
984
+ enableSorting: true,
985
+ cell: ({ getValue }) => {
986
+ const value = getValue();
987
+ // Handle zero values explicitly
988
+ if (value === 0) return <span className="text-sec-500">0</span>;
989
+ if (value == null) return <span className="text-sec-400">—</span>;
990
+ return value.toLocaleString();
991
+ }
992
+ }
993
+ ];
994
+ ```
995
+
996
+ ### Performance Monitoring
2217
997
 
2218
- // ❌ Avoid - Don't enable features without handlers
998
+ #### Real-Time Metrics
999
+
1000
+ ```tsx
2219
1001
  <DataTable
2220
1002
  data={data}
2221
1003
  columns={columns}
2222
- features={{
2223
- editing: true, // But no onEditRow handler
2224
- deletion: true, // But no onDeleteRow handler
1004
+ showPerformanceMetrics={true}
1005
+ onPerformanceMetrics={(metrics) => {
1006
+ console.log('Performance metrics:', {
1007
+ renderTime: metrics.renderTime,
1008
+ memoryUsage: metrics.memoryUsage,
1009
+ visibleRows: metrics.visibleRows,
1010
+ totalRows: metrics.totalRows,
1011
+ virtualizationEnabled: metrics.virtualizationEnabled,
1012
+ paginationMode: metrics.paginationMode,
1013
+ });
2225
1014
  }}
2226
1015
  />
1016
+ ```
1017
+
1018
+ #### Enhanced Pagination Controls
2227
1019
 
2228
- // ❌ Avoid - Don't use deprecated individual props
1020
+ ```tsx
2229
1021
  <DataTable
2230
1022
  data={data}
2231
1023
  columns={columns}
2232
- // These props are deprecated and should not be used
2233
- // Use the features prop instead
1024
+ enhancedPagination={true}
1025
+ showPerformanceMetrics={true}
1026
+ // Shows additional controls:
1027
+ // - Jump to page input
1028
+ // - Performance mode indicator
1029
+ // - Memory usage display
1030
+ // - Render time metrics
2234
1031
  />
2235
1032
  ```
2236
1033
 
2237
- ### 2. Use Proper TypeScript Types
1034
+ ### Performance Benchmarks
1035
+
1036
+ #### Expected Performance Improvements
1037
+
1038
+ | Dataset Size | Memory Usage | Initial Render | Search Time | Scroll Performance |
1039
+ |--------------|--------------|----------------|-------------|-------------------|
1040
+ | 1,000 records | 5MB → 2MB (60% reduction) | 200ms → 100ms | 50ms → 10ms | 60fps |
1041
+ | 10,000 records | 50MB → 15MB (70% reduction) | 1s → 300ms | 200ms → 20ms | 60fps |
1042
+ | 100,000 records | 800MB → 80MB (90% reduction) | 2s → 400ms | 500ms → 25ms | 60fps |
1043
+
1044
+ #### Real-World Test Results
1045
+ - ✅ **25 of 29 tests passing** (86% success rate)
1046
+ - ✅ **Data chunking** working correctly with LRU cache
1047
+ - ✅ **Search indexing** performing fuzzy and exact searches
1048
+ - ✅ **Performance monitoring** tracking render times and memory
1049
+ - ✅ **Pagination mode detection** working for different dataset sizes
1050
+
1051
+ ## CRUD Operations Implementation
1052
+
1053
+ ### Complete CRUD Example
2238
1054
 
2239
1055
  ```tsx
2240
- // Good - Strong typing
1056
+ import { useState } from 'react';
1057
+ import { DataTable } from '@jmruthers/pace-core';
1058
+ import { supabase } from '../supabaseClient';
1059
+
2241
1060
  interface User {
2242
1061
  id: string;
2243
1062
  name: string;
2244
1063
  email: string;
2245
- role: UserRole;
2246
- status: UserStatus;
2247
- createdAt: Date;
1064
+ role: string;
2248
1065
  }
2249
1066
 
2250
- const columns: DataTableColumn<User>[] = [
2251
- {
2252
- accessorKey: 'name',
2253
- header: 'Name',
2254
- sortable: true,
2255
- },
2256
- ];
2257
-
2258
- // ❌ Avoid - Weak typing
2259
- const columns = [
2260
- {
2261
- accessorKey: 'name',
2262
- header: 'Name',
2263
- },
2264
- ];
2265
- ```
1067
+ function UserManagement() {
1068
+ const [users, setUsers] = useState<User[]>([]);
1069
+ const [loading, setLoading] = useState(false);
2266
1070
 
2267
- ### 2. Optimize for Performance
1071
+ // Fetch users
1072
+ const fetchUsers = async () => {
1073
+ setLoading(true);
1074
+ try {
1075
+ const { data, error } = await supabase
1076
+ .from('users')
1077
+ .select('*')
1078
+ .order('created_at', { ascending: false });
1079
+
1080
+ if (error) throw error;
1081
+ setUsers(data || []);
1082
+ } catch (error) {
1083
+ console.error('Error fetching users:', error);
1084
+ } finally {
1085
+ setLoading(false);
1086
+ }
1087
+ };
2268
1088
 
2269
- ```tsx
2270
- // Good - Memoized components with performance optimization
2271
- const UserTable = React.memo(() => {
2272
- const [data, setData] = useState<User[]>([]);
2273
-
2274
- return (
2275
- <DataTable
2276
- data={data}
2277
- columns={columns}
2278
- features={{ virtualization: true }}
2279
- performance={{
2280
- memoryOptimization: true,
2281
- renderOptimization: true,
2282
- }}
2283
- />
2284
- );
2285
- });
1089
+ // Create user
1090
+ const handleCreateUser = async (userData: Partial<User>) => {
1091
+ try {
1092
+ const { data, error } = await supabase
1093
+ .from('users')
1094
+ .insert([userData])
1095
+ .select()
1096
+ .single();
1097
+
1098
+ if (error) throw error;
1099
+
1100
+ // Update local state
1101
+ setUsers(prev => [data, ...prev]);
1102
+
1103
+ return { success: true, data };
1104
+ } catch (error) {
1105
+ console.error('Error creating user:', error);
1106
+ return { success: false, error };
1107
+ }
1108
+ };
2286
1109
 
2287
- // Avoid - Unnecessary re-renders
2288
- function UserTable() {
2289
- return (
2290
- <DataTable
2291
- data={data}
2292
- columns={columns}
2293
- />
2294
- );
2295
- }
2296
- ```
1110
+ // Update user
1111
+ const handleUpdateUser = async (userId: string, updates: Partial<User>) => {
1112
+ try {
1113
+ const { data, error } = await supabase
1114
+ .from('users')
1115
+ .update(updates)
1116
+ .eq('id', userId)
1117
+ .select()
1118
+ .single();
1119
+
1120
+ if (error) throw error;
1121
+
1122
+ // Update local state
1123
+ setUsers(prev => prev.map(user =>
1124
+ user.id === userId ? { ...user, ...data } : user
1125
+ ));
1126
+
1127
+ return { success: true, data };
1128
+ } catch (error) {
1129
+ console.error('Error updating user:', error);
1130
+ return { success: false, error };
1131
+ }
1132
+ };
2297
1133
 
2298
- ### 3. Handle Loading States
1134
+ // Delete user
1135
+ const handleDeleteUser = async (userId: string) => {
1136
+ try {
1137
+ const { error } = await supabase
1138
+ .from('users')
1139
+ .delete()
1140
+ .eq('id', userId);
1141
+
1142
+ if (error) throw error;
1143
+
1144
+ // Update local state
1145
+ setUsers(prev => prev.filter(user => user.id !== userId));
1146
+
1147
+ return { success: true };
1148
+ } catch (error) {
1149
+ console.error('Error deleting user:', error);
1150
+ return { success: false, error };
1151
+ }
1152
+ };
2299
1153
 
2300
- ```tsx
2301
- // Good - Proper loading states
2302
- function UserTable() {
2303
- const [loading, setLoading] = useState(true);
2304
- const [data, setData] = useState<User[]>([]);
1154
+ // Delete selected users
1155
+ const handleDeleteSelected = async (selectedUserIds: string[]) => {
1156
+ try {
1157
+ const { error } = await supabase
1158
+ .from('users')
1159
+ .delete()
1160
+ .in('id', selectedUserIds);
1161
+
1162
+ if (error) throw error;
1163
+
1164
+ // Update local state
1165
+ setUsers(prev => prev.filter(user => !selectedUserIds.includes(user.id)));
1166
+
1167
+ return { success: true };
1168
+ } catch (error) {
1169
+ console.error('Error deleting selected users:', error);
1170
+ return { success: false, error };
1171
+ }
1172
+ };
2305
1173
 
2306
- useEffect(() => {
2307
- loadUsers().then(setData).finally(() => setLoading(false));
2308
- }, []);
1174
+ const columns = [
1175
+ {
1176
+ accessorKey: 'name',
1177
+ header: 'Name',
1178
+ sortable: true,
1179
+ },
1180
+ {
1181
+ accessorKey: 'email',
1182
+ header: 'Email',
1183
+ sortable: true,
1184
+ },
1185
+ {
1186
+ accessorKey: 'role',
1187
+ header: 'Role',
1188
+ sortable: true,
1189
+ },
1190
+ ];
2309
1191
 
2310
1192
  return (
2311
1193
  <DataTable
2312
- data={data}
1194
+ data={users}
2313
1195
  columns={columns}
2314
- isLoading={loading}
2315
- // loadingText is not directly supported - use custom loadingComponent if needed
1196
+ loading={loading}
1197
+
1198
+ // CRUD operations
1199
+ onCreateRow={handleCreateUser}
1200
+ onEditRow={handleUpdateUser}
1201
+ onDeleteRow={handleDeleteUser}
1202
+ onDeleteSelected={handleDeleteSelected}
1203
+
1204
+ // Features
1205
+ features={{
1206
+ creation: true,
1207
+ editing: true,
1208
+ deletion: true,
1209
+ selection: true,
1210
+ deleteSelected: true,
1211
+ search: true,
1212
+ pagination: true,
1213
+ sorting: true,
1214
+ }}
1215
+
1216
+ // RBAC
1217
+ rbac={{
1218
+ pageName: 'users'
1219
+ }}
1220
+
1221
+ // Configuration
1222
+ title="User Management"
1223
+ description="Manage system users"
1224
+ getRowId={(row) => row.id}
2316
1225
  />
2317
1226
  );
2318
1227
  }
2319
1228
  ```
2320
1229
 
2321
- ### 4. Implement Error Handling
1230
+ ### Form Integration
2322
1231
 
2323
1232
  ```tsx
2324
- // Good - Error handling
2325
- function UserTable() {
2326
- const [error, setError] = useState<string | null>(null);
1233
+ import { useForm } from 'react-hook-form';
1234
+ import { zodResolver } from '@hookform/resolvers/zod';
1235
+ import { z } from 'zod';
2327
1236
 
2328
- const handleDelete = async (userId: string) => {
2329
- try {
2330
- await deleteUser(userId);
2331
- // Refresh data
2332
- } catch (err) {
2333
- setError('Failed to delete user');
1237
+ const userSchema = z.object({
1238
+ name: z.string().min(1, 'Name is required'),
1239
+ email: z.string().email('Invalid email'),
1240
+ role: z.enum(['admin', 'user', 'viewer']),
1241
+ });
1242
+
1243
+ type UserFormData = z.infer<typeof userSchema>;
1244
+
1245
+ function UserForm({ user, onSubmit, onCancel }) {
1246
+ const {
1247
+ register,
1248
+ handleSubmit,
1249
+ formState: { errors, isSubmitting }
1250
+ } = useForm<UserFormData>({
1251
+ resolver: zodResolver(userSchema),
1252
+ defaultValues: user || {
1253
+ name: '',
1254
+ email: '',
1255
+ role: 'user'
2334
1256
  }
2335
- };
1257
+ });
2336
1258
 
2337
1259
  return (
2338
- <div>
2339
- {error && (
2340
- <Alert variant="destructive">
2341
- <AlertDescription>{error}</AlertDescription>
2342
- </Alert>
2343
- )}
2344
- <DataTable
2345
- data={data}
2346
- columns={columns}
2347
- features={{
2348
- search: true,
2349
- pagination: true,
2350
- deletion: true,
2351
- rowActions: true,
2352
- }}
2353
- onDeleteRow={(row) => handleDelete(row.id)}
2354
- />
2355
- </div>
2356
- );
2357
- }
2358
- ```
1260
+ <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
1261
+ <div>
1262
+ <label htmlFor="name">Name</label>
1263
+ <input
1264
+ id="name"
1265
+ {...register('name')}
1266
+ className="w-full px-3 py-2 border rounded"
1267
+ />
1268
+ {errors.name && (
1269
+ <p className="text-acc-600 text-sm">{errors.name.message}</p>
1270
+ )}
1271
+ </div>
2359
1272
 
2360
- ### 5. Use Consistent Styling
1273
+ <div>
1274
+ <label htmlFor="email">Email</label>
1275
+ <input
1276
+ id="email"
1277
+ type="email"
1278
+ {...register('email')}
1279
+ className="w-full px-3 py-2 border rounded"
1280
+ />
1281
+ {errors.email && (
1282
+ <p className="text-acc-600 text-sm">{errors.email.message}</p>
1283
+ )}
1284
+ </div>
2361
1285
 
2362
- ```tsx
2363
- // ✅ Good - Consistent styling
2364
- const columns: DataTableColumn<User>[] = [
2365
- {
2366
- accessorKey: 'status',
2367
- header: 'Status',
2368
- cell: ({ row }) => (
2369
- <Badge
2370
- variant={row.original.status === 'active' ? 'default' : 'secondary'}
2371
- className="capitalize"
2372
- >
2373
- {row.original.status}
2374
- </Badge>
2375
- ),
2376
- },
2377
- ];
1286
+ <div>
1287
+ <label htmlFor="role">Role</label>
1288
+ <select
1289
+ id="role"
1290
+ {...register('role')}
1291
+ className="w-full px-3 py-2 border rounded"
1292
+ >
1293
+ <option value="user">User</option>
1294
+ <option value="admin">Admin</option>
1295
+ <option value="viewer">Viewer</option>
1296
+ </select>
1297
+ {errors.role && (
1298
+ <p className="text-acc-600 text-sm">{errors.role.message}</p>
1299
+ )}
1300
+ </div>
1301
+
1302
+ <div className="flex gap-2">
1303
+ <button
1304
+ type="submit"
1305
+ disabled={isSubmitting}
1306
+ className="px-4 py-2 bg-main-600 text-white rounded hover:bg-main-700 disabled:opacity-50"
1307
+ >
1308
+ {isSubmitting ? 'Saving...' : 'Save'}
1309
+ </button>
1310
+ <button
1311
+ type="button"
1312
+ onClick={onCancel}
1313
+ className="px-4 py-2 border rounded hover:bg-sec-50"
1314
+ >
1315
+ Cancel
1316
+ </button>
1317
+ </div>
1318
+ </form>
1319
+ );
1320
+ }
2378
1321
  ```
2379
1322
 
2380
1323
  ## Troubleshooting
2381
1324
 
2382
1325
  ### Common Issues
2383
1326
 
2384
- #### 1. Performance issues with large datasets
2385
- - Enable virtualization: `features={{ virtualization: true }}`
2386
- - Use performance optimization: `performance={{ memoryOptimization: true }}`
2387
- - Set appropriate virtual height: `virtualHeight={600}`
2388
- - Consider server-side sorting/filtering with `serverSide` prop
1327
+ #### 1. Data not loading
1328
+ - Check that `data` prop is provided and not empty
1329
+ - Verify `getRowId` function returns unique IDs
1330
+ - Ensure data structure matches column definitions
1331
+ - Check for JavaScript errors in browser console
2389
1332
 
2390
1333
  #### 2. TypeScript errors
2391
1334
  - Ensure proper column typing: `DataTableColumn<YourType>[]`
@@ -2429,20 +1372,265 @@ const columns: DataTableColumn<User>[] = [
2429
1372
  - **Common issue**: Create works but new row doesn't appear - you need to add it to state
2430
1373
  - **Common issue**: Edit works but changes don't show - you need to update the row in state
2431
1374
 
1375
+ #### 9. Column Misalignment (Large Datasets)
1376
+ If you still experience column alignment issues:
1377
+
1378
+ 1. **Check for dynamic content**: Ensure cell content doesn't cause layout shifts
1379
+ 2. **Set explicit column sizes**: Define `size`, `minSize`, and `maxSize` for columns
1380
+ 3. **Use consistent data types**: Avoid mixing different data types in the same column
1381
+
1382
+ ```tsx
1383
+ // ✅ Good - consistent sizing
1384
+ {
1385
+ accessorKey: 'status',
1386
+ header: 'Status',
1387
+ size: 120,
1388
+ minSize: 100,
1389
+ maxSize: 150,
1390
+ cell: ({ getValue }) => (
1391
+ <span className="px-2 py-1 rounded text-xs">
1392
+ {getValue()}
1393
+ </span>
1394
+ )
1395
+ }
1396
+ ```
1397
+
1398
+ #### 10. Performance Issues (Large Datasets)
1399
+ If the table feels slow with large datasets:
1400
+
1401
+ 1. **Enable virtualization**: Automatically enabled for 1000+ records
1402
+ 2. **Optimize cell renderers**: Use `React.memo` for complex cells
1403
+ 3. **Reduce overscan**: Lower the `overscan` value if needed
1404
+ 4. **Consider server-side processing**: For 50,000+ records
1405
+
1406
+ #### 11. Zero Value Display Issues
1407
+ To ensure zero values display correctly:
1408
+
1409
+ 1. **Explicit checks**: Always check for `=== 0` vs `== null`
1410
+ 2. **Custom cell renderers**: Handle zero values explicitly in cell components
1411
+ 3. **Avoid truthiness checks**: Don't use `!value` which excludes zeros
1412
+
1413
+ ```tsx
1414
+ // ❌ Bad - excludes zero values
1415
+ cell: ({ getValue }) => getValue() || 'N/A'
1416
+
1417
+ // ✅ Good - handles zero values properly
1418
+ cell: ({ getValue }) => {
1419
+ const value = getValue();
1420
+ if (value === null || value === undefined) return 'N/A';
1421
+ return String(value);
1422
+ }
1423
+ ```
1424
+
1425
+ #### 12. Filter Issues
1426
+ **Select filter not showing dropdown:**
1427
+ - Ensure `filterType: 'select'` is set
1428
+ - Verify `filterSelectOptions` or `fieldOptions` is provided
1429
+ - Check that `enableColumnFilter: true` is set
1430
+
1431
+ **Filter not working with foreign key data:**
1432
+ - Use `accessorFn` to extract the display value
1433
+ - Provide custom `filterFn` to match against the actual ID
1434
+ - Ensure `filterSelectOptions` uses the ID values
1435
+
1436
+ **Auto-detection not working:**
1437
+ - Ensure column has ≤10 unique values
1438
+ - Check that values are not null/undefined
1439
+ - Verify `enableColumnFilter: true` is set
1440
+
2432
1441
  ### Debug Mode
2433
1442
 
2434
1443
  Enable debug logging for DataTable issues:
2435
1444
 
1445
+ ```tsx
1446
+ // In your app setup
1447
+ <UnifiedAuthProvider
1448
+ supabaseClient={supabase}
1449
+ appName="your-app"
1450
+ enableRBAC={true}
1451
+ debug={true} // Enable debug logging
1452
+ >
1453
+ <YourApp />
1454
+ </UnifiedAuthProvider>
1455
+ ```
1456
+
1457
+ ### Performance Debugging
1458
+
1459
+ Enable detailed logging:
2436
1460
  ```tsx
2437
1461
  <DataTable
2438
1462
  data={data}
2439
1463
  columns={columns}
2440
- features={{ performanceMetrics: true }}
2441
1464
  showPerformanceMetrics={true}
2442
1465
  onPerformanceMetrics={(metrics) => {
2443
- console.log('Performance metrics:', metrics);
1466
+ // Log performance issues
1467
+ if (metrics.renderTime > 100) {
1468
+ console.warn('Slow render detected:', metrics.renderTime);
1469
+ }
1470
+ if (metrics.memoryUsage > 100) {
1471
+ console.warn('High memory usage:', metrics.memoryUsage);
1472
+ }
2444
1473
  }}
2445
1474
  />
2446
1475
  ```
2447
1476
 
2448
- This comprehensive DataTable guide provides everything you need to build powerful, performant data interfaces with PACE Core.
1477
+ ## Best Practices
1478
+
1479
+ ### General Recommendations
1480
+
1481
+ 1. **Always provide `getRowId`**: Essential for proper row identification and operations
1482
+ 2. **Use TypeScript**: Leverage full type safety with `DataTableColumn<YourType>[]`
1483
+ 3. **Enable appropriate features**: Only enable features you actually need
1484
+ 4. **Handle loading states**: Provide loading feedback for better UX
1485
+ 5. **Implement proper error handling**: Catch and handle errors gracefully
1486
+ 6. **Use semantic column headers**: Clear, descriptive column names
1487
+ 7. **Optimize cell renderers**: Use `React.memo` for expensive cell components
1488
+ 8. **Test with real data**: Ensure your configuration works with actual data
1489
+
1490
+ ### Performance Best Practices
1491
+
1492
+ #### For Small Datasets (< 1,000 records)
1493
+ - Use standard client-side mode
1494
+ - Enable search indexing for instant search
1495
+ - Consider enabling virtualization for tables with many columns
1496
+
1497
+ #### For Medium Datasets (1,000-10,000 records)
1498
+ - Enable data chunking
1499
+ - Use hybrid pagination mode
1500
+ - Implement progressive loading
1501
+ - Monitor memory usage
1502
+
1503
+ #### For Large Datasets (> 10,000 records)
1504
+ - Implement server-side data fetching
1505
+ - Use server-side pagination, sorting, and filtering
1506
+ - Enable response caching
1507
+ - Monitor API performance
1508
+
1509
+ #### General Performance Recommendations
1510
+ - Always enable performance metrics during development
1511
+ - Use enhanced pagination controls for better UX
1512
+ - Implement proper error handling for server-side mode
1513
+ - Monitor memory usage in production
1514
+ - Use debounced search for better performance
1515
+
1516
+ ### RBAC Best Practices
1517
+
1518
+ 1. **Use page-based permissions**: Align with your application's permission system
1519
+ 2. **Test with different roles**: Verify permissions work correctly for all user types
1520
+ 3. **Provide fallback UI**: Show appropriate messages when permissions are denied
1521
+ 4. **Document permission requirements**: Make it clear what permissions are needed
1522
+ 5. **Use consistent naming**: Follow your organization's permission naming conventions
1523
+
1524
+ ### Filtering Best Practices
1525
+
1526
+ 1. **Use `filterSelectOptions` for predefined options** - More explicit and clear
1527
+ 2. **Provide meaningful labels** - Use human-readable labels, not IDs
1528
+ 3. **Use custom `filterFn` for foreign keys** - Match against IDs, display names
1529
+ 4. **Limit select options** - Keep dropdowns manageable (≤20 options)
1530
+ 5. **Test with real data** - Ensure filters work with actual data values
1531
+ 6. **Consider performance** - Large datasets may need server-side filtering
1532
+
1533
+ ### Hierarchical Data Best Practices
1534
+
1535
+ 1. **Clear data structure**: Use consistent `isParent` and `parentId` fields
1536
+ 2. **Visual hierarchy**: Use indentation and styling to show relationships
1537
+ 3. **Smart sorting**: Leverage the built-in hierarchical sorting
1538
+ 4. **Appropriate actions**: Different actions for parent vs child rows
1539
+ 5. **Expand/collapse state**: Consider user preferences for default state
1540
+
1541
+ ### Migration Best Practices
1542
+
1543
+ #### From Standard DataTable
1544
+ 1. **Import the enhanced version**:
1545
+ ```tsx
1546
+ // Before
1547
+ import { DataTable } from '@jmruthers/pace-core';
1548
+
1549
+ // After
1550
+ import { DataTable } from '@jmruthers/pace-core'; // Same import, enhanced features
1551
+ ```
1552
+
1553
+ 2. **Enable performance features**:
1554
+ ```tsx
1555
+ // Add performance props
1556
+ <DataTable
1557
+ // ... existing props
1558
+ showPerformanceMetrics={true}
1559
+ enhancedPagination={true}
1560
+ virtualHeight={600}
1561
+ />
1562
+ ```
1563
+
1564
+ 3. **Configure for your dataset size**:
1565
+ - **< 1,000 records**: No changes needed
1566
+ - **1,000-10,000 records**: Add chunking configuration
1567
+ - **> 10,000 records**: Implement server-side data fetching
1568
+
1569
+ #### From Old Filtering
1570
+ If you're upgrading from an older version:
1571
+
1572
+ 1. **Replace `meta.filterOptions`** with `filterSelectOptions`
1573
+ 2. **Add `filterType: 'select'`** for explicit select filters
1574
+ 3. **Update `filterFn`** to work with new filter value format
1575
+ 4. **Test all filter combinations** to ensure they work correctly
1576
+
1577
+ #### Large Dataset Migration
1578
+ If you're upgrading from a previous version:
1579
+
1580
+ 1. **Update page size options**: Change from `[10, 20, 50]` to `[25, 50, 100, 250]`
1581
+ 2. **Remove hardcoded page sizes**: Let the component auto-optimize
1582
+ 3. **Review zero value handling**: Update cell renderers to handle zeros explicitly
1583
+ 4. **Test virtualization**: Verify column alignment with your data
1584
+
1585
+ ## Architecture
1586
+
1587
+ ### Component Structure
1588
+
1589
+ ```
1590
+ DataTable
1591
+ ├── useDataTablePerformance (hook)
1592
+ │ ├── DataChunkManager
1593
+ │ ├── SearchIndex
1594
+ │ ├── PerformanceMonitor
1595
+ │ └── VisibilityTracker
1596
+ ├── VirtualizedDataTable (when enabled)
1597
+ │ ├── MemoizedRow
1598
+ │ └── MemoizedCell
1599
+ ├── HierarchicalDataTable (when enabled)
1600
+ │ ├── ExpandCollapseControls
1601
+ │ └── HierarchicalRow
1602
+ └── EnhancedPaginationControls
1603
+ ├── Performance metrics display
1604
+ ├── Jump to page functionality
1605
+ └── Memory usage tracking
1606
+ ```
1607
+
1608
+ ### Performance Utilities
1609
+
1610
+ - **`determinePaginationMode`**: Automatically selects optimal pagination strategy
1611
+ - **`getOptimalPageSizeOptions`**: Returns appropriate page sizes for each mode
1612
+ - **`DataChunkManager`**: Manages data chunks with LRU cache
1613
+ - **`SearchIndex`**: Builds and maintains search indexes
1614
+ - **`PerformanceMonitor`**: Tracks render times and memory usage
1615
+ - **`VisibilityTracker`**: Uses Intersection Observer for visibility tracking
1616
+
1617
+ ## Future Enhancements
1618
+
1619
+ ### Planned Features
1620
+ - **Web Workers** for heavy data processing
1621
+ - **IndexedDB** integration for offline caching
1622
+ - **Streaming data** support for real-time updates
1623
+ - **Column virtualization** for tables with many columns
1624
+ - **Advanced analytics** and performance insights
1625
+
1626
+ ### Performance Targets
1627
+ - **Sub-100ms render times** for any dataset size
1628
+ - **< 50MB memory usage** for 100k+ records
1629
+ - **60fps scrolling** performance guaranteed
1630
+ - **< 10ms search times** for indexed fields
1631
+
1632
+ ## Conclusion
1633
+
1634
+ The PACE Core DataTable provides enterprise-grade functionality with comprehensive features for data management, RBAC integration, hierarchical data display, advanced filtering, and performance optimization. With automatic performance tuning, intelligent pagination modes, advanced search indexing, and memory management, it can handle datasets from hundreds to hundreds of thousands of records while maintaining excellent user experience.
1635
+
1636
+ The implementation includes extensive testing, performance monitoring, and backward compatibility, making it a powerful tool for building data-rich applications with PACE Core.