@jmruthers/pace-core 0.6.10 → 0.6.11

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 (726) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/audit-tool/00-dependencies.cjs +46 -13
  3. package/audit-tool/audits/01-pace-core-compliance.cjs +96 -21
  4. package/audit-tool/audits/02-project-structure.cjs +13 -3
  5. package/audit-tool/audits/03-architecture.cjs +78 -4
  6. package/audit-tool/audits/04-code-quality.cjs +9 -2
  7. package/audit-tool/audits/05-styling.cjs +19 -7
  8. package/audit-tool/audits/06-security-rbac.cjs +105 -14
  9. package/audit-tool/audits/07-api-tech-stack.cjs +31 -15
  10. package/audit-tool/audits/08-testing-documentation.cjs +11 -3
  11. package/audit-tool/audits/09-operations.cjs +19 -7
  12. package/audit-tool/index.cjs +22 -11
  13. package/audit-tool/utils/report-utils.cjs +4 -0
  14. package/cursor-rules/01-pace-core-compliance.mdc +1 -0
  15. package/cursor-rules/02-project-structure.mdc +1 -0
  16. package/cursor-rules/03-architecture.mdc +3 -1
  17. package/cursor-rules/04-code-quality.mdc +1 -0
  18. package/cursor-rules/05-styling.mdc +41 -7
  19. package/cursor-rules/06-security-rbac.mdc +2 -1
  20. package/cursor-rules/07-api-tech-stack.mdc +1 -0
  21. package/cursor-rules/08-testing-documentation.mdc +1 -0
  22. package/cursor-rules/09-operations.mdc +1 -0
  23. package/dist/{DataTable-SAXFG4XI.js → DataTable-EFYP2QLE.js} +10 -7
  24. package/dist/{InactivityServiceProvider-DHryoh6K.d.ts → InactivityServiceProvider-BbxwwDz1.d.ts} +10 -1
  25. package/dist/{UnifiedAuthProvider-CiBAl9-s.d.ts → UnifiedAuthProvider-Bkt_tzdS.d.ts} +56 -24
  26. package/dist/{api-F47QJ7FX.js → api-BZR2CYXL.js} +3 -2
  27. package/dist/api-result-USV1Czr-.d.ts +51 -0
  28. package/dist/{audit-Z6ZZBWLU.js → audit-HI2DHUVU.js} +2 -1
  29. package/dist/{auth-BZOJqrdd.d.ts → auth-JvdRVaud.d.ts} +1 -1
  30. package/dist/{chunk-KSNLMI7N.js → chunk-2DL2WSOE.js} +1 -155
  31. package/dist/{chunk-MPY44PWB.js → chunk-2OEVOGGR.js} +4648 -3560
  32. package/dist/chunk-44CNXN4P.js +15 -0
  33. package/dist/{chunk-Y4PF6HIM.js → chunk-4R3T5ENU.js} +867 -786
  34. package/dist/{chunk-LNHFAF4X.js → chunk-7A6IMHH2.js} +289 -247
  35. package/dist/chunk-CU2BU2MQ.js +2 -0
  36. package/dist/{chunk-JJEYZ3DX.js → chunk-D6BMFMQZ.js} +37 -2
  37. package/dist/{chunk-BCTXBU6U.js → chunk-ENLXB7GP.js} +88 -71
  38. package/dist/{chunk-FBZ7U3ID.js → chunk-J2KQK6DG.js} +937 -987
  39. package/dist/{chunk-TFIPNIPE.js → chunk-KJXRL3XE.js} +3300 -2245
  40. package/dist/{chunk-3GWSPISD.js → chunk-L5LFKKLJ.js} +1 -1
  41. package/dist/{chunk-X5EAU5G7.js → chunk-PCSHBLPB.js} +132 -114
  42. package/dist/{chunk-NIU6DPQV.js → chunk-QRYSEPHB.js} +2 -0
  43. package/dist/{chunk-KYURMOQM.js → chunk-V7FTM2LU.js} +423 -320
  44. package/dist/chunk-WY6Y7KC3.js +264 -0
  45. package/dist/{chunk-FN52B75D.js → chunk-XOJME5T7.js} +176 -15
  46. package/dist/{chunk-7YDC7LMU.js → chunk-XPFVT3GN.js} +71 -66
  47. package/dist/{chunk-66R6RLUZ.js → chunk-YFTFFJIV.js} +3 -3
  48. package/dist/{chunk-W46INAVW.js → chunk-YYTWKVHO.js} +688 -570
  49. package/dist/components.d.ts +8 -7
  50. package/dist/components.js +17 -15
  51. package/dist/{database.generated-DT8JTZiP.d.ts → database.generated-qkdoiVrJ.d.ts} +45 -10
  52. package/dist/eslint-rules/index.cjs +3 -0
  53. package/dist/eslint-rules/rules/03-architecture.cjs +74 -0
  54. package/dist/eslint-rules/rules/06-security-rbac.cjs +74 -0
  55. package/dist/{event-WTAQuGcq.d.ts → event-BfCox3N2.d.ts} +36 -10
  56. package/dist/{file-reference-BavO2eQj.d.ts → file-reference-DU1hcawx.d.ts} +29 -13
  57. package/dist/hooks.d.ts +22 -9
  58. package/dist/hooks.js +34 -25
  59. package/dist/icons/index.d.ts +1 -0
  60. package/dist/icons/index.js +1 -0
  61. package/dist/index.d.ts +66 -177
  62. package/dist/index.js +316 -340
  63. package/dist/pagination-BW1mqywp.d.ts +201 -0
  64. package/dist/providers.d.ts +6 -5
  65. package/dist/providers.js +5 -3
  66. package/dist/rbac/index.d.ts +123 -138
  67. package/dist/rbac/index.js +10 -8
  68. package/dist/theming/runtime.d.ts +19 -2
  69. package/dist/theming/runtime.js +1 -1
  70. package/dist/{timezone-K-ptz3HO.d.ts → timezone-BTWWXKVY.d.ts} +1 -1
  71. package/dist/types.d.ts +17 -10
  72. package/dist/types.js +1 -0
  73. package/dist/{usePublicPageContext-vxBlEHO9.d.ts → usePublicPageContext-B91dGYW1.d.ts} +433 -356
  74. package/dist/{usePublicRouteParams-G3Ks53mk.d.ts → usePublicRouteParams-BgV6VhMi.d.ts} +73 -4
  75. package/dist/utils.d.ts +163 -145
  76. package/dist/utils.js +42 -25
  77. package/docs/api/modules.md +782 -643
  78. package/docs/api-reference/rpc-functions.md +12 -3
  79. package/docs/core-concepts/rbac-system.md +8 -0
  80. package/docs/getting-started/cursor-rules.md +17 -20
  81. package/docs/getting-started/dependencies.md +1 -1
  82. package/docs/getting-started/setup.md +235 -0
  83. package/docs/implementation-guides/authentication.md +27 -0
  84. package/docs/implementation-guides/data-tables.md +176 -3
  85. package/docs/migration/ApiResult-migration.md +25 -0
  86. package/docs/rbac/api-reference.md +33 -31
  87. package/docs/standards/0-standards-overview.md +50 -15
  88. package/docs/standards/1-pace-core-compliance-standards.md +62 -57
  89. package/docs/standards/2-project-structure-standards.md +33 -16
  90. package/docs/standards/3-architecture-standards.md +41 -1
  91. package/docs/standards/4-code-quality-standards.md +26 -6
  92. package/docs/standards/5-styling-standards.md +35 -1
  93. package/docs/standards/6-security-rbac-standards.md +66 -0
  94. package/docs/standards/7-api-tech-stack-standards.md +25 -14
  95. package/docs/standards/8-testing-documentation-standards.md +31 -0
  96. package/docs/standards/9-operations-standards.md +19 -0
  97. package/docs/standards/README.md +20 -201
  98. package/docs/testing/test-setup-for-consumers.md +2 -0
  99. package/docs/troubleshooting/common-issues.md +17 -1
  100. package/docs/troubleshooting/organisation-context-setup.md +8 -0
  101. package/docs/troubleshooting/print-event-name-css-variable-analysis.md +217 -0
  102. package/eslint-config-pace-core.cjs +20 -0
  103. package/package.json +14 -20
  104. package/scripts/{build-docs-incremental.js → build-docs.js} +3 -2
  105. package/scripts/setup.cjs +536 -0
  106. package/scripts/validate.cjs +480 -0
  107. package/src/__tests__/helpers/{__tests__/component-test-utils.test.tsx → component-test-utils.test.tsx} +3 -3
  108. package/src/__tests__/helpers/{__tests__/optimized-test-setup.test.ts → optimized-test-setup.test.ts} +2 -2
  109. package/src/__tests__/helpers/{__tests__/supabaseMock.test.ts → supabaseMock.test.ts} +2 -2
  110. package/src/__tests__/helpers/{__tests__/test-providers.test.tsx → test-providers.test.tsx} +1 -1
  111. package/src/__tests__/helpers/test-providers.tsx +37 -39
  112. package/src/__tests__/helpers/{__tests__/test-utils.test.tsx → test-utils.test.tsx} +4 -3
  113. package/src/__tests__/helpers/{__tests__/timer-utils.test.ts → timer-utils.test.ts} +2 -2
  114. package/src/assets/app-icons/index.test.ts +304 -0
  115. package/src/components/AddressField/AddressField.test.tsx +1 -1
  116. package/src/components/AddressField/AddressField.tsx +238 -212
  117. package/src/components/Button/Button.tsx +1 -1
  118. package/src/components/Card/Card.test.tsx +172 -17
  119. package/src/components/Card/Card.tsx +19 -10
  120. package/src/components/ContextSelector/ContextSelector.internals.tsx +204 -0
  121. package/src/components/ContextSelector/{__tests__/ContextSelector.test.tsx → ContextSelector.test.tsx} +6 -6
  122. package/src/components/ContextSelector/ContextSelector.tsx +66 -280
  123. package/src/components/ContextSelector/ContextSelector.types.ts +35 -0
  124. package/src/components/ContextSelector/useContextSelectorState.tsx +195 -0
  125. package/src/components/DataTable/AUDIT_REPORT.md +59 -44
  126. package/src/components/DataTable/{__tests__/DataTable.comprehensive.test.tsx → DataTable.comprehensive.test.tsx} +6 -6
  127. package/src/components/DataTable/{__tests__/DataTable.default-state.test.tsx → DataTable.default-state.test.tsx} +5 -5
  128. package/src/components/DataTable/{__tests__/DataTable.export.test.tsx → DataTable.export.test.tsx} +10 -10
  129. package/src/components/DataTable/{__tests__/DataTable.grouping-aggregation.test.tsx → DataTable.grouping-aggregation.test.tsx} +6 -6
  130. package/src/components/DataTable/{__tests__/DataTable.hooks.test.tsx → DataTable.hooks.test.tsx} +6 -6
  131. package/src/components/DataTable/{__tests__/DataTable.select-label-display.test.tsx → DataTable.select-label-display.test.tsx} +6 -6
  132. package/src/components/DataTable/DataTable.test.tsx +787 -416
  133. package/src/components/DataTable/DataTable.tsx +12 -12
  134. package/src/components/DataTable/DataTableCore.integration.test.tsx +458 -0
  135. package/src/components/DataTable/{__tests__/DataTableCore.test-setup.ts → DataTableCore.test-setup.ts} +10 -9
  136. package/src/components/DataTable/{__tests__/DataTableCore.test.tsx → DataTableCore.test.tsx} +8 -8
  137. package/src/components/DataTable/{__tests__/README.md → README.md} +17 -7
  138. package/src/components/DataTable/TESTING.md +101 -0
  139. package/src/components/DataTable/{__tests__/a11y.basic.test.tsx → a11y.basic.test.tsx} +34 -34
  140. package/src/components/DataTable/components/DataTableCore.tsx +104 -864
  141. package/src/components/DataTable/components/{__tests__/GroupingDropdown.test.tsx → GroupingDropdown.test.tsx} +17 -8
  142. package/src/components/DataTable/components/GroupingDropdown.tsx +2 -2
  143. package/src/components/DataTable/components/ImportModal.tsx +61 -559
  144. package/src/components/DataTable/components/ImportModalFileSection.tsx +148 -0
  145. package/src/components/DataTable/context/{__tests__/DataTableContext.test.tsx → DataTableContext.test.tsx} +2 -2
  146. package/src/components/DataTable/context/DataTableContext.tsx +7 -6
  147. package/src/components/DataTable/core/{__tests__/ColumnFactory.test.ts → ColumnFactory.test.ts} +2 -2
  148. package/src/components/DataTable/hooks/{__tests__/useColumnOrderPersistence.test.ts → useColumnOrderPersistence.test.ts} +2 -2
  149. package/src/components/DataTable/hooks/{__tests__/useColumnVisibilityPersistence.test.ts → useColumnVisibilityPersistence.test.ts} +2 -2
  150. package/src/components/DataTable/hooks/{__tests__/useDataTableConfiguration.test.ts → useDataTableConfiguration.test.ts} +3 -3
  151. package/src/components/DataTable/hooks/useDataTableConfiguration.ts +14 -2
  152. package/src/components/DataTable/hooks/{__tests__/useDataTableDataPipeline.test.ts → useDataTableDataPipeline.test.ts} +6 -6
  153. package/src/components/DataTable/hooks/useDataTableDeletionBatching.test.ts +127 -0
  154. package/src/components/DataTable/hooks/useDataTableDeletionBatching.ts +106 -0
  155. package/src/components/DataTable/hooks/useDataTableEffectiveActions.test.ts +461 -0
  156. package/src/components/DataTable/hooks/useDataTableEffectiveActions.ts +238 -0
  157. package/src/components/DataTable/hooks/useDataTableLayoutHandlers.test.ts +296 -0
  158. package/src/components/DataTable/hooks/useDataTableLayoutHandlers.ts +175 -0
  159. package/src/components/DataTable/hooks/useDataTablePaginationSync.test.ts +203 -0
  160. package/src/components/DataTable/hooks/useDataTablePaginationSync.ts +109 -0
  161. package/src/components/DataTable/hooks/{__tests__/useDataTablePermissions.test.ts → useDataTablePermissions.test.ts} +11 -11
  162. package/src/components/DataTable/hooks/useDataTablePermissions.ts +79 -247
  163. package/src/components/DataTable/hooks/useDataTablePipeline.test.tsx +219 -0
  164. package/src/components/DataTable/hooks/useDataTablePipeline.tsx +239 -0
  165. package/src/components/DataTable/hooks/useDataTableRenderGuard.test.tsx +316 -0
  166. package/src/components/DataTable/hooks/useDataTableRenderGuard.tsx +195 -0
  167. package/src/components/DataTable/hooks/useDataTableScope.test.ts +110 -0
  168. package/src/components/DataTable/hooks/useDataTableScope.ts +123 -0
  169. package/src/components/DataTable/hooks/{__tests__/useDataTableState.test.ts → useDataTableState.test.ts} +47 -5
  170. package/src/components/DataTable/hooks/useDataTableState.ts +145 -94
  171. package/src/components/DataTable/hooks/useDataTableStateAndPersistence.test.ts +277 -0
  172. package/src/components/DataTable/hooks/useDataTableStateAndPersistence.ts +222 -0
  173. package/src/components/DataTable/hooks/useDataTableSuperAdmin.test.ts +93 -0
  174. package/src/components/DataTable/hooks/useDataTableSuperAdmin.ts +86 -0
  175. package/src/components/DataTable/hooks/useDataTableTableInstance.test.ts +185 -0
  176. package/src/components/DataTable/hooks/useDataTableTableInstance.ts +178 -0
  177. package/src/components/DataTable/hooks/{__tests__/useEffectiveColumnOrder.test.ts → useEffectiveColumnOrder.test.ts} +2 -2
  178. package/src/components/DataTable/hooks/{__tests__/useHierarchicalState.test.ts → useHierarchicalState.test.ts} +2 -2
  179. package/src/components/DataTable/{components/hooks → hooks}/useImportModalFocus.test.ts +3 -3
  180. package/src/components/DataTable/{components/hooks → hooks}/useImportModalFocus.ts +2 -2
  181. package/src/components/DataTable/hooks/useImportModalState.test.ts +390 -0
  182. package/src/components/DataTable/hooks/useImportModalState.ts +345 -0
  183. package/src/components/DataTable/hooks/{__tests__/useKeyboardNavigation.test.ts → useKeyboardNavigation.test.ts} +3 -3
  184. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +309 -269
  185. package/src/components/DataTable/{components/hooks → hooks}/usePermissionTracking.test.ts +3 -3
  186. package/src/components/DataTable/{components/hooks → hooks}/usePermissionTracking.ts +3 -3
  187. package/src/components/DataTable/hooks/{__tests__/useServerSideDataEffect.test.ts → useServerSideDataEffect.test.ts} +2 -2
  188. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +14 -3
  189. package/src/components/DataTable/hooks/{__tests__/useTableColumns.test.ts → useTableColumns.test.ts} +2 -2
  190. package/src/components/DataTable/hooks/{__tests__/useTableHandlers.test.ts → useTableHandlers.test.ts} +25 -4
  191. package/src/components/DataTable/hooks/useTableHandlers.ts +5 -2
  192. package/src/components/DataTable/index.ts +18 -17
  193. package/src/components/DataTable/{__tests__/keyboard.test.tsx → keyboard.test.tsx} +41 -63
  194. package/src/components/DataTable/{__tests__/mocks → mocks}/MockRBACProvider.tsx +1 -1
  195. package/src/components/DataTable/{__tests__/pagination.modes.test.tsx → pagination.modes.test.tsx} +6 -6
  196. package/src/components/DataTable/{__tests__/ssr.strict-mode.test.tsx → ssr.strict-mode.test.tsx} +2 -2
  197. package/src/components/DataTable/{__tests__/styles.test.ts → styles.test.ts} +1 -4
  198. package/src/components/DataTable/styles.ts +0 -1
  199. package/src/components/DataTable/test-utils/MockDataTableComponents.tsx +55 -0
  200. package/src/components/DataTable/{__tests__/test-utils → test-utils}/dataFactories.ts +2 -2
  201. package/src/components/DataTable/test-utils/featureConfig.ts +10 -0
  202. package/src/components/DataTable/{__tests__/test-utils/sharedTestUtils.tsx → test-utils/sharedTestUtils.ts} +97 -66
  203. package/src/components/DataTable/{__tests__/test-utils.ts → test-utils.ts} +1 -1
  204. package/src/components/DataTable/types/actions.ts +71 -0
  205. package/src/components/DataTable/types/base.ts +39 -0
  206. package/src/components/DataTable/types/columns.ts +125 -0
  207. package/src/components/DataTable/types/export.ts +32 -0
  208. package/src/components/DataTable/types/features.ts +81 -0
  209. package/src/components/DataTable/types/hierarchical.ts +44 -0
  210. package/src/components/DataTable/types/index.ts +43 -0
  211. package/src/components/DataTable/types/pagination.ts +85 -0
  212. package/src/components/DataTable/types/performance.ts +47 -0
  213. package/src/components/DataTable/types/props.ts +62 -0
  214. package/src/components/DataTable/types/rbac.ts +45 -0
  215. package/src/components/DataTable/{components/__tests__ → ui/layout}/DataTableCore.test.tsx +430 -28
  216. package/src/components/DataTable/ui/layout/DataTableCore.tsx +345 -0
  217. package/src/components/DataTable/{components/__tests__ → ui/layout}/DataTableErrorBoundary.test.tsx +4 -4
  218. package/src/components/DataTable/{components → ui/layout}/DataTableErrorBoundary.tsx +7 -7
  219. package/src/components/DataTable/ui/layout/DataTableLayout.test.tsx +1352 -0
  220. package/src/components/DataTable/ui/layout/DataTableLayout.tsx +661 -0
  221. package/src/components/DataTable/ui/modals/BulkDeleteConfirmDialog.test.tsx +91 -0
  222. package/src/components/DataTable/ui/modals/BulkDeleteConfirmDialog.tsx +43 -0
  223. package/src/components/DataTable/ui/modals/DataTableModals.test.tsx +749 -0
  224. package/src/components/DataTable/{components → ui/modals}/DataTableModals.tsx +36 -28
  225. package/src/components/DataTable/ui/modals/ImportModal.test.tsx +1834 -0
  226. package/src/components/DataTable/ui/modals/ImportModal.tsx +197 -0
  227. package/src/components/DataTable/ui/modals/ImportModalFailedRowsSection.tsx +60 -0
  228. package/src/components/DataTable/ui/modals/ImportModalFileSection.tsx +148 -0
  229. package/src/components/DataTable/ui/modals/ImportModalPreviewSection.tsx +60 -0
  230. package/src/components/DataTable/ui/modals/ImportModalSummarySection.tsx +59 -0
  231. package/src/components/DataTable/ui/modals/importModalPersistence.ts +73 -0
  232. package/src/components/DataTable/{components/__tests__ → ui/shared}/AccessDeniedPage.test.tsx +2 -2
  233. package/src/components/DataTable/{components → ui/shared}/AccessDeniedPage.tsx +2 -2
  234. package/src/components/DataTable/{components/__tests__ → ui/shared}/ActionButtons.test.tsx +6 -4
  235. package/src/components/DataTable/{components → ui/shared}/ActionButtons.tsx +4 -4
  236. package/src/components/DataTable/{components/__tests__ → ui/shared}/ColumnFilter.test.tsx +29 -16
  237. package/src/components/DataTable/{components → ui/shared}/ColumnFilter.tsx +4 -4
  238. package/src/components/DataTable/{components/__tests__ → ui/shared}/PaginationControls.test.tsx +38 -16
  239. package/src/components/DataTable/{components → ui/shared}/PaginationControls.tsx +21 -15
  240. package/src/components/DataTable/{components/__tests__ → ui/shared}/SortIndicator.test.tsx +2 -2
  241. package/src/components/DataTable/{components → ui/shared}/SortIndicator.tsx +1 -1
  242. package/src/components/DataTable/{components/__tests__ → ui/table}/EditFields.test.tsx +3 -3
  243. package/src/components/DataTable/{components → ui/table}/EditFields.tsx +138 -69
  244. package/src/components/DataTable/{components/__tests__ → ui/table}/EditableRow.test.tsx +36 -27
  245. package/src/components/DataTable/{components → ui/table}/EditableRow.tsx +86 -104
  246. package/src/components/DataTable/{components/__tests__ → ui/table}/EmptyState.test.tsx +2 -62
  247. package/src/components/DataTable/{components → ui/table}/EmptyState.tsx +7 -15
  248. package/src/components/DataTable/{components/__tests__ → ui/table}/FilterRow.test.tsx +5 -4
  249. package/src/components/DataTable/{components → ui/table}/FilterRow.tsx +3 -3
  250. package/src/components/DataTable/{components/__tests__ → ui/table}/LoadingState.test.tsx +6 -10
  251. package/src/components/DataTable/{components → ui/table}/LoadingState.tsx +4 -4
  252. package/src/components/DataTable/{components/__tests__ → ui/table}/RowComponent.test.tsx +412 -17
  253. package/src/components/DataTable/{components → ui/table}/RowComponent.tsx +183 -177
  254. package/src/components/DataTable/{components/__tests__ → ui/table}/UnifiedTableBody.test.tsx +425 -16
  255. package/src/components/DataTable/ui/table/UnifiedTableBody.tsx +440 -0
  256. package/src/components/DataTable/{components/__tests__ → ui/table}/cellValueUtils.test.ts +2 -2
  257. package/src/components/DataTable/{components → ui/table}/cellValueUtils.ts +1 -1
  258. package/src/components/DataTable/{components/__tests__ → ui/toolbar}/BulkOperationsDropdown.test.tsx +12 -5
  259. package/src/components/DataTable/{components → ui/toolbar}/BulkOperationsDropdown.tsx +3 -3
  260. package/src/components/DataTable/{components/__tests__ → ui/toolbar}/ColumnVisibilityDropdown.test.tsx +7 -4
  261. package/src/components/DataTable/{components → ui/toolbar}/ColumnVisibilityDropdown.tsx +7 -7
  262. package/src/components/DataTable/{components/__tests__ → ui/toolbar}/DataTableToolbar.test.tsx +4 -4
  263. package/src/components/DataTable/{components → ui/toolbar}/DataTableToolbar.tsx +4 -4
  264. package/src/components/DataTable/ui/toolbar/GroupingDropdown.test.tsx +621 -0
  265. package/src/components/DataTable/ui/toolbar/GroupingDropdown.tsx +107 -0
  266. package/src/components/DataTable/utils/{__tests__/a11yUtils.test.ts → a11yUtils.test.ts} +2 -2
  267. package/src/components/DataTable/utils/{__tests__/aggregationUtils.test.ts → aggregationUtils.test.ts} +3 -3
  268. package/src/components/DataTable/utils/{__tests__/columnUtils.test.ts → columnUtils.test.ts} +2 -2
  269. package/src/components/DataTable/utils/csvParse.test.ts +74 -0
  270. package/src/components/DataTable/utils/csvParse.ts +65 -0
  271. package/src/components/DataTable/utils/{__tests__/errorHandling.test.ts → errorHandling.test.ts} +2 -2
  272. package/src/components/DataTable/utils/{__tests__/exportUtils.test.ts → exportUtils.test.ts} +3 -3
  273. package/src/components/DataTable/utils/{__tests__/flexibleImport.test.ts → flexibleImport.test.ts} +2 -2
  274. package/src/components/DataTable/utils/flexibleImport.ts +3 -186
  275. package/src/components/DataTable/utils/{__tests__/hierarchicalSorting.test.ts → hierarchicalSorting.test.ts} +3 -3
  276. package/src/components/DataTable/utils/{__tests__/hierarchicalUtils.test.ts → hierarchicalUtils.test.ts} +3 -3
  277. package/src/components/DataTable/utils/importDateParser.test.ts +162 -0
  278. package/src/components/DataTable/utils/importDateParser.ts +114 -0
  279. package/src/components/DataTable/utils/importValueParser.test.ts +138 -0
  280. package/src/components/DataTable/utils/importValueParser.ts +91 -0
  281. package/src/components/DataTable/utils/{__tests__/paginationUtils.test.ts → paginationUtils.test.ts} +2 -2
  282. package/src/components/DataTable/utils/paginationUtils.ts +6 -3
  283. package/src/components/DataTable/utils/{__tests__/performanceUtils.test.ts → performanceUtils.test.ts} +3 -3
  284. package/src/components/DataTable/utils/{__tests__/rowUtils.test.ts → rowUtils.test.ts} +3 -3
  285. package/src/components/DataTable/utils/{__tests__/selectFieldUtils.test.ts → selectFieldUtils.test.ts} +66 -3
  286. package/src/components/DataTable/utils/selectFieldUtils.ts +97 -60
  287. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +1 -1
  288. package/src/components/DateTimeField/DateTimeField.test.tsx +1 -1
  289. package/src/components/Dialog/Dialog.test-utils.ts +49 -0
  290. package/src/components/Dialog/Dialog.test.tsx +896 -89
  291. package/src/components/Dialog/Dialog.tsx +174 -882
  292. package/src/components/Dialog/dialogLock.test.ts +238 -0
  293. package/src/components/Dialog/dialogLock.ts +98 -0
  294. package/src/components/Dialog/index.ts +2 -0
  295. package/src/components/Dialog/useDialogDimensions.test.ts +163 -0
  296. package/src/components/Dialog/useDialogDimensions.ts +140 -0
  297. package/src/components/Dialog/useDialogLifecycle.test.ts +358 -0
  298. package/src/components/Dialog/useDialogLifecycle.ts +135 -0
  299. package/src/components/Dialog/useDialogPersistence.test.ts +381 -0
  300. package/src/components/Dialog/useDialogPersistence.ts +357 -0
  301. package/src/components/FileDisplay/FileDisplay.test.tsx +40 -40
  302. package/src/components/FileDisplay/FileDisplay.tsx +24 -656
  303. package/src/components/FileDisplay/FileDisplayContent.test.tsx +395 -0
  304. package/src/components/FileDisplay/FileDisplayContent.tsx +242 -0
  305. package/src/components/FileDisplay/FileDisplayDeleteConfirmDialog.test.tsx +74 -0
  306. package/src/components/FileDisplay/FileDisplayDeleteConfirmDialog.tsx +38 -0
  307. package/src/components/FileDisplay/FileDisplayEmptyView.test.tsx +33 -0
  308. package/src/components/FileDisplay/FileDisplayEmptyView.tsx +33 -0
  309. package/src/components/FileDisplay/FileDisplayErrorView.test.tsx +71 -0
  310. package/src/components/FileDisplay/FileDisplayErrorView.tsx +50 -0
  311. package/src/components/FileDisplay/FileDisplayLoadingFallbackView.test.tsx +22 -0
  312. package/src/components/FileDisplay/FileDisplayLoadingFallbackView.tsx +22 -0
  313. package/src/components/FileDisplay/FileDisplayLoadingView.test.tsx +21 -0
  314. package/src/components/FileDisplay/FileDisplayLoadingView.tsx +23 -0
  315. package/src/components/FileDisplay/FileDisplayMultipleFilesView.test.tsx +101 -0
  316. package/src/components/FileDisplay/FileDisplayMultipleFilesView.tsx +109 -0
  317. package/src/components/FileDisplay/FileDisplaySingleDocumentLinkView.test.tsx +58 -0
  318. package/src/components/FileDisplay/FileDisplaySingleDocumentLinkView.tsx +48 -0
  319. package/src/components/FileDisplay/FileDisplaySingleFileWithActionsView.test.tsx +111 -0
  320. package/src/components/FileDisplay/FileDisplaySingleFileWithActionsView.tsx +270 -0
  321. package/src/components/FileDisplay/FileDisplaySingleImageView.test.tsx +78 -0
  322. package/src/components/FileDisplay/FileDisplaySingleImageView.tsx +67 -0
  323. package/src/components/FileDisplay/fallbackUtils.test.ts +50 -0
  324. package/src/components/FileDisplay/fallbackUtils.ts +44 -0
  325. package/src/components/FileDisplay/fetchFileDisplayData.ts +24 -0
  326. package/src/components/FileDisplay/fetchFileDisplayData.unit.test.ts +183 -0
  327. package/src/components/FileDisplay/fileDisplayUtils.test.ts +58 -0
  328. package/src/components/FileDisplay/fileDisplayUtils.ts +24 -0
  329. package/src/{hooks/__tests__ → components/FileDisplay}/useFileDisplay.test.ts +40 -42
  330. package/src/components/FileDisplay/useFileDisplay.ts +515 -0
  331. package/src/{hooks/__tests__ → components/FileDisplay}/useFileDisplay.unit.test.ts +406 -77
  332. package/src/components/FileDisplay/useFileDisplayData.ts +126 -0
  333. package/src/{hooks/public → components/FileDisplay}/usePublicFileDisplay.test.ts +94 -88
  334. package/src/components/FileDisplay/usePublicFileDisplay.ts +579 -0
  335. package/src/components/FileUpload/FileUpload.test.tsx +16 -10
  336. package/src/components/FileUpload/FileUpload.tsx +107 -525
  337. package/src/components/FileUpload/FileUploadDropZone.tsx +112 -0
  338. package/src/components/FileUpload/FileUploadProgressItem.tsx +86 -0
  339. package/src/components/FileUpload/FileUploadProgressList.tsx +40 -0
  340. package/src/components/FileUpload/useFileUploadManager.test.ts +308 -0
  341. package/src/components/FileUpload/useFileUploadManager.ts +454 -0
  342. package/src/components/FileUpload/useResolvedAppId.test.ts +102 -0
  343. package/src/components/FileUpload/useResolvedAppId.ts +77 -0
  344. package/src/components/Footer/Footer.test.tsx +6 -292
  345. package/src/components/Footer/Footer.tsx +8 -125
  346. package/src/components/Form/Form.test.tsx +44 -27
  347. package/src/components/Form/Form.tsx +64 -287
  348. package/src/components/Form/useFormPersistence.ts +257 -0
  349. package/src/components/Header/Header.test.tsx +17 -18
  350. package/src/components/Header/Header.tsx +10 -1
  351. package/src/components/Input/Input.tsx +1 -1
  352. package/src/components/Label/Label.test.tsx +1 -1
  353. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +1 -1
  354. package/src/components/NavigationMenu/HierarchicalNavItem.tsx +104 -0
  355. package/src/components/NavigationMenu/NavigationMenu.test.tsx +1029 -26
  356. package/src/components/NavigationMenu/NavigationMenu.tsx +61 -361
  357. package/src/components/NavigationMenu/index.ts +6 -1
  358. package/src/components/NavigationMenu/navigationPermissionHelper.ts +188 -0
  359. package/src/components/NavigationMenu/{__tests__/useNavigationFiltering.test.ts → useNavigationFiltering.test.ts} +68 -53
  360. package/src/components/NavigationMenu/useNavigationFiltering.ts +197 -296
  361. package/src/components/NavigationMenu/useNavigationScope.ts +125 -0
  362. package/src/components/PaceAppLayout/PaceAppLayout.edge-cases.test.tsx +77 -62
  363. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +3 -3
  364. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +16 -19
  365. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +529 -5
  366. package/src/components/PaceAppLayout/PaceAppLayout.tsx +280 -756
  367. package/src/components/PaceAppLayout/useFilteredNavItems.ts +304 -0
  368. package/src/components/PaceAppLayout/usePaceAppLayoutConfig.ts +142 -0
  369. package/src/components/PaceAppLayout/usePaceAppLayoutGate.tsx +150 -0
  370. package/src/components/PaceAppLayout/usePaceAppLayoutPermissions.ts +162 -0
  371. package/src/components/PaceAppLayout/usePaceAppLayoutScope.ts +79 -0
  372. package/src/components/PaceAppLayout/useRoleBasedRouteAccess.ts +157 -0
  373. package/src/components/PaceAppLayout/useSuperAdminFallback.ts +58 -0
  374. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +31 -25
  375. package/src/components/PaceLoginPage/PaceLoginPage.tsx +31 -122
  376. package/src/components/PaceLoginPage/useLoginAppAccess.ts +153 -0
  377. package/src/components/Progress/Progress.tsx +1 -2
  378. package/src/components/ProtectedRoute/ProtectedRoute.tsx +29 -235
  379. package/src/components/ProtectedRoute/useProtectedRouteState.ts +128 -0
  380. package/src/components/ProtectedRoute/useVisibilityRedirectGrace.ts +89 -0
  381. package/src/components/PublicLayout/PublicLayout.test.tsx +217 -36
  382. package/src/components/PublicLayout/PublicPageLayout.tsx +132 -73
  383. package/src/components/PublicLayout/PublicPageProvider.tsx +5 -1
  384. package/src/components/Select/Select.test.tsx +1 -1
  385. package/src/components/Select/Select.tsx +28 -18
  386. package/src/components/Select/{__tests__/context.test.tsx → context.test.tsx} +3 -3
  387. package/src/components/Select/{utils/__tests__/text.test.tsx → text.test.tsx} +2 -2
  388. package/src/components/Select/{utils/text.ts → text.ts} +1 -1
  389. package/src/components/Select/{hooks/__tests__/useSelectEvents.test.ts → useSelectEvents.test.ts} +5 -5
  390. package/src/components/Select/{hooks/useSelectEvents.ts → useSelectEvents.ts} +2 -2
  391. package/src/components/Select/{hooks/__tests__/useSelectSearch.test.tsx → useSelectSearch.test.tsx} +7 -7
  392. package/src/components/Select/{hooks/useSelectSearch.ts → useSelectSearch.ts} +2 -2
  393. package/src/components/Select/{hooks/__tests__/useSelectState.test.ts → useSelectState.test.ts} +16 -2
  394. package/src/components/Select/{hooks/useSelectState.ts → useSelectState.ts} +3 -3
  395. package/src/components/Table/Table.test.tsx +348 -0
  396. package/src/components/Tabs/Tabs.test.tsx +270 -0
  397. package/src/components/Tabs/Tabs.tsx +1 -1
  398. package/src/components/Toast/Toast.test.tsx +420 -0
  399. package/src/components/{__tests__/index.test.ts → index.test.ts} +2 -2
  400. package/src/constants/{__tests__/performance.test.ts → performance.test.ts} +2 -2
  401. package/src/hooks/{__tests__/ServiceHooks.test.tsx → ServiceHooks.test.tsx} +8 -8
  402. package/src/hooks/{__tests__/hooks.integration.test.tsx → hooks.integration.test.tsx} +11 -11
  403. package/src/hooks/index.ts +7 -4
  404. package/src/hooks/{__tests__/index.unit.test.ts → index.unit.test.ts} +2 -2
  405. package/src/hooks/public/usePublicEvent.test.ts +1 -1
  406. package/src/hooks/public/usePublicEventLogo.test.ts +1 -1
  407. package/src/hooks/public/usePublicRouteParams.test.ts +1 -1
  408. package/src/hooks/services/useAuth.ts +9 -7
  409. package/src/hooks/useAddressAutocomplete.test.ts +22 -22
  410. package/src/hooks/useAddressAutocomplete.ts +90 -75
  411. package/src/hooks/{__tests__/useAppConfig.unit.test.ts → useAppConfig.unit.test.ts} +328 -22
  412. package/src/hooks/{__tests__/useComponentPerformance.unit.test.tsx → useComponentPerformance.unit.test.tsx} +27 -41
  413. package/src/hooks/useDataTablePerformance.ts +100 -120
  414. package/src/hooks/{__tests__/useDataTablePerformance.unit.test.ts → useDataTablePerformance.unit.test.ts} +5 -5
  415. package/src/hooks/{__tests__/useDataTableState.test.ts → useDataTableState.test.ts} +2 -2
  416. package/src/hooks/{__tests__/useDebounce.unit.test.ts → useDebounce.unit.test.ts} +2 -2
  417. package/src/hooks/useEventTheme.test.ts +4 -1
  418. package/src/hooks/useEventTheme.ts +49 -21
  419. package/src/hooks/useEvents.ts +41 -1
  420. package/src/hooks/{__tests__/useEvents.unit.test.ts → useEvents.unit.test.ts} +5 -5
  421. package/src/hooks/useFileReference.test.ts +44 -41
  422. package/src/hooks/useFileReference.ts +182 -173
  423. package/src/hooks/useFileUrl.ts +1 -1
  424. package/src/hooks/{__tests__/useFileUrl.unit.test.ts → useFileUrl.unit.test.ts} +26 -36
  425. package/src/hooks/{__tests__/useFileUrlCache.test.ts → useFileUrlCache.test.ts} +8 -8
  426. package/src/hooks/useFileUrlCache.ts +1 -1
  427. package/src/hooks/{__tests__/useFocusManagement.unit.test.ts → useFocusManagement.unit.test.ts} +2 -2
  428. package/src/hooks/{__tests__/useFocusTrap.unit.test.tsx → useFocusTrap.unit.test.tsx} +2 -2
  429. package/src/hooks/{__tests__/useFormDialog.test.ts → useFormDialog.test.ts} +2 -2
  430. package/src/hooks/useInactivityTracker.ts +138 -131
  431. package/src/hooks/{__tests__/useInactivityTracker.unit.test.ts → useInactivityTracker.unit.test.ts} +3 -3
  432. package/src/hooks/{__tests__/useIsMobile.unit.test.ts → useIsMobile.unit.test.ts} +2 -2
  433. package/src/hooks/useIsPrint.ts +62 -0
  434. package/src/hooks/useIsPrint.unit.test.ts +545 -0
  435. package/src/hooks/{__tests__/useKeyboardShortcuts.unit.test.ts → useKeyboardShortcuts.unit.test.ts} +2 -2
  436. package/src/hooks/{__tests__/useOrganisationPermissions.unit.test.tsx → useOrganisationPermissions.unit.test.tsx} +4 -4
  437. package/src/hooks/useOrganisationSecurity.test.ts +3 -3
  438. package/src/hooks/useOrganisationSecurity.ts +190 -201
  439. package/src/hooks/{__tests__/useOrganisationSecurity.unit.test.tsx → useOrganisationSecurity.unit.test.tsx} +61 -63
  440. package/src/hooks/{__tests__/useOrganisations.unit.test.ts → useOrganisations.unit.test.ts} +5 -5
  441. package/src/hooks/{__tests__/usePerformanceMonitor.unit.test.ts → usePerformanceMonitor.unit.test.ts} +13 -14
  442. package/src/hooks/{__tests__/usePermissionCache.test.ts → usePermissionCache.test.ts} +26 -27
  443. package/src/hooks/usePermissionCache.ts +276 -271
  444. package/src/hooks/{__tests__/usePreventTabReload.test.ts → usePreventTabReload.test.ts} +2 -2
  445. package/src/hooks/{__tests__/usePublicEvent.simple.test.ts → usePublicEvent.simple.test.ts} +4 -4
  446. package/src/hooks/{__tests__/usePublicEvent.test.ts → usePublicEvent.test.ts} +4 -4
  447. package/src/hooks/{__tests__/usePublicEvent.unit.test.ts → usePublicEvent.unit.test.ts} +4 -4
  448. package/src/hooks/{__tests__/usePublicFileDisplay.test.ts → usePublicFileDisplay.test.ts} +12 -12
  449. package/src/hooks/{__tests__/usePublicRouteParams.unit.test.ts → usePublicRouteParams.unit.test.ts} +3 -3
  450. package/src/hooks/{__tests__/useQueryCache.test.ts → useQueryCache.test.ts} +2 -2
  451. package/src/hooks/useQueryCache.ts +0 -2
  452. package/src/hooks/{__tests__/useRBAC.unit.test.ts → useRBAC.unit.test.ts} +55 -38
  453. package/src/hooks/{__tests__/useSessionDraft.test.ts → useSessionDraft.test.ts} +2 -2
  454. package/src/hooks/{__tests__/useSessionRestoration.unit.test.tsx → useSessionRestoration.unit.test.tsx} +10 -19
  455. package/src/hooks/useStorage.ts +21 -16
  456. package/src/hooks/{__tests__/useStorage.unit.test.ts → useStorage.unit.test.ts} +38 -75
  457. package/src/hooks/{__tests__/useToast.test.ts → useToast.test.ts} +2 -2
  458. package/src/hooks/{__tests__/useToast.unit.test.tsx → useToast.unit.test.tsx} +2 -2
  459. package/src/hooks/{__tests__/useZodForm.unit.test.tsx → useZodForm.unit.test.tsx} +2 -2
  460. package/src/icons/{__tests__/index.test.ts → index.test.ts} +2 -2
  461. package/src/icons/index.ts +2 -0
  462. package/src/{__tests__/index.test.ts → index.test.ts} +3 -7
  463. package/src/index.ts +15 -7
  464. package/src/providers/{__tests__/AuthProvider.test.tsx → AuthProvider.test.tsx} +3 -3
  465. package/src/providers/{__tests__/EventProvider.test.tsx → EventProvider.test.tsx} +3 -3
  466. package/src/providers/InactivityProvider.test-helper.tsx +40 -0
  467. package/src/providers/{__tests__/InactivityProvider.test.tsx → InactivityProvider.test.tsx} +14 -21
  468. package/src/providers/{__tests__/ProviderLifecycle.test.tsx → ProviderLifecycle.test.tsx} +4 -4
  469. package/src/providers/{__tests__/UnifiedAuthProvider.test.tsx → UnifiedAuthProvider.test.tsx} +1 -1
  470. package/src/providers/{__tests__/index.test.ts → index.test.ts} +2 -2
  471. package/src/providers/services/{__tests__/AuthServiceProvider.integration.test.tsx → AuthServiceProvider.integration.test.tsx} +4 -4
  472. package/src/providers/services/{__tests__/AuthServiceProvider.test.tsx → AuthServiceProvider.test.tsx} +7 -7
  473. package/src/providers/services/{__tests__/EventServiceProvider.test.tsx → EventServiceProvider.test.tsx} +7 -7
  474. package/src/providers/services/{__tests__/InactivityServiceProvider.test.tsx → InactivityServiceProvider.test.tsx} +5 -5
  475. package/src/providers/services/{__tests__/OrganisationServiceProvider.test.tsx → OrganisationServiceProvider.test.tsx} +6 -6
  476. package/src/providers/services/UnifiedAuthContext.ts +30 -27
  477. package/src/providers/services/{__tests__/UnifiedAuthProvider.advanced.test.tsx → UnifiedAuthProvider.advanced.test.tsx} +8 -9
  478. package/src/providers/services/{__tests__/UnifiedAuthProvider.appId.test.tsx → UnifiedAuthProvider.appId.test.tsx} +25 -25
  479. package/src/providers/services/{__tests__/UnifiedAuthProvider.integration.test.tsx → UnifiedAuthProvider.integration.test.tsx} +14 -11
  480. package/src/providers/services/UnifiedAuthProvider.tsx +115 -360
  481. package/src/providers/services/{__tests__/contexts.test.tsx → contexts.test.tsx} +6 -6
  482. package/src/providers/services/{__tests__/useUnifiedAuth.test.tsx → useUnifiedAuth.test.tsx} +6 -6
  483. package/src/providers/services/useUnifiedAuthContextValue.ts +279 -0
  484. package/src/providers/useInactivity.test-helper.ts +27 -0
  485. package/src/rbac/{__tests__/adapters.comprehensive.test.tsx → adapters.comprehensive.test.tsx} +24 -24
  486. package/src/rbac/adapters.test.tsx +22 -22
  487. package/src/rbac/adapters.tsx +29 -29
  488. package/src/rbac/api.test.ts +973 -42
  489. package/src/rbac/api.ts +228 -253
  490. package/src/rbac/{__tests__/audit-batched.test.ts → audit-batched.test.ts} +6 -6
  491. package/src/rbac/audit.ts +4 -1
  492. package/src/rbac/{__tests__/auth-rbac-security.integration.test.tsx → auth-rbac-security.integration.test.tsx} +1 -1
  493. package/src/rbac/{__tests__/auth-rbac.e2e.test.tsx → auth-rbac.e2e.test.tsx} +27 -34
  494. package/src/rbac/cache-invalidation.test.ts +715 -0
  495. package/src/rbac/components/{__tests__/AccessDenied.test.tsx → AccessDenied.test.tsx} +3 -3
  496. package/src/rbac/components/{__tests__/NavigationGuard.test.tsx → NavigationGuard.test.tsx} +13 -11
  497. package/src/{__tests__/rbac/PagePermissionGuard.test.tsx → rbac/components/PagePermissionGuard.guard.test.tsx} +33 -19
  498. package/src/rbac/components/{__tests__/PagePermissionGuard.performance.test.tsx → PagePermissionGuard.performance.test.tsx} +30 -9
  499. package/src/rbac/components/{__tests__/PagePermissionGuard.race-condition.test.tsx → PagePermissionGuard.race-condition.test.tsx} +7 -7
  500. package/src/rbac/components/{__tests__/PagePermissionGuard.test.tsx → PagePermissionGuard.test.tsx} +10 -10
  501. package/src/rbac/components/PagePermissionGuard.tsx +177 -372
  502. package/src/rbac/components/{__tests__/PagePermissionGuard.verification.test.tsx → PagePermissionGuard.verification.test.tsx} +7 -7
  503. package/src/rbac/config.ts +58 -18
  504. package/src/rbac/{__tests__/engine.comprehensive.test.ts → engine.comprehensive.test.ts} +3 -3
  505. package/src/rbac/engine.test.ts +494 -0
  506. package/src/rbac/errors.ts +89 -55
  507. package/src/rbac/hooks/permissions/runPermissionCheck.ts +77 -0
  508. package/src/rbac/hooks/permissions/{__tests__/useAccessLevel.test.ts → useAccessLevel.test.ts} +40 -40
  509. package/src/rbac/hooks/permissions/useAccessLevel.ts +16 -6
  510. package/src/rbac/hooks/permissions/{__tests__/useCan.test.ts → useCan.test.ts} +41 -41
  511. package/src/rbac/hooks/permissions/useCan.ts +170 -252
  512. package/src/rbac/hooks/permissions/{__tests__/useMultiplePermissions.test.ts → useMultiplePermissions.test.ts} +49 -49
  513. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +6 -2
  514. package/src/rbac/hooks/permissions/{__tests__/usePermissions.test.ts → usePermissions.test.ts} +10 -12
  515. package/src/rbac/hooks/permissions/usePermissions.ts +36 -65
  516. package/src/rbac/hooks/useCan.test.ts +42 -42
  517. package/src/rbac/hooks/usePageAccessLogging.ts +160 -0
  518. package/src/rbac/hooks/usePageGuardScope.ts +117 -0
  519. package/src/rbac/hooks/usePagePermissionCheck.ts +67 -0
  520. package/src/rbac/hooks/{__tests__/usePermissions.integration.test.ts → usePermissions.integration.test.ts} +9 -9
  521. package/src/{__tests__/hooks/usePermissions.test.ts → rbac/hooks/usePermissions.stability.test.ts} +18 -18
  522. package/src/rbac/hooks/usePermissions.test.ts +54 -54
  523. package/src/rbac/hooks/useRBAC.test.ts +313 -217
  524. package/src/rbac/hooks/useRBAC.ts +145 -81
  525. package/src/rbac/hooks/useResourcePermissions.test.ts +25 -25
  526. package/src/rbac/hooks/useResourcePermissions.ts +68 -134
  527. package/src/rbac/hooks/useResourcePermissionsSuperAdmin.ts +67 -0
  528. package/src/rbac/hooks/useRoleManagement.test.ts +27 -112
  529. package/src/rbac/hooks/useRoleManagement.ts +153 -585
  530. package/src/rbac/hooks/{__tests__/useSecureSupabase.test.ts → useSecureSupabase.test.ts} +17 -17
  531. package/src/rbac/hooks/useSecureSupabase.ts +10 -2
  532. package/src/rbac/hooks/useSuperAdminCheck.ts +80 -0
  533. package/src/rbac/{__tests__/performance.test.ts → performance.test.ts} +1 -1
  534. package/src/rbac/{__tests__/rbac-core.test.tsx → rbac-core.test.tsx} +3 -3
  535. package/src/rbac/{__tests__/rbac-engine-core-logic.test.ts → rbac-engine-core-logic.test.ts} +2 -2
  536. package/src/rbac/{__tests__/rbac-engine-simplified.test.ts → rbac-engine-simplified.test.ts} +3 -3
  537. package/src/rbac/{__tests__/rbac-functions.test.ts → rbac-functions.test.ts} +57 -0
  538. package/src/rbac/{__tests__/rbac-role-isolation.test.ts → rbac-role-isolation.test.ts} +2 -2
  539. package/src/rbac/request-deduplication.test.ts +14 -9
  540. package/src/rbac/request-deduplication.ts +5 -4
  541. package/src/rbac/{__tests__/scenarios.user-role.test.tsx → scenarios.user-role.test.tsx} +23 -23
  542. package/src/rbac/secureClient.test.ts +514 -83
  543. package/src/rbac/secureClient.ts +8 -2
  544. package/src/rbac/security.test.ts +323 -0
  545. package/src/rbac/types/roleManagement.ts +66 -0
  546. package/src/rbac/utils/{__tests__/clientSecurity.test.ts → clientSecurity.test.ts} +4 -4
  547. package/src/rbac/utils/{__tests__/contextValidator.test.ts → contextValidator.test.ts} +4 -4
  548. package/src/rbac/utils/contextValidator.ts +5 -1
  549. package/src/rbac/utils/{__tests__/deep-equal.test.ts → deep-equal.test.ts} +1 -1
  550. package/src/rbac/utils/{__tests__/eventContext.test.ts → eventContext.test.ts} +36 -21
  551. package/src/rbac/utils/eventContext.ts +37 -33
  552. package/src/rbac/utils/fetchPermissionMap.ts +13 -0
  553. package/src/rbac/utils/permissionMapHelpers.ts +34 -0
  554. package/src/rbac/utils/roleManagementRpc.ts +303 -0
  555. package/src/services/{__tests__/AuthService.edge-cases.test.ts → AuthService.edge-cases.test.ts} +19 -19
  556. package/src/services/{__tests__/AuthService.restoreSession.test.ts → AuthService.restoreSession.test.ts} +2 -2
  557. package/src/services/{__tests__/AuthService.test.ts → AuthService.test.ts} +89 -55
  558. package/src/services/AuthService.ts +184 -205
  559. package/src/services/{__tests__/BaseService.edge-cases.test.ts → BaseService.edge-cases.test.ts} +3 -3
  560. package/src/services/{__tests__/BaseService.test.ts → BaseService.test.ts} +2 -2
  561. package/src/services/{__tests__/EventService.edge-cases.test.ts → EventService.edge-cases.test.ts} +27 -24
  562. package/src/services/{__tests__/EventService.eventColours.test.ts → EventService.eventColours.test.ts} +1 -1
  563. package/src/services/{__tests__/EventService.test.ts → EventService.test.ts} +256 -24
  564. package/src/services/EventService.ts +242 -312
  565. package/src/services/{__tests__/InactivityService.edge-cases.test.ts → InactivityService.edge-cases.test.ts} +3 -3
  566. package/src/services/{__tests__/InactivityService.lifecycle.test.ts → InactivityService.lifecycle.test.ts} +2 -2
  567. package/src/services/{__tests__/InactivityService.test.ts → InactivityService.test.ts} +179 -4
  568. package/src/services/InactivityService.ts +172 -213
  569. package/src/services/{__tests__/OrganisationService.edge-cases.test.ts → OrganisationService.edge-cases.test.ts} +5 -5
  570. package/src/services/{__tests__/OrganisationService.pagination.test.ts → OrganisationService.pagination.test.ts} +4 -4
  571. package/src/services/{__tests__/OrganisationService.test.ts → OrganisationService.test.ts} +410 -7
  572. package/src/services/OrganisationService.ts +184 -238
  573. package/src/services/base/BaseService.test.ts +1 -1
  574. package/src/services/interfaces/{__tests__/IAuthService.test.ts → IAuthService.test.ts} +21 -27
  575. package/src/services/interfaces/IAuthService.ts +10 -9
  576. package/src/services/interfaces/{__tests__/IEventService.test.ts → IEventService.test.ts} +4 -4
  577. package/src/services/interfaces/{__tests__/IInactivityService.test.ts → IInactivityService.test.ts} +3 -3
  578. package/src/services/interfaces/{__tests__/IOrganisationService.test.ts → IOrganisationService.test.ts} +3 -3
  579. package/src/styles/core.css +243 -12
  580. package/src/theming/{__tests__/parseEventColours.test.ts → parseEventColours.test.ts} +1 -1
  581. package/src/theming/{__tests__/runtime.test.ts → runtime.test.ts} +8 -17
  582. package/src/theming/runtime.ts +71 -2
  583. package/src/types/api-result.ts +53 -0
  584. package/src/types/{__tests__/core.test.ts → core.test.ts} +2 -2
  585. package/src/types/{__tests__/database-generated.test.ts → database-generated.test.ts} +3 -3
  586. package/src/types/database.generated.ts +45 -10
  587. package/src/types/event.ts +38 -18
  588. package/src/types/{__tests__/file-reference.test.ts → file-reference.test.ts} +13 -13
  589. package/src/types/file-reference.ts +37 -12
  590. package/src/types/{__tests__/guards.test.ts → guards.test.ts} +2 -2
  591. package/src/types/{__tests__/index.test.ts → index.test.ts} +2 -2
  592. package/src/types/index.ts +3 -0
  593. package/src/types/{__tests__/organisation.roles.test.ts → organisation.roles.test.ts} +1 -1
  594. package/src/types/{__tests__/organisation.test.ts → organisation.test.ts} +3 -31
  595. package/src/types/organisation.ts +15 -15
  596. package/src/types/supabase.ts +13 -4
  597. package/src/types/{__tests__/theme.test.ts → theme.test.ts} +1 -1
  598. package/src/types/{__tests__/type-validation.test.ts → type-validation.test.ts} +1 -1
  599. package/src/types/{__tests__/validation.test.ts → validation.test.ts} +2 -2
  600. package/src/utils/app/appIdResolver.test.ts +98 -71
  601. package/src/utils/app/appIdResolver.ts +31 -20
  602. package/src/utils/{__tests__/appConfig.unit.test.ts → appConfig.unit.test.ts} +1 -1
  603. package/src/utils/{__tests__/audit.unit.test.ts → audit.unit.test.ts} +1 -1
  604. package/src/utils/{__tests__/auth-utils.unit.test.ts → auth-utils.unit.test.ts} +16 -17
  605. package/src/utils/{__tests__/bundleAnalysis.unit.test.ts → bundleAnalysis.unit.test.ts} +35 -35
  606. package/src/utils/{__tests__/cn.unit.test.ts → cn.unit.test.ts} +1 -1
  607. package/src/utils/context/organisationContext.test.ts +105 -91
  608. package/src/utils/context/organisationContext.ts +29 -40
  609. package/src/utils/core/{__tests__/cn.test.ts → cn.test.ts} +3 -3
  610. package/src/utils/core/{__tests__/debugLogger.test.ts → debugLogger.test.ts} +2 -2
  611. package/src/utils/core/{__tests__/logger.test.ts → logger.test.ts} +2 -2
  612. package/src/utils/core/mergeRefs.ts +24 -0
  613. package/src/utils/{__tests__/debugLogger.test.ts → debugLogger.test.ts} +1 -1
  614. package/src/utils/{__tests__/deviceFingerprint.unit.test.ts → deviceFingerprint.unit.test.ts} +1 -1
  615. package/src/utils/dynamic/createLazyComponent.tsx +9 -1
  616. package/src/utils/dynamic/{__tests__/dynamicUtils.test.ts → dynamicUtils.test.ts} +2 -2
  617. package/src/utils/dynamic/{__tests__/lazyLoad.test.tsx → lazyLoad.test.tsx} +2 -2
  618. package/src/utils/{__tests__/dynamicUtils.unit.test.ts → dynamicUtils.unit.test.ts} +1 -1
  619. package/src/utils/file-reference/{__tests__/file-reference.test.ts → file-reference.test.ts} +214 -289
  620. package/src/utils/file-reference/index.ts +330 -347
  621. package/src/utils/{__tests__/formatDate.unit.test.ts → formatDate.unit.test.ts} +2 -2
  622. package/src/utils/formatting/formatDateTimeTimezone.test.ts +1 -1
  623. package/src/utils/formatting/formatNumber.test.ts +1 -1
  624. package/src/utils/{__tests__/formatting.unit.test.ts → formatting.unit.test.ts} +1 -1
  625. package/src/utils/google-places/googlePlacesUtils.test.ts +70 -48
  626. package/src/utils/google-places/googlePlacesUtils.ts +67 -99
  627. package/src/utils/google-places/loadGoogleMapsScript.test.ts +25 -22
  628. package/src/utils/google-places/loadGoogleMapsScript.ts +138 -117
  629. package/src/utils/{__tests__/index.unit.test.ts → index.unit.test.ts} +1 -1
  630. package/src/utils/{__tests__/lazyLoad.unit.test.tsx → lazyLoad.unit.test.tsx} +13 -14
  631. package/src/utils/location/location.test.ts +1 -1
  632. package/src/utils/{__tests__/logger.unit.test.ts → logger.unit.test.ts} +1 -1
  633. package/src/utils/{__tests__/organisationContext.unit.test.ts → organisationContext.unit.test.ts} +37 -48
  634. package/src/utils/performance/{__tests__/bundleAnalysis.test.ts → bundleAnalysis.test.ts} +2 -2
  635. package/src/utils/performance/{__tests__/performanceBenchmark.test.ts → performanceBenchmark.test.ts} +2 -2
  636. package/src/utils/performance/{__tests__/performanceBudgets.test.ts → performanceBudgets.test.ts} +2 -2
  637. package/src/utils/{__tests__/performanceBenchmark.test.ts → performanceBenchmark.test.ts} +2 -2
  638. package/src/utils/{__tests__/performanceBudgets.unit.test.ts → performanceBudgets.unit.test.ts} +2 -2
  639. package/src/utils/{__tests__/permissionTypes.unit.test.ts → permissionTypes.unit.test.ts} +1 -1
  640. package/src/utils/{__tests__/permissionUtils.unit.test.ts → permissionUtils.unit.test.ts} +1 -1
  641. package/src/utils/permissions/{__tests__/permissionTypes.test.ts → permissionTypes.test.ts} +2 -2
  642. package/src/utils/persistence/{__tests__/keyDerivation.test.ts → keyDerivation.test.ts} +2 -2
  643. package/src/utils/persistence/{__tests__/sensitiveFieldDetection.test.ts → sensitiveFieldDetection.test.ts} +2 -2
  644. package/src/utils/{__tests__/request-deduplication.test.ts → request-deduplication.test.ts} +2 -2
  645. package/src/utils/{__tests__/sanitization.unit.test.ts → sanitization.unit.test.ts} +1 -1
  646. package/src/utils/{__tests__/schemaUtils.unit.test.ts → schemaUtils.unit.test.ts} +1 -1
  647. package/src/utils/{__tests__/secureDataAccess.unit.test.ts → secureDataAccess.unit.test.ts} +2 -2
  648. package/src/utils/{__tests__/secureErrors.unit.test.ts → secureErrors.unit.test.ts} +4 -4
  649. package/src/utils/{__tests__/secureStorage.unit.test.ts → secureStorage.unit.test.ts} +1 -1
  650. package/src/utils/security/auth-utils.ts +34 -23
  651. package/src/utils/security/secureDataAccess.ts +241 -281
  652. package/src/utils/security/secureErrors.test.ts +1 -1
  653. package/src/utils/security/secureStorage.test.ts +1 -1
  654. package/src/utils/security/security.test.ts +25 -17
  655. package/src/utils/security/security.ts +15 -18
  656. package/src/utils/security/securityMonitor.test.ts +1 -1
  657. package/src/utils/{__tests__/security.unit.test.ts → security.unit.test.ts} +21 -15
  658. package/src/utils/{__tests__/securityMonitor.unit.test.ts → securityMonitor.unit.test.ts} +1 -1
  659. package/src/utils/{__tests__/sessionTracking.unit.test.ts → sessionTracking.unit.test.ts} +12 -12
  660. package/src/utils/storage/{__tests__/config.unit.test.ts → config.unit.test.ts} +2 -2
  661. package/src/utils/storage/helpers.test.ts +88 -102
  662. package/src/utils/storage/helpers.ts +173 -251
  663. package/src/utils/storage/{__tests__/index.unit.test.ts → index.unit.test.ts} +3 -3
  664. package/src/utils/storage/types.ts +7 -0
  665. package/src/utils/supabase/createBaseClient.test.ts +1 -1
  666. package/src/utils/timezone/timezone.test.ts +1 -1
  667. package/src/utils/{__tests__/timezone.test.ts → timezone.test.ts} +2 -2
  668. package/src/utils/validation/{__tests__/common.test.ts → common.test.ts} +2 -2
  669. package/src/utils/validation/{__tests__/csrf.test.ts → csrf.test.ts} +56 -28
  670. package/src/utils/validation/csrf.ts +42 -41
  671. package/src/utils/validation/{__tests__/htmlSanitization.unit.test.ts → htmlSanitization.unit.test.ts} +2 -2
  672. package/src/utils/validation/{__tests__/passwordSchema.test.ts → passwordSchema.test.ts} +2 -2
  673. package/src/utils/validation/{__tests__/schema.test.ts → schema.test.ts} +2 -2
  674. package/src/utils/validation/{__tests__/sqlInjectionProtection.test.ts → sqlInjectionProtection.test.ts} +2 -2
  675. package/src/utils/validation/{__tests__/user.test.ts → user.test.ts} +2 -2
  676. package/src/utils/validation/{__tests__/validation.test.ts → validation.test.ts} +2 -2
  677. package/src/utils/validation/{__tests__/validationUtils.test.ts → validationUtils.test.ts} +2 -2
  678. package/src/utils/{__tests__/validation.unit.test.ts → validation.unit.test.ts} +1 -1
  679. package/src/utils/{__tests__/validationUtils.unit.test.ts → validationUtils.unit.test.ts} +5 -2
  680. package/dist/UnifiedAuthProvider-BBD2PS3Q.js +0 -7
  681. package/dist/chunk-KPYQWGFQ.js +0 -183
  682. package/dist/types-D05dCGma.d.ts +0 -521
  683. package/scripts/eslint-audit.cjs +0 -222
  684. package/scripts/generate-docs.js +0 -157
  685. package/scripts/install-cursor-rules.cjs +0 -255
  686. package/scripts/install-eslint-config.cjs +0 -349
  687. package/scripts/setup-build-cache.js +0 -73
  688. package/scripts/validate-pre-publish.js +0 -145
  689. package/src/__tests__/integration/UserProfile.test.tsx +0 -124
  690. package/src/__tests__/public-recipe-view.test.ts +0 -228
  691. package/src/__tests__/rls-policies.test.ts +0 -472
  692. package/src/components/DataTable/__tests__/DataTable.test.tsx +0 -876
  693. package/src/components/DataTable/components/DataTableLayout.tsx +0 -584
  694. package/src/components/DataTable/components/UnifiedTableBody.tsx +0 -395
  695. package/src/components/DataTable/components/__tests__/DataTableLayout.test.tsx +0 -467
  696. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +0 -358
  697. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +0 -957
  698. package/src/components/DataTable/core/ActionManager.ts +0 -235
  699. package/src/components/DataTable/core/ColumnManager.ts +0 -204
  700. package/src/components/DataTable/core/DataManager.ts +0 -190
  701. package/src/components/DataTable/core/LocalDataAdapter.ts +0 -274
  702. package/src/components/DataTable/core/PluginRegistry.ts +0 -229
  703. package/src/components/DataTable/core/StateManager.ts +0 -312
  704. package/src/components/DataTable/core/__tests__/ActionManager.test.ts +0 -235
  705. package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +0 -141
  706. package/src/components/DataTable/core/__tests__/DataManager.test.ts +0 -178
  707. package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +0 -133
  708. package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +0 -142
  709. package/src/components/DataTable/core/__tests__/StateManager.test.ts +0 -158
  710. package/src/components/DataTable/core/interfaces.ts +0 -338
  711. package/src/components/DataTable/types.ts +0 -764
  712. package/src/hooks/public/usePublicFileDisplay.ts +0 -534
  713. package/src/hooks/useFileDisplay.ts +0 -748
  714. package/src/providers/OrganisationProvider.test.tsx +0 -40
  715. package/src/providers/OrganisationProvider.tsx +0 -92
  716. package/src/providers/__tests__/InactivityProvider.test-helper.tsx +0 -65
  717. package/src/providers/__tests__/OrganisationProvider.test.tsx +0 -616
  718. package/src/providers/__tests__/OrganisationProvider.wrapper.test.tsx +0 -591
  719. package/src/rbac/__tests__/cache-invalidation.test.ts +0 -393
  720. /package/src/components/DataTable/{components/__tests__ → ui}/COVERAGE_NOTE.md +0 -0
  721. /package/src/components/DataTable/utils/{__tests__/COVERAGE_NOTE.md → COVERAGE_NOTE.md} +0 -0
  722. /package/src/hooks/{__tests__/useApiFetch.unit.test.ts → useApiFetch.unit.test.ts} +0 -0
  723. /package/src/providers/{__tests__/README.md → README.md} +0 -0
  724. /package/src/rbac/{__tests__/index.test.ts → index.test.ts} +0 -0
  725. /package/src/rbac/{__tests__/rbac-integration.test.ts → rbac-integration.test.ts} +0 -0
  726. /package/src/types/{__tests__/README.md → README.md} +0 -0
@@ -8,37 +8,23 @@
8
8
  * This is the main component that consumers will use.
9
9
  */
10
10
 
11
- import React, { useMemo, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
12
- import { useReactTable, type SortingState } from '@tanstack/react-table';
11
+ import React, { useMemo, useCallback } from 'react';
12
+ import type { SortingState } from '@tanstack/react-table';
13
13
  import { useLocation } from 'react-router-dom';
14
- import { Edit, Trash2 } from 'lucide-react';
15
- import { useDataTablePerformance } from '../../../hooks/useDataTablePerformance';
16
- import { LoadingState } from './LoadingState';
17
- import { DataTableLayout } from './DataTableLayout';
18
- import { DataTableErrorBoundary } from './DataTableErrorBoundary';
19
- import { useColumnOrderPersistence } from '../hooks/useColumnOrderPersistence';
20
- import { useColumnVisibilityPersistence } from '../hooks/useColumnVisibilityPersistence';
21
- import { useDataTableState } from '../hooks/useDataTableState';
22
- import { useDataTableDataPipeline } from '../hooks/useDataTableDataPipeline';
23
- import { useServerSideDataEffect } from '../hooks/useServerSideDataEffect';
24
- import { useEffectiveColumnOrder } from '../hooks/useEffectiveColumnOrder';
25
- import { useTableHandlers, type TableStateSnapshot } from '../hooks/useTableHandlers';
26
- import { useDataTableConfiguration } from '../hooks/useDataTableConfiguration';
27
- import { AccessDeniedPage } from './AccessDeniedPage';
28
- // NOTE: All toast() calls in this component use the default timeout (5 seconds).
29
- // Do NOT set duration or timeout properties - let the toast system use its default.
30
- import { toast } from '../../../hooks/useToast';
14
+ import { useIsPrint } from '../../../hooks/useIsPrint';
15
+ import { LoadingState } from '../ui/table/LoadingState';
16
+ import { DataTableLayout } from '../ui/layout/DataTableLayout';
17
+ import { DataTableErrorBoundary } from '../ui/layout/DataTableErrorBoundary';
18
+ import { useDataTableDeletionBatching } from '../hooks/useDataTableDeletionBatching';
19
+ import { useDataTableStateAndPersistence } from '../hooks/useDataTableStateAndPersistence';
20
+ import { useDataTablePipeline } from '../hooks/useDataTablePipeline';
21
+ import { useDataTableRenderGuard } from '../hooks/useDataTableRenderGuard';
22
+ import { AccessDeniedPage } from '../ui/shared/AccessDeniedPage';
31
23
  import { useUnifiedAuth } from '../../../providers/services/UnifiedAuthProvider';
32
24
  import { useDataTablePermissions } from '../hooks/useDataTablePermissions';
33
- import { useTableColumns } from '../hooks/useTableColumns';
34
- import { initializeLiveRegion } from '../utils/a11yUtils';
35
- import { useKeyboardNavigation } from '../hooks/useKeyboardNavigation';
36
25
  import { getRowIdSafe } from '../utils/rowUtils';
37
26
  import { createLogger } from '../../../utils/core/logger';
38
- import { usePermissionTracking } from './hooks/usePermissionTracking';
39
- import { useImportModalFocus } from './hooks/useImportModalFocus';
40
- import { toCellValueRecord } from './cellValueUtils';
41
-
27
+ import { usePermissionTracking } from '../hooks/usePermissionTracking';
42
28
  import {
43
29
  normalizeDataTableFeatures,
44
30
  type DataRecord,
@@ -106,7 +92,7 @@ export interface DataTableCoreProps<TData extends DataRecord> {
106
92
  onEditRow?: (row: TData, data: Partial<TData>) => void;
107
93
  onDeleteRow?: (row: TData) => void;
108
94
  onCreateRow?: (data: Partial<TData>) => void;
109
- onImport?: (data: TData[]) => void | Promise<void>;
95
+ onImport?: (data: TData[]) => import('../ui/modals/importModalPersistence').ImportHandlerResult;
110
96
  onExport?: (options: import('../types').ExportOptions<TData>) => void | Promise<void>;
111
97
  onRowSelectionChange?: (selection: Record<string, boolean>) => void;
112
98
  selection?: Record<string, boolean>;
@@ -118,6 +104,8 @@ export interface DataTableCoreProps<TData extends DataRecord> {
118
104
  emptyState?: EmptyStateConfig | React.ReactElement;
119
105
  aggregates?: AggregateConfig[];
120
106
  importModalConfig?: ImportModalConfig;
107
+ /** Called when the import modal is closed (e.g. to refetch table data). */
108
+ onImportModalClose?: () => void;
121
109
  actions?: DataTableAction<TData>[];
122
110
  columnOrder?: string[];
123
111
 
@@ -170,6 +158,7 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
170
158
  emptyState,
171
159
  aggregates = [],
172
160
  importModalConfig,
161
+ onImportModalClose,
173
162
  actions = [],
174
163
  columnOrder: externalColumnOrder,
175
164
  defaultGrouping,
@@ -180,85 +169,26 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
180
169
  } = props;
181
170
 
182
171
  const logger = createLogger('DataTableCore');
183
-
184
- // Track deletion state to batch updates and prevent flashing
185
- const [isDeleting, setIsDeleting] = useState(false);
186
- const deletionTimeoutRef = useRef<NodeJS.Timeout | null>(null);
187
- const pendingDeletionsRef = useRef<Set<string>>(new Set());
188
- const dataSnapshotRef = useRef<TData[]>(data);
189
- const dataChangeCountRef = useRef(0);
190
- const previousDataLengthRef = useRef(data.length);
191
-
192
- // CRITICAL: Use useLayoutEffect to capture previous length synchronously
193
- // This runs before paint, so we can detect changes before React commits
194
- useLayoutEffect(() => {
195
- const currentLength = data.length;
196
- const previousLength = previousDataLengthRef.current;
197
-
198
- // Check if length decreased - this is the key detection
199
- const lengthDecreased = currentLength < previousLength;
200
-
201
- if (lengthDecreased && !isDeleting) {
202
- // Data length decreased - start deletion batching
203
- const snapshotLength = dataSnapshotRef.current.length;
204
-
205
- // Data decrease detected (logging removed for performance)
206
-
207
- // If snapshot is larger, it's valid (has pre-deletion state)
208
- // Otherwise, update it to preserve current state
209
- if (snapshotLength <= currentLength) {
210
- dataSnapshotRef.current = [...data];
211
- }
212
-
213
- setIsDeleting(true);
214
-
215
- // Set timeout to reset after batching
216
- if (deletionTimeoutRef.current) {
217
- clearTimeout(deletionTimeoutRef.current);
218
- }
219
- deletionTimeoutRef.current = setTimeout(() => {
220
- setIsDeleting(false);
221
- dataSnapshotRef.current = data;
222
- previousDataLengthRef.current = data.length;
223
- // Batching complete (logging removed for performance)
224
- }, 150);
225
-
226
- // Update previous length AFTER setting up batching
227
- previousDataLengthRef.current = currentLength;
228
- } else if (!lengthDecreased && !isDeleting) {
229
- // No deletion - update snapshot and previous length normally
230
- dataSnapshotRef.current = data;
231
- previousDataLengthRef.current = currentLength;
232
- } else if (isDeleting) {
233
- // Already deleting - just update previous length for next comparison
234
- previousDataLengthRef.current = currentLength;
235
- }
236
- }, [data, isDeleting]);
237
-
238
- // Keep data snapshot stable during deletions to prevent flashing
239
- // Logging effect - runs after layout to show what happened
240
- useEffect(() => {
241
- dataChangeCountRef.current += 1;
242
-
243
- // Data prop changed (logging removed for performance)
244
- }, [data, isDeleting]);
245
-
246
- // Use snapshot data during deletions to prevent flashing
247
- // CRITICAL: If we're deleting and data length decreased, use snapshot to prevent flashing
248
- const dataLengthChanged = data.length !== dataSnapshotRef.current.length;
249
- const isDataDecreasing = data.length < dataSnapshotRef.current.length;
250
- const effectiveData = (isDeleting && isDataDecreasing) ? dataSnapshotRef.current : data;
251
-
252
- // Effective data tracking (logging removed for performance)
253
-
172
+
173
+ const { effectiveData, isDeleting, setIsDeleting, deletionRefs } =
174
+ useDataTableDeletionBatching<TData>(data);
175
+
254
176
  // ============================================================================
255
177
  // ALL HOOKS MUST BE CALLED IN THE SAME ORDER EVERY RENDER
178
+ // Responsibilities: useDataTableDeletionBatching (in-flight delete UI),
179
+ // useUnifiedAuth (user), useIsPrint (print), useDataTablePermissions (RBAC),
180
+ // usePermissionTracking (timeout/loading), useDataTableStateAndPersistence (state +
181
+ // column order/visibility), useDataTablePipeline (table instance + layout props),
182
+ // useDataTableRenderGuard (loading / access-denied gate).
256
183
  // ============================================================================
257
-
184
+
258
185
  // MANDATORY: Get authenticated user - ALWAYS call this hook
259
186
  const authResult = useUnifiedAuth();
260
187
  const user = authResult.user;
261
188
 
189
+ // Detect print mode to disable pagination when printing
190
+ const isPrint = useIsPrint();
191
+
262
192
  const requestedFeatures = useMemo<NormalizedDataTableFeatureConfig>(() =>
263
193
  normalizeDataTableFeatures(incomingFeatures),
264
194
  [incomingFeatures]);
@@ -278,807 +208,117 @@ function DataTableInternal<TData extends DataRecord>(props: DataTableCoreProps<T
278
208
 
279
209
 
280
210
  // ============================================================================
281
- // UNIFIED STATE MANAGEMENT - Use ONLY useDataTableState for all state
211
+ // STATE AND PERSISTENCE - column order/visibility, core state, sync effects
282
212
  // ============================================================================
283
-
284
- const effectiveColumnOrder = useEffectiveColumnOrder({
213
+
214
+ const location = useLocation();
215
+
216
+ const {
217
+ state,
218
+ stateActions,
219
+ rowSelection,
220
+ effectiveColumnOrder,
221
+ updateSavedColumnVisibility,
222
+ updateColumnOrder,
223
+ } = useDataTableStateAndPersistence<TData>({
285
224
  columns,
286
225
  externalColumnOrder,
287
- selectionEnabled: secureFeatures.selection,
288
- });
289
-
290
- // ============================================================================
291
- // COLUMN VISIBILITY PERSISTENCE - ALWAYS call these hooks
292
- // ============================================================================
293
-
294
- const {
295
- columnVisibility: savedColumnVisibility,
296
- isLoaded: isColumnVisibilityLoaded,
297
- updateColumnVisibility: updateSavedColumnVisibility,
298
- } = useColumnVisibilityPersistence({
299
- tableId: title ? `datatable-${title.toLowerCase().replace(/\s+/g, '-')}` : undefined,
300
- defaultVisibility: initialColumnVisibilityProp || {},
301
- enablePersistence: secureFeatures.columnVisibility,
226
+ title,
227
+ initialColumnVisibility: initialColumnVisibilityProp || {},
302
228
  storageKey,
303
- });
304
-
305
- // Merge saved column visibility into initial state (saved visibility takes precedence over prop)
306
- const initialColumnVisibility = useMemo(() => {
307
- if (secureFeatures.columnVisibility && savedColumnVisibility && Object.keys(savedColumnVisibility).length > 0) {
308
- return savedColumnVisibility;
309
- }
310
- // Fall back to prop if no saved visibility
311
- return initialColumnVisibilityProp || {};
312
- }, [secureFeatures.columnVisibility, savedColumnVisibility, initialColumnVisibilityProp]);
313
-
314
- // Get location for route-based key derivation
315
- let location: { pathname: string } | null = null;
316
- try {
317
- // useLocation may not be available if React Router is not set up
318
- const routerLocation = useLocation();
319
- location = routerLocation;
320
- } catch {
321
- // Router not available, location will be null
322
- location = null;
323
- }
324
-
325
- // Extract column field names for sensitive field filtering
326
- const columnFieldNames = useMemo(() => {
327
- return columns
328
- .map((col) => col.accessorKey || col.id)
329
- .filter((key): key is string => Boolean(key));
330
- }, [columns]);
331
-
332
- // Use the centralized state management hook for ALL table state
333
- // Note: 'actions' prop parameter is shadowed by destructuring, so we rename to stateActions
334
- const { state, actions: stateActions } = useDataTableState<TData>({
229
+ secureFeatures: {
230
+ columnVisibility: secureFeatures.columnVisibility,
231
+ columnReordering: secureFeatures.columnReordering,
232
+ selection: secureFeatures.selection,
233
+ },
234
+ rbacPageId: rbac.pageId,
335
235
  initialPageSize,
336
- columnIds: effectiveColumnOrder,
337
- initialRowSelection: selection || {},
236
+ selection,
338
237
  onRowSelectionChange,
339
238
  defaultSorting: defaultSorting || [],
340
239
  defaultGrouping: defaultGrouping || [],
341
- // Persistence props
342
- rbacPageId: rbac.pageId,
343
- title,
344
240
  location,
345
- columnFieldNames,
346
241
  });
347
-
348
- // Apply saved visibility to state if available
349
- useEffect(() => {
350
- if (secureFeatures.columnVisibility && isColumnVisibilityLoaded && Object.keys(initialColumnVisibility).length > 0) {
351
- stateActions.setColumnVisibility(initialColumnVisibility);
352
- }
353
- }, [secureFeatures.columnVisibility, isColumnVisibilityLoaded, initialColumnVisibility, stateActions]);
354
-
355
- // Initialize live region for accessibility announcements
356
- useEffect(() => {
357
- initializeLiveRegion();
358
- }, []);
359
-
360
- // Row selection: prefer controlled prop, fall back to state
361
- const rowSelection = selection !== undefined ? selection : state.rowSelection;
362
242
 
363
243
  const resolvedGetRowId = useCallback(
364
244
  (row: TData, index: number) => getRowIdSafe(row, index, getRowId),
365
245
  [getRowId]
366
246
  );
367
-
368
- // ============================================================================
369
- // AUTO-EXPAND GROUPS WHEN DEFAULT GROUPING IS PROVIDED
370
- // ============================================================================
371
-
372
- // Track if we've already initialized expansion state
373
- const hasInitializedExpansion = useRef(false);
374
-
375
- // Auto-expand all groups when default grouping is provided (only once on mount)
376
- useEffect(() => {
377
- if (!hasInitializedExpansion.current && defaultGrouping && defaultGrouping.length > 0) {
378
- stateActions.setExpanded(true); // Expand all groups
379
- hasInitializedExpansion.current = true;
380
- }
381
- }, [defaultGrouping, stateActions]);
382
247
 
383
- // Extract commonly used state values (only what's needed)
384
- // Note: Most state values are accessed via state object directly when needed
385
-
386
- // ============================================================================
387
- // PERFORMANCE HOOK - ALWAYS call this hook
388
- // ============================================================================
389
-
390
- const performanceHook = useDataTablePerformance({
248
+ const pipeline = useDataTablePipeline<TData>({
391
249
  data,
250
+ effectiveData,
251
+ columns,
252
+ state,
253
+ stateActions,
254
+ rowSelection,
255
+ effectiveColumnOrder,
256
+ updateSavedColumnVisibility,
257
+ updateColumnOrder,
258
+ secureFeatures,
259
+ permissions,
260
+ deletionRefs,
261
+ isDeleting,
262
+ setIsDeleting,
263
+ onCreateRow,
264
+ onEditRow,
265
+ onDeleteRow,
266
+ onImport,
267
+ onExport,
268
+ onDeleteSelected,
269
+ actions,
270
+ resolvedGetRowId,
392
271
  performance,
393
272
  serverSide,
394
273
  chunking,
395
274
  searchIndex,
396
- });
397
-
398
- const {
399
- paginationMode: detectedMode,
400
- pageSizeOptions: optimizedPageSizeOptions,
401
- isLoading: performanceLoading,
402
- searchQuery,
403
- setSearchQuery,
404
- fetchServerData,
405
- serverData,
406
- cleanup,
407
- } = performanceHook;
408
-
409
- const finalPaginationMode = paginationMode || detectedMode;
410
-
411
- // ============================================================================
412
- // KEYBOARD NAVIGATION - ALWAYS call this hook
413
- // ============================================================================
414
-
415
- const keyboardNavigation = useKeyboardNavigation(
416
- data.length,
417
- columns.length,
418
- {
419
- enabled: true,
420
- announceNavigation: true,
421
- supportsColumnReorder: secureFeatures.columnReordering,
422
- supportsColumnResize: false, // Column resizing is not currently supported
423
- }
424
- );
425
-
426
- const { lastFocusedElementRef } = useImportModalFocus(state.showImportModal, keyboardNavigation);
427
-
428
- // ============================================================================
429
- // HIERARCHICAL DATA VALIDATION AND PROCESSING - ALWAYS call these hooks
430
- // ============================================================================
431
-
432
- const {
433
- finalTableData,
434
- dataCount: finalDataCount,
435
- hierarchicalState,
436
- hierarchicalValidation,
437
- } = useDataTableDataPipeline<TData>({
438
- data: effectiveData,
439
- features: secureFeatures,
275
+ paginationMode,
276
+ initialPageSize,
277
+ externalIsLoading,
278
+ isPrint,
279
+ effectivePageId,
280
+ logger,
281
+ title,
282
+ description,
283
+ variant,
284
+ className,
285
+ emptyState,
286
+ virtualHeight,
440
287
  hierarchical,
441
- sorting: state.sorting,
442
- finalPaginationMode,
443
- serverData,
444
- });
445
-
446
- useEffect(() => {
447
- if (!hierarchicalValidation.isValid) {
448
- logger.error('Hierarchical data validation failed:', hierarchicalValidation.errors);
449
- }
450
- }, [hierarchicalValidation, logger]);
451
-
452
- // Final table data tracking (logging removed for performance)
453
-
454
- // Cleanup deletion timeout on unmount
455
- useEffect(() => {
456
- const pendingDeletions = pendingDeletionsRef.current;
457
- return () => {
458
- if (deletionTimeoutRef.current) {
459
- clearTimeout(deletionTimeoutRef.current);
460
- }
461
- pendingDeletions.clear();
462
- };
463
- }, []);
464
-
465
- // Update data snapshot when data changes and we're not deleting
466
- useEffect(() => {
467
- if (!isDeleting && data !== dataSnapshotRef.current) {
468
- dataSnapshotRef.current = data;
469
- }
470
- }, [data, isDeleting]);
471
-
472
- // Cleanup deletion timeout on unmount
473
- useEffect(() => {
474
- return () => {
475
- if (deletionTimeoutRef.current) {
476
- clearTimeout(deletionTimeoutRef.current);
477
- }
478
- };
479
- }, []);
480
-
481
- const {
482
- columnOrder: savedColumnOrder,
483
- isLoaded: isColumnOrderLoaded,
484
- updateColumnOrder,
485
- } = useColumnOrderPersistence({
486
- tableId: title ? `datatable-${title.toLowerCase().replace(/\s+/g, '-')}` : undefined,
487
- defaultOrder: columns.map(col => col.id || col.accessorKey || ''),
488
- enablePersistence: secureFeatures.columnReordering,
288
+ aggregates,
289
+ importModalConfig,
290
+ onImportModalClose,
489
291
  storageKey,
490
- });
491
-
492
- useEffect(() => {
493
- if (
494
- secureFeatures.columnReordering &&
495
- isColumnOrderLoaded &&
496
- savedColumnOrder &&
497
- savedColumnOrder.length > 0
498
- ) {
499
- // Normalize: ensure 'select' is first if selection is enabled
500
- const normalizedOrder = secureFeatures.selection && savedColumnOrder.includes('select')
501
- ? ['select', ...savedColumnOrder.filter(id => id !== 'select')]
502
- : savedColumnOrder;
503
- stateActions.setColumnOrder(normalizedOrder);
504
- }
505
- }, [secureFeatures.columnReordering, secureFeatures.selection, isColumnOrderLoaded, savedColumnOrder, stateActions]);
506
-
507
- // CRITICAL: Always ensure state.columnOrder has 'select' first if selection is enabled
508
- // This fixes any state that gets out of sync (e.g., from localStorage or user reordering)
509
- useEffect(() => {
510
- if (secureFeatures.selection && state.columnOrder.includes('select') && state.columnOrder[0] !== 'select') {
511
- const normalizedOrder = ['select', ...state.columnOrder.filter(id => id !== 'select')];
512
- stateActions.setColumnOrder(normalizedOrder);
513
- // Also update persisted order if persistence is enabled
514
- if (secureFeatures.columnReordering) {
515
- updateColumnOrder(normalizedOrder);
516
- }
517
- }
518
- }, [secureFeatures.selection, secureFeatures.columnReordering, state.columnOrder, stateActions, updateColumnOrder]);
519
-
520
- // ============================================================================
521
- // CONFIGURATION RESOLUTION - ALWAYS call these hooks
522
- // ============================================================================
523
-
524
- const finalPageSizeOptions = optimizedPageSizeOptions;
525
-
526
- const validatedInitialPageSize = useMemo(() => {
527
- if (!secureFeatures.pagination || !finalPageSizeOptions.length) {
528
- return initialPageSize;
529
- }
530
-
531
- if (finalPageSizeOptions.includes(initialPageSize)) {
532
- return initialPageSize;
533
- }
534
-
535
- const sortedOptions = [...finalPageSizeOptions].sort((a, b) => a - b);
536
- const closestOption = sortedOptions.reduce((prev, curr) =>
537
- Math.abs(curr - initialPageSize) < Math.abs(prev - initialPageSize) ? curr : prev
538
- );
539
-
540
- logger.warn(
541
- `initialPageSize ${initialPageSize} is not available in page size options [${finalPageSizeOptions.join(', ')}]. Using closest option: ${closestOption}`
542
- );
543
-
544
- return closestOption;
545
- }, [initialPageSize, finalPageSizeOptions, secureFeatures.pagination, logger]);
546
-
547
- // Determine the effective pageSize to use (validated or current state)
548
- // CRITICAL: This ensures we always pass a valid pageSize to TanStack Table configuration.
549
- // An invalid pageSize can cause getPaginationRowModel() to return empty rows.
550
- const effectivePageSize = useMemo(() => {
551
- if (!secureFeatures.pagination || !finalPageSizeOptions.length) {
552
- return state.pagination.pageSize;
553
- }
554
-
555
- // If current pageSize is invalid (not in options), use validated value immediately
556
- // This is a safety net in case the useEffect hasn't run yet
557
- if (!finalPageSizeOptions.includes(state.pagination.pageSize)) {
558
- return validatedInitialPageSize;
559
- }
560
-
561
- return state.pagination.pageSize;
562
- }, [state.pagination.pageSize, validatedInitialPageSize, secureFeatures.pagination, finalPageSizeOptions]);
563
-
564
- // CRITICAL FIX: Ensure pagination state always uses a valid pageSize and pageIndex
565
- // An invalid pageSize (not in page size options) causes getPaginationRowModel() to return empty rows,
566
- // which manifests as "DataTable shows record count but no rows" bug for large datasets.
567
- // An out-of-bounds pageIndex (beyond available pages) also causes empty rows.
568
- // This fixes the bug where DataTable shows record count but no rows for large datasets.
569
- useEffect(() => {
570
- if (secureFeatures.pagination && finalPageSizeOptions.length > 0) {
571
- const needsFix = !finalPageSizeOptions.includes(state.pagination.pageSize);
572
-
573
- // Also check if pageIndex is out of bounds for the current data
574
- const currentPageSize = effectivePageSize || validatedInitialPageSize;
575
- const totalPages = currentPageSize > 0 ? Math.ceil(finalDataCount / currentPageSize) : 0;
576
- const pageIndexOutOfBounds = totalPages > 0 && state.pagination.pageIndex >= totalPages;
577
-
578
- if (needsFix || pageIndexOutOfBounds) {
579
- // PageSize is invalid OR pageIndex is out of bounds - correct both to prevent empty rows
580
- stateActions.setPagination({
581
- pageSize: validatedInitialPageSize,
582
- pageIndex: 0, // Reset to first page when correcting pagination issues
583
- });
584
- }
585
- }
586
- }, [
587
- secureFeatures.pagination,
588
- finalPageSizeOptions,
589
- state.pagination.pageSize,
590
- state.pagination.pageIndex,
591
- validatedInitialPageSize,
592
- stateActions,
593
- effectivePageSize,
594
- finalDataCount
595
- ]);
596
-
597
- // React 19 fix: Use useMemo to ensure isLoading updates when props change
598
- // This prevents the component from getting stuck in loading state when externalIsLoading
599
- // changes from true to false in React 19 with automatic memoization
600
- const isLoading = useMemo(
601
- () => externalIsLoading || performanceLoading,
602
- [externalIsLoading, performanceLoading]
603
- );
604
-
605
- // ============================================================================
606
- // DATA PROCESSING - ALWAYS call these hooks
607
- // ============================================================================
608
-
609
- // ============================================================================
610
- // ACTIONS PROCESSING - ALWAYS call these hooks
611
- // ============================================================================
612
-
613
- // ============================================================================
614
- // COLUMN PROCESSING - ALWAYS call these hooks
615
- // ============================================================================
616
-
617
- // ============================================================================
618
- // TABLE CONFIGURATION - ALWAYS call these hooks
619
- // ============================================================================
620
-
621
- // ============================================================================
622
- // SEARCH HANDLERS - ALWAYS call these hooks
623
- // ============================================================================
624
-
625
- /**
626
- * Handle search input changes.
627
- *
628
- * When a search query is entered, we reset to the first page to show results
629
- * from the beginning. This provides a better user experience when searching
630
- * large datasets.
631
- *
632
- * CRITICAL: Must sync both state.searchQuery (used by state management) and
633
- * performance hook's searchQuery (used by table filtering and search index).
634
- *
635
- * @param value - The search query string
636
- */
637
- const handleSearch = useCallback((value: string) => {
638
- // Update both search query states to keep them in sync
639
- stateActions.setSearchQuery(value);
640
- setSearchQuery(value);
641
-
642
- // Reset to first page when searching to show results from the beginning
643
- if (secureFeatures.pagination) {
644
- stateActions.setPagination({ ...state.pagination, pageIndex: 0 });
645
- }
646
- }, [stateActions, setSearchQuery, secureFeatures.pagination, state.pagination]);
647
-
648
- // ============================================================================
649
- // SERVER-SIDE DATA FETCHING - ALWAYS call these hooks
650
- // ============================================================================
651
-
652
- /**
653
- * Handle server-side data fetching.
654
- *
655
- * This function is called when any state that affects server-side data changes:
656
- * - pagination (page/size)
657
- * - sorting (column/sort order)
658
- * - filtering (column filters or global search)
659
- * - grouping
660
- *
661
- * It collects all current table state and makes a single request to the server
662
- * with those parameters, allowing for efficient server-side pagination, sorting,
663
- * and filtering without loading all data client-side.
664
- *
665
- * Early return guards prevent unnecessary server calls when:
666
- * - Component is in client-side mode (finalPaginationMode !== 'server')
667
- * - No server-side config is provided (!serverSide)
668
- */
669
- useServerSideDataEffect<TData>({
670
- finalPaginationMode,
671
- serverSide,
672
- pagination: state.pagination,
673
- sorting: state.sorting,
674
- columnFilters: state.columnFilters,
675
- grouping: state.grouping,
676
- searchQuery,
677
- tableDataLength: finalTableData.length,
678
- fetchServerData,
679
- cleanup,
680
- });
681
-
682
- // ============================================================================
683
- // RBAC VALIDATION AND SECURE CONFIGURATION - ALWAYS call these hooks
684
- // ============================================================================
685
-
686
- // Wrap handlers - persistence is handled automatically via useDataTableState
687
- // The state actions (clearCreationData, clearEditing) will automatically update persisted state
688
- const wrappedOnCreateRow = onCreateRow;
689
- const wrappedOnEditRow = onEditRow;
690
-
691
- // MANDATORY: Handlers are automatically secured
692
- const secureHandlers = useMemo(() => {
693
- const handlers = {
694
- onEditRow: permissions.canUpdate.can ? wrappedOnEditRow : undefined,
695
- onDeleteRow: permissions.canDelete.can ? onDeleteRow : undefined,
696
- onCreateRow: permissions.canCreate.can ? wrappedOnCreateRow : undefined,
697
- onImport: permissions.canImport.can ? onImport : undefined,
698
- onExport: permissions.canExport.can ? onExport : undefined,
699
- onDeleteSelected: permissions.canDelete.can ? onDeleteSelected : undefined,
700
- };
701
-
702
- return handlers;
703
- }, [permissions.canUpdate.can, permissions.canDelete.can, permissions.canCreate.can, permissions.canImport.can, permissions.canExport.can, wrappedOnEditRow, onDeleteRow, wrappedOnCreateRow, onImport, onExport, onDeleteSelected]);
704
-
705
- // MANDATORY: Process actions with RBAC checks
706
- const effectiveActions = useMemo(() => {
707
- // Create a new array to avoid mutating the original
708
- const result = [...actions];
709
-
710
- // Add Edit action with RBAC check
711
- if (secureFeatures.editing && secureHandlers.onEditRow && !result.some(a => a.label === 'Edit')) {
712
- result.push({
713
- label: 'Edit',
714
- onClick: (row: TData) => {
715
- if (!permissions.canUpdate.can) {
716
- throw new Error('Insufficient permissions to edit this resource');
717
- }
718
-
719
- const rowIndex = data.findIndex(r => r === row);
720
- const rowId = resolvedGetRowId(row, rowIndex >= 0 ? rowIndex : 0);
721
-
722
- // Set the row into editing mode with the current row data
723
- stateActions.setEditingRow(rowId, toCellValueRecord(row));
724
- },
725
- icon: Edit,
726
- testId: 'edit',
727
- hidden: !permissions.canUpdate.can,
728
- });
729
- }
730
-
731
- // Add Delete action with RBAC check
732
- if (secureFeatures.deletion && secureHandlers.onDeleteRow && !result.some(a => a.label === 'Delete')) {
733
- result.push({
734
- label: 'Delete',
735
- onClick: async (row: TData) => {
736
- if (!permissions.canDelete.can) {
737
- // NOTE: Toast notifications use default timeout (5 seconds) - do not set duration property
738
- toast({
739
- title: "Delete Failed",
740
- description: "Insufficient permissions to delete this resource",
741
- variant: "destructive"
742
- });
743
- return;
744
- }
745
-
746
- // Get row ID for tracking - use current data, not effectiveData
747
- const rowIndex = data.findIndex(r => r === row);
748
- const rowId = resolvedGetRowId(row, rowIndex >= 0 ? rowIndex : 0);
749
-
750
- // Mark deletion as in progress and track this deletion
751
- // CRITICAL: Set isDeleting and snapshot BEFORE calling the handler
752
- // This prevents the parent's data update from causing a flash
753
- if (!isDeleting) {
754
- // Snapshot current data BEFORE deletion
755
- dataSnapshotRef.current = [...data]; // Create a copy to prevent reference issues
756
- setIsDeleting(true);
757
- }
758
- pendingDeletionsRef.current.add(rowId);
759
-
760
- // Clear any existing timeout
761
- if (deletionTimeoutRef.current) {
762
- clearTimeout(deletionTimeoutRef.current);
763
- }
764
-
765
- try {
766
- // Call the delete handler - this may update the parent's data prop
767
- const result = secureHandlers.onDeleteRow!(row);
768
- // Handle async operations (even though typed as void, handlers might return Promise)
769
- if (result !== undefined && result !== null && typeof result === 'object' && 'then' in result && typeof (result as { then: unknown }).then === 'function') {
770
- await (result as Promise<unknown>);
771
- }
772
- // Remove from pending deletions
773
- pendingDeletionsRef.current.delete(rowId);
774
-
775
- // Reset deletion state after a delay to allow batching
776
- // This prevents the table from refreshing after each individual deletion
777
- deletionTimeoutRef.current = setTimeout(() => {
778
- // Only reset if all deletions are complete
779
- if (pendingDeletionsRef.current.size === 0) {
780
- setIsDeleting(false);
781
- // Update snapshot to latest data
782
- dataSnapshotRef.current = data;
783
- toast({
784
- title: "Delete Successful",
785
- description: "Row deleted successfully",
786
- variant: "default"
787
- });
788
- }
789
- }, 150); // Delay to batch rapid deletions
790
-
791
- } catch (error) {
792
- logger.error('Delete error:', error);
793
- pendingDeletionsRef.current.delete(rowId);
794
- if (pendingDeletionsRef.current.size === 0) {
795
- setIsDeleting(false);
796
- dataSnapshotRef.current = data;
797
- }
798
- toast({
799
- title: "Delete Failed",
800
- description: error instanceof Error ? error.message : 'Failed to delete row',
801
- variant: "destructive"
802
- });
803
- }
804
- },
805
- icon: Trash2,
806
- testId: 'delete',
807
- variant: 'destructive' as const,
808
- hidden: !permissions.canDelete.can,
809
- });
810
- }
811
-
812
- return result;
813
- }, [actions, secureFeatures, permissions, secureHandlers, resolvedGetRowId, stateActions, data, isDeleting, logger]);
814
-
815
- // Normalize columnOrder for useTableColumns: ensure 'select' is always first
816
- const normalizedColumnOrderForColumns = useMemo(() => {
817
- if (secureFeatures.selection && state.columnOrder.includes('select')) {
818
- return ['select', ...state.columnOrder.filter(id => id !== 'select')];
819
- }
820
- return state.columnOrder;
821
- }, [state.columnOrder, secureFeatures.selection]);
822
-
823
- // MANDATORY: Process columns with actions
824
- const { enhancedColumns } = useTableColumns({
825
- columns,
826
- features: secureFeatures,
827
- effectiveActions,
828
- columnOrder: normalizedColumnOrderForColumns
829
- });
830
-
831
- // Use effective pageSize in pagination state snapshot to ensure table receives valid pageSize
832
- const paginationStateWithValidatedSize = useMemo(() => ({
833
- ...state.pagination,
834
- pageSize: effectivePageSize,
835
- }), [state.pagination, effectivePageSize]);
836
-
837
- const tableStateSnapshot = useMemo<TableStateSnapshot>(() => {
838
- // Normalize columnOrder in snapshot: ensure 'select' is always first if selection is enabled
839
- const normalizedColumnOrder = secureFeatures.selection && state.columnOrder.includes('select')
840
- ? ['select', ...state.columnOrder.filter(id => id !== 'select')]
841
- : state.columnOrder;
842
-
843
- return {
844
- sorting: state.sorting,
845
- columnFilters: state.columnFilters,
846
- columnVisibility: state.columnVisibility,
847
- rowSelection,
848
- grouping: state.grouping,
849
- expanded: state.expanded,
850
- pagination: paginationStateWithValidatedSize,
851
- globalFilter: searchQuery,
852
- columnOrder: normalizedColumnOrder,
853
- };
854
- }, [
855
- state.sorting,
856
- state.columnFilters,
857
- state.columnVisibility,
858
- rowSelection,
859
- state.grouping,
860
- state.expanded,
861
- paginationStateWithValidatedSize,
862
- searchQuery,
863
- state.columnOrder,
864
- secureFeatures.selection,
865
- ]);
866
-
867
- const tableHandlers = useTableHandlers({
868
- state,
869
- stateSnapshot: tableStateSnapshot,
870
- actions: stateActions,
871
292
  selection,
872
293
  onRowSelectionChange,
873
- effectiveColumnOrder,
874
- canPersistVisibility: secureFeatures.columnVisibility && Boolean(storageKey),
875
- canPersistOrder: secureFeatures.columnReordering && Boolean(storageKey),
876
- updateSavedColumnVisibility,
877
- updateColumnOrder,
294
+ shouldAllowRenderAfterTimeout,
295
+ rbac,
296
+ enhancedPagination,
878
297
  onLayoutChange,
879
298
  });
880
299
 
881
- // PERFORMANCE FIX: If permissions still loading after timeout, filter data to empty array
882
- // This allows table structure to render but keeps data hidden until permissions confirm
883
- // SECURITY: Data remains protected - only table structure (headers) will show
884
- const safeTableData = useMemo(() => {
885
- if (permissions.canRead.isLoading && shouldAllowRenderAfterTimeout) {
886
- // Permissions still loading after timeout - return empty array to hide data
887
- return [] as TData[];
888
- }
889
- if (!permissions.canRead.can) {
890
- // Permissions denied - return empty array
891
- return [] as TData[];
892
- }
893
- // Permissions confirmed - return actual data
894
- return finalTableData as TData[];
895
- }, [finalTableData, permissions.canRead.isLoading, permissions.canRead.can, shouldAllowRenderAfterTimeout]);
896
-
897
- const tableConfig = useDataTableConfiguration({
898
- data: safeTableData,
899
- columns: enhancedColumns,
900
- stateSnapshot: tableStateSnapshot,
901
- handlers: tableHandlers,
902
- features: secureFeatures,
903
- getRowId: resolvedGetRowId,
904
- finalPaginationMode,
905
- finalDataCount: safeTableData.length > 0 ? finalDataCount : 0,
906
- pageSize: effectivePageSize,
907
- hasServerSideConfig: !!serverSide,
300
+ const renderGuard = useDataTableRenderGuard<TData>({
301
+ ...pipeline.guardInputs,
302
+ user,
303
+ permissions,
304
+ isPermissionLoading,
305
+ permissionElapsed,
306
+ shouldAllowRenderAfterTimeout,
307
+ logger,
308
+ LoadingComponent,
309
+ AccessDeniedPage,
908
310
  });
909
-
910
311
 
911
- const table = useReactTable(tableConfig);
912
-
913
- // ============================================================================
914
- // RBAC VALIDATION AND EARLY RETURNS - AFTER ALL HOOKS
915
- // ============================================================================
916
-
917
- // DIAGNOSTIC: Log render state for debugging
918
- const renderDiagnostics = {
919
- hasUser: !!user,
920
- userId: user?.id,
921
- permissionLoading: permissions.canRead.isLoading,
922
- permissionCan: permissions.canRead.can,
923
- permissionError: permissions.canRead.error,
924
- effectivePageId,
925
- externalIsLoading,
926
- performanceLoading,
927
- computedIsLoading: isLoading,
928
- dataLength: data.length,
929
- finalTableDataLength: finalTableData.length,
930
- columnsLength: columns.length,
931
- tableRowsCount: table?.getRowModel().rows.length || 0,
932
- };
933
-
934
- // Log diagnostics in development mode
935
- if (process.env.NODE_ENV === 'development') {
936
- logger.debug('DataTable render diagnostics:', renderDiagnostics);
937
- }
938
-
939
- // MANDATORY: Every DataTable must have a user
940
- if (!user) {
941
- logger.error('DataTable render blocked: No user', renderDiagnostics);
312
+ if (renderGuard.gate === 'throw') {
942
313
  throw new Error('DataTable requires authenticated user for RBAC');
943
314
  }
944
-
945
- // PERFORMANCE FIX: Allow rendering after timeout to prevent infinite blocking
946
- // After 3 seconds, allow table to render but keep data hidden until permissions confirm
947
- // This provides better UX while maintaining security (data remains protected)
948
- // Note: permissionElapsed and shouldAllowRenderAfterTimeout are calculated above for useMemo
949
- // Wait for permission check to complete before making access decisions
950
- // BUT: After 3s timeout, allow table structure to render (data will remain hidden)
951
- if (isPermissionLoading) {
952
- // Enhanced diagnostics for hanging permission checks
953
- if (permissionElapsed > 10000) {
954
- logger.error('DataTableCore', 'DataTable: Permission check hanging (>10s)', {
955
- ...renderDiagnostics,
956
- permissionState: {
957
- can: permissions.canRead.can,
958
- isLoading: permissions.canRead.isLoading,
959
- error: permissions.canRead.error?.message,
960
- },
961
- elapsedMs: permissionElapsed,
962
- diagnostic: 'Permission check has been loading for over 10 seconds. This likely indicates a hanging database query or network issue. Check browser network tab for pending requests to Supabase.',
963
- recommendation: 'Check: 1) Browser network tab for pending requests, 2) Supabase connection, 3) Database query performance, 4) Super admin check completion',
964
- });
965
- }
966
-
967
- if (process.env.NODE_ENV === 'development') {
968
- logger.debug('DataTable render blocked: Permissions loading', {
969
- ...renderDiagnostics,
970
- permissionState: permissions.canRead,
971
- elapsedMs: permissionElapsed,
972
- });
973
- }
974
- return <LoadingComponent />;
975
- }
976
-
977
- // If timeout reached but permissions still loading, log warning and proceed with caution
978
- if (permissions.canRead.isLoading && shouldAllowRenderAfterTimeout) {
979
- logger.warn('DataTable: Rendering after timeout - permissions still loading. Data will remain hidden until confirmed.', {
980
- pageId: effectivePageId,
981
- elapsedMs: permissionElapsed,
982
- permissionState: permissions.canRead,
983
- });
984
- // Continue to render check below - we'll show empty state until permissions confirm
985
- }
986
-
987
- // MANDATORY: No data access without read permission
988
- // SECURITY: If permissions are still loading after timeout, allow table structure but hide data
989
- // If permissions are confirmed as denied, show access denied page
990
- if (!permissions.canRead.isLoading && !permissions.canRead.can) {
991
- logger.warn('Access denied - no read permission:', {
992
- canRead: permissions.canRead,
993
- pageId: effectivePageId,
994
- isLoading: permissions.canRead.isLoading,
995
- diagnostics: renderDiagnostics,
996
- });
997
- return <AccessDeniedPage resource={effectivePageId || 'unknown-page'} operation="read" />;
998
- }
999
-
1000
- // If permissions still loading after timeout, proceed to render but data will be empty/hidden
1001
- // The table structure will render, but rows will be empty until permissions confirm
1002
- if (permissions.canRead.isLoading && shouldAllowRenderAfterTimeout) {
1003
- // Log that we're proceeding with timeout
1004
- logger.debug('DataTable: Proceeding to render after timeout - permissions still loading', {
1005
- pageId: effectivePageId,
1006
- elapsedMs: permissionElapsed,
1007
- });
1008
- // Continue to render - data will be empty until permissions confirm
1009
- }
1010
-
1011
- // ============================================================================
1012
- // RENDER
1013
- // ============================================================================
1014
-
1015
- if (isLoading) {
1016
- if (process.env.NODE_ENV === 'development') {
1017
- logger.debug('DataTable render blocked: External isLoading', {
1018
- ...renderDiagnostics,
1019
- isLoadingSource: {
1020
- externalIsLoading,
1021
- performanceLoading,
1022
- computed: isLoading,
1023
- },
1024
- });
1025
- }
1026
- return <LoadingComponent />;
1027
- }
1028
-
1029
- // DIAGNOSTIC: Log successful render path
1030
- if (process.env.NODE_ENV === 'development') {
1031
- logger.debug('DataTable proceeding to render:', {
1032
- ...renderDiagnostics,
1033
- willRender: true,
1034
- tableState: {
1035
- rowCount: table?.getRowModel().rows.length || 0,
1036
- columnCount: table?.getVisibleFlatColumns().length || 0,
1037
- paginationMode: finalPaginationMode,
1038
- },
1039
- });
315
+ if (renderGuard.component) {
316
+ return renderGuard.component;
1040
317
  }
1041
318
 
1042
319
  return (
1043
320
  <DataTableLayout
1044
- table={table}
1045
- title={title}
1046
- description={description}
1047
- variant={variant}
1048
- className={className}
1049
- columns={columns}
1050
- secureFeatures={secureFeatures}
1051
- enhancedPagination={enhancedPagination}
1052
- searchQuery={searchQuery}
1053
- onSearch={handleSearch}
1054
- state={state}
1055
- stateActions={stateActions}
1056
- rowSelection={rowSelection}
1057
- onCreateRow={secureHandlers.onCreateRow}
1058
- onEditRow={secureHandlers.onEditRow}
1059
- onImport={secureHandlers.onImport}
1060
- onExport={secureHandlers.onExport}
1061
- onDeleteSelected={secureHandlers.onDeleteSelected}
1062
- rbac={rbac}
1063
- permissions={permissions}
1064
- effectiveActions={effectiveActions}
1065
- finalPageSizeOptions={finalPageSizeOptions}
1066
- finalPaginationMode={finalPaginationMode}
1067
- finalDataCount={finalDataCount}
1068
- isLoading={isLoading}
1069
- finalTableData={finalTableData}
1070
- aggregates={aggregates}
1071
- resolvedGetRowId={resolvedGetRowId}
1072
- data={data}
1073
- emptyState={emptyState}
1074
- virtualHeight={virtualHeight}
1075
- hierarchical={hierarchical}
1076
- hierarchicalState={hierarchicalState}
1077
- logger={logger}
1078
- secureHandlers={secureHandlers}
1079
- importModalConfig={importModalConfig}
1080
- keyboardNavigation={keyboardNavigation}
1081
- lastFocusedElementRef={lastFocusedElementRef}
321
+ {...(pipeline.layoutProps as unknown as React.ComponentProps<typeof DataTableLayout>)}
1082
322
  />
1083
323
  );
1084
324
  }