@freighttech/ui 0.13.18

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 (1477) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +31 -0
  3. package/dist/backend/AppShell.js +1289 -0
  4. package/dist/backend/AppShell.js.map +7 -0
  5. package/dist/backend/BackendChromeProvider.js +83 -0
  6. package/dist/backend/BackendChromeProvider.js.map +7 -0
  7. package/dist/backend/BackendPageSurface.js +19 -0
  8. package/dist/backend/BackendPageSurface.js.map +7 -0
  9. package/dist/backend/CollapsibleNavSection.js +149 -0
  10. package/dist/backend/CollapsibleNavSection.js.map +7 -0
  11. package/dist/backend/ContextHelp.js +33 -0
  12. package/dist/backend/ContextHelp.js.map +7 -0
  13. package/dist/backend/CrudForm.js +3163 -0
  14. package/dist/backend/CrudForm.js.map +7 -0
  15. package/dist/backend/DataTable.js +2091 -0
  16. package/dist/backend/DataTable.js.map +7 -0
  17. package/dist/backend/EmptyState.js +52 -0
  18. package/dist/backend/EmptyState.js.map +7 -0
  19. package/dist/backend/FilterBar.js +147 -0
  20. package/dist/backend/FilterBar.js.map +7 -0
  21. package/dist/backend/FilterOverlay.js +337 -0
  22. package/dist/backend/FilterOverlay.js.map +7 -0
  23. package/dist/backend/FlashMessages.js +158 -0
  24. package/dist/backend/FlashMessages.js.map +7 -0
  25. package/dist/backend/HtmlRichTextEditor.js +129 -0
  26. package/dist/backend/HtmlRichTextEditor.js.map +7 -0
  27. package/dist/backend/IntegrationsButton.js +25 -0
  28. package/dist/backend/IntegrationsButton.js.map +7 -0
  29. package/dist/backend/JsonBuilder.js +323 -0
  30. package/dist/backend/JsonBuilder.js.map +7 -0
  31. package/dist/backend/JsonDisplay.js +207 -0
  32. package/dist/backend/JsonDisplay.js.map +7 -0
  33. package/dist/backend/NextStepCallout.js +79 -0
  34. package/dist/backend/NextStepCallout.js.map +7 -0
  35. package/dist/backend/Page.js +27 -0
  36. package/dist/backend/Page.js.map +7 -0
  37. package/dist/backend/PerspectiveSidebar.js +456 -0
  38. package/dist/backend/PerspectiveSidebar.js.map +7 -0
  39. package/dist/backend/ProfileDropdown.js +341 -0
  40. package/dist/backend/ProfileDropdown.js.map +7 -0
  41. package/dist/backend/RowActions.js +155 -0
  42. package/dist/backend/RowActions.js.map +7 -0
  43. package/dist/backend/SectionHeader.js +74 -0
  44. package/dist/backend/SectionHeader.js.map +7 -0
  45. package/dist/backend/SettingsButton.js +25 -0
  46. package/dist/backend/SettingsButton.js.map +7 -0
  47. package/dist/backend/Sidebar.js +510 -0
  48. package/dist/backend/Sidebar.js.map +7 -0
  49. package/dist/backend/Sidebar.stories.js +166 -0
  50. package/dist/backend/Sidebar.stories.js.map +7 -0
  51. package/dist/backend/TruncatedCell.js +92 -0
  52. package/dist/backend/TruncatedCell.js.map +7 -0
  53. package/dist/backend/UserMenu.js +146 -0
  54. package/dist/backend/UserMenu.js.map +7 -0
  55. package/dist/backend/ValueIcons.js +34 -0
  56. package/dist/backend/ValueIcons.js.map +7 -0
  57. package/dist/backend/WebhookSetupGuide.js +79 -0
  58. package/dist/backend/WebhookSetupGuide.js.map +7 -0
  59. package/dist/backend/charts/BarChart.js +154 -0
  60. package/dist/backend/charts/BarChart.js.map +7 -0
  61. package/dist/backend/charts/ChartUtils.js +141 -0
  62. package/dist/backend/charts/ChartUtils.js.map +7 -0
  63. package/dist/backend/charts/ComboChart.js +261 -0
  64. package/dist/backend/charts/ComboChart.js.map +7 -0
  65. package/dist/backend/charts/ComboChart.stories.js +88 -0
  66. package/dist/backend/charts/ComboChart.stories.js.map +7 -0
  67. package/dist/backend/charts/KpiCard.js +96 -0
  68. package/dist/backend/charts/KpiCard.js.map +7 -0
  69. package/dist/backend/charts/LineChart.js +165 -0
  70. package/dist/backend/charts/LineChart.js.map +7 -0
  71. package/dist/backend/charts/PieChart.js +121 -0
  72. package/dist/backend/charts/PieChart.js.map +7 -0
  73. package/dist/backend/charts/ScatterChart.js +320 -0
  74. package/dist/backend/charts/ScatterChart.js.map +7 -0
  75. package/dist/backend/charts/TopNTable.js +91 -0
  76. package/dist/backend/charts/TopNTable.js.map +7 -0
  77. package/dist/backend/charts/WaterfallChart.js +136 -0
  78. package/dist/backend/charts/WaterfallChart.js.map +7 -0
  79. package/dist/backend/charts/index.js +30 -0
  80. package/dist/backend/charts/index.js.map +7 -0
  81. package/dist/backend/columns/ColumnChooserPanel.js +244 -0
  82. package/dist/backend/columns/ColumnChooserPanel.js.map +7 -0
  83. package/dist/backend/confirm-dialog/ConfirmDialog.js +214 -0
  84. package/dist/backend/confirm-dialog/ConfirmDialog.js.map +7 -0
  85. package/dist/backend/confirm-dialog/index.js +7 -0
  86. package/dist/backend/confirm-dialog/index.js.map +7 -0
  87. package/dist/backend/confirm-dialog/useConfirmDialog.js +113 -0
  88. package/dist/backend/confirm-dialog/useConfirmDialog.js.map +7 -0
  89. package/dist/backend/custom-fields/FieldDefinitionsEditor.js +1352 -0
  90. package/dist/backend/custom-fields/FieldDefinitionsEditor.js.map +7 -0
  91. package/dist/backend/custom-fields/FieldDefinitionsManager.js +332 -0
  92. package/dist/backend/custom-fields/FieldDefinitionsManager.js.map +7 -0
  93. package/dist/backend/dashboard/DashboardScreen.js +598 -0
  94. package/dist/backend/dashboard/DashboardScreen.js.map +7 -0
  95. package/dist/backend/dashboard/index.js +5 -0
  96. package/dist/backend/dashboard/index.js.map +7 -0
  97. package/dist/backend/dashboard/widgetRegistry.js +55 -0
  98. package/dist/backend/dashboard/widgetRegistry.js.map +7 -0
  99. package/dist/backend/date-range/DateRangeSelect.js +39 -0
  100. package/dist/backend/date-range/DateRangeSelect.js.map +7 -0
  101. package/dist/backend/date-range/InlineDateRangeSelect.js +42 -0
  102. package/dist/backend/date-range/InlineDateRangeSelect.js.map +7 -0
  103. package/dist/backend/date-range/InlineGranularitySelect.js +40 -0
  104. package/dist/backend/date-range/InlineGranularitySelect.js.map +7 -0
  105. package/dist/backend/date-range/dateRanges.js +176 -0
  106. package/dist/backend/date-range/dateRanges.js.map +7 -0
  107. package/dist/backend/date-range/index.js +25 -0
  108. package/dist/backend/date-range/index.js.map +7 -0
  109. package/dist/backend/detail/AccessDeniedMessage.js +28 -0
  110. package/dist/backend/detail/AccessDeniedMessage.js.map +7 -0
  111. package/dist/backend/detail/ActivitiesSection.js +947 -0
  112. package/dist/backend/detail/ActivitiesSection.js.map +7 -0
  113. package/dist/backend/detail/AddressEditor.js +417 -0
  114. package/dist/backend/detail/AddressEditor.js.map +7 -0
  115. package/dist/backend/detail/AddressTiles.js +441 -0
  116. package/dist/backend/detail/AddressTiles.js.map +7 -0
  117. package/dist/backend/detail/AddressesSection.js +274 -0
  118. package/dist/backend/detail/AddressesSection.js.map +7 -0
  119. package/dist/backend/detail/AttachmentDeleteDialog.js +41 -0
  120. package/dist/backend/detail/AttachmentDeleteDialog.js.map +7 -0
  121. package/dist/backend/detail/AttachmentMetadataDialog.js +519 -0
  122. package/dist/backend/detail/AttachmentMetadataDialog.js.map +7 -0
  123. package/dist/backend/detail/AttachmentVisualPreview.js +132 -0
  124. package/dist/backend/detail/AttachmentVisualPreview.js.map +7 -0
  125. package/dist/backend/detail/AttachmentsSection.js +281 -0
  126. package/dist/backend/detail/AttachmentsSection.js.map +7 -0
  127. package/dist/backend/detail/CustomDataSection.js +575 -0
  128. package/dist/backend/detail/CustomDataSection.js.map +7 -0
  129. package/dist/backend/detail/DetailFieldsSection.js +85 -0
  130. package/dist/backend/detail/DetailFieldsSection.js.map +7 -0
  131. package/dist/backend/detail/ErrorMessage.js +28 -0
  132. package/dist/backend/detail/ErrorMessage.js.map +7 -0
  133. package/dist/backend/detail/InlineEditors.js +697 -0
  134. package/dist/backend/detail/InlineEditors.js.map +7 -0
  135. package/dist/backend/detail/LoadingMessage.js +14 -0
  136. package/dist/backend/detail/LoadingMessage.js.map +7 -0
  137. package/dist/backend/detail/NotesSection.js +1022 -0
  138. package/dist/backend/detail/NotesSection.js.map +7 -0
  139. package/dist/backend/detail/TabEmptyState.js +25 -0
  140. package/dist/backend/detail/TabEmptyState.js.map +7 -0
  141. package/dist/backend/detail/TagsSection.js +375 -0
  142. package/dist/backend/detail/TagsSection.js.map +7 -0
  143. package/dist/backend/detail/addressFormat.js +77 -0
  144. package/dist/backend/detail/addressFormat.js.map +7 -0
  145. package/dist/backend/detail/index.js +36 -0
  146. package/dist/backend/detail/index.js.map +7 -0
  147. package/dist/backend/devtools/UmesDevToolsPanel.js +109 -0
  148. package/dist/backend/devtools/UmesDevToolsPanel.js.map +7 -0
  149. package/dist/backend/devtools/components/ConflictWarnings.js +32 -0
  150. package/dist/backend/devtools/components/ConflictWarnings.js.map +7 -0
  151. package/dist/backend/devtools/components/EnricherTiming.js +46 -0
  152. package/dist/backend/devtools/components/EnricherTiming.js.map +7 -0
  153. package/dist/backend/devtools/components/EventFlow.js +34 -0
  154. package/dist/backend/devtools/components/EventFlow.js.map +7 -0
  155. package/dist/backend/devtools/components/ExtensionPointList.js +58 -0
  156. package/dist/backend/devtools/components/ExtensionPointList.js.map +7 -0
  157. package/dist/backend/devtools/components/InterceptorActivity.js +36 -0
  158. package/dist/backend/devtools/components/InterceptorActivity.js.map +7 -0
  159. package/dist/backend/devtools/index.js +7 -0
  160. package/dist/backend/devtools/index.js.map +7 -0
  161. package/dist/backend/devtools/useUmesDevTools.js +125 -0
  162. package/dist/backend/devtools/useUmesDevTools.js.map +7 -0
  163. package/dist/backend/document-viewer/RegionOverlay.js +92 -0
  164. package/dist/backend/document-viewer/RegionOverlay.js.map +7 -0
  165. package/dist/backend/document-viewer/index.js +6 -0
  166. package/dist/backend/document-viewer/index.js.map +7 -0
  167. package/dist/backend/dynamic-grid/DynamicGrid.js +130 -0
  168. package/dist/backend/dynamic-grid/DynamicGrid.js.map +7 -0
  169. package/dist/backend/dynamic-grid/adapters.js +34 -0
  170. package/dist/backend/dynamic-grid/adapters.js.map +7 -0
  171. package/dist/backend/dynamic-grid/components/GridCell.js +149 -0
  172. package/dist/backend/dynamic-grid/components/GridCell.js.map +7 -0
  173. package/dist/backend/dynamic-grid/components/GridRow.js +32 -0
  174. package/dist/backend/dynamic-grid/components/GridRow.js.map +7 -0
  175. package/dist/backend/dynamic-grid/hooks/useGridKeyboardNav.js +115 -0
  176. package/dist/backend/dynamic-grid/hooks/useGridKeyboardNav.js.map +7 -0
  177. package/dist/backend/dynamic-grid/index.js +9 -0
  178. package/dist/backend/dynamic-grid/index.js.map +7 -0
  179. package/dist/backend/dynamic-grid/types.js +1 -0
  180. package/dist/backend/dynamic-grid/types.js.map +7 -0
  181. package/dist/backend/dynamic-table/DynamicTable.js +2607 -0
  182. package/dist/backend/dynamic-table/DynamicTable.js.map +7 -0
  183. package/dist/backend/dynamic-table/DynamicTable.stories.js +233 -0
  184. package/dist/backend/dynamic-table/DynamicTable.stories.js.map +7 -0
  185. package/dist/backend/dynamic-table/components/ActiveFilterChips.js +60 -0
  186. package/dist/backend/dynamic-table/components/ActiveFilterChips.js.map +7 -0
  187. package/dist/backend/dynamic-table/components/BulkActionsBar.js +58 -0
  188. package/dist/backend/dynamic-table/components/BulkActionsBar.js.map +7 -0
  189. package/dist/backend/dynamic-table/components/Cell.js +155 -0
  190. package/dist/backend/dynamic-table/components/Cell.js.map +7 -0
  191. package/dist/backend/dynamic-table/components/CellCommentDialog.js +726 -0
  192. package/dist/backend/dynamic-table/components/CellCommentDialog.js.map +7 -0
  193. package/dist/backend/dynamic-table/components/CellCommentHoverPopup.js +77 -0
  194. package/dist/backend/dynamic-table/components/CellCommentHoverPopup.js.map +7 -0
  195. package/dist/backend/dynamic-table/components/ColumnFilterPopover.js +485 -0
  196. package/dist/backend/dynamic-table/components/ColumnFilterPopover.js.map +7 -0
  197. package/dist/backend/dynamic-table/components/ColumnHeaderMenu.js +173 -0
  198. package/dist/backend/dynamic-table/components/ColumnHeaderMenu.js.map +7 -0
  199. package/dist/backend/dynamic-table/components/ColumnHeaders.js +528 -0
  200. package/dist/backend/dynamic-table/components/ColumnHeaders.js.map +7 -0
  201. package/dist/backend/dynamic-table/components/CompactLabelCell.js +64 -0
  202. package/dist/backend/dynamic-table/components/CompactLabelCell.js.map +7 -0
  203. package/dist/backend/dynamic-table/components/ConfigureViewFields.js +139 -0
  204. package/dist/backend/dynamic-table/components/ConfigureViewFields.js.map +7 -0
  205. package/dist/backend/dynamic-table/components/ConfigureViewFilters.js +305 -0
  206. package/dist/backend/dynamic-table/components/ConfigureViewFilters.js.map +7 -0
  207. package/dist/backend/dynamic-table/components/ConfigureViewFormatting.js +169 -0
  208. package/dist/backend/dynamic-table/components/ConfigureViewFormatting.js.map +7 -0
  209. package/dist/backend/dynamic-table/components/ConfigureViewFormulas.js +298 -0
  210. package/dist/backend/dynamic-table/components/ConfigureViewFormulas.js.map +7 -0
  211. package/dist/backend/dynamic-table/components/ConfigureViewGrouping.js +224 -0
  212. package/dist/backend/dynamic-table/components/ConfigureViewGrouping.js.map +7 -0
  213. package/dist/backend/dynamic-table/components/ConfigureViewLinkedColumns.js +167 -0
  214. package/dist/backend/dynamic-table/components/ConfigureViewLinkedColumns.js.map +7 -0
  215. package/dist/backend/dynamic-table/components/ConfigureViewPanel.js +592 -0
  216. package/dist/backend/dynamic-table/components/ConfigureViewPanel.js.map +7 -0
  217. package/dist/backend/dynamic-table/components/ConfigureViewRollups.js +269 -0
  218. package/dist/backend/dynamic-table/components/ConfigureViewRollups.js.map +7 -0
  219. package/dist/backend/dynamic-table/components/ConfigureViewSorting.js +107 -0
  220. package/dist/backend/dynamic-table/components/ConfigureViewSorting.js.map +7 -0
  221. package/dist/backend/dynamic-table/components/ContextMenu.js +91 -0
  222. package/dist/backend/dynamic-table/components/ContextMenu.js.map +7 -0
  223. package/dist/backend/dynamic-table/components/DateCell.js +80 -0
  224. package/dist/backend/dynamic-table/components/DateCell.js.map +7 -0
  225. package/dist/backend/dynamic-table/components/Debugger.js +311 -0
  226. package/dist/backend/dynamic-table/components/Debugger.js.map +7 -0
  227. package/dist/backend/dynamic-table/components/DensityControl.js +289 -0
  228. package/dist/backend/dynamic-table/components/DensityControl.js.map +7 -0
  229. package/dist/backend/dynamic-table/components/EmptyState.js +42 -0
  230. package/dist/backend/dynamic-table/components/EmptyState.js.map +7 -0
  231. package/dist/backend/dynamic-table/components/EntitySearchEditor.js +369 -0
  232. package/dist/backend/dynamic-table/components/EntitySearchEditor.js.map +7 -0
  233. package/dist/backend/dynamic-table/components/ExportMenu.js +63 -0
  234. package/dist/backend/dynamic-table/components/ExportMenu.js.map +7 -0
  235. package/dist/backend/dynamic-table/components/FilterDatePicker.js +165 -0
  236. package/dist/backend/dynamic-table/components/FilterDatePicker.js.map +7 -0
  237. package/dist/backend/dynamic-table/components/FilterValueInput.js +200 -0
  238. package/dist/backend/dynamic-table/components/FilterValueInput.js.map +7 -0
  239. package/dist/backend/dynamic-table/components/FooterTotalsRow.js +165 -0
  240. package/dist/backend/dynamic-table/components/FooterTotalsRow.js.map +7 -0
  241. package/dist/backend/dynamic-table/components/FullscreenOverlay.js +51 -0
  242. package/dist/backend/dynamic-table/components/FullscreenOverlay.js.map +7 -0
  243. package/dist/backend/dynamic-table/components/GroupHeaderRow.js +108 -0
  244. package/dist/backend/dynamic-table/components/GroupHeaderRow.js.map +7 -0
  245. package/dist/backend/dynamic-table/components/GroupSummaryRow.js +134 -0
  246. package/dist/backend/dynamic-table/components/GroupSummaryRow.js.map +7 -0
  247. package/dist/backend/dynamic-table/components/ImportPanel.js +341 -0
  248. package/dist/backend/dynamic-table/components/ImportPanel.js.map +7 -0
  249. package/dist/backend/dynamic-table/components/ImportPreviewGrid.js +114 -0
  250. package/dist/backend/dynamic-table/components/ImportPreviewGrid.js.map +7 -0
  251. package/dist/backend/dynamic-table/components/LoadErrorState.js +23 -0
  252. package/dist/backend/dynamic-table/components/LoadErrorState.js.map +7 -0
  253. package/dist/backend/dynamic-table/components/MentionPopup.js +149 -0
  254. package/dist/backend/dynamic-table/components/MentionPopup.js.map +7 -0
  255. package/dist/backend/dynamic-table/components/PerspectiveDeleteDialog.js +57 -0
  256. package/dist/backend/dynamic-table/components/PerspectiveDeleteDialog.js.map +7 -0
  257. package/dist/backend/dynamic-table/components/PerspectivePublishDialog.js +100 -0
  258. package/dist/backend/dynamic-table/components/PerspectivePublishDialog.js.map +7 -0
  259. package/dist/backend/dynamic-table/components/PerspectiveTabMenu.js +159 -0
  260. package/dist/backend/dynamic-table/components/PerspectiveTabMenu.js.map +7 -0
  261. package/dist/backend/dynamic-table/components/PerspectiveTabs.js +319 -0
  262. package/dist/backend/dynamic-table/components/PerspectiveTabs.js.map +7 -0
  263. package/dist/backend/dynamic-table/components/PerspectiveTemplateMenu.js +90 -0
  264. package/dist/backend/dynamic-table/components/PerspectiveTemplateMenu.js.map +7 -0
  265. package/dist/backend/dynamic-table/components/RowHeaderCell.js +58 -0
  266. package/dist/backend/dynamic-table/components/RowHeaderCell.js.map +7 -0
  267. package/dist/backend/dynamic-table/components/SearchBar.js +221 -0
  268. package/dist/backend/dynamic-table/components/SearchBar.js.map +7 -0
  269. package/dist/backend/dynamic-table/components/SelectMenu.js +186 -0
  270. package/dist/backend/dynamic-table/components/SelectMenu.js.map +7 -0
  271. package/dist/backend/dynamic-table/components/StaleDataBar.js +26 -0
  272. package/dist/backend/dynamic-table/components/StaleDataBar.js.map +7 -0
  273. package/dist/backend/dynamic-table/components/TableDeleteDialog.js +48 -0
  274. package/dist/backend/dynamic-table/components/TableDeleteDialog.js.map +7 -0
  275. package/dist/backend/dynamic-table/components/TablePagination.js +69 -0
  276. package/dist/backend/dynamic-table/components/TablePagination.js.map +7 -0
  277. package/dist/backend/dynamic-table/components/TableSkeleton.js +43 -0
  278. package/dist/backend/dynamic-table/components/TableSkeleton.js.map +7 -0
  279. package/dist/backend/dynamic-table/components/ToolbarOverflow.js +204 -0
  280. package/dist/backend/dynamic-table/components/ToolbarOverflow.js.map +7 -0
  281. package/dist/backend/dynamic-table/components/ViewModeSwitch.js +50 -0
  282. package/dist/backend/dynamic-table/components/ViewModeSwitch.js.map +7 -0
  283. package/dist/backend/dynamic-table/components/VirtualRow.js +214 -0
  284. package/dist/backend/dynamic-table/components/VirtualRow.js.map +7 -0
  285. package/dist/backend/dynamic-table/components/editors.js +1483 -0
  286. package/dist/backend/dynamic-table/components/editors.js.map +7 -0
  287. package/dist/backend/dynamic-table/components/renderers.js +131 -0
  288. package/dist/backend/dynamic-table/components/renderers.js.map +7 -0
  289. package/dist/backend/dynamic-table/events/events.js +73 -0
  290. package/dist/backend/dynamic-table/events/events.js.map +7 -0
  291. package/dist/backend/dynamic-table/formula/check.js +243 -0
  292. package/dist/backend/dynamic-table/formula/check.js.map +7 -0
  293. package/dist/backend/dynamic-table/formula/evaluate.js +302 -0
  294. package/dist/backend/dynamic-table/formula/evaluate.js.map +7 -0
  295. package/dist/backend/dynamic-table/formula/functions.js +602 -0
  296. package/dist/backend/dynamic-table/formula/functions.js.map +7 -0
  297. package/dist/backend/dynamic-table/formula/index.js +47 -0
  298. package/dist/backend/dynamic-table/formula/index.js.map +7 -0
  299. package/dist/backend/dynamic-table/formula/parse.js +293 -0
  300. package/dist/backend/dynamic-table/formula/parse.js.map +7 -0
  301. package/dist/backend/dynamic-table/formula/types.js +24 -0
  302. package/dist/backend/dynamic-table/formula/types.js.map +7 -0
  303. package/dist/backend/dynamic-table/handlers/cellWrites.js +143 -0
  304. package/dist/backend/dynamic-table/handlers/cellWrites.js.map +7 -0
  305. package/dist/backend/dynamic-table/handlers/index.js +504 -0
  306. package/dist/backend/dynamic-table/handlers/index.js.map +7 -0
  307. package/dist/backend/dynamic-table/handlers/perspectiveHandlers.js +377 -0
  308. package/dist/backend/dynamic-table/handlers/perspectiveHandlers.js.map +7 -0
  309. package/dist/backend/dynamic-table/hooks/index.js +524 -0
  310. package/dist/backend/dynamic-table/hooks/index.js.map +7 -0
  311. package/dist/backend/dynamic-table/hooks/useAbbreviations.js +19 -0
  312. package/dist/backend/dynamic-table/hooks/useAbbreviations.js.map +7 -0
  313. package/dist/backend/dynamic-table/hooks/useAnnotations.js +115 -0
  314. package/dist/backend/dynamic-table/hooks/useAnnotations.js.map +7 -0
  315. package/dist/backend/dynamic-table/hooks/useColumnVirtualizer.js +155 -0
  316. package/dist/backend/dynamic-table/hooks/useColumnVirtualizer.js.map +7 -0
  317. package/dist/backend/dynamic-table/hooks/useColumnWidthPersistence.js +135 -0
  318. package/dist/backend/dynamic-table/hooks/useColumnWidthPersistence.js.map +7 -0
  319. package/dist/backend/dynamic-table/hooks/useDensityPreference.js +203 -0
  320. package/dist/backend/dynamic-table/hooks/useDensityPreference.js.map +7 -0
  321. package/dist/backend/dynamic-table/hooks/useDynamicTablePage.js +1005 -0
  322. package/dist/backend/dynamic-table/hooks/useDynamicTablePage.js.map +7 -0
  323. package/dist/backend/dynamic-table/hooks/useEscapeLayer.js +32 -0
  324. package/dist/backend/dynamic-table/hooks/useEscapeLayer.js.map +7 -0
  325. package/dist/backend/dynamic-table/hooks/useGrouping.js +256 -0
  326. package/dist/backend/dynamic-table/hooks/useGrouping.js.map +7 -0
  327. package/dist/backend/dynamic-table/hooks/useSuggestionFetch.js +63 -0
  328. package/dist/backend/dynamic-table/hooks/useSuggestionFetch.js.map +7 -0
  329. package/dist/backend/dynamic-table/hooks/useTableDateFormat.js +11 -0
  330. package/dist/backend/dynamic-table/hooks/useTableDateFormat.js.map +7 -0
  331. package/dist/backend/dynamic-table/index.js +476 -0
  332. package/dist/backend/dynamic-table/index.js.map +7 -0
  333. package/dist/backend/dynamic-table/registry/ContentRegistryContext.js +126 -0
  334. package/dist/backend/dynamic-table/registry/ContentRegistryContext.js.map +7 -0
  335. package/dist/backend/dynamic-table/registry/TableRegistryContext.js +43 -0
  336. package/dist/backend/dynamic-table/registry/TableRegistryContext.js.map +7 -0
  337. package/dist/backend/dynamic-table/registry/filterAccessibleTables.js +13 -0
  338. package/dist/backend/dynamic-table/registry/filterAccessibleTables.js.map +7 -0
  339. package/dist/backend/dynamic-table/registry/index.js +44 -0
  340. package/dist/backend/dynamic-table/registry/index.js.map +7 -0
  341. package/dist/backend/dynamic-table/registry/lazyTable.js +55 -0
  342. package/dist/backend/dynamic-table/registry/lazyTable.js.map +7 -0
  343. package/dist/backend/dynamic-table/registry/types.js +1 -0
  344. package/dist/backend/dynamic-table/registry/types.js.map +7 -0
  345. package/dist/backend/dynamic-table/registry/widgetCatalog.js +96 -0
  346. package/dist/backend/dynamic-table/registry/widgetCatalog.js.map +7 -0
  347. package/dist/backend/dynamic-table/server/aggregate.js +261 -0
  348. package/dist/backend/dynamic-table/server/aggregate.js.map +7 -0
  349. package/dist/backend/dynamic-table/server/dateWindows.js +105 -0
  350. package/dist/backend/dynamic-table/server/dateWindows.js.map +7 -0
  351. package/dist/backend/dynamic-table/server/filterParser.js +97 -0
  352. package/dist/backend/dynamic-table/server/filterParser.js.map +7 -0
  353. package/dist/backend/dynamic-table/server/filterSuggestions.js +70 -0
  354. package/dist/backend/dynamic-table/server/filterSuggestions.js.map +7 -0
  355. package/dist/backend/dynamic-table/server/index.js +47 -0
  356. package/dist/backend/dynamic-table/server/index.js.map +7 -0
  357. package/dist/backend/dynamic-table/server/makeDynamicTableRoute.js +270 -0
  358. package/dist/backend/dynamic-table/server/makeDynamicTableRoute.js.map +7 -0
  359. package/dist/backend/dynamic-table/split-view/ContentPicker.js +171 -0
  360. package/dist/backend/dynamic-table/split-view/ContentPicker.js.map +7 -0
  361. package/dist/backend/dynamic-table/split-view/EmptySlot.js +47 -0
  362. package/dist/backend/dynamic-table/split-view/EmptySlot.js.map +7 -0
  363. package/dist/backend/dynamic-table/split-view/SplitViewHost.js +863 -0
  364. package/dist/backend/dynamic-table/split-view/SplitViewHost.js.map +7 -0
  365. package/dist/backend/dynamic-table/split-view/WidgetPane.js +280 -0
  366. package/dist/backend/dynamic-table/split-view/WidgetPane.js.map +7 -0
  367. package/dist/backend/dynamic-table/split-view/WorkspaceFilterBar.js +524 -0
  368. package/dist/backend/dynamic-table/split-view/WorkspaceFilterBar.js.map +7 -0
  369. package/dist/backend/dynamic-table/split-view/index.js +61 -0
  370. package/dist/backend/dynamic-table/split-view/index.js.map +7 -0
  371. package/dist/backend/dynamic-table/split-view/layoutPersistence.js +91 -0
  372. package/dist/backend/dynamic-table/split-view/layoutPersistence.js.map +7 -0
  373. package/dist/backend/dynamic-table/split-view/sharedCriteria.js +62 -0
  374. package/dist/backend/dynamic-table/split-view/sharedCriteria.js.map +7 -0
  375. package/dist/backend/dynamic-table/split-view/types.js +426 -0
  376. package/dist/backend/dynamic-table/split-view/types.js.map +7 -0
  377. package/dist/backend/dynamic-table/split-view/useSplitViewLayouts.js +79 -0
  378. package/dist/backend/dynamic-table/split-view/useSplitViewLayouts.js.map +7 -0
  379. package/dist/backend/dynamic-table/store/index.js +611 -0
  380. package/dist/backend/dynamic-table/store/index.js.map +7 -0
  381. package/dist/backend/dynamic-table/styles/ContextMenu.css.js +2 -0
  382. package/dist/backend/dynamic-table/styles/DynamicTable.css.js +2 -0
  383. package/dist/backend/dynamic-table/styles/DynamicTable.v2.css.js +2 -0
  384. package/dist/backend/dynamic-table/styles/SearchBar.css.js +2 -0
  385. package/dist/backend/dynamic-table/styles/dateCell.css.js +2 -0
  386. package/dist/backend/dynamic-table/styles/density.css.js +2 -0
  387. package/dist/backend/dynamic-table/types/density.js +29 -0
  388. package/dist/backend/dynamic-table/types/density.js.map +7 -0
  389. package/dist/backend/dynamic-table/types/filters.js +124 -0
  390. package/dist/backend/dynamic-table/types/filters.js.map +7 -0
  391. package/dist/backend/dynamic-table/types/grouping.js +34 -0
  392. package/dist/backend/dynamic-table/types/grouping.js.map +7 -0
  393. package/dist/backend/dynamic-table/types/import.js +7 -0
  394. package/dist/backend/dynamic-table/types/import.js.map +7 -0
  395. package/dist/backend/dynamic-table/types/index.js +46 -0
  396. package/dist/backend/dynamic-table/types/index.js.map +7 -0
  397. package/dist/backend/dynamic-table/types/perspective.js +79 -0
  398. package/dist/backend/dynamic-table/types/perspective.js.map +7 -0
  399. package/dist/backend/dynamic-table/types/rollup.js +13 -0
  400. package/dist/backend/dynamic-table/types/rollup.js.map +7 -0
  401. package/dist/backend/dynamic-table/utils/abbreviations.js +228 -0
  402. package/dist/backend/dynamic-table/utils/abbreviations.js.map +7 -0
  403. package/dist/backend/dynamic-table/utils/anchoredPosition.js +21 -0
  404. package/dist/backend/dynamic-table/utils/anchoredPosition.js.map +7 -0
  405. package/dist/backend/dynamic-table/utils/badgeAutoFit.js +35 -0
  406. package/dist/backend/dynamic-table/utils/badgeAutoFit.js.map +7 -0
  407. package/dist/backend/dynamic-table/utils/clipboard.js +172 -0
  408. package/dist/backend/dynamic-table/utils/clipboard.js.map +7 -0
  409. package/dist/backend/dynamic-table/utils/coerceCellValue.js +52 -0
  410. package/dist/backend/dynamic-table/utils/coerceCellValue.js.map +7 -0
  411. package/dist/backend/dynamic-table/utils/colorAdjacency.js +27 -0
  412. package/dist/backend/dynamic-table/utils/colorAdjacency.js.map +7 -0
  413. package/dist/backend/dynamic-table/utils/columnFill.js +36 -0
  414. package/dist/backend/dynamic-table/utils/columnFill.js.map +7 -0
  415. package/dist/backend/dynamic-table/utils/columnSpacerStyle.js +21 -0
  416. package/dist/backend/dynamic-table/utils/columnSpacerStyle.js.map +7 -0
  417. package/dist/backend/dynamic-table/utils/columnWindow.js +293 -0
  418. package/dist/backend/dynamic-table/utils/columnWindow.js.map +7 -0
  419. package/dist/backend/dynamic-table/utils/conditionalFormat.js +188 -0
  420. package/dist/backend/dynamic-table/utils/conditionalFormat.js.map +7 -0
  421. package/dist/backend/dynamic-table/utils/exportTable.js +82 -0
  422. package/dist/backend/dynamic-table/utils/exportTable.js.map +7 -0
  423. package/dist/backend/dynamic-table/utils/fillGeometry.js +92 -0
  424. package/dist/backend/dynamic-table/utils/fillGeometry.js.map +7 -0
  425. package/dist/backend/dynamic-table/utils/fillPatterns.js +241 -0
  426. package/dist/backend/dynamic-table/utils/fillPatterns.js.map +7 -0
  427. package/dist/backend/dynamic-table/utils/formatAggregate.js +82 -0
  428. package/dist/backend/dynamic-table/utils/formatAggregate.js.map +7 -0
  429. package/dist/backend/dynamic-table/utils/formatDateCompact.js +508 -0
  430. package/dist/backend/dynamic-table/utils/formatDateCompact.js.map +7 -0
  431. package/dist/backend/dynamic-table/utils/formatLabelCompact.js +312 -0
  432. package/dist/backend/dynamic-table/utils/formatLabelCompact.js.map +7 -0
  433. package/dist/backend/dynamic-table/utils/formulaColumns.js +158 -0
  434. package/dist/backend/dynamic-table/utils/formulaColumns.js.map +7 -0
  435. package/dist/backend/dynamic-table/utils/importMapping.js +118 -0
  436. package/dist/backend/dynamic-table/utils/importMapping.js.map +7 -0
  437. package/dist/backend/dynamic-table/utils/importParse.js +121 -0
  438. package/dist/backend/dynamic-table/utils/importParse.js.map +7 -0
  439. package/dist/backend/dynamic-table/utils/lookupColumns.js +28 -0
  440. package/dist/backend/dynamic-table/utils/lookupColumns.js.map +7 -0
  441. package/dist/backend/dynamic-table/utils/perspectiveTransforms.js +191 -0
  442. package/dist/backend/dynamic-table/utils/perspectiveTransforms.js.map +7 -0
  443. package/dist/backend/dynamic-table/utils/rollupColumns.js +81 -0
  444. package/dist/backend/dynamic-table/utils/rollupColumns.js.map +7 -0
  445. package/dist/backend/dynamic-table/validators/index.js +36 -0
  446. package/dist/backend/dynamic-table/validators/index.js.map +7 -0
  447. package/dist/backend/fields/registry.generated.js +8 -0
  448. package/dist/backend/fields/registry.generated.js.map +7 -0
  449. package/dist/backend/fields/registry.js +29 -0
  450. package/dist/backend/fields/registry.js.map +7 -0
  451. package/dist/backend/filters/AdvancedFilterBuilder.js +280 -0
  452. package/dist/backend/filters/AdvancedFilterBuilder.js.map +7 -0
  453. package/dist/backend/forms/ActionsDropdown.js +160 -0
  454. package/dist/backend/forms/ActionsDropdown.js.map +7 -0
  455. package/dist/backend/forms/FormActionButtons.js +59 -0
  456. package/dist/backend/forms/FormActionButtons.js.map +7 -0
  457. package/dist/backend/forms/FormFooter.js +18 -0
  458. package/dist/backend/forms/FormFooter.js.map +7 -0
  459. package/dist/backend/forms/FormHeader.js +125 -0
  460. package/dist/backend/forms/FormHeader.js.map +7 -0
  461. package/dist/backend/forms/index.js +11 -0
  462. package/dist/backend/forms/index.js.map +7 -0
  463. package/dist/backend/hooks/useAdvancedFilter.js +64 -0
  464. package/dist/backend/hooks/useAdvancedFilter.js.map +7 -0
  465. package/dist/backend/indexes/PartialIndexBanner.js +74 -0
  466. package/dist/backend/indexes/PartialIndexBanner.js.map +7 -0
  467. package/dist/backend/indexes/store.js +62 -0
  468. package/dist/backend/indexes/store.js.map +7 -0
  469. package/dist/backend/injection/ComponentOverrideProvider.js +55 -0
  470. package/dist/backend/injection/ComponentOverrideProvider.js.map +7 -0
  471. package/dist/backend/injection/InjectedField.js +166 -0
  472. package/dist/backend/injection/InjectedField.js.map +7 -0
  473. package/dist/backend/injection/InjectionSpot.js +323 -0
  474. package/dist/backend/injection/InjectionSpot.js.map +7 -0
  475. package/dist/backend/injection/InjectionWizard.js +256 -0
  476. package/dist/backend/injection/InjectionWizard.js.map +7 -0
  477. package/dist/backend/injection/PageInjectionBoundary.js +26 -0
  478. package/dist/backend/injection/PageInjectionBoundary.js.map +7 -0
  479. package/dist/backend/injection/SseEventIndicator.js +8 -0
  480. package/dist/backend/injection/SseEventIndicator.js.map +7 -0
  481. package/dist/backend/injection/StatusBadgeInjectionSpot.js +34 -0
  482. package/dist/backend/injection/StatusBadgeInjectionSpot.js.map +7 -0
  483. package/dist/backend/injection/StatusBadgeRenderer.js +58 -0
  484. package/dist/backend/injection/StatusBadgeRenderer.js.map +7 -0
  485. package/dist/backend/injection/WidgetSharedState.js +49 -0
  486. package/dist/backend/injection/WidgetSharedState.js.map +7 -0
  487. package/dist/backend/injection/WizardInjectionSpot.js +30 -0
  488. package/dist/backend/injection/WizardInjectionSpot.js.map +7 -0
  489. package/dist/backend/injection/eventBridge.js +129 -0
  490. package/dist/backend/injection/eventBridge.js.map +7 -0
  491. package/dist/backend/injection/helpers.js +26 -0
  492. package/dist/backend/injection/helpers.js.map +7 -0
  493. package/dist/backend/injection/mergeMenuItems.js +49 -0
  494. package/dist/backend/injection/mergeMenuItems.js.map +7 -0
  495. package/dist/backend/injection/mutationEvents.js +20 -0
  496. package/dist/backend/injection/mutationEvents.js.map +7 -0
  497. package/dist/backend/injection/resolveInjectedIcon.js +23 -0
  498. package/dist/backend/injection/resolveInjectedIcon.js.map +7 -0
  499. package/dist/backend/injection/spotIds.js +72 -0
  500. package/dist/backend/injection/spotIds.js.map +7 -0
  501. package/dist/backend/injection/useAppEvent.js +35 -0
  502. package/dist/backend/injection/useAppEvent.js.map +7 -0
  503. package/dist/backend/injection/useGuardedMutation.js +71 -0
  504. package/dist/backend/injection/useGuardedMutation.js.map +7 -0
  505. package/dist/backend/injection/useInjectedMenuItems.js +49 -0
  506. package/dist/backend/injection/useInjectedMenuItems.js.map +7 -0
  507. package/dist/backend/injection/useInjectionDataWidgets.js +36 -0
  508. package/dist/backend/injection/useInjectionDataWidgets.js.map +7 -0
  509. package/dist/backend/injection/useOperationProgress.js +64 -0
  510. package/dist/backend/injection/useOperationProgress.js.map +7 -0
  511. package/dist/backend/injection/useRegisteredComponent.js +89 -0
  512. package/dist/backend/injection/useRegisteredComponent.js.map +7 -0
  513. package/dist/backend/injection/useWidgetSharedState.js +26 -0
  514. package/dist/backend/injection/useWidgetSharedState.js.map +7 -0
  515. package/dist/backend/injection/visibility-utils.js +29 -0
  516. package/dist/backend/injection/visibility-utils.js.map +7 -0
  517. package/dist/backend/injection/widgetRegistry.js +55 -0
  518. package/dist/backend/injection/widgetRegistry.js.map +7 -0
  519. package/dist/backend/inputs/ComboboxInput.js +228 -0
  520. package/dist/backend/inputs/ComboboxInput.js.map +7 -0
  521. package/dist/backend/inputs/DatePicker.js +148 -0
  522. package/dist/backend/inputs/DatePicker.js.map +7 -0
  523. package/dist/backend/inputs/DateTimePicker.js +188 -0
  524. package/dist/backend/inputs/DateTimePicker.js.map +7 -0
  525. package/dist/backend/inputs/EventPatternInput.js +38 -0
  526. package/dist/backend/inputs/EventPatternInput.js.map +7 -0
  527. package/dist/backend/inputs/EventSelect.js +115 -0
  528. package/dist/backend/inputs/EventSelect.js.map +7 -0
  529. package/dist/backend/inputs/LookupSelect.js +197 -0
  530. package/dist/backend/inputs/LookupSelect.js.map +7 -0
  531. package/dist/backend/inputs/PhoneNumberField.js +133 -0
  532. package/dist/backend/inputs/PhoneNumberField.js.map +7 -0
  533. package/dist/backend/inputs/SwitchableMarkdownInput.js +73 -0
  534. package/dist/backend/inputs/SwitchableMarkdownInput.js.map +7 -0
  535. package/dist/backend/inputs/TagsInput.js +258 -0
  536. package/dist/backend/inputs/TagsInput.js.map +7 -0
  537. package/dist/backend/inputs/TimeInput.js +132 -0
  538. package/dist/backend/inputs/TimeInput.js.map +7 -0
  539. package/dist/backend/inputs/TimePicker.js +105 -0
  540. package/dist/backend/inputs/TimePicker.js.map +7 -0
  541. package/dist/backend/inputs/index.js +12 -0
  542. package/dist/backend/inputs/index.js.map +7 -0
  543. package/dist/backend/markdown/MarkdownContent.js +20 -0
  544. package/dist/backend/markdown/MarkdownContent.js.map +7 -0
  545. package/dist/backend/markdown/index.js +3 -0
  546. package/dist/backend/markdown/index.js.map +7 -0
  547. package/dist/backend/markdown/useMarkdownRemarkPlugins.js +33 -0
  548. package/dist/backend/markdown/useMarkdownRemarkPlugins.js.map +7 -0
  549. package/dist/backend/messages/MessageComposer.js +158 -0
  550. package/dist/backend/messages/MessageComposer.js.map +7 -0
  551. package/dist/backend/messages/MessageObjectDetail.js +81 -0
  552. package/dist/backend/messages/MessageObjectDetail.js.map +7 -0
  553. package/dist/backend/messages/MessageObjectPreview.js +48 -0
  554. package/dist/backend/messages/MessageObjectPreview.js.map +7 -0
  555. package/dist/backend/messages/MessageObjectRecordPicker.js +89 -0
  556. package/dist/backend/messages/MessageObjectRecordPicker.js.map +7 -0
  557. package/dist/backend/messages/MessagePrioritySelector.js +74 -0
  558. package/dist/backend/messages/MessagePrioritySelector.js.map +7 -0
  559. package/dist/backend/messages/MessagesIcon.js +22 -0
  560. package/dist/backend/messages/MessagesIcon.js.map +7 -0
  561. package/dist/backend/messages/SendObjectMessageDialog.js +65 -0
  562. package/dist/backend/messages/SendObjectMessageDialog.js.map +7 -0
  563. package/dist/backend/messages/index.js +17 -0
  564. package/dist/backend/messages/index.js.map +7 -0
  565. package/dist/backend/messages/message-compose-form-groups.js +333 -0
  566. package/dist/backend/messages/message-compose-form-groups.js.map +7 -0
  567. package/dist/backend/messages/message-composer.types.js +1 -0
  568. package/dist/backend/messages/message-composer.types.js.map +7 -0
  569. package/dist/backend/messages/message-priority.js +40 -0
  570. package/dist/backend/messages/message-priority.js.map +7 -0
  571. package/dist/backend/messages/useMessageCompose.js +488 -0
  572. package/dist/backend/messages/useMessageCompose.js.map +7 -0
  573. package/dist/backend/messages/useMessageComposeOperations.js +163 -0
  574. package/dist/backend/messages/useMessageComposeOperations.js.map +7 -0
  575. package/dist/backend/messages/useMessagesPoll.js +79 -0
  576. package/dist/backend/messages/useMessagesPoll.js.map +7 -0
  577. package/dist/backend/notifications/NotificationBell.js +83 -0
  578. package/dist/backend/notifications/NotificationBell.js.map +7 -0
  579. package/dist/backend/notifications/NotificationCountBadge.js +9 -0
  580. package/dist/backend/notifications/NotificationCountBadge.js.map +7 -0
  581. package/dist/backend/notifications/NotificationDispatcher.js +165 -0
  582. package/dist/backend/notifications/NotificationDispatcher.js.map +7 -0
  583. package/dist/backend/notifications/NotificationItem.js +187 -0
  584. package/dist/backend/notifications/NotificationItem.js.map +7 -0
  585. package/dist/backend/notifications/NotificationPanel.js +146 -0
  586. package/dist/backend/notifications/NotificationPanel.js.map +7 -0
  587. package/dist/backend/notifications/index.js +19 -0
  588. package/dist/backend/notifications/index.js.map +7 -0
  589. package/dist/backend/notifications/useNotificationActions.js +124 -0
  590. package/dist/backend/notifications/useNotificationActions.js.map +7 -0
  591. package/dist/backend/notifications/useNotificationEffect.js +13 -0
  592. package/dist/backend/notifications/useNotificationEffect.js.map +7 -0
  593. package/dist/backend/notifications/useNotifications.js +11 -0
  594. package/dist/backend/notifications/useNotifications.js.map +7 -0
  595. package/dist/backend/notifications/useNotificationsPoll.js +135 -0
  596. package/dist/backend/notifications/useNotificationsPoll.js.map +7 -0
  597. package/dist/backend/notifications/useNotificationsSse.js +173 -0
  598. package/dist/backend/notifications/useNotificationsSse.js.map +7 -0
  599. package/dist/backend/operations/LastOperationBanner.js +80 -0
  600. package/dist/backend/operations/LastOperationBanner.js.map +7 -0
  601. package/dist/backend/operations/store.js +183 -0
  602. package/dist/backend/operations/store.js.map +7 -0
  603. package/dist/backend/progress/ProgressTopBar.js +129 -0
  604. package/dist/backend/progress/ProgressTopBar.js.map +7 -0
  605. package/dist/backend/progress/useProgress.js +11 -0
  606. package/dist/backend/progress/useProgress.js.map +7 -0
  607. package/dist/backend/progress/useProgressPoll.js +69 -0
  608. package/dist/backend/progress/useProgressPoll.js.map +7 -0
  609. package/dist/backend/progress/useProgressSse.js +104 -0
  610. package/dist/backend/progress/useProgressSse.js.map +7 -0
  611. package/dist/backend/schedule/ScheduleAgenda.js +107 -0
  612. package/dist/backend/schedule/ScheduleAgenda.js.map +7 -0
  613. package/dist/backend/schedule/ScheduleGrid.js +107 -0
  614. package/dist/backend/schedule/ScheduleGrid.js.map +7 -0
  615. package/dist/backend/schedule/ScheduleToolbar.js +166 -0
  616. package/dist/backend/schedule/ScheduleToolbar.js.map +7 -0
  617. package/dist/backend/schedule/ScheduleView.js +168 -0
  618. package/dist/backend/schedule/ScheduleView.js.map +7 -0
  619. package/dist/backend/schedule/index.js +6 -0
  620. package/dist/backend/schedule/index.js.map +7 -0
  621. package/dist/backend/schedule/recurrence.js +83 -0
  622. package/dist/backend/schedule/recurrence.js.map +7 -0
  623. package/dist/backend/schedule/types.js +1 -0
  624. package/dist/backend/schedule/types.js.map +7 -0
  625. package/dist/backend/section-page/SectionNav.js +104 -0
  626. package/dist/backend/section-page/SectionNav.js.map +7 -0
  627. package/dist/backend/section-page/SectionPage.js +33 -0
  628. package/dist/backend/section-page/SectionPage.js.map +7 -0
  629. package/dist/backend/section-page/index.js +7 -0
  630. package/dist/backend/section-page/index.js.map +7 -0
  631. package/dist/backend/section-page/types.js +1 -0
  632. package/dist/backend/section-page/types.js.map +7 -0
  633. package/dist/backend/settings/SettingsNavigation.js +43 -0
  634. package/dist/backend/settings/SettingsNavigation.js.map +7 -0
  635. package/dist/backend/settings/SettingsPageWrapper.js +66 -0
  636. package/dist/backend/settings/SettingsPageWrapper.js.map +7 -0
  637. package/dist/backend/settings/index.js +6 -0
  638. package/dist/backend/settings/index.js.map +7 -0
  639. package/dist/backend/upgrades/UpgradeActionBanner.js +91 -0
  640. package/dist/backend/upgrades/UpgradeActionBanner.js.map +7 -0
  641. package/dist/backend/utils/api.js +178 -0
  642. package/dist/backend/utils/api.js.map +7 -0
  643. package/dist/backend/utils/apiCall.js +68 -0
  644. package/dist/backend/utils/apiCall.js.map +7 -0
  645. package/dist/backend/utils/crud.js +126 -0
  646. package/dist/backend/utils/crud.js.map +7 -0
  647. package/dist/backend/utils/customFieldColumns.js +100 -0
  648. package/dist/backend/utils/customFieldColumns.js.map +7 -0
  649. package/dist/backend/utils/customFieldDefs.js +143 -0
  650. package/dist/backend/utils/customFieldDefs.js.map +7 -0
  651. package/dist/backend/utils/customFieldFilters.js +151 -0
  652. package/dist/backend/utils/customFieldFilters.js.map +7 -0
  653. package/dist/backend/utils/customFieldForms.js +169 -0
  654. package/dist/backend/utils/customFieldForms.js.map +7 -0
  655. package/dist/backend/utils/customFieldRelationDisplay.js +239 -0
  656. package/dist/backend/utils/customFieldRelationDisplay.js.map +7 -0
  657. package/dist/backend/utils/customFieldValues.js +26 -0
  658. package/dist/backend/utils/customFieldValues.js.map +7 -0
  659. package/dist/backend/utils/flash.js +16 -0
  660. package/dist/backend/utils/flash.js.map +7 -0
  661. package/dist/backend/utils/nav.js +280 -0
  662. package/dist/backend/utils/nav.js.map +7 -0
  663. package/dist/backend/utils/richTextSanitizer.js +11 -0
  664. package/dist/backend/utils/richTextSanitizer.js.map +7 -0
  665. package/dist/backend/utils/scopedHeaderStack.js +44 -0
  666. package/dist/backend/utils/scopedHeaderStack.js.map +7 -0
  667. package/dist/backend/utils/serverErrors.js +237 -0
  668. package/dist/backend/utils/serverErrors.js.map +7 -0
  669. package/dist/backend/utils/useAutoDiscoveredFields.js +96 -0
  670. package/dist/backend/utils/useAutoDiscoveredFields.js.map +7 -0
  671. package/dist/backend/version-history/VersionHistoryAction.js +67 -0
  672. package/dist/backend/version-history/VersionHistoryAction.js.map +7 -0
  673. package/dist/backend/version-history/VersionHistoryDetail.js +57 -0
  674. package/dist/backend/version-history/VersionHistoryDetail.js.map +7 -0
  675. package/dist/backend/version-history/VersionHistoryPanel.js +270 -0
  676. package/dist/backend/version-history/VersionHistoryPanel.js.map +7 -0
  677. package/dist/backend/version-history/index.js +13 -0
  678. package/dist/backend/version-history/index.js.map +7 -0
  679. package/dist/backend/version-history/labels.js +52 -0
  680. package/dist/backend/version-history/labels.js.map +7 -0
  681. package/dist/backend/version-history/types.js +1 -0
  682. package/dist/backend/version-history/types.js.map +7 -0
  683. package/dist/backend/version-history/useAuditPermissions.js +72 -0
  684. package/dist/backend/version-history/useAuditPermissions.js.map +7 -0
  685. package/dist/backend/version-history/useVersionHistory.js +141 -0
  686. package/dist/backend/version-history/useVersionHistory.js.map +7 -0
  687. package/dist/backend/views/NewViewForm.js +56 -0
  688. package/dist/backend/views/NewViewForm.js.map +7 -0
  689. package/dist/backend/views/ShareForm.js +49 -0
  690. package/dist/backend/views/ShareForm.js.map +7 -0
  691. package/dist/backend/views/ViewChip.js +112 -0
  692. package/dist/backend/views/ViewChip.js.map +7 -0
  693. package/dist/backend/views/ViewChipMenu.js +89 -0
  694. package/dist/backend/views/ViewChipMenu.js.map +7 -0
  695. package/dist/backend/views/types.js +1 -0
  696. package/dist/backend/views/types.js.map +7 -0
  697. package/dist/frontend/AuthFooter.js +23 -0
  698. package/dist/frontend/AuthFooter.js.map +7 -0
  699. package/dist/frontend/LanguageSwitcher.js +57 -0
  700. package/dist/frontend/LanguageSwitcher.js.map +7 -0
  701. package/dist/frontend/Layout.js +14 -0
  702. package/dist/frontend/Layout.js.map +7 -0
  703. package/dist/hooks/useIsMobile.js +21 -0
  704. package/dist/hooks/useIsMobile.js.map +7 -0
  705. package/dist/index.js +40 -0
  706. package/dist/index.js.map +7 -0
  707. package/dist/portal/PortalContext.js +113 -0
  708. package/dist/portal/PortalContext.js.map +7 -0
  709. package/dist/portal/PortalLayoutProvider.js +5 -0
  710. package/dist/portal/PortalLayoutProvider.js.map +7 -0
  711. package/dist/portal/PortalLayoutShell.js +44 -0
  712. package/dist/portal/PortalLayoutShell.js.map +7 -0
  713. package/dist/portal/PortalShell.js +229 -0
  714. package/dist/portal/PortalShell.js.map +7 -0
  715. package/dist/portal/components/PortalCard.js +31 -0
  716. package/dist/portal/components/PortalCard.js.map +7 -0
  717. package/dist/portal/components/PortalEmptyState.js +16 -0
  718. package/dist/portal/components/PortalEmptyState.js.map +7 -0
  719. package/dist/portal/components/PortalFeatureCard.js +21 -0
  720. package/dist/portal/components/PortalFeatureCard.js.map +7 -0
  721. package/dist/portal/components/PortalNotificationBell.js +67 -0
  722. package/dist/portal/components/PortalNotificationBell.js.map +7 -0
  723. package/dist/portal/components/PortalNotificationPanel.js +158 -0
  724. package/dist/portal/components/PortalNotificationPanel.js.map +7 -0
  725. package/dist/portal/components/PortalPageHeader.js +16 -0
  726. package/dist/portal/components/PortalPageHeader.js.map +7 -0
  727. package/dist/portal/components/index.js +18 -0
  728. package/dist/portal/components/index.js.map +7 -0
  729. package/dist/portal/hooks/useCustomerAuth.js +69 -0
  730. package/dist/portal/hooks/useCustomerAuth.js.map +7 -0
  731. package/dist/portal/hooks/usePortalAppEvent.js +26 -0
  732. package/dist/portal/hooks/usePortalAppEvent.js.map +7 -0
  733. package/dist/portal/hooks/usePortalDashboardWidgets.js +37 -0
  734. package/dist/portal/hooks/usePortalDashboardWidgets.js.map +7 -0
  735. package/dist/portal/hooks/usePortalEventBridge.js +129 -0
  736. package/dist/portal/hooks/usePortalEventBridge.js.map +7 -0
  737. package/dist/portal/hooks/usePortalInjectedMenuItems.js +88 -0
  738. package/dist/portal/hooks/usePortalInjectedMenuItems.js.map +7 -0
  739. package/dist/portal/hooks/usePortalNotifications.js +85 -0
  740. package/dist/portal/hooks/usePortalNotifications.js.map +7 -0
  741. package/dist/portal/hooks/useTenantContext.js +53 -0
  742. package/dist/portal/hooks/useTenantContext.js.map +7 -0
  743. package/dist/primitives/DataLoader.js +68 -0
  744. package/dist/primitives/DataLoader.js.map +7 -0
  745. package/dist/primitives/ErrorNotice.js +14 -0
  746. package/dist/primitives/ErrorNotice.js.map +7 -0
  747. package/dist/primitives/Notice.js +55 -0
  748. package/dist/primitives/Notice.js.map +7 -0
  749. package/dist/primitives/alert.js +38 -0
  750. package/dist/primitives/alert.js.map +7 -0
  751. package/dist/primitives/badge.js +33 -0
  752. package/dist/primitives/badge.js.map +7 -0
  753. package/dist/primitives/button.js +45 -0
  754. package/dist/primitives/button.js.map +7 -0
  755. package/dist/primitives/calendar.js +62 -0
  756. package/dist/primitives/calendar.js.map +7 -0
  757. package/dist/primitives/card.js +91 -0
  758. package/dist/primitives/card.js.map +7 -0
  759. package/dist/primitives/checkbox.js +28 -0
  760. package/dist/primitives/checkbox.js.map +7 -0
  761. package/dist/primitives/dialog.js +101 -0
  762. package/dist/primitives/dialog.js.map +7 -0
  763. package/dist/primitives/form-field.js +78 -0
  764. package/dist/primitives/form-field.js.map +7 -0
  765. package/dist/primitives/icon-button.js +40 -0
  766. package/dist/primitives/icon-button.js.map +7 -0
  767. package/dist/primitives/input.js +22 -0
  768. package/dist/primitives/input.js.map +7 -0
  769. package/dist/primitives/label.js +21 -0
  770. package/dist/primitives/label.js.map +7 -0
  771. package/dist/primitives/popover.js +36 -0
  772. package/dist/primitives/popover.js.map +7 -0
  773. package/dist/primitives/primitives.stories.js +187 -0
  774. package/dist/primitives/primitives.stories.js.map +7 -0
  775. package/dist/primitives/progress.js +28 -0
  776. package/dist/primitives/progress.js.map +7 -0
  777. package/dist/primitives/separator.js +9 -0
  778. package/dist/primitives/separator.js.map +7 -0
  779. package/dist/primitives/sheet.js +103 -0
  780. package/dist/primitives/sheet.js.map +7 -0
  781. package/dist/primitives/spinner.js +24 -0
  782. package/dist/primitives/spinner.js.map +7 -0
  783. package/dist/primitives/status-badge.js +38 -0
  784. package/dist/primitives/status-badge.js.map +7 -0
  785. package/dist/primitives/switch.js +80 -0
  786. package/dist/primitives/switch.js.map +7 -0
  787. package/dist/primitives/table.js +29 -0
  788. package/dist/primitives/table.js.map +7 -0
  789. package/dist/primitives/tabs.js +90 -0
  790. package/dist/primitives/tabs.js.map +7 -0
  791. package/dist/primitives/textarea.js +21 -0
  792. package/dist/primitives/textarea.js.map +7 -0
  793. package/dist/primitives/tooltip.js +60 -0
  794. package/dist/primitives/tooltip.js.map +7 -0
  795. package/dist/primitives-v2/Alert.js +69 -0
  796. package/dist/primitives-v2/Alert.js.map +7 -0
  797. package/dist/primitives-v2/Alert.stories.js +42 -0
  798. package/dist/primitives-v2/Alert.stories.js.map +7 -0
  799. package/dist/primitives-v2/Avatar.js +93 -0
  800. package/dist/primitives-v2/Avatar.js.map +7 -0
  801. package/dist/primitives-v2/Avatar.stories.js +57 -0
  802. package/dist/primitives-v2/Avatar.stories.js.map +7 -0
  803. package/dist/primitives-v2/Badge.js +50 -0
  804. package/dist/primitives-v2/Badge.js.map +7 -0
  805. package/dist/primitives-v2/Badge.stories.js +66 -0
  806. package/dist/primitives-v2/Badge.stories.js.map +7 -0
  807. package/dist/primitives-v2/Breadcrumb.js +53 -0
  808. package/dist/primitives-v2/Breadcrumb.js.map +7 -0
  809. package/dist/primitives-v2/Breadcrumb.stories.js +70 -0
  810. package/dist/primitives-v2/Breadcrumb.stories.js.map +7 -0
  811. package/dist/primitives-v2/Button.js +85 -0
  812. package/dist/primitives-v2/Button.js.map +7 -0
  813. package/dist/primitives-v2/Button.stories.js +48 -0
  814. package/dist/primitives-v2/Button.stories.js.map +7 -0
  815. package/dist/primitives-v2/Checkbox.js +65 -0
  816. package/dist/primitives-v2/Checkbox.js.map +7 -0
  817. package/dist/primitives-v2/Checkbox.stories.js +31 -0
  818. package/dist/primitives-v2/Checkbox.stories.js.map +7 -0
  819. package/dist/primitives-v2/IconButton.js +13 -0
  820. package/dist/primitives-v2/IconButton.js.map +7 -0
  821. package/dist/primitives-v2/IconButton.stories.js +37 -0
  822. package/dist/primitives-v2/IconButton.stories.js.map +7 -0
  823. package/dist/primitives-v2/Input.js +74 -0
  824. package/dist/primitives-v2/Input.js.map +7 -0
  825. package/dist/primitives-v2/Input.stories.js +56 -0
  826. package/dist/primitives-v2/Input.stories.js.map +7 -0
  827. package/dist/primitives-v2/NavGroup.js +54 -0
  828. package/dist/primitives-v2/NavGroup.js.map +7 -0
  829. package/dist/primitives-v2/NavGroup.stories.js +39 -0
  830. package/dist/primitives-v2/NavGroup.stories.js.map +7 -0
  831. package/dist/primitives-v2/NavItem.js +61 -0
  832. package/dist/primitives-v2/NavItem.js.map +7 -0
  833. package/dist/primitives-v2/NavItem.stories.js +56 -0
  834. package/dist/primitives-v2/NavItem.stories.js.map +7 -0
  835. package/dist/primitives-v2/SearchInput.js +93 -0
  836. package/dist/primitives-v2/SearchInput.js.map +7 -0
  837. package/dist/primitives-v2/SearchInput.stories.js +41 -0
  838. package/dist/primitives-v2/SearchInput.stories.js.map +7 -0
  839. package/dist/primitives-v2/Sidebar.js +48 -0
  840. package/dist/primitives-v2/Sidebar.js.map +7 -0
  841. package/dist/primitives-v2/Sidebar.stories.js +76 -0
  842. package/dist/primitives-v2/Sidebar.stories.js.map +7 -0
  843. package/dist/primitives-v2/Tab.js +78 -0
  844. package/dist/primitives-v2/Tab.js.map +7 -0
  845. package/dist/primitives-v2/Tab.stories.js +51 -0
  846. package/dist/primitives-v2/Tab.stories.js.map +7 -0
  847. package/dist/primitives-v2/Tag.js +61 -0
  848. package/dist/primitives-v2/Tag.js.map +7 -0
  849. package/dist/primitives-v2/Tag.stories.js +121 -0
  850. package/dist/primitives-v2/Tag.stories.js.map +7 -0
  851. package/dist/primitives-v2/Toggle.js +72 -0
  852. package/dist/primitives-v2/Toggle.js.map +7 -0
  853. package/dist/primitives-v2/Toggle.stories.js +34 -0
  854. package/dist/primitives-v2/Toggle.stories.js.map +7 -0
  855. package/dist/primitives-v2/Topbar.js +24 -0
  856. package/dist/primitives-v2/Topbar.js.map +7 -0
  857. package/dist/primitives-v2/Topbar.stories.js +58 -0
  858. package/dist/primitives-v2/Topbar.stories.js.map +7 -0
  859. package/dist/primitives-v2/index.js +41 -0
  860. package/dist/primitives-v2/index.js.map +7 -0
  861. package/dist/primitives-v2/utils.js +7 -0
  862. package/dist/primitives-v2/utils.js.map +7 -0
  863. package/dist/theme/BrandThemeProvider.js +49 -0
  864. package/dist/theme/BrandThemeProvider.js.map +7 -0
  865. package/dist/theme/QueryProvider.js +44 -0
  866. package/dist/theme/QueryProvider.js.map +7 -0
  867. package/dist/theme/ThemeProvider.js +96 -0
  868. package/dist/theme/ThemeProvider.js.map +7 -0
  869. package/dist/theme/ThemeToggle.js +88 -0
  870. package/dist/theme/ThemeToggle.js.map +7 -0
  871. package/dist/theme/index.js +12 -0
  872. package/dist/theme/index.js.map +7 -0
  873. package/dist/theme/m3.css.js +2 -0
  874. package/dist/theme/tokens.v2.css.js +2 -0
  875. package/dist/theme/typography.css.js +2 -0
  876. package/dist/theme/typography.stories.js +123 -0
  877. package/dist/theme/typography.stories.js.map +7 -0
  878. package/dist/theme/typography.tokens.js +281 -0
  879. package/dist/theme/typography.tokens.js.map +7 -0
  880. package/dist/types/react-big-calendar.d.js +1 -0
  881. package/dist/types/react-big-calendar.d.js.map +7 -0
  882. package/package.json +411 -0
  883. package/src/backend/AppShell.tsx +1606 -0
  884. package/src/backend/BackendChromeProvider.tsx +99 -0
  885. package/src/backend/BackendPageSurface.tsx +57 -0
  886. package/src/backend/CollapsibleNavSection.tsx +201 -0
  887. package/src/backend/ContextHelp.tsx +40 -0
  888. package/src/backend/CrudForm.tsx +3796 -0
  889. package/src/backend/DataTable.tsx +2584 -0
  890. package/src/backend/EmptyState.tsx +65 -0
  891. package/src/backend/FilterBar.tsx +171 -0
  892. package/src/backend/FilterOverlay.tsx +388 -0
  893. package/src/backend/FlashMessages.tsx +189 -0
  894. package/src/backend/HtmlRichTextEditor.tsx +124 -0
  895. package/src/backend/IntegrationsButton.tsx +29 -0
  896. package/src/backend/JsonBuilder.tsx +359 -0
  897. package/src/backend/JsonDisplay.tsx +258 -0
  898. package/src/backend/NextStepCallout.tsx +134 -0
  899. package/src/backend/Page.tsx +30 -0
  900. package/src/backend/PerspectiveSidebar.tsx +506 -0
  901. package/src/backend/ProfileDropdown.tsx +369 -0
  902. package/src/backend/RowActions.tsx +158 -0
  903. package/src/backend/SectionHeader.tsx +119 -0
  904. package/src/backend/SettingsButton.tsx +28 -0
  905. package/src/backend/Sidebar.stories.tsx +183 -0
  906. package/src/backend/Sidebar.tsx +737 -0
  907. package/src/backend/TruncatedCell.tsx +133 -0
  908. package/src/backend/UserMenu.tsx +153 -0
  909. package/src/backend/ValueIcons.tsx +48 -0
  910. package/src/backend/WebhookSetupGuide.tsx +127 -0
  911. package/src/backend/__tests__/AppShell.test.tsx +392 -0
  912. package/src/backend/__tests__/CrudForm.custom-fields.test.tsx +180 -0
  913. package/src/backend/__tests__/CrudForm.datetime.test.tsx +95 -0
  914. package/src/backend/__tests__/CrudForm.defaults.test.tsx +195 -0
  915. package/src/backend/__tests__/CrudForm.navigation.test.tsx +156 -0
  916. package/src/backend/__tests__/CrudForm.render.test.tsx +146 -0
  917. package/src/backend/__tests__/CrudForm.transformData.test.tsx +231 -0
  918. package/src/backend/__tests__/CrudForm.unsavedNavigation.test.tsx +145 -0
  919. package/src/backend/__tests__/CrudForm.validation.test.tsx +178 -0
  920. package/src/backend/__tests__/CustomDataSection.test.tsx +465 -0
  921. package/src/backend/__tests__/DataTable.extensions.test.tsx +339 -0
  922. package/src/backend/__tests__/DataTable.namespaces.test.ts +32 -0
  923. package/src/backend/__tests__/DataTable.render.test.tsx +48 -0
  924. package/src/backend/__tests__/FieldDefinitionsEditor.test.tsx +64 -0
  925. package/src/backend/__tests__/FlashMessages.test.tsx +105 -0
  926. package/src/backend/__tests__/FormHeader.test.tsx +81 -0
  927. package/src/backend/__tests__/NotificationDispatcher.test.ts +172 -0
  928. package/src/backend/__tests__/VersionHistoryPanel.test.tsx +109 -0
  929. package/src/backend/__tests__/component-replacement.test.tsx +270 -0
  930. package/src/backend/__tests__/custom-field-filters.test.ts +72 -0
  931. package/src/backend/__tests__/custom-field-forms.test.ts +54 -0
  932. package/src/backend/__tests__/nav-utils.test.ts +175 -0
  933. package/src/backend/__tests__/serverErrors.test.ts +83 -0
  934. package/src/backend/__tests__/useAuditPermissions.test.tsx +51 -0
  935. package/src/backend/__tests__/useNotifications.strategy.test.ts +58 -0
  936. package/src/backend/__tests__/useProgress.strategy.test.ts +62 -0
  937. package/src/backend/charts/BarChart.tsx +208 -0
  938. package/src/backend/charts/ChartUtils.tsx +196 -0
  939. package/src/backend/charts/ComboChart.stories.tsx +103 -0
  940. package/src/backend/charts/ComboChart.tsx +350 -0
  941. package/src/backend/charts/KpiCard.tsx +155 -0
  942. package/src/backend/charts/LineChart.tsx +207 -0
  943. package/src/backend/charts/PieChart.tsx +157 -0
  944. package/src/backend/charts/ScatterChart.tsx +498 -0
  945. package/src/backend/charts/TopNTable.tsx +141 -0
  946. package/src/backend/charts/WaterfallChart.tsx +213 -0
  947. package/src/backend/charts/index.ts +21 -0
  948. package/src/backend/columns/ColumnChooserPanel.tsx +293 -0
  949. package/src/backend/confirm-dialog/ConfirmDialog.tsx +275 -0
  950. package/src/backend/confirm-dialog/index.ts +7 -0
  951. package/src/backend/confirm-dialog/useConfirmDialog.tsx +146 -0
  952. package/src/backend/custom-fields/FieldDefinitionsEditor.tsx +1413 -0
  953. package/src/backend/custom-fields/FieldDefinitionsManager.tsx +381 -0
  954. package/src/backend/dashboard/DashboardScreen.tsx +706 -0
  955. package/src/backend/dashboard/__tests__/DashboardScreen.test.tsx +114 -0
  956. package/src/backend/dashboard/index.ts +1 -0
  957. package/src/backend/dashboard/widgetRegistry.ts +68 -0
  958. package/src/backend/date-range/DateRangeSelect.tsx +50 -0
  959. package/src/backend/date-range/InlineDateRangeSelect.tsx +52 -0
  960. package/src/backend/date-range/InlineGranularitySelect.tsx +63 -0
  961. package/src/backend/date-range/__tests__/dateRanges.test.ts +224 -0
  962. package/src/backend/date-range/dateRanges.ts +218 -0
  963. package/src/backend/date-range/index.ts +15 -0
  964. package/src/backend/detail/AccessDeniedMessage.tsx +32 -0
  965. package/src/backend/detail/ActivitiesSection.tsx +1261 -0
  966. package/src/backend/detail/AddressEditor.tsx +479 -0
  967. package/src/backend/detail/AddressTiles.tsx +591 -0
  968. package/src/backend/detail/AddressesSection.tsx +362 -0
  969. package/src/backend/detail/AttachmentDeleteDialog.tsx +56 -0
  970. package/src/backend/detail/AttachmentMetadataDialog.tsx +674 -0
  971. package/src/backend/detail/AttachmentVisualPreview.tsx +155 -0
  972. package/src/backend/detail/AttachmentsSection.tsx +327 -0
  973. package/src/backend/detail/CustomDataSection.tsx +700 -0
  974. package/src/backend/detail/DetailFieldsSection.tsx +163 -0
  975. package/src/backend/detail/ErrorMessage.tsx +32 -0
  976. package/src/backend/detail/InlineEditors.tsx +894 -0
  977. package/src/backend/detail/LoadingMessage.tsx +14 -0
  978. package/src/backend/detail/NotesSection.tsx +1266 -0
  979. package/src/backend/detail/TabEmptyState.tsx +48 -0
  980. package/src/backend/detail/TagsSection.tsx +470 -0
  981. package/src/backend/detail/__tests__/InlineEditors.test.tsx +59 -0
  982. package/src/backend/detail/addressFormat.tsx +121 -0
  983. package/src/backend/detail/index.ts +46 -0
  984. package/src/backend/devtools/UmesDevToolsPanel.tsx +122 -0
  985. package/src/backend/devtools/components/ConflictWarnings.tsx +36 -0
  986. package/src/backend/devtools/components/EnricherTiming.tsx +50 -0
  987. package/src/backend/devtools/components/EventFlow.tsx +45 -0
  988. package/src/backend/devtools/components/ExtensionPointList.tsx +59 -0
  989. package/src/backend/devtools/components/InterceptorActivity.tsx +48 -0
  990. package/src/backend/devtools/index.ts +2 -0
  991. package/src/backend/devtools/useUmesDevTools.ts +188 -0
  992. package/src/backend/document-viewer/RegionOverlay.tsx +150 -0
  993. package/src/backend/document-viewer/index.ts +2 -0
  994. package/src/backend/dynamic-grid/DynamicGrid.tsx +161 -0
  995. package/src/backend/dynamic-grid/adapters.ts +57 -0
  996. package/src/backend/dynamic-grid/components/GridCell.tsx +194 -0
  997. package/src/backend/dynamic-grid/components/GridRow.tsx +40 -0
  998. package/src/backend/dynamic-grid/hooks/useGridKeyboardNav.ts +134 -0
  999. package/src/backend/dynamic-grid/index.ts +4 -0
  1000. package/src/backend/dynamic-grid/types.ts +55 -0
  1001. package/src/backend/dynamic-table/DynamicTable.stories.tsx +274 -0
  1002. package/src/backend/dynamic-table/DynamicTable.tsx +4419 -0
  1003. package/src/backend/dynamic-table/README.md +1199 -0
  1004. package/src/backend/dynamic-table/URL_FILTERING.md +217 -0
  1005. package/src/backend/dynamic-table/__e2e__/GridHarness.ts +1155 -0
  1006. package/src/backend/dynamic-table/__e2e__/SplitViewHarness.ts +366 -0
  1007. package/src/backend/dynamic-table/__tests__/BulkActionsBar.test.tsx +143 -0
  1008. package/src/backend/dynamic-table/__tests__/ColumnQuickFilter.test.tsx +607 -0
  1009. package/src/backend/dynamic-table/__tests__/ConfigureViewFilters.suggestions.test.tsx +177 -0
  1010. package/src/backend/dynamic-table/__tests__/ConfigureViewFormatting.test.tsx +125 -0
  1011. package/src/backend/dynamic-table/__tests__/ConfigureViewFormulas.test.tsx +228 -0
  1012. package/src/backend/dynamic-table/__tests__/ConfigureViewGrouping.aggregations.test.tsx +131 -0
  1013. package/src/backend/dynamic-table/__tests__/ConfigureViewPanel.baseView.test.tsx +162 -0
  1014. package/src/backend/dynamic-table/__tests__/ConfigureViewPanel.focus.test.tsx +54 -0
  1015. package/src/backend/dynamic-table/__tests__/ConfigureViewPanel.sections.test.tsx +142 -0
  1016. package/src/backend/dynamic-table/__tests__/ConfigureViewRollups.test.tsx +228 -0
  1017. package/src/backend/dynamic-table/__tests__/ContextMenu.test.tsx +89 -0
  1018. package/src/backend/dynamic-table/__tests__/DynamicTable.density.test.tsx +299 -0
  1019. package/src/backend/dynamic-table/__tests__/DynamicTable.exportTruncation.test.tsx +145 -0
  1020. package/src/backend/dynamic-table/__tests__/DynamicTable.loadError.test.tsx +173 -0
  1021. package/src/backend/dynamic-table/__tests__/DynamicTable.wiring.test.tsx +357 -0
  1022. package/src/backend/dynamic-table/__tests__/EmptyState.test.tsx +70 -0
  1023. package/src/backend/dynamic-table/__tests__/ExportMenu.test.tsx +82 -0
  1024. package/src/backend/dynamic-table/__tests__/FooterTotalsRow.test.tsx +154 -0
  1025. package/src/backend/dynamic-table/__tests__/GroupSummaryRow.render.test.tsx +131 -0
  1026. package/src/backend/dynamic-table/__tests__/LoadErrorState.test.tsx +74 -0
  1027. package/src/backend/dynamic-table/__tests__/MentionPopup.search.test.tsx +127 -0
  1028. package/src/backend/dynamic-table/__tests__/PerspectiveTabs.test.tsx +170 -0
  1029. package/src/backend/dynamic-table/__tests__/RowHeaderCell.selection.test.tsx +80 -0
  1030. package/src/backend/dynamic-table/__tests__/abbreviationsSubscription.test.ts +171 -0
  1031. package/src/backend/dynamic-table/__tests__/aggregate.spec.test.ts +224 -0
  1032. package/src/backend/dynamic-table/__tests__/aggregateDimension.test.tsx +191 -0
  1033. package/src/backend/dynamic-table/__tests__/anchoredPosition.test.ts +88 -0
  1034. package/src/backend/dynamic-table/__tests__/badgeAutoFit.test.ts +121 -0
  1035. package/src/backend/dynamic-table/__tests__/cellHandlers.save.test.ts +282 -0
  1036. package/src/backend/dynamic-table/__tests__/cellWrites.test.ts +274 -0
  1037. package/src/backend/dynamic-table/__tests__/clipboard.test.ts +230 -0
  1038. package/src/backend/dynamic-table/__tests__/coerceCellValue.test.ts +117 -0
  1039. package/src/backend/dynamic-table/__tests__/colorAdjacency.test.ts +151 -0
  1040. package/src/backend/dynamic-table/__tests__/columnFill.test.ts +66 -0
  1041. package/src/backend/dynamic-table/__tests__/columnHeaderHandlers.sort.test.ts +100 -0
  1042. package/src/backend/dynamic-table/__tests__/columnHeaderReorder.test.tsx +209 -0
  1043. package/src/backend/dynamic-table/__tests__/columnReorderPersistence.test.tsx +127 -0
  1044. package/src/backend/dynamic-table/__tests__/columnVirtualization.render.test.tsx +420 -0
  1045. package/src/backend/dynamic-table/__tests__/columnVisibilityPersistence.test.tsx +136 -0
  1046. package/src/backend/dynamic-table/__tests__/columnWindow.test.ts +643 -0
  1047. package/src/backend/dynamic-table/__tests__/commentAudience.test.tsx +137 -0
  1048. package/src/backend/dynamic-table/__tests__/commentScope.test.tsx +214 -0
  1049. package/src/backend/dynamic-table/__tests__/conditionalFormat.test.ts +175 -0
  1050. package/src/backend/dynamic-table/__tests__/contentRegistry.test.ts +267 -0
  1051. package/src/backend/dynamic-table/__tests__/dateWindows.test.ts +149 -0
  1052. package/src/backend/dynamic-table/__tests__/density.test.ts +204 -0
  1053. package/src/backend/dynamic-table/__tests__/exportTable.test.ts +146 -0
  1054. package/src/backend/dynamic-table/__tests__/fillGeometry.test.ts +201 -0
  1055. package/src/backend/dynamic-table/__tests__/fillPatterns.test.ts +272 -0
  1056. package/src/backend/dynamic-table/__tests__/filterParser.presets.test.ts +64 -0
  1057. package/src/backend/dynamic-table/__tests__/filterParser.relative.test.ts +109 -0
  1058. package/src/backend/dynamic-table/__tests__/filterSuggestions.test.ts +152 -0
  1059. package/src/backend/dynamic-table/__tests__/formatAggregate.test.ts +99 -0
  1060. package/src/backend/dynamic-table/__tests__/formatDateCompact.test.ts +489 -0
  1061. package/src/backend/dynamic-table/__tests__/formatLabelCompact.test.ts +717 -0
  1062. package/src/backend/dynamic-table/__tests__/formula.check.test.ts +257 -0
  1063. package/src/backend/dynamic-table/__tests__/formula.evaluate.test.ts +274 -0
  1064. package/src/backend/dynamic-table/__tests__/formula.functions.test.ts +226 -0
  1065. package/src/backend/dynamic-table/__tests__/formula.noEval.test.ts +92 -0
  1066. package/src/backend/dynamic-table/__tests__/formula.parse.test.ts +233 -0
  1067. package/src/backend/dynamic-table/__tests__/formulaColumns.test.tsx +213 -0
  1068. package/src/backend/dynamic-table/__tests__/handlerIdentity.test.ts +77 -0
  1069. package/src/backend/dynamic-table/__tests__/importMapping.test.ts +174 -0
  1070. package/src/backend/dynamic-table/__tests__/importParse.test.ts +130 -0
  1071. package/src/backend/dynamic-table/__tests__/importServerOnly.test.ts +37 -0
  1072. package/src/backend/dynamic-table/__tests__/layoutPersistence.test.ts +372 -0
  1073. package/src/backend/dynamic-table/__tests__/mouseHandlers.doubleClick.test.ts +155 -0
  1074. package/src/backend/dynamic-table/__tests__/perspectivePersonalization.test.ts +188 -0
  1075. package/src/backend/dynamic-table/__tests__/perspectiveResolver.test.ts +133 -0
  1076. package/src/backend/dynamic-table/__tests__/perspectiveState.defaultGrouping.test.ts +306 -0
  1077. package/src/backend/dynamic-table/__tests__/perspectiveState.defaults.test.ts +107 -0
  1078. package/src/backend/dynamic-table/__tests__/perspectiveTemplates.test.tsx +269 -0
  1079. package/src/backend/dynamic-table/__tests__/perspectiveTransforms.aggregations.test.ts +105 -0
  1080. package/src/backend/dynamic-table/__tests__/perspectiveTransforms.conditionalFormats.test.ts +107 -0
  1081. package/src/backend/dynamic-table/__tests__/perspectiveTransforms.dateFormat.test.ts +61 -0
  1082. package/src/backend/dynamic-table/__tests__/perspectiveTransforms.formulas.test.ts +85 -0
  1083. package/src/backend/dynamic-table/__tests__/perspectiveTransforms.frozenColumns.test.ts +124 -0
  1084. package/src/backend/dynamic-table/__tests__/rollupColumns.test.ts +149 -0
  1085. package/src/backend/dynamic-table/__tests__/sharedCriteria.test.ts +255 -0
  1086. package/src/backend/dynamic-table/__tests__/splitViewTree.test.ts +324 -0
  1087. package/src/backend/dynamic-table/__tests__/store.clipboardMarker.test.ts +143 -0
  1088. package/src/backend/dynamic-table/__tests__/store.fillPreview.test.ts +168 -0
  1089. package/src/backend/dynamic-table/__tests__/store.mutations.test.ts +345 -0
  1090. package/src/backend/dynamic-table/__tests__/store.selection.test.ts +157 -0
  1091. package/src/backend/dynamic-table/__tests__/store.undo.test.ts +225 -0
  1092. package/src/backend/dynamic-table/__tests__/store.valueSource.test.tsx +204 -0
  1093. package/src/backend/dynamic-table/__tests__/useAnnotations.legacyOptions.test.tsx +58 -0
  1094. package/src/backend/dynamic-table/__tests__/useColumnVirtualizer.test.ts +290 -0
  1095. package/src/backend/dynamic-table/__tests__/useDensityPreference.test.ts +435 -0
  1096. package/src/backend/dynamic-table/__tests__/useDynamicTablePage.duplicate.test.tsx +99 -0
  1097. package/src/backend/dynamic-table/__tests__/useDynamicTablePage.export.test.tsx +281 -0
  1098. package/src/backend/dynamic-table/__tests__/useDynamicTablePage.personalization.test.tsx +313 -0
  1099. package/src/backend/dynamic-table/__tests__/useDynamicTablePage.perspectiveRestore.test.tsx +126 -0
  1100. package/src/backend/dynamic-table/__tests__/useGrouping.aggregations.test.ts +158 -0
  1101. package/src/backend/dynamic-table/__tests__/useGrouping.groupKey.test.ts +58 -0
  1102. package/src/backend/dynamic-table/__tests__/useKeyboardNavigation.test.ts +594 -0
  1103. package/src/backend/dynamic-table/__tests__/useStickyOffsets.memo.test.tsx +75 -0
  1104. package/src/backend/dynamic-table/__tests__/useStickyOffsets.test.ts +106 -0
  1105. package/src/backend/dynamic-table/__tests__/widgetPane.test.tsx +338 -0
  1106. package/src/backend/dynamic-table/components/ActiveFilterChips.tsx +109 -0
  1107. package/src/backend/dynamic-table/components/BulkActionsBar.tsx +89 -0
  1108. package/src/backend/dynamic-table/components/Cell.tsx +292 -0
  1109. package/src/backend/dynamic-table/components/CellCommentDialog.tsx +887 -0
  1110. package/src/backend/dynamic-table/components/CellCommentHoverPopup.tsx +94 -0
  1111. package/src/backend/dynamic-table/components/ColumnFilterPopover.tsx +775 -0
  1112. package/src/backend/dynamic-table/components/ColumnHeaderMenu.tsx +189 -0
  1113. package/src/backend/dynamic-table/components/ColumnHeaders.tsx +867 -0
  1114. package/src/backend/dynamic-table/components/CompactLabelCell.tsx +293 -0
  1115. package/src/backend/dynamic-table/components/ConfigureViewFields.tsx +173 -0
  1116. package/src/backend/dynamic-table/components/ConfigureViewFilters.tsx +346 -0
  1117. package/src/backend/dynamic-table/components/ConfigureViewFormatting.tsx +205 -0
  1118. package/src/backend/dynamic-table/components/ConfigureViewFormulas.tsx +423 -0
  1119. package/src/backend/dynamic-table/components/ConfigureViewGrouping.tsx +264 -0
  1120. package/src/backend/dynamic-table/components/ConfigureViewLinkedColumns.tsx +249 -0
  1121. package/src/backend/dynamic-table/components/ConfigureViewPanel.tsx +857 -0
  1122. package/src/backend/dynamic-table/components/ConfigureViewRollups.tsx +364 -0
  1123. package/src/backend/dynamic-table/components/ConfigureViewSorting.tsx +121 -0
  1124. package/src/backend/dynamic-table/components/ContextMenu.tsx +140 -0
  1125. package/src/backend/dynamic-table/components/DateCell.tsx +205 -0
  1126. package/src/backend/dynamic-table/components/Debugger.tsx +340 -0
  1127. package/src/backend/dynamic-table/components/DensityControl.tsx +441 -0
  1128. package/src/backend/dynamic-table/components/EmptyState.tsx +103 -0
  1129. package/src/backend/dynamic-table/components/EntitySearchEditor.tsx +528 -0
  1130. package/src/backend/dynamic-table/components/ExportMenu.tsx +90 -0
  1131. package/src/backend/dynamic-table/components/FilterDatePicker.tsx +194 -0
  1132. package/src/backend/dynamic-table/components/FilterValueInput.tsx +294 -0
  1133. package/src/backend/dynamic-table/components/FooterTotalsRow.tsx +291 -0
  1134. package/src/backend/dynamic-table/components/FullscreenOverlay.tsx +66 -0
  1135. package/src/backend/dynamic-table/components/GroupHeaderRow.tsx +136 -0
  1136. package/src/backend/dynamic-table/components/GroupSummaryRow.tsx +184 -0
  1137. package/src/backend/dynamic-table/components/ImportPanel.tsx +445 -0
  1138. package/src/backend/dynamic-table/components/ImportPreviewGrid.tsx +155 -0
  1139. package/src/backend/dynamic-table/components/LoadErrorState.tsx +70 -0
  1140. package/src/backend/dynamic-table/components/MentionPopup.tsx +224 -0
  1141. package/src/backend/dynamic-table/components/PerspectiveDeleteDialog.tsx +96 -0
  1142. package/src/backend/dynamic-table/components/PerspectivePublishDialog.tsx +152 -0
  1143. package/src/backend/dynamic-table/components/PerspectiveTabMenu.tsx +215 -0
  1144. package/src/backend/dynamic-table/components/PerspectiveTabs.tsx +426 -0
  1145. package/src/backend/dynamic-table/components/PerspectiveTemplateMenu.tsx +135 -0
  1146. package/src/backend/dynamic-table/components/RowHeaderCell.tsx +73 -0
  1147. package/src/backend/dynamic-table/components/SearchBar.tsx +309 -0
  1148. package/src/backend/dynamic-table/components/SelectMenu.tsx +237 -0
  1149. package/src/backend/dynamic-table/components/StaleDataBar.tsx +74 -0
  1150. package/src/backend/dynamic-table/components/TableDeleteDialog.tsx +74 -0
  1151. package/src/backend/dynamic-table/components/TablePagination.tsx +96 -0
  1152. package/src/backend/dynamic-table/components/TableSkeleton.tsx +123 -0
  1153. package/src/backend/dynamic-table/components/ToolbarOverflow.tsx +284 -0
  1154. package/src/backend/dynamic-table/components/ViewModeSwitch.tsx +92 -0
  1155. package/src/backend/dynamic-table/components/VirtualRow.tsx +346 -0
  1156. package/src/backend/dynamic-table/components/editors.tsx +1727 -0
  1157. package/src/backend/dynamic-table/components/renderers.tsx +162 -0
  1158. package/src/backend/dynamic-table/events/events.ts +107 -0
  1159. package/src/backend/dynamic-table/formula/check.ts +359 -0
  1160. package/src/backend/dynamic-table/formula/evaluate.ts +473 -0
  1161. package/src/backend/dynamic-table/formula/functions.ts +737 -0
  1162. package/src/backend/dynamic-table/formula/index.ts +57 -0
  1163. package/src/backend/dynamic-table/formula/parse.ts +414 -0
  1164. package/src/backend/dynamic-table/formula/types.ts +151 -0
  1165. package/src/backend/dynamic-table/handlers/cellWrites.ts +362 -0
  1166. package/src/backend/dynamic-table/handlers/index.ts +789 -0
  1167. package/src/backend/dynamic-table/handlers/perspectiveHandlers.ts +684 -0
  1168. package/src/backend/dynamic-table/hooks/index.ts +888 -0
  1169. package/src/backend/dynamic-table/hooks/useAbbreviations.ts +67 -0
  1170. package/src/backend/dynamic-table/hooks/useAnnotations.ts +215 -0
  1171. package/src/backend/dynamic-table/hooks/useColumnVirtualizer.ts +303 -0
  1172. package/src/backend/dynamic-table/hooks/useColumnWidthPersistence.ts +214 -0
  1173. package/src/backend/dynamic-table/hooks/useDensityPreference.ts +451 -0
  1174. package/src/backend/dynamic-table/hooks/useDynamicTablePage.tsx +1681 -0
  1175. package/src/backend/dynamic-table/hooks/useEscapeLayer.ts +72 -0
  1176. package/src/backend/dynamic-table/hooks/useGrouping.ts +428 -0
  1177. package/src/backend/dynamic-table/hooks/useSuggestionFetch.ts +109 -0
  1178. package/src/backend/dynamic-table/hooks/useTableDateFormat.tsx +19 -0
  1179. package/src/backend/dynamic-table/index.ts +497 -0
  1180. package/src/backend/dynamic-table/registry/ContentRegistryContext.tsx +244 -0
  1181. package/src/backend/dynamic-table/registry/TableRegistryContext.tsx +84 -0
  1182. package/src/backend/dynamic-table/registry/filterAccessibleTables.ts +36 -0
  1183. package/src/backend/dynamic-table/registry/index.ts +38 -0
  1184. package/src/backend/dynamic-table/registry/lazyTable.tsx +80 -0
  1185. package/src/backend/dynamic-table/registry/types.ts +181 -0
  1186. package/src/backend/dynamic-table/registry/widgetCatalog.ts +226 -0
  1187. package/src/backend/dynamic-table/server/aggregate.ts +474 -0
  1188. package/src/backend/dynamic-table/server/dateWindows.ts +201 -0
  1189. package/src/backend/dynamic-table/server/filterParser.ts +132 -0
  1190. package/src/backend/dynamic-table/server/filterSuggestions.ts +150 -0
  1191. package/src/backend/dynamic-table/server/index.ts +47 -0
  1192. package/src/backend/dynamic-table/server/makeDynamicTableRoute.ts +426 -0
  1193. package/src/backend/dynamic-table/split-view/ContentPicker.tsx +251 -0
  1194. package/src/backend/dynamic-table/split-view/EmptySlot.tsx +69 -0
  1195. package/src/backend/dynamic-table/split-view/SplitViewHost.tsx +1336 -0
  1196. package/src/backend/dynamic-table/split-view/WidgetPane.tsx +390 -0
  1197. package/src/backend/dynamic-table/split-view/WorkspaceFilterBar.tsx +728 -0
  1198. package/src/backend/dynamic-table/split-view/index.ts +48 -0
  1199. package/src/backend/dynamic-table/split-view/layoutPersistence.ts +185 -0
  1200. package/src/backend/dynamic-table/split-view/sharedCriteria.ts +196 -0
  1201. package/src/backend/dynamic-table/split-view/types.ts +778 -0
  1202. package/src/backend/dynamic-table/split-view/useSplitViewLayouts.ts +97 -0
  1203. package/src/backend/dynamic-table/store/index.ts +1011 -0
  1204. package/src/backend/dynamic-table/styles/ContextMenu.css +387 -0
  1205. package/src/backend/dynamic-table/styles/DynamicTable.css +3659 -0
  1206. package/src/backend/dynamic-table/styles/DynamicTable.v2.css +2651 -0
  1207. package/src/backend/dynamic-table/styles/SearchBar.css +426 -0
  1208. package/src/backend/dynamic-table/styles/dateCell.css +146 -0
  1209. package/src/backend/dynamic-table/styles/density.css +690 -0
  1210. package/src/backend/dynamic-table/types/density.ts +179 -0
  1211. package/src/backend/dynamic-table/types/filters.ts +156 -0
  1212. package/src/backend/dynamic-table/types/grouping.ts +189 -0
  1213. package/src/backend/dynamic-table/types/import.ts +132 -0
  1214. package/src/backend/dynamic-table/types/index.ts +1198 -0
  1215. package/src/backend/dynamic-table/types/perspective.ts +436 -0
  1216. package/src/backend/dynamic-table/types/rollup.ts +111 -0
  1217. package/src/backend/dynamic-table/utils/abbreviations.ts +556 -0
  1218. package/src/backend/dynamic-table/utils/anchoredPosition.ts +106 -0
  1219. package/src/backend/dynamic-table/utils/badgeAutoFit.ts +108 -0
  1220. package/src/backend/dynamic-table/utils/clipboard.ts +294 -0
  1221. package/src/backend/dynamic-table/utils/coerceCellValue.ts +96 -0
  1222. package/src/backend/dynamic-table/utils/colorAdjacency.ts +87 -0
  1223. package/src/backend/dynamic-table/utils/columnFill.ts +93 -0
  1224. package/src/backend/dynamic-table/utils/columnSpacerStyle.ts +53 -0
  1225. package/src/backend/dynamic-table/utils/columnWindow.ts +668 -0
  1226. package/src/backend/dynamic-table/utils/conditionalFormat.ts +300 -0
  1227. package/src/backend/dynamic-table/utils/exportTable.ts +140 -0
  1228. package/src/backend/dynamic-table/utils/fillGeometry.ts +187 -0
  1229. package/src/backend/dynamic-table/utils/fillPatterns.ts +422 -0
  1230. package/src/backend/dynamic-table/utils/formatAggregate.ts +145 -0
  1231. package/src/backend/dynamic-table/utils/formatDateCompact.ts +879 -0
  1232. package/src/backend/dynamic-table/utils/formatLabelCompact.ts +640 -0
  1233. package/src/backend/dynamic-table/utils/formulaColumns.tsx +237 -0
  1234. package/src/backend/dynamic-table/utils/importMapping.ts +202 -0
  1235. package/src/backend/dynamic-table/utils/importParse.ts +220 -0
  1236. package/src/backend/dynamic-table/utils/lookupColumns.tsx +47 -0
  1237. package/src/backend/dynamic-table/utils/perspectiveTransforms.ts +368 -0
  1238. package/src/backend/dynamic-table/utils/rollupColumns.ts +162 -0
  1239. package/src/backend/dynamic-table/validators/index.ts +40 -0
  1240. package/src/backend/fields/registry.generated.ts +8 -0
  1241. package/src/backend/fields/registry.ts +38 -0
  1242. package/src/backend/filters/AdvancedFilterBuilder.tsx +339 -0
  1243. package/src/backend/forms/ActionsDropdown.tsx +198 -0
  1244. package/src/backend/forms/FormActionButtons.tsx +98 -0
  1245. package/src/backend/forms/FormFooter.tsx +27 -0
  1246. package/src/backend/forms/FormHeader.tsx +204 -0
  1247. package/src/backend/forms/index.ts +4 -0
  1248. package/src/backend/hooks/useAdvancedFilter.ts +77 -0
  1249. package/src/backend/indexes/PartialIndexBanner.tsx +86 -0
  1250. package/src/backend/indexes/store.ts +88 -0
  1251. package/src/backend/injection/ComponentOverrideProvider.tsx +66 -0
  1252. package/src/backend/injection/InjectedField.tsx +196 -0
  1253. package/src/backend/injection/InjectionSpot.tsx +457 -0
  1254. package/src/backend/injection/InjectionWizard.tsx +335 -0
  1255. package/src/backend/injection/PageInjectionBoundary.tsx +31 -0
  1256. package/src/backend/injection/SseEventIndicator.tsx +11 -0
  1257. package/src/backend/injection/StatusBadgeInjectionSpot.tsx +50 -0
  1258. package/src/backend/injection/StatusBadgeRenderer.tsx +100 -0
  1259. package/src/backend/injection/WidgetSharedState.ts +58 -0
  1260. package/src/backend/injection/WizardInjectionSpot.tsx +46 -0
  1261. package/src/backend/injection/eventBridge.ts +158 -0
  1262. package/src/backend/injection/helpers.ts +35 -0
  1263. package/src/backend/injection/mergeMenuItems.ts +80 -0
  1264. package/src/backend/injection/mutationEvents.ts +22 -0
  1265. package/src/backend/injection/resolveInjectedIcon.tsx +30 -0
  1266. package/src/backend/injection/spotIds.ts +64 -0
  1267. package/src/backend/injection/useAppEvent.ts +76 -0
  1268. package/src/backend/injection/useGuardedMutation.ts +101 -0
  1269. package/src/backend/injection/useInjectedMenuItems.ts +67 -0
  1270. package/src/backend/injection/useInjectionDataWidgets.ts +41 -0
  1271. package/src/backend/injection/useOperationProgress.ts +105 -0
  1272. package/src/backend/injection/useRegisteredComponent.tsx +106 -0
  1273. package/src/backend/injection/useWidgetSharedState.ts +28 -0
  1274. package/src/backend/injection/visibility-utils.ts +31 -0
  1275. package/src/backend/injection/widgetRegistry.ts +68 -0
  1276. package/src/backend/inputs/ComboboxInput.tsx +272 -0
  1277. package/src/backend/inputs/DatePicker.tsx +162 -0
  1278. package/src/backend/inputs/DateTimePicker.tsx +199 -0
  1279. package/src/backend/inputs/EventPatternInput.tsx +46 -0
  1280. package/src/backend/inputs/EventSelect.tsx +183 -0
  1281. package/src/backend/inputs/LookupSelect.tsx +253 -0
  1282. package/src/backend/inputs/PhoneNumberField.tsx +166 -0
  1283. package/src/backend/inputs/SwitchableMarkdownInput.tsx +106 -0
  1284. package/src/backend/inputs/TagsInput.tsx +297 -0
  1285. package/src/backend/inputs/TimeInput.tsx +146 -0
  1286. package/src/backend/inputs/TimePicker.tsx +120 -0
  1287. package/src/backend/inputs/__tests__/DatePicker.test.tsx +76 -0
  1288. package/src/backend/inputs/__tests__/DateTimePicker.logic.test.ts +126 -0
  1289. package/src/backend/inputs/__tests__/PhoneNumberField.test.tsx +73 -0
  1290. package/src/backend/inputs/__tests__/TagsInput.test.tsx +78 -0
  1291. package/src/backend/inputs/__tests__/TimeInput.test.tsx +134 -0
  1292. package/src/backend/inputs/__tests__/TimePicker.test.tsx +50 -0
  1293. package/src/backend/inputs/index.ts +11 -0
  1294. package/src/backend/markdown/MarkdownContent.tsx +33 -0
  1295. package/src/backend/markdown/index.ts +2 -0
  1296. package/src/backend/markdown/useMarkdownRemarkPlugins.ts +39 -0
  1297. package/src/backend/messages/MessageComposer.tsx +197 -0
  1298. package/src/backend/messages/MessageObjectDetail.tsx +95 -0
  1299. package/src/backend/messages/MessageObjectPreview.tsx +64 -0
  1300. package/src/backend/messages/MessageObjectRecordPicker.tsx +149 -0
  1301. package/src/backend/messages/MessagePrioritySelector.tsx +78 -0
  1302. package/src/backend/messages/MessagesIcon.tsx +32 -0
  1303. package/src/backend/messages/SendObjectMessageDialog.tsx +74 -0
  1304. package/src/backend/messages/__tests__/MessageComposer.test.tsx +350 -0
  1305. package/src/backend/messages/__tests__/SendObjectMessageDialog.test.tsx +77 -0
  1306. package/src/backend/messages/index.ts +22 -0
  1307. package/src/backend/messages/message-compose-form-groups.tsx +383 -0
  1308. package/src/backend/messages/message-composer.types.ts +82 -0
  1309. package/src/backend/messages/message-priority.ts +53 -0
  1310. package/src/backend/messages/useMessageCompose.ts +666 -0
  1311. package/src/backend/messages/useMessageComposeOperations.ts +259 -0
  1312. package/src/backend/messages/useMessagesPoll.ts +118 -0
  1313. package/src/backend/notifications/NotificationBell.tsx +91 -0
  1314. package/src/backend/notifications/NotificationCountBadge.tsx +14 -0
  1315. package/src/backend/notifications/NotificationDispatcher.ts +213 -0
  1316. package/src/backend/notifications/NotificationItem.tsx +262 -0
  1317. package/src/backend/notifications/NotificationPanel.tsx +225 -0
  1318. package/src/backend/notifications/__tests__/NotificationCountBadge.test.tsx +34 -0
  1319. package/src/backend/notifications/index.ts +20 -0
  1320. package/src/backend/notifications/useNotificationActions.ts +161 -0
  1321. package/src/backend/notifications/useNotificationEffect.ts +17 -0
  1322. package/src/backend/notifications/useNotifications.ts +12 -0
  1323. package/src/backend/notifications/useNotificationsPoll.ts +167 -0
  1324. package/src/backend/notifications/useNotificationsSse.ts +220 -0
  1325. package/src/backend/operations/LastOperationBanner.tsx +85 -0
  1326. package/src/backend/operations/__tests__/LastOperationBanner.test.tsx +101 -0
  1327. package/src/backend/operations/store.ts +230 -0
  1328. package/src/backend/progress/ProgressTopBar.tsx +189 -0
  1329. package/src/backend/progress/useProgress.ts +12 -0
  1330. package/src/backend/progress/useProgressPoll.ts +100 -0
  1331. package/src/backend/progress/useProgressSse.ts +118 -0
  1332. package/src/backend/schedule/ScheduleAgenda.tsx +136 -0
  1333. package/src/backend/schedule/ScheduleGrid.tsx +136 -0
  1334. package/src/backend/schedule/ScheduleToolbar.tsx +178 -0
  1335. package/src/backend/schedule/ScheduleView.tsx +201 -0
  1336. package/src/backend/schedule/index.ts +5 -0
  1337. package/src/backend/schedule/recurrence.ts +99 -0
  1338. package/src/backend/schedule/types.ts +26 -0
  1339. package/src/backend/section-page/SectionNav.tsx +140 -0
  1340. package/src/backend/section-page/SectionPage.tsx +34 -0
  1341. package/src/backend/section-page/index.ts +3 -0
  1342. package/src/backend/section-page/types.ts +30 -0
  1343. package/src/backend/settings/SettingsNavigation.tsx +74 -0
  1344. package/src/backend/settings/SettingsPageWrapper.tsx +78 -0
  1345. package/src/backend/settings/index.ts +3 -0
  1346. package/src/backend/upgrades/UpgradeActionBanner.tsx +128 -0
  1347. package/src/backend/utils/__tests__/api.scoped-headers.test.ts +58 -0
  1348. package/src/backend/utils/__tests__/api.test.ts +148 -0
  1349. package/src/backend/utils/__tests__/apiCall.scoped-request-headers.test.ts +58 -0
  1350. package/src/backend/utils/__tests__/apiCall.test.ts +109 -0
  1351. package/src/backend/utils/__tests__/crud.test.ts +87 -0
  1352. package/src/backend/utils/__tests__/customFieldDefs.test.ts +25 -0
  1353. package/src/backend/utils/__tests__/customFieldValues.test.ts +35 -0
  1354. package/src/backend/utils/__tests__/richTextSanitizer.test.ts +57 -0
  1355. package/src/backend/utils/api.ts +214 -0
  1356. package/src/backend/utils/apiCall.ts +123 -0
  1357. package/src/backend/utils/crud.ts +174 -0
  1358. package/src/backend/utils/customFieldColumns.ts +118 -0
  1359. package/src/backend/utils/customFieldDefs.ts +247 -0
  1360. package/src/backend/utils/customFieldFilters.ts +174 -0
  1361. package/src/backend/utils/customFieldForms.ts +203 -0
  1362. package/src/backend/utils/customFieldRelationDisplay.ts +302 -0
  1363. package/src/backend/utils/customFieldValues.ts +41 -0
  1364. package/src/backend/utils/flash.ts +17 -0
  1365. package/src/backend/utils/nav.ts +402 -0
  1366. package/src/backend/utils/richTextSanitizer.ts +7 -0
  1367. package/src/backend/utils/scopedHeaderStack.ts +49 -0
  1368. package/src/backend/utils/serverErrors.ts +313 -0
  1369. package/src/backend/utils/useAutoDiscoveredFields.ts +138 -0
  1370. package/src/backend/version-history/VersionHistoryAction.tsx +76 -0
  1371. package/src/backend/version-history/VersionHistoryDetail.tsx +87 -0
  1372. package/src/backend/version-history/VersionHistoryPanel.tsx +331 -0
  1373. package/src/backend/version-history/index.ts +9 -0
  1374. package/src/backend/version-history/labels.ts +56 -0
  1375. package/src/backend/version-history/types.ts +27 -0
  1376. package/src/backend/version-history/useAuditPermissions.ts +94 -0
  1377. package/src/backend/version-history/useVersionHistory.ts +173 -0
  1378. package/src/backend/views/NewViewForm.tsx +54 -0
  1379. package/src/backend/views/ShareForm.tsx +62 -0
  1380. package/src/backend/views/ViewChip.tsx +123 -0
  1381. package/src/backend/views/ViewChipMenu.tsx +94 -0
  1382. package/src/backend/views/types.ts +4 -0
  1383. package/src/frontend/AuthFooter.tsx +29 -0
  1384. package/src/frontend/LanguageSwitcher.tsx +66 -0
  1385. package/src/frontend/Layout.tsx +13 -0
  1386. package/src/hooks/useIsMobile.ts +27 -0
  1387. package/src/index.ts +40 -0
  1388. package/src/portal/PortalContext.tsx +180 -0
  1389. package/src/portal/PortalLayoutProvider.tsx +5 -0
  1390. package/src/portal/PortalLayoutShell.tsx +64 -0
  1391. package/src/portal/PortalShell.tsx +370 -0
  1392. package/src/portal/components/PortalCard.tsx +77 -0
  1393. package/src/portal/components/PortalEmptyState.tsx +32 -0
  1394. package/src/portal/components/PortalFeatureCard.tsx +41 -0
  1395. package/src/portal/components/PortalNotificationBell.tsx +81 -0
  1396. package/src/portal/components/PortalNotificationPanel.tsx +230 -0
  1397. package/src/portal/components/PortalPageHeader.tsx +33 -0
  1398. package/src/portal/components/index.ts +6 -0
  1399. package/src/portal/hooks/useCustomerAuth.ts +97 -0
  1400. package/src/portal/hooks/usePortalAppEvent.ts +56 -0
  1401. package/src/portal/hooks/usePortalDashboardWidgets.ts +50 -0
  1402. package/src/portal/hooks/usePortalEventBridge.ts +162 -0
  1403. package/src/portal/hooks/usePortalInjectedMenuItems.ts +130 -0
  1404. package/src/portal/hooks/usePortalNotifications.ts +116 -0
  1405. package/src/portal/hooks/useTenantContext.ts +83 -0
  1406. package/src/primitives/DataLoader.tsx +93 -0
  1407. package/src/primitives/ErrorNotice.tsx +17 -0
  1408. package/src/primitives/Notice.tsx +83 -0
  1409. package/src/primitives/alert.tsx +59 -0
  1410. package/src/primitives/badge.tsx +43 -0
  1411. package/src/primitives/button.tsx +60 -0
  1412. package/src/primitives/calendar.tsx +69 -0
  1413. package/src/primitives/card.tsx +92 -0
  1414. package/src/primitives/checkbox.tsx +34 -0
  1415. package/src/primitives/dialog.tsx +122 -0
  1416. package/src/primitives/form-field.tsx +106 -0
  1417. package/src/primitives/icon-button.tsx +48 -0
  1418. package/src/primitives/input.tsx +26 -0
  1419. package/src/primitives/label.tsx +18 -0
  1420. package/src/primitives/popover.tsx +37 -0
  1421. package/src/primitives/primitives.stories.tsx +223 -0
  1422. package/src/primitives/progress.tsx +28 -0
  1423. package/src/primitives/separator.tsx +7 -0
  1424. package/src/primitives/sheet.tsx +137 -0
  1425. package/src/primitives/spinner.tsx +27 -0
  1426. package/src/primitives/status-badge.tsx +62 -0
  1427. package/src/primitives/switch.tsx +92 -0
  1428. package/src/primitives/table.tsx +27 -0
  1429. package/src/primitives/tabs.tsx +131 -0
  1430. package/src/primitives/textarea.tsx +20 -0
  1431. package/src/primitives/tooltip.tsx +85 -0
  1432. package/src/primitives-v2/AGENTS.md +66 -0
  1433. package/src/primitives-v2/Alert.stories.tsx +56 -0
  1434. package/src/primitives-v2/Alert.tsx +108 -0
  1435. package/src/primitives-v2/Avatar.stories.tsx +60 -0
  1436. package/src/primitives-v2/Avatar.tsx +139 -0
  1437. package/src/primitives-v2/Badge.stories.tsx +80 -0
  1438. package/src/primitives-v2/Badge.tsx +99 -0
  1439. package/src/primitives-v2/Breadcrumb.stories.tsx +61 -0
  1440. package/src/primitives-v2/Breadcrumb.tsx +96 -0
  1441. package/src/primitives-v2/Button.stories.tsx +72 -0
  1442. package/src/primitives-v2/Button.tsx +178 -0
  1443. package/src/primitives-v2/Checkbox.stories.tsx +34 -0
  1444. package/src/primitives-v2/Checkbox.tsx +103 -0
  1445. package/src/primitives-v2/IconButton.stories.tsx +40 -0
  1446. package/src/primitives-v2/IconButton.tsx +40 -0
  1447. package/src/primitives-v2/Input.stories.tsx +60 -0
  1448. package/src/primitives-v2/Input.tsx +113 -0
  1449. package/src/primitives-v2/NavGroup.stories.tsx +50 -0
  1450. package/src/primitives-v2/NavGroup.tsx +76 -0
  1451. package/src/primitives-v2/NavItem.stories.tsx +68 -0
  1452. package/src/primitives-v2/NavItem.tsx +105 -0
  1453. package/src/primitives-v2/SearchInput.stories.tsx +44 -0
  1454. package/src/primitives-v2/SearchInput.tsx +123 -0
  1455. package/src/primitives-v2/Sidebar.stories.tsx +91 -0
  1456. package/src/primitives-v2/Sidebar.tsx +92 -0
  1457. package/src/primitives-v2/Tab.stories.tsx +62 -0
  1458. package/src/primitives-v2/Tab.tsx +148 -0
  1459. package/src/primitives-v2/Tag.stories.tsx +123 -0
  1460. package/src/primitives-v2/Tag.tsx +115 -0
  1461. package/src/primitives-v2/Toggle.stories.tsx +37 -0
  1462. package/src/primitives-v2/Toggle.tsx +93 -0
  1463. package/src/primitives-v2/Topbar.stories.tsx +65 -0
  1464. package/src/primitives-v2/Topbar.tsx +37 -0
  1465. package/src/primitives-v2/index.ts +31 -0
  1466. package/src/primitives-v2/utils.ts +9 -0
  1467. package/src/theme/BrandThemeProvider.tsx +122 -0
  1468. package/src/theme/QueryProvider.tsx +46 -0
  1469. package/src/theme/ThemeProvider.tsx +120 -0
  1470. package/src/theme/ThemeToggle.tsx +88 -0
  1471. package/src/theme/index.ts +4 -0
  1472. package/src/theme/m3.css +622 -0
  1473. package/src/theme/tokens.v2.css +559 -0
  1474. package/src/theme/typography.css +163 -0
  1475. package/src/theme/typography.stories.tsx +157 -0
  1476. package/src/theme/typography.tokens.ts +326 -0
  1477. package/src/types/react-big-calendar.d.ts +16 -0
@@ -0,0 +1,2584 @@
1
+ "use client"
2
+ import * as React from 'react'
3
+ import { useRouter } from 'next/navigation'
4
+ import { useReactTable, getCoreRowModel, getSortedRowModel, flexRender, type ColumnDef, type SortingState, type Column as TableColumn, type VisibilityState, type RowSelectionState } from '@tanstack/react-table'
5
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
6
+ import { RefreshCw, Loader2, SlidersHorizontal, MoreHorizontal, Circle, Filter, ChevronDown, Check } from 'lucide-react'
7
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../primitives/table'
8
+ import { Button } from '../primitives/button'
9
+ import { Checkbox } from '../primitives/checkbox'
10
+ import { Spinner } from '../primitives/spinner'
11
+ import { TooltipProvider } from '../primitives/tooltip'
12
+ import { TruncatedCell } from './TruncatedCell'
13
+ import { FilterBar, type FilterDef, type FilterValues } from './FilterBar'
14
+ import { useCustomFieldFilterDefs } from './utils/customFieldFilters'
15
+ import { fetchCustomFieldDefinitionsPayload, type CustomFieldsetDto } from './utils/customFieldDefs'
16
+ import { RowActions, type RowActionItem } from './RowActions'
17
+ import { subscribeOrganizationScopeChanged, type OrganizationScopeChangedDetail } from '@open-mercato/shared/lib/frontend/organizationEvents'
18
+ import { InjectionSpot } from './injection/InjectionSpot'
19
+ import { useAppEvent } from './injection/useAppEvent'
20
+ import { useInjectionDataWidgets } from './injection/useInjectionDataWidgets'
21
+ import { resolveInjectedIcon } from './injection/resolveInjectedIcon'
22
+ import { serializeExport, defaultExportFilename, type PreparedExport } from '@open-mercato/shared/lib/crud/exporters'
23
+ import { apiCall } from './utils/apiCall'
24
+ import { raiseCrudError } from './utils/serverErrors'
25
+ import { PerspectiveSidebar } from './PerspectiveSidebar'
26
+ import { Popover, PopoverTrigger, PopoverContent } from '../primitives/popover'
27
+ import { cn } from '@open-mercato/shared/lib/utils'
28
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
29
+ import { flash } from './FlashMessages'
30
+ import { useConfirmDialog } from './confirm-dialog'
31
+ import type {
32
+ PerspectiveDto,
33
+ RolePerspectiveDto,
34
+ PerspectivesIndexResponse,
35
+ PerspectiveSettings,
36
+ PerspectiveSaveResponse,
37
+ } from '@open-mercato/shared/modules/perspectives/types'
38
+ import type {
39
+ InjectionColumnDefinition,
40
+ InjectionBulkActionDefinition,
41
+ InjectionFilterDefinition,
42
+ InjectionRowActionDefinition,
43
+ } from '@open-mercato/shared/modules/widgets/injection'
44
+ import { ComponentReplacementHandles } from '@open-mercato/shared/modules/widgets/component-registry'
45
+ import { insertByInjectionPlacement } from '@open-mercato/shared/modules/widgets/injection-position'
46
+ import { useVirtualizer } from '@tanstack/react-virtual'
47
+ import type { AdvancedFilterState, FilterFieldDef as AdvancedFilterFieldDef } from '@open-mercato/shared/lib/query/advanced-filter'
48
+ import { createEmptyCondition, getDefaultOperator } from '@open-mercato/shared/lib/query/advanced-filter'
49
+ import { AdvancedFilterBuilder } from './filters/AdvancedFilterBuilder'
50
+ import { type ColumnChooserField } from './columns/ColumnChooserPanel'
51
+ import { useAutoDiscoveredFields } from './utils/useAutoDiscoveredFields'
52
+ import { useCustomFieldDefs } from './utils/customFieldDefs'
53
+ import {
54
+ DndContext,
55
+ closestCenter,
56
+ PointerSensor,
57
+ useSensor,
58
+ useSensors,
59
+ type DragEndEvent,
60
+ } from '@dnd-kit/core'
61
+ import {
62
+ SortableContext,
63
+ horizontalListSortingStrategy,
64
+ useSortable,
65
+ } from '@dnd-kit/sortable'
66
+ import { CSS } from '@dnd-kit/utilities'
67
+
68
+ let refreshScheduled = false
69
+
70
+ function scheduleRouterRefresh(router: ReturnType<typeof useRouter>) {
71
+ if (refreshScheduled) return
72
+ refreshScheduled = true
73
+ if (typeof window === 'undefined') {
74
+ refreshScheduled = false
75
+ return
76
+ }
77
+ window.requestAnimationFrame(() => {
78
+ refreshScheduled = false
79
+ try { router.refresh() } catch {}
80
+ })
81
+ }
82
+
83
+ export type PaginationProps = {
84
+ page: number
85
+ pageSize: number
86
+ total: number
87
+ totalPages: number
88
+ onPageChange: (page: number) => void
89
+ durationMs?: number | null
90
+ cacheStatus?: 'hit' | 'miss' | null
91
+ pageSizeOptions?: number[]
92
+ onPageSizeChange?: (pageSize: number) => void
93
+ }
94
+
95
+ export type DataTableRefreshButton = {
96
+ onRefresh: () => void
97
+ label: string
98
+ isRefreshing?: boolean
99
+ disabled?: boolean
100
+ }
101
+
102
+ const DEFAULT_ROW_CLICK_ACTION_IDS = ['edit', 'open']
103
+
104
+ export function withDataTableNamespaces<T extends Record<string, unknown>>(
105
+ mappedRow: T,
106
+ sourceItem: Record<string, unknown>,
107
+ ): T & Record<string, unknown> {
108
+ const namespaced: Record<string, unknown> = {}
109
+ for (const [key, value] of Object.entries(sourceItem)) {
110
+ if (!key.startsWith('_')) continue
111
+ namespaced[key] = value
112
+ }
113
+ return {
114
+ ...mappedRow,
115
+ ...namespaced,
116
+ }
117
+ }
118
+
119
+ function resolveDataTableRowId<T>(row: T, index: number): string {
120
+ if (row && typeof row === 'object') {
121
+ const candidate = (row as Record<string, unknown>).id
122
+ if (typeof candidate === 'string' && candidate.trim().length > 0) {
123
+ return candidate
124
+ }
125
+ }
126
+ return String(index)
127
+ }
128
+
129
+ function resolveDefaultRowAction(items: RowActionItem[], preferredIds: string[]): RowActionItem | null {
130
+ for (const preferredId of preferredIds) {
131
+ const match = items.find((item) => item.id === preferredId && (item.href || item.onSelect))
132
+ if (match) return match
133
+ }
134
+ for (const preferredId of preferredIds) {
135
+ const match = items.find((item) => item.label.toLowerCase() === preferredId && (item.href || item.onSelect))
136
+ if (match) return match
137
+ }
138
+ return null
139
+ }
140
+
141
+ function pickDefaultRowAction(node: React.ReactNode, preferredIds: string[]): RowActionItem | null {
142
+ if (!React.isValidElement(node)) return null
143
+ const items = (node.props as { items?: RowActionItem[] }).items
144
+ if (!Array.isArray(items)) return null
145
+ return resolveDefaultRowAction(items, preferredIds)
146
+ }
147
+
148
+ export type DataTableExportFormat = 'csv' | 'json' | 'xml' | 'markdown'
149
+
150
+ export type DataTableExportSectionConfig = {
151
+ title?: string
152
+ description?: string
153
+ getUrl?: (format: DataTableExportFormat) => string
154
+ prepare?: (format: DataTableExportFormat) => Promise<PreparedExport | { prepared: PreparedExport; filename?: string } | null> | PreparedExport | { prepared: PreparedExport; filename?: string } | null
155
+ formats?: DataTableExportFormat[]
156
+ disabled?: boolean
157
+ filename?: (format: DataTableExportFormat) => string
158
+ }
159
+
160
+ export type DataTableExportConfig = {
161
+ label?: string
162
+ disabled?: boolean
163
+ formats?: DataTableExportFormat[]
164
+ getUrl?: (format: DataTableExportFormat) => string
165
+ sections?: DataTableExportSectionConfig[]
166
+ view?: DataTableExportSectionConfig
167
+ full?: DataTableExportSectionConfig
168
+ filename?: (format: DataTableExportFormat) => string
169
+ }
170
+
171
+ export type DataTablePerspectiveConfig = {
172
+ tableId: string
173
+ initialState?: {
174
+ response?: PerspectivesIndexResponse
175
+ activePerspectiveId?: string | null
176
+ initialSettings?: PerspectiveSettings | null
177
+ }
178
+ }
179
+
180
+ export type BulkAction<T = Record<string, unknown>> = {
181
+ id: string
182
+ label: string
183
+ icon?: React.ComponentType<{ className?: string }>
184
+ destructive?: boolean
185
+ onExecute: (selectedRows: T[]) => Promise<void | boolean | BulkActionExecuteResult> | void | boolean | BulkActionExecuteResult
186
+ }
187
+
188
+ export type DataTableProps<T> = {
189
+ columns: ColumnDef<T, any>[]
190
+ data: T[]
191
+ toolbar?: React.ReactNode
192
+ title?: React.ReactNode
193
+ actions?: React.ReactNode
194
+ refreshButton?: DataTableRefreshButton
195
+ sortable?: boolean
196
+ sorting?: SortingState
197
+ onSortingChange?: (s: SortingState) => void
198
+ pagination?: PaginationProps
199
+ isLoading?: boolean
200
+ emptyState?: React.ReactNode
201
+ error?: React.ReactNode | string | null
202
+ rowActions?: (row: T) => React.ReactNode
203
+ onRowClick?: (row: T) => void
204
+ rowClickActionIds?: string[]
205
+ disableRowClick?: boolean
206
+ bulkActions?: BulkAction<T>[]
207
+
208
+ // Auto FilterBar options (rendered as toolbar when provided and no custom toolbar passed)
209
+ searchValue?: string
210
+ onSearchChange?: (v: string) => void
211
+ searchPlaceholder?: string
212
+ searchAlign?: 'left' | 'right'
213
+ filters?: FilterDef[]
214
+ filterValues?: FilterValues
215
+ onFiltersApply?: (values: FilterValues) => void
216
+ onFiltersClear?: () => void
217
+ entityId?: string
218
+ entityIds?: string[]
219
+ exporter?: DataTableExportConfig | false
220
+ perspective?: DataTablePerspectiveConfig
221
+ embedded?: boolean
222
+ onCustomFieldFilterFieldsetChange?: (fieldset: string | null, entityId?: string) => void
223
+ customFieldFilterKeyExtras?: Array<string | number | boolean | null | undefined>
224
+ injectionSpotId?: string
225
+ injectionContext?: Record<string, unknown>
226
+ replacementHandle?: string
227
+ stickyFirstColumn?: boolean
228
+ stickyActionsColumn?: boolean
229
+ virtualized?: boolean
230
+ virtualizedMaxHeight?: number | string
231
+ virtualizedOverscan?: number
232
+ advancedFilter?: {
233
+ fields?: AdvancedFilterFieldDef[]
234
+ auto?: boolean
235
+ value: AdvancedFilterState
236
+ onChange: (state: AdvancedFilterState) => void
237
+ onApply: () => void
238
+ onClear: () => void
239
+ }
240
+ columnChooser?: {
241
+ availableColumns?: ColumnChooserField[]
242
+ auto?: boolean
243
+ }
244
+ }
245
+
246
+ const DEFAULT_EXPORT_FORMATS: DataTableExportFormat[] = ['csv', 'json', 'xml', 'markdown']
247
+ const EXPORT_LABELS: Record<DataTableExportFormat, string> = {
248
+ csv: 'CSV',
249
+ json: 'JSON',
250
+ xml: 'XML',
251
+ markdown: 'Markdown',
252
+ }
253
+ const EMPTY_FILTER_DEFS: FilterDef[] = []
254
+ const EMPTY_FILTER_VALUES: FilterValues = Object.freeze({}) as FilterValues
255
+
256
+ type BulkActionExecuteResult = {
257
+ ok: boolean
258
+ message?: string
259
+ affectedCount?: number
260
+ progressJobId?: string | null
261
+ }
262
+
263
+ function collectUniqueById<T extends { id: string }>(
264
+ entries: T[],
265
+ warningScope: string,
266
+ ): T[] {
267
+ const byId = new Map<string, T>()
268
+ for (const entry of entries) {
269
+ if (!entry.id) continue
270
+ if (byId.has(entry.id)) {
271
+ if (process.env.NODE_ENV !== 'production') {
272
+ console.warn(`[UMES] Duplicate injected ${warningScope} id "${entry.id}" detected. Keeping the first entry.`)
273
+ }
274
+ continue
275
+ }
276
+ byId.set(entry.id, entry)
277
+ }
278
+ return Array.from(byId.values())
279
+ }
280
+
281
+ type ResolvedExportSection = {
282
+ key: string
283
+ title: string
284
+ description?: string
285
+ formats: DataTableExportFormat[]
286
+ getUrl?: (format: DataTableExportFormat) => string
287
+ prepare?: (format: DataTableExportFormat) => Promise<{ prepared: PreparedExport; filename?: string } | null> | { prepared: PreparedExport; filename?: string } | null
288
+ filename?: (format: DataTableExportFormat) => string
289
+ disabled: boolean
290
+ }
291
+
292
+ function resolveExportSections(config: DataTableExportConfig | null | undefined): ResolvedExportSection[] {
293
+ if (!config) return []
294
+ const sections: ResolvedExportSection[] = []
295
+ const baseFormats = config.formats && config.formats.length > 0 ? config.formats : DEFAULT_EXPORT_FORMATS
296
+ const addSection = (key: string, section: DataTableExportSectionConfig | undefined | null, fallbackTitle: string) => {
297
+ if (!section || (!section.getUrl && !section.prepare)) return
298
+ const title = section.title?.trim().length ? section.title!.trim() : fallbackTitle
299
+ const seen = new Set<DataTableExportFormat>()
300
+ const formatsSource = section.formats && section.formats.length > 0 ? section.formats : baseFormats
301
+ const formats = formatsSource.filter((format) => {
302
+ if (seen.has(format)) return false
303
+ seen.add(format)
304
+ return true
305
+ })
306
+ if (formats.length === 0) return
307
+ sections.push({
308
+ key,
309
+ title,
310
+ description: section.description,
311
+ formats,
312
+ getUrl: section.getUrl,
313
+ prepare: section.prepare
314
+ ? async (format: DataTableExportFormat) => {
315
+ const result = await section.prepare!(format)
316
+ if (!result) return null
317
+ if ('prepared' in result) return result
318
+ return { prepared: result }
319
+ }
320
+ : undefined,
321
+ filename: section.filename,
322
+ disabled: Boolean(config.disabled || section.disabled),
323
+ })
324
+ }
325
+
326
+ // Allow legacy config (getUrl without sections/view)
327
+ const hasExplicitSections = Array.isArray(config.sections) && config.sections.length > 0
328
+ if (!config.view && !config.full && !hasExplicitSections && config.getUrl) {
329
+ addSection('view', { getUrl: config.getUrl, formats: config.formats }, 'Export what you view')
330
+ } else {
331
+ addSection('view', config.view, 'Export what you view')
332
+ }
333
+
334
+ if (hasExplicitSections) {
335
+ config.sections!.forEach((section, idx) => {
336
+ addSection(`section-${idx}`, section, section.title?.trim().length ? section.title! : `Export ${idx + 1}`)
337
+ })
338
+ }
339
+
340
+ addSection('full', config.full, 'Full data export')
341
+ return sections
342
+ }
343
+
344
+ const PERSPECTIVE_COOKIE_PREFIX = 'om_table_perspective'
345
+ const PERSPECTIVE_STORAGE_PREFIX = 'om_table_perspective_snapshot'
346
+
347
+ function formatDurationLabel(durationMs?: number | null): string {
348
+ if (durationMs == null) return ''
349
+ if (!Number.isFinite(durationMs)) return ''
350
+ if (durationMs < 0) return ''
351
+ if (durationMs < 1000) return `${Math.round(durationMs)}ms`
352
+ if (durationMs < 10_000) return `${(durationMs / 1000).toFixed(1)}s`
353
+ if (durationMs < 60_000) return `${Math.round(durationMs / 1000)}s`
354
+ if (durationMs < 3_600_000) return `${(durationMs / 60_000).toFixed(durationMs < 600_000 ? 1 : 0)}m`
355
+ return `${(durationMs / 3_600_000).toFixed(durationMs < 7_200_000 ? 1 : 0)}h`
356
+ }
357
+
358
+ type PerspectiveSnapshot = {
359
+ perspectiveId: string | null
360
+ settings: PerspectiveSettings
361
+ updatedAt: number
362
+ }
363
+
364
+ function readPerspectiveCookie(tableId: string): string | null {
365
+ if (typeof document === 'undefined') return null
366
+ const key = `${PERSPECTIVE_COOKIE_PREFIX}:${tableId}`
367
+ const pattern = new RegExp(`(?:^|;\\s*)${key}=([^;]+)`)
368
+ const match = document.cookie.match(pattern)
369
+ return match ? decodeURIComponent(match[1]) : null
370
+ }
371
+
372
+ function writePerspectiveCookie(tableId: string, perspectiveId: string | null): void {
373
+ if (typeof document === 'undefined') return
374
+ const key = `${PERSPECTIVE_COOKIE_PREFIX}:${tableId}`
375
+ const expires = perspectiveId ? 'Max-Age=31536000' : 'Max-Age=0'
376
+ const value = perspectiveId ? encodeURIComponent(perspectiveId) : ''
377
+ document.cookie = `${key}=${value}; Path=/; ${expires}; SameSite=Lax`
378
+ }
379
+
380
+ function readPerspectiveSnapshot(tableId: string): PerspectiveSnapshot | null {
381
+ if (typeof window === 'undefined') return null
382
+ try {
383
+ const raw = window.localStorage.getItem(`${PERSPECTIVE_STORAGE_PREFIX}:${tableId}`)
384
+ if (!raw) return null
385
+ const parsed = JSON.parse(raw)
386
+ if (!parsed || typeof parsed !== 'object') return null
387
+ const perspectiveId =
388
+ typeof parsed.perspectiveId === 'string' && parsed.perspectiveId.trim().length > 0
389
+ ? parsed.perspectiveId
390
+ : null
391
+ const settings = typeof parsed.settings === 'object' && parsed.settings !== null
392
+ ? parsed.settings as PerspectiveSettings
393
+ : null
394
+ const updatedAt = typeof parsed.updatedAt === 'number' ? parsed.updatedAt : Date.now()
395
+ if (!settings) return null
396
+ return { perspectiveId, settings, updatedAt }
397
+ } catch {
398
+ return null
399
+ }
400
+ }
401
+
402
+ function writePerspectiveSnapshot(tableId: string, snapshot: PerspectiveSnapshot | null) {
403
+ if (typeof window === 'undefined') return
404
+ const key = `${PERSPECTIVE_STORAGE_PREFIX}:${tableId}`
405
+ try {
406
+ if (!snapshot) {
407
+ window.localStorage.removeItem(key)
408
+ return
409
+ }
410
+ window.localStorage.setItem(key, JSON.stringify(snapshot))
411
+ } catch {
412
+ // ignore storage errors
413
+ }
414
+ }
415
+
416
+ function sanitizePerspectiveSettings(source?: PerspectiveSettings | null): PerspectiveSettings | null {
417
+ if (!source || typeof source !== 'object') return null
418
+ const forbidden = new Set(['__proto__', 'prototype', 'constructor'])
419
+ const result: PerspectiveSettings = {}
420
+
421
+ if (Array.isArray(source.columnOrder)) {
422
+ const seen = new Set<string>()
423
+ const order = source.columnOrder
424
+ .map((id) => (typeof id === 'string' ? id.trim() : ''))
425
+ .filter((id) => id.length > 0 && !seen.has(id) && (seen.add(id), true))
426
+ if (order.length) result.columnOrder = order
427
+ }
428
+
429
+ if (source.columnVisibility && typeof source.columnVisibility === 'object') {
430
+ const entries = Object.entries(source.columnVisibility)
431
+ .filter(([key, value]) => typeof key === 'string' && key.trim().length > 0 && !forbidden.has(key) && typeof value === 'boolean')
432
+ if (entries.length) {
433
+ const visibility: Record<string, boolean> = {}
434
+ entries.forEach(([key, value]) => { visibility[key] = value })
435
+ result.columnVisibility = visibility
436
+ }
437
+ }
438
+
439
+ if (Array.isArray(source.sorting)) {
440
+ const sorting = source.sorting
441
+ .map((item) => {
442
+ const id = typeof item?.id === 'string' ? item.id.trim() : ''
443
+ if (!id || forbidden.has(id)) return null
444
+ return { id, desc: Boolean(item?.desc) }
445
+ })
446
+ .filter((item): item is { id: string; desc: boolean } => item !== null)
447
+ if (sorting.length) result.sorting = sorting
448
+ }
449
+
450
+ if (typeof source.pageSize === 'number' && Number.isFinite(source.pageSize)) {
451
+ const pageSize = Math.max(1, Math.min(500, Math.floor(source.pageSize)))
452
+ result.pageSize = pageSize
453
+ }
454
+
455
+ if (typeof source.searchValue === 'string' && source.searchValue.trim().length > 0) {
456
+ result.searchValue = source.searchValue.trim().slice(0, 200)
457
+ }
458
+
459
+ if (source.filters && typeof source.filters === 'object') {
460
+ const filters: Record<string, unknown> = {}
461
+ for (const [key, value] of Object.entries(source.filters)) {
462
+ if (typeof key === 'string') {
463
+ const trimmed = key.trim()
464
+ if (trimmed.length > 0 && !forbidden.has(trimmed)) filters[trimmed] = value
465
+ }
466
+ }
467
+ if (Object.keys(filters).length) result.filters = filters
468
+ }
469
+
470
+ return Object.keys(result).length ? result : null
471
+ }
472
+
473
+ function normalizeLabel(input: string): string {
474
+ if (!input) return ''
475
+ return input
476
+ .replace(/^cf[_:]/, '')
477
+ .replace(/[_:\-]+/g, ' ')
478
+ .replace(/\b\w/g, (ch) => ch.toUpperCase())
479
+ }
480
+
481
+ // Column width configuration based on column type
482
+ type ColumnTruncateConfig = {
483
+ maxWidth: string
484
+ truncate: boolean
485
+ }
486
+
487
+ type ColumnTruncateMeta = {
488
+ truncate?: boolean
489
+ maxWidth?: string
490
+ }
491
+
492
+ function getColumnTruncateConfig(columnId: string, accessorKey?: string, columnMeta?: ColumnTruncateMeta): ColumnTruncateConfig {
493
+ const key = accessorKey || columnId
494
+ const metaMaxWidth = typeof columnMeta?.maxWidth === 'string' ? columnMeta.maxWidth.trim() : ''
495
+
496
+ // Custom fields get narrower width
497
+ if (key.startsWith('cf_') || key.startsWith('cf:')) {
498
+ return {
499
+ maxWidth: metaMaxWidth || '120px',
500
+ truncate: typeof columnMeta?.truncate === 'boolean' ? columnMeta.truncate : true,
501
+ }
502
+ }
503
+
504
+ // Core informative columns get wider width
505
+ const wideColumns = ['title', 'name', 'description', 'source', 'companies', 'people']
506
+ if (wideColumns.includes(key)) {
507
+ return {
508
+ maxWidth: metaMaxWidth || '250px',
509
+ truncate: typeof columnMeta?.truncate === 'boolean' ? columnMeta.truncate : true,
510
+ }
511
+ }
512
+
513
+ // Medium width for status-like columns
514
+ const mediumColumns = ['status', 'pipelineStage', 'pipeline_stage', 'type', 'category']
515
+ if (mediumColumns.includes(key)) {
516
+ return {
517
+ maxWidth: metaMaxWidth || '180px',
518
+ truncate: typeof columnMeta?.truncate === 'boolean' ? columnMeta.truncate : true,
519
+ }
520
+ }
521
+
522
+ // Date columns
523
+ if (key.endsWith('_at') || key.endsWith('At') || key.includes('date') || key.includes('Date')) {
524
+ return {
525
+ maxWidth: metaMaxWidth || '120px',
526
+ truncate: typeof columnMeta?.truncate === 'boolean' ? columnMeta.truncate : true,
527
+ }
528
+ }
529
+
530
+ // Default for other columns
531
+ return {
532
+ maxWidth: metaMaxWidth || '150px',
533
+ truncate: typeof columnMeta?.truncate === 'boolean' ? columnMeta.truncate : true,
534
+ }
535
+ }
536
+
537
+ function readInjectedColumnValue(row: unknown, accessorKey: string): unknown {
538
+ if (!row || typeof row !== 'object' || !accessorKey) return undefined
539
+ const segments = accessorKey.split('.').filter((segment) => segment.length > 0)
540
+ if (segments.length === 0) return undefined
541
+ let current: unknown = row
542
+ for (const segment of segments) {
543
+ if (!current || typeof current !== 'object') return undefined
544
+ current = (current as Record<string, unknown>)[segment]
545
+ }
546
+ return current
547
+ }
548
+
549
+ // Check if a column should skip truncation (e.g., actions column)
550
+ function shouldSkipTruncation(columnId: string): boolean {
551
+ const skipColumns = ['actions', 'select', 'checkbox', 'expand']
552
+ return skipColumns.includes(columnId.toLowerCase())
553
+ }
554
+
555
+ function ExportMenu({ config, sections }: { config: DataTableExportConfig; sections: ResolvedExportSection[] }) {
556
+ const t = useT()
557
+ const { label } = config
558
+ const defaultLabel = label ?? t('ui.dataTable.export.label', 'Export')
559
+ const disabled = Boolean(config.disabled)
560
+ const hasSections = sections.length > 0
561
+ const [open, setOpen] = React.useState(false)
562
+ const buttonRef = React.useRef<HTMLButtonElement>(null)
563
+ const menuRef = React.useRef<HTMLDivElement>(null)
564
+
565
+ React.useEffect(() => {
566
+ if (!open || !hasSections) return
567
+ const onDocClick = (event: MouseEvent) => {
568
+ const target = event.target as Node
569
+ if (menuRef.current && !menuRef.current.contains(target) && buttonRef.current && !buttonRef.current.contains(target)) {
570
+ setOpen(false)
571
+ }
572
+ }
573
+ const onKeyDown = (event: KeyboardEvent) => {
574
+ if (event.key === 'Escape') {
575
+ setOpen(false)
576
+ buttonRef.current?.focus()
577
+ }
578
+ }
579
+ document.addEventListener('mousedown', onDocClick)
580
+ document.addEventListener('keydown', onKeyDown)
581
+ return () => {
582
+ document.removeEventListener('mousedown', onDocClick)
583
+ document.removeEventListener('keydown', onKeyDown)
584
+ }
585
+ }, [hasSections, open])
586
+
587
+ if (!hasSections) return null
588
+
589
+ const handleSelect = async (section: ResolvedExportSection, format: DataTableExportFormat) => {
590
+ try {
591
+ if (section.prepare) {
592
+ const preparedResult = await section.prepare(format)
593
+ if (!preparedResult) return
594
+ const prepared = preparedResult.prepared
595
+ const serialized = serializeExport(prepared, format)
596
+ const filename =
597
+ preparedResult.filename
598
+ ?? section.filename?.(format)
599
+ ?? config.filename?.(format)
600
+ ?? defaultExportFilename(section.title, format)
601
+ if (typeof window !== 'undefined') {
602
+ const blob = new Blob([serialized.body], { type: serialized.contentType })
603
+ const href = URL.createObjectURL(blob)
604
+ const a = document.createElement('a')
605
+ a.href = href
606
+ a.download = filename
607
+ document.body.appendChild(a)
608
+ a.click()
609
+ document.body.removeChild(a)
610
+ URL.revokeObjectURL(href)
611
+ }
612
+ } else if (section.getUrl) {
613
+ const url = section.getUrl(format)
614
+ if (url && typeof window !== 'undefined') {
615
+ window.open(url, '_blank', 'noopener,noreferrer')
616
+ }
617
+ }
618
+ } catch {
619
+ // ignore export errors
620
+ } finally {
621
+ setOpen(false)
622
+ }
623
+ }
624
+
625
+ return (
626
+ <div className="relative inline-block">
627
+ <Button
628
+ ref={buttonRef}
629
+ variant="outline"
630
+ size="sm"
631
+ type="button"
632
+ onClick={() => {
633
+ if (disabled) return
634
+ setOpen((prev) => !prev)
635
+ }}
636
+ aria-haspopup="menu"
637
+ aria-expanded={open}
638
+ disabled={disabled}
639
+ >
640
+ {defaultLabel}
641
+ </Button>
642
+ {open ? (
643
+ <div
644
+ ref={menuRef}
645
+ role="menu"
646
+ className="absolute right-0 mt-2 w-60 rounded-md border bg-background py-2 shadow z-20"
647
+ >
648
+ {sections.map((section, idx) => (
649
+ <div key={section.key} className={idx > 0 ? 'mt-2 border-t pt-3' : ''}>
650
+ <div className="px-3">
651
+ <div className="text-xs font-semibold uppercase text-muted-foreground">{section.title}</div>
652
+ {section.description ? (
653
+ <p className="mt-1 text-xs text-muted-foreground leading-snug">{section.description}</p>
654
+ ) : null}
655
+ </div>
656
+ <div className="mt-2 space-y-1 px-2 pb-1">
657
+ {section.formats.map((format) => (
658
+ <Button
659
+ key={`${section.key}-${format}`}
660
+ type="button"
661
+ variant="ghost"
662
+ size="sm"
663
+ className="w-full justify-start font-normal"
664
+ onClick={() => void handleSelect(section, format)}
665
+ disabled={section.disabled}
666
+ >
667
+ {EXPORT_LABELS[format]}
668
+ </Button>
669
+ ))}
670
+ </div>
671
+ </div>
672
+ ))}
673
+ </div>
674
+ ) : null}
675
+ </div>
676
+ )
677
+ }
678
+
679
+ function sanitizeDndContextId(value: string): string {
680
+ const normalized = value
681
+ .trim()
682
+ .toLowerCase()
683
+ .replace(/[^a-z0-9_-]+/g, '-')
684
+ .replace(/^-+|-+$/g, '')
685
+ return normalized.length > 0 ? normalized : 'data-table'
686
+ }
687
+
688
+ function HeaderDndWrapper({ enabled, contextId, sensors, columnIds, onDragEnd, children }: {
689
+ enabled: boolean
690
+ contextId: string
691
+ sensors: ReturnType<typeof useSensors>
692
+ columnIds: string[]
693
+ onDragEnd: (event: DragEndEvent) => void
694
+ children: React.ReactNode
695
+ }) {
696
+ if (!enabled) return <>{children}</>
697
+ return (
698
+ <DndContext id={contextId} sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd}>
699
+ <SortableContext items={columnIds} strategy={horizontalListSortingStrategy}>
700
+ {children}
701
+ </SortableContext>
702
+ </DndContext>
703
+ )
704
+ }
705
+
706
+ function SortableHeaderCell({ id, children, className }: { id: string; children: React.ReactNode; className?: string }) {
707
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id })
708
+ const isSticky = typeof className === 'string' && className.includes('sticky')
709
+ const style: React.CSSProperties = {
710
+ transform: CSS.Transform.toString(transform),
711
+ transition,
712
+ opacity: isDragging ? 0.5 : 1,
713
+ cursor: 'grab',
714
+ position: isSticky ? 'sticky' : 'relative',
715
+ }
716
+ return (
717
+ <TableHead ref={setNodeRef} style={style} className={className} {...attributes} {...listeners}>
718
+ {children}
719
+ </TableHead>
720
+ )
721
+ }
722
+
723
+ function ViewSwitcherDropdown({
724
+ activePerspectiveId,
725
+ perspectives,
726
+ rolePerspectives,
727
+ onClear,
728
+ onActivate,
729
+ onOpenSidebar,
730
+ t,
731
+ }: {
732
+ activePerspectiveId: string | null
733
+ perspectives: PerspectiveDto[]
734
+ rolePerspectives: RolePerspectiveDto[]
735
+ onClear: () => void
736
+ onActivate: (item: PerspectiveDto | RolePerspectiveDto, source: 'personal' | 'role') => void
737
+ onOpenSidebar: () => void
738
+ t: (key: string, fallback: string) => string
739
+ }) {
740
+ const [open, setOpen] = React.useState(false)
741
+ const allViews = [...perspectives, ...rolePerspectives]
742
+ const activeName = allViews.find((v) => v.id === activePerspectiveId)?.name.trim() || ''
743
+ const activeLabel = activeName || t('ui.dataTable.perspectives.allViews', 'All views')
744
+ return (
745
+ <div className="inline-flex h-9 items-center rounded-md border border-input text-sm">
746
+ <Button
747
+ data-testid="data-table-open-views-sidebar"
748
+ type="button"
749
+ variant="ghost"
750
+ size="sm"
751
+ onClick={onOpenSidebar}
752
+ className="h-full rounded-none rounded-l-md px-3 font-medium"
753
+ >
754
+ <SlidersHorizontal className="size-4 mr-1.5" />
755
+ {t('ui.dataTable.perspectives.button', 'Views')}
756
+ </Button>
757
+ <div className="h-5 w-px bg-border" />
758
+ <Popover open={open} onOpenChange={setOpen}>
759
+ <PopoverTrigger asChild>
760
+ <Button
761
+ type="button"
762
+ variant="ghost"
763
+ size="sm"
764
+ className="h-full max-w-[200px] rounded-none rounded-r-md px-3 font-normal text-muted-foreground"
765
+ >
766
+ <span className="truncate">{activeLabel}</span>
767
+ <ChevronDown className="size-3.5 shrink-0 ml-1.5" />
768
+ </Button>
769
+ </PopoverTrigger>
770
+ <PopoverContent align="start" className="w-[220px] p-1">
771
+ <Button
772
+ type="button"
773
+ variant="ghost"
774
+ size="sm"
775
+ className={cn(
776
+ 'w-full justify-start h-auto px-2 py-1.5 text-sm font-normal',
777
+ !activePerspectiveId && 'bg-accent text-accent-foreground'
778
+ )}
779
+ onClick={() => { onClear(); setOpen(false) }}
780
+ >
781
+ <Check className={cn('size-4 shrink-0 mr-2', activePerspectiveId ? 'invisible' : '')} />
782
+ {t('ui.dataTable.perspectives.noView', '— No view —')}
783
+ </Button>
784
+ {perspectives.map((p) => (
785
+ <Button
786
+ key={p.id}
787
+ type="button"
788
+ variant="ghost"
789
+ size="sm"
790
+ className={cn(
791
+ 'w-full justify-start h-auto px-2 py-1.5 text-sm font-normal',
792
+ activePerspectiveId === p.id && 'bg-accent text-accent-foreground'
793
+ )}
794
+ onClick={() => { onActivate(p, 'personal'); setOpen(false) }}
795
+ >
796
+ <Check className={cn('size-4 shrink-0 mr-2', activePerspectiveId !== p.id ? 'invisible' : '')} />
797
+ <span className="truncate">{p.name.trim() || t('ui.perspectives.untitled', 'Untitled view')}</span>
798
+ </Button>
799
+ ))}
800
+ {rolePerspectives.map((p) => (
801
+ <Button
802
+ key={p.id}
803
+ type="button"
804
+ variant="ghost"
805
+ size="sm"
806
+ className={cn(
807
+ 'w-full justify-start h-auto px-2 py-1.5 text-sm font-normal',
808
+ activePerspectiveId === p.id && 'bg-accent text-accent-foreground'
809
+ )}
810
+ onClick={() => { onActivate(p, 'role'); setOpen(false) }}
811
+ >
812
+ <Check className={cn('size-4 shrink-0 mr-2', activePerspectiveId !== p.id ? 'invisible' : '')} />
813
+ <span className="truncate">{p.name.trim() || t('ui.perspectives.untitled', 'Untitled view')}</span>
814
+ </Button>
815
+ ))}
816
+ </PopoverContent>
817
+ </Popover>
818
+ </div>
819
+ )
820
+ }
821
+
822
+ export function DataTable<T>({
823
+ columns,
824
+ data,
825
+ toolbar,
826
+ title,
827
+ actions,
828
+ refreshButton,
829
+ sortable,
830
+ sorting: sortingProp,
831
+ onSortingChange,
832
+ pagination,
833
+ isLoading,
834
+ emptyState,
835
+ error,
836
+ rowActions,
837
+ onRowClick,
838
+ rowClickActionIds,
839
+ disableRowClick = false,
840
+ bulkActions: bulkActionsProp,
841
+ searchValue,
842
+ onSearchChange,
843
+ searchPlaceholder,
844
+ searchAlign = 'left',
845
+ filters: baseFilters = EMPTY_FILTER_DEFS,
846
+ filterValues = EMPTY_FILTER_VALUES,
847
+ onFiltersApply,
848
+ onFiltersClear,
849
+ entityId,
850
+ entityIds,
851
+ exporter,
852
+ perspective,
853
+ embedded = false,
854
+ onCustomFieldFilterFieldsetChange,
855
+ customFieldFilterKeyExtras,
856
+ injectionSpotId,
857
+ injectionContext,
858
+ replacementHandle,
859
+ stickyFirstColumn = false,
860
+ stickyActionsColumn = false,
861
+ virtualized = false,
862
+ virtualizedMaxHeight,
863
+ virtualizedOverscan = 10,
864
+ advancedFilter,
865
+ columnChooser,
866
+ }: DataTableProps<T>) {
867
+ const t = useT()
868
+ const { confirm, ConfirmDialogElement } = useConfirmDialog()
869
+ const router = useRouter()
870
+ const resolvedRowClickActionIds = rowClickActionIds ?? DEFAULT_ROW_CLICK_ACTION_IDS
871
+ const containerRef = React.useRef<HTMLDivElement>(null)
872
+ const lastScopeRef = React.useRef<OrganizationScopeChangedDetail | null>(null)
873
+ const hasInitializedScopeRef = React.useRef(false)
874
+ React.useEffect(() => {
875
+ return subscribeOrganizationScopeChanged((detail) => {
876
+ const prev = lastScopeRef.current
877
+ lastScopeRef.current = detail
878
+ if (!hasInitializedScopeRef.current) {
879
+ hasInitializedScopeRef.current = true
880
+ return
881
+ }
882
+ if (
883
+ prev &&
884
+ prev.organizationId === detail.organizationId &&
885
+ prev.tenantId === detail.tenantId
886
+ ) {
887
+ return
888
+ }
889
+ scheduleRouterRefresh(router)
890
+ })
891
+ }, [router])
892
+ const queryClient = useQueryClient()
893
+ const perspectiveConfig = perspective ?? null
894
+ const perspectiveTableId = perspectiveConfig?.tableId ?? null
895
+ const perspectiveEnabled = Boolean(perspectiveTableId)
896
+ // Snapshot from localStorage is read post-mount via useLayoutEffect to avoid SSR/CSR
897
+ // hydration mismatch. Initial render uses only props-derived state (identical on both sides).
898
+ const initialSnapshotRef = React.useRef<PerspectiveSnapshot | null>(null)
899
+ const snapshotHydratedTableRef = React.useRef<string | null>(null)
900
+ const initialSettingsFromConfig = sanitizePerspectiveSettings(perspectiveConfig?.initialState?.initialSettings ?? null)
901
+ const mergedInitialSettings = initialSettingsFromConfig
902
+ const initialActiveId = perspectiveConfig?.initialState?.activePerspectiveId ?? null
903
+ const [isPerspectiveOpen, setPerspectiveOpen] = React.useState(false)
904
+ const [isAdvancedFilterOpen, setAdvancedFilterOpen] = React.useState(false)
905
+ const [activePerspectiveId, setActivePerspectiveId] = React.useState<string | null>(initialActiveId)
906
+ const [columnVisibility, setColumnVisibility] = React.useState<VisibilityState>(() => mergedInitialSettings?.columnVisibility ?? {})
907
+ const [columnOrder, setColumnOrder] = React.useState<string[]>(() => mergedInitialSettings?.columnOrder ?? [])
908
+ const [deletingIds, setDeletingIds] = React.useState<string[]>([])
909
+ const [roleClearingIds, setRoleClearingIds] = React.useState<string[]>([])
910
+ const [perspectiveApiMissing, setPerspectiveApiMissing] = React.useState(false)
911
+
912
+ const perspectiveFeatureQuery = useQuery<{ use: boolean; roleDefaults: boolean }>({
913
+ queryKey: ['feature-check', 'perspectives'],
914
+ enabled: perspectiveEnabled,
915
+ staleTime: 5 * 60 * 1000,
916
+ queryFn: async () => {
917
+ try {
918
+ const call = await apiCall<{ granted?: unknown[] }>(
919
+ '/api/auth/feature-check',
920
+ {
921
+ method: 'POST',
922
+ headers: { 'Content-Type': 'application/json' },
923
+ body: JSON.stringify({ features: ['perspectives.use', 'perspectives.role_defaults'] }),
924
+ },
925
+ )
926
+ if (!call.ok) throw new Error(`feature-check failed (${call.status})`)
927
+ const data = call.result ?? {}
928
+ const granted = Array.isArray(data?.granted) ? data.granted.map((f: any) => String(f)) : []
929
+ const has = (feature: string) => granted.some((grantedFeature: string) => {
930
+ if (grantedFeature === '*') return true
931
+ if (grantedFeature === feature) return true
932
+ if (grantedFeature.endsWith('.*')) {
933
+ const prefix = grantedFeature.slice(0, -2)
934
+ return feature === prefix || feature.startsWith(`${prefix}.`)
935
+ }
936
+ return false
937
+ })
938
+ return {
939
+ use: has('perspectives.use'),
940
+ roleDefaults: has('perspectives.role_defaults'),
941
+ }
942
+ } catch {
943
+ return {
944
+ use: true,
945
+ roleDefaults: true,
946
+ }
947
+ }
948
+ },
949
+ })
950
+ const perspectivePermissions = perspectiveFeatureQuery.data
951
+ const canUsePerspectives = perspectiveEnabled && Boolean(perspectivePermissions?.use)
952
+ const canUseRoleDefaultsFeature = Boolean(perspectivePermissions?.roleDefaults)
953
+
954
+ React.useEffect(() => {
955
+ if (!canUsePerspectives && isPerspectiveOpen) {
956
+ setPerspectiveOpen(false)
957
+ }
958
+ }, [canUsePerspectives, isPerspectiveOpen])
959
+
960
+ React.useEffect(() => {
961
+ if (!perspectiveTableId) return
962
+ if (!mergedInitialSettings) return
963
+ const snapshot: PerspectiveSnapshot = {
964
+ perspectiveId: initialActiveId,
965
+ settings: mergedInitialSettings,
966
+ updatedAt: Date.now(),
967
+ }
968
+ writePerspectiveSnapshot(perspectiveTableId, snapshot)
969
+ initialSnapshotRef.current = snapshot
970
+ }, [perspectiveTableId, mergedInitialSettings, initialActiveId])
971
+
972
+ const perspectiveQuery = useQuery<PerspectivesIndexResponse>({
973
+ queryKey: ['table-perspectives', perspectiveTableId],
974
+ queryFn: async () => {
975
+ if (!perspectiveTableId) throw new Error('Missing table id')
976
+ const call = await apiCall<PerspectivesIndexResponse>(`/api/perspectives/${encodeURIComponent(perspectiveTableId)}`)
977
+ if (call.status === 404) {
978
+ setPerspectiveApiMissing(true)
979
+ return {
980
+ tableId: perspectiveTableId,
981
+ perspectives: [],
982
+ defaultPerspectiveId: null,
983
+ rolePerspectives: [],
984
+ roles: [],
985
+ canApplyToRoles: false,
986
+ }
987
+ }
988
+ if (!call.ok) {
989
+ await raiseCrudError(call.response, t('ui.dataTable.perspectives.error.load', 'Failed to load perspectives'))
990
+ }
991
+ setPerspectiveApiMissing(false)
992
+ const payload = call.result
993
+ if (!payload) throw new Error(t('ui.dataTable.perspectives.error.load', 'Failed to load perspectives'))
994
+ return payload
995
+ },
996
+ enabled: canUsePerspectives,
997
+ initialData: perspectiveConfig?.initialState?.response,
998
+ staleTime: 5 * 60 * 1000,
999
+ gcTime: 10 * 60 * 1000,
1000
+ })
1001
+ const perspectiveData = perspectiveQuery.data
1002
+ const initialPerspectiveAppliedRef = React.useRef(Boolean(mergedInitialSettings))
1003
+
1004
+ const extensionTableId = React.useMemo(() => {
1005
+ if (perspective?.tableId) return perspective.tableId
1006
+ if (injectionSpotId?.startsWith('data-table:')) return injectionSpotId.slice('data-table:'.length)
1007
+ return null
1008
+ }, [injectionSpotId, perspective?.tableId])
1009
+ const resolvedInjectionSpotId = injectionSpotId ?? (perspective?.tableId ? `data-table:${perspective.tableId}` : null)
1010
+ const resolvedReplacementHandle = replacementHandle ?? ComponentReplacementHandles.dataTable(extensionTableId ?? 'unknown')
1011
+ const resolvedInjectionContext = React.useMemo(
1012
+ () => injectionContext ?? { tableId: perspective?.tableId ?? null, title: typeof title === 'string' ? title : undefined },
1013
+ [injectionContext, perspective?.tableId, title]
1014
+ )
1015
+ const headerInjectionSpotId = React.useMemo(
1016
+ () => (resolvedInjectionSpotId ? `${resolvedInjectionSpotId}:header` : null),
1017
+ [resolvedInjectionSpotId]
1018
+ )
1019
+ const toolbarInjectionSpotId = React.useMemo(
1020
+ () => (resolvedInjectionSpotId ? `${resolvedInjectionSpotId}:toolbar` : null),
1021
+ [resolvedInjectionSpotId]
1022
+ )
1023
+ const footerInjectionSpotId = React.useMemo(
1024
+ () => (resolvedInjectionSpotId ? `${resolvedInjectionSpotId}:footer` : null),
1025
+ [resolvedInjectionSpotId]
1026
+ )
1027
+ const { widgets: columnWidgets } = useInjectionDataWidgets(
1028
+ extensionTableId ? `data-table:${extensionTableId}:columns` : '__disabled__:columns',
1029
+ )
1030
+ const { widgets: rowActionWidgets } = useInjectionDataWidgets(
1031
+ extensionTableId ? `data-table:${extensionTableId}:row-actions` : '__disabled__:row-actions',
1032
+ )
1033
+ const { widgets: bulkActionWidgets } = useInjectionDataWidgets(
1034
+ extensionTableId ? `data-table:${extensionTableId}:bulk-actions` : '__disabled__:bulk-actions',
1035
+ )
1036
+ const { widgets: filterWidgets } = useInjectionDataWidgets(
1037
+ extensionTableId ? `data-table:${extensionTableId}:filters` : '__disabled__:filters',
1038
+ )
1039
+ const injectedColumnDefs = React.useMemo<{ def: ColumnDef<T, unknown>; placement: InjectionColumnDefinition['placement'] }[]>(() => {
1040
+ const entries: InjectionColumnDefinition[] = []
1041
+ for (const widget of columnWidgets) {
1042
+ if (!('columns' in widget)) continue
1043
+ for (const definition of widget.columns ?? []) {
1044
+ entries.push(definition)
1045
+ }
1046
+ }
1047
+ return collectUniqueById(entries, 'column').map((definition) => ({
1048
+ def: {
1049
+ id: definition.id,
1050
+ accessorFn: (row: T) => readInjectedColumnValue(row, definition.accessorKey),
1051
+ header: t(definition.header, definition.header),
1052
+ cell: definition.cell as ColumnDef<T, unknown>['cell'],
1053
+ size: definition.size,
1054
+ enableSorting: definition.sortable === true,
1055
+ },
1056
+ placement: definition.placement,
1057
+ }))
1058
+ }, [columnWidgets, t])
1059
+ const injectedRowActions = React.useMemo<InjectionRowActionDefinition[]>(() => {
1060
+ const entries: InjectionRowActionDefinition[] = []
1061
+ for (const widget of rowActionWidgets) {
1062
+ if (!('rowActions' in widget)) continue
1063
+ for (const definition of widget.rowActions ?? []) {
1064
+ entries.push(definition)
1065
+ }
1066
+ }
1067
+ return collectUniqueById(entries, 'row action')
1068
+ }, [rowActionWidgets])
1069
+ const injectedBulkActions = React.useMemo<InjectionBulkActionDefinition[]>(() => {
1070
+ const entries: InjectionBulkActionDefinition[] = []
1071
+ for (const widget of bulkActionWidgets) {
1072
+ if (!('bulkActions' in widget)) continue
1073
+ for (const definition of widget.bulkActions ?? []) {
1074
+ entries.push(definition)
1075
+ }
1076
+ }
1077
+ return collectUniqueById(entries, 'bulk action')
1078
+ }, [bulkActionWidgets])
1079
+ const { serverFilters: injectedFilters, clientFilters: injectedClientFilters } = React.useMemo<{
1080
+ serverFilters: FilterDef[]
1081
+ clientFilters: { id: string; filterFn: (row: unknown, value: unknown) => boolean }[]
1082
+ }>(() => {
1083
+ const byId = new Map<string, FilterDef>()
1084
+ const clientEntries: { id: string; filterFn: (row: unknown, value: unknown) => boolean }[] = []
1085
+ for (const widget of filterWidgets) {
1086
+ if (!('filters' in widget)) continue
1087
+ for (const definition of widget.filters ?? []) {
1088
+ const filter = definition as InjectionFilterDefinition
1089
+ const mappedType: FilterDef['type'] =
1090
+ filter.type === 'date-range'
1091
+ ? 'dateRange'
1092
+ : filter.type === 'boolean'
1093
+ ? 'checkbox'
1094
+ : filter.type === 'select'
1095
+ ? 'select'
1096
+ : 'text'
1097
+ const id = filter.queryParam ?? filter.id
1098
+ if (filter.strategy === 'client' && filter.filterFn) {
1099
+ clientEntries.push({ id, filterFn: filter.filterFn })
1100
+ }
1101
+ if (!byId.has(id)) {
1102
+ const translatedOptions = Array.isArray(filter.options)
1103
+ ? filter.options.map((option) => ({
1104
+ ...option,
1105
+ label: t(option.label, option.label),
1106
+ }))
1107
+ : filter.options
1108
+ byId.set(id, {
1109
+ id,
1110
+ label: t(filter.label, filter.label),
1111
+ type: mappedType,
1112
+ options: translatedOptions,
1113
+ })
1114
+ }
1115
+ }
1116
+ }
1117
+ return { serverFilters: Array.from(byId.values()), clientFilters: clientEntries }
1118
+ }, [filterWidgets, t])
1119
+ const mergedColumns = React.useMemo<ColumnDef<T, unknown>[]>(() => {
1120
+ if (!injectedColumnDefs.length) return columns
1121
+ let result = [...columns]
1122
+ for (const { def, placement } of injectedColumnDefs) {
1123
+ result = insertByInjectionPlacement(
1124
+ result,
1125
+ def,
1126
+ placement,
1127
+ (col) => (col as { id?: string }).id ?? '',
1128
+ )
1129
+ }
1130
+ return result
1131
+ }, [columns, injectedColumnDefs])
1132
+ const resolvedRowActions = React.useCallback((row: T) => {
1133
+ const injectedItems: (RowActionItem & { placement?: InjectionRowActionDefinition['placement'] })[] = injectedRowActions.map((action) => ({
1134
+ id: action.id,
1135
+ label: t(action.label, action.label),
1136
+ onSelect: () => action.onSelect(row, { navigate: (href: string) => router.push(href) }),
1137
+ placement: action.placement,
1138
+ }))
1139
+ const baseNode = rowActions ? rowActions(row) : null
1140
+ if (!injectedItems.length) return baseNode
1141
+ if (React.isValidElement(baseNode)) {
1142
+ const baseItems = (baseNode.props as { items?: RowActionItem[] }).items
1143
+ if (Array.isArray(baseItems)) {
1144
+ let merged = [...baseItems]
1145
+ const existingIds = new Set(
1146
+ baseItems.map((item) => item.id).filter((id): id is string => typeof id === 'string' && id.length > 0)
1147
+ )
1148
+ for (const item of injectedItems) {
1149
+ if (item.id && existingIds.has(item.id)) continue
1150
+ merged = insertByInjectionPlacement(
1151
+ merged,
1152
+ item,
1153
+ item.placement,
1154
+ (entry) => entry.id ?? '',
1155
+ )
1156
+ }
1157
+ return <RowActions items={merged} />
1158
+ }
1159
+ }
1160
+ return <RowActions items={injectedItems} />
1161
+ }, [injectedRowActions, rowActions, router, t])
1162
+
1163
+ // Date formatting setup
1164
+ const DATE_FORMAT = (process.env.NEXT_PUBLIC_DATE_FORMAT || 'YYYY-MM-DD HH:mm') as string
1165
+
1166
+ const pad2 = (n: number) => (n < 10 ? `0${n}` : String(n))
1167
+ const simpleFormat = (d: Date, fmt: string) => {
1168
+ // Supports tokens: YYYY, MM, DD, HH, mm, ss
1169
+ const YYYY = String(d.getFullYear())
1170
+ const MM = pad2(d.getMonth() + 1)
1171
+ const DD = pad2(d.getDate())
1172
+ const HH = pad2(d.getHours())
1173
+ const mm = pad2(d.getMinutes())
1174
+ const ss = pad2(d.getSeconds())
1175
+ return fmt
1176
+ .replace(/YYYY/g, YYYY)
1177
+ .replace(/MM/g, MM)
1178
+ .replace(/DD/g, DD)
1179
+ .replace(/HH/g, HH)
1180
+ .replace(/mm/g, mm)
1181
+ .replace(/ss/g, ss)
1182
+ }
1183
+
1184
+ const tryParseDate = (v: unknown): Date | null => {
1185
+ if (v == null) return null
1186
+ if (v instanceof Date) return isNaN(v.getTime()) ? null : v
1187
+ if (typeof v === 'number') {
1188
+ const d = new Date(v)
1189
+ return isNaN(d.getTime()) ? null : d
1190
+ }
1191
+ if (typeof v === 'string') {
1192
+ const s = v.trim()
1193
+ if (!s) return null
1194
+ // ISO-like detection (YYYY-MM-DD ...)
1195
+ if (/^\d{4}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)?$/.test(s)) {
1196
+ const d = new Date(s)
1197
+ return isNaN(d.getTime()) ? null : d
1198
+ }
1199
+ // Fallback: Date.parse
1200
+ const d = new Date(s)
1201
+ return isNaN(d.getTime()) ? null : d
1202
+ }
1203
+ return null
1204
+ }
1205
+
1206
+ // Guess date columns once using first non-empty row
1207
+ const [dateColumnIds, setDateColumnIds] = React.useState<Set<string> | null>(null)
1208
+ React.useEffect(() => {
1209
+ if (dateColumnIds) return
1210
+ if (!data || data.length === 0) return
1211
+ // Build a cheap row accessor using column defs
1212
+ const accessors = mergedColumns.map((c) => {
1213
+ const key = (c as any).accessorKey as string | undefined
1214
+ const id = (c as any).id as string | undefined
1215
+ return { id: id || key || '', key }
1216
+ })
1217
+ const guessed = new Set<string>()
1218
+ accessors.forEach((a) => {
1219
+ if (!a.id) return
1220
+ const name = a.id
1221
+ // Name-based guess: snake_case '_at' suffix
1222
+ if (name.endsWith('_at')) {
1223
+ guessed.add(name)
1224
+ return
1225
+ }
1226
+ })
1227
+ setDateColumnIds(guessed)
1228
+ }, [dateColumnIds, data, mergedColumns])
1229
+ // Column visibility: only hide columns explicitly marked as hidden.
1230
+ // All other columns are always rendered; horizontal scroll (min-w + overflow-auto)
1231
+ // handles narrow viewports so users can swipe to reach every column.
1232
+ const responsiveClass = (_priority?: number, hidden?: boolean) => {
1233
+ if (hidden) return ''
1234
+ return ''
1235
+ }
1236
+
1237
+ const resolvePriority = React.useCallback((column: TableColumn<T, unknown>) => {
1238
+ const meta = (column.columnDef as any)?.meta
1239
+ const rawPriority = typeof meta?.priority === 'number' ? meta.priority : undefined
1240
+ if (rawPriority && rawPriority > 0) return rawPriority
1241
+ const index = column.getIndex()
1242
+ return index <= 1 ? 1 : 2
1243
+ }, [])
1244
+
1245
+ const initialSorting = React.useMemo<SortingState>(() => {
1246
+ if (mergedInitialSettings?.sorting) {
1247
+ return mergedInitialSettings.sorting.map((item) => ({ id: item.id, desc: Boolean(item.desc) }))
1248
+ }
1249
+ return []
1250
+ }, [mergedInitialSettings])
1251
+ const [sorting, setSorting] = React.useState<SortingState>(() => {
1252
+ if (sortingProp && sortingProp.length) return sortingProp
1253
+ if (initialSorting.length) return initialSorting
1254
+ return []
1255
+ })
1256
+ const clientFilteredData = React.useMemo(() => {
1257
+ if (!injectedClientFilters.length || !filterValues) return data
1258
+ const activeClientFilters = injectedClientFilters.filter(
1259
+ (cf) => filterValues[cf.id] !== undefined && filterValues[cf.id] !== '' && filterValues[cf.id] !== null,
1260
+ )
1261
+ if (!activeClientFilters.length) return data
1262
+ return data.filter((row) =>
1263
+ activeClientFilters.every((cf) => cf.filterFn(row, filterValues[cf.id])),
1264
+ )
1265
+ }, [data, injectedClientFilters, filterValues])
1266
+ const hasPropBulkActions = Array.isArray(bulkActionsProp) && bulkActionsProp.length > 0
1267
+ const hasInjectedBulkActions = injectedBulkActions.length > 0 || hasPropBulkActions
1268
+ const [rowSelection, setRowSelection] = React.useState<RowSelectionState>({})
1269
+ const table = useReactTable<T>({
1270
+ data: clientFilteredData,
1271
+ columns: mergedColumns,
1272
+ getCoreRowModel: getCoreRowModel(),
1273
+ ...(sortable ? { getSortedRowModel: getSortedRowModel() } : {}),
1274
+ getRowId: resolveDataTableRowId,
1275
+ state: { sorting, columnVisibility, columnOrder, rowSelection },
1276
+ enableRowSelection: hasInjectedBulkActions,
1277
+ onSortingChange: (updater) => {
1278
+ const next = typeof updater === 'function' ? updater(sorting) : updater
1279
+ setSorting(next)
1280
+ onSortingChange?.(next)
1281
+ },
1282
+ onColumnVisibilityChange: (updater) => {
1283
+ const next = typeof updater === 'function' ? updater(columnVisibility) : updater
1284
+ setColumnVisibility(next)
1285
+ },
1286
+ onColumnOrderChange: (updater) => {
1287
+ const next = typeof updater === 'function' ? updater(columnOrder) : updater
1288
+ setColumnOrder(next)
1289
+ },
1290
+ onRowSelectionChange: setRowSelection,
1291
+ })
1292
+ React.useEffect(() => { if (sortingProp) setSorting(sortingProp) }, [sortingProp])
1293
+ React.useEffect(() => {
1294
+ if (hasInjectedBulkActions) return
1295
+ if (Object.keys(rowSelection).length === 0) return
1296
+ setRowSelection({})
1297
+ }, [hasInjectedBulkActions, rowSelection])
1298
+ React.useEffect(() => {
1299
+ const ids = table.getAllLeafColumns().map((column) => column.id)
1300
+ if (!ids.length) return
1301
+ setColumnOrder((prev) => {
1302
+ if (!prev.length) return ids
1303
+ const allowed = ids
1304
+ const filtered = prev.filter((id) => allowed.includes(id))
1305
+ const seen = new Set(filtered)
1306
+ for (const id of allowed) {
1307
+ if (!seen.has(id)) {
1308
+ filtered.push(id)
1309
+ seen.add(id)
1310
+ }
1311
+ }
1312
+ const changed = filtered.length !== prev.length || filtered.some((id, index) => id !== prev[index])
1313
+ return changed ? filtered : prev
1314
+ })
1315
+ }, [table, mergedColumns])
1316
+
1317
+ const initialVisibilityApplied = React.useRef(Boolean(mergedInitialSettings?.columnVisibility))
1318
+ React.useEffect(() => {
1319
+ if (initialVisibilityApplied.current) return
1320
+ const hidden: VisibilityState = {}
1321
+ table.getAllLeafColumns().forEach((column) => {
1322
+ const hiddenMeta = (column.columnDef as any)?.meta?.hidden
1323
+ if (hiddenMeta) hidden[column.id] = false
1324
+ })
1325
+ if (Object.keys(hidden).length) {
1326
+ setColumnVisibility((prev) => ({ ...hidden, ...prev }))
1327
+ }
1328
+ initialVisibilityApplied.current = true
1329
+ }, [table, mergedColumns])
1330
+
1331
+ const getCurrentSettings = React.useCallback((): PerspectiveSettings => {
1332
+ const visibility: Record<string, boolean> = {}
1333
+ for (const [key, value] of Object.entries(columnVisibility)) {
1334
+ if (typeof key === 'string' && typeof value === 'boolean') {
1335
+ visibility[key] = value
1336
+ }
1337
+ }
1338
+ const filtersRecord: Record<string, unknown> = {}
1339
+ for (const [key, value] of Object.entries(filterValues ?? {})) {
1340
+ if (typeof key === 'string') filtersRecord[key] = value
1341
+ }
1342
+ const candidate: PerspectiveSettings = {
1343
+ columnOrder,
1344
+ columnVisibility: visibility,
1345
+ sorting,
1346
+ filters: filtersRecord,
1347
+ searchValue,
1348
+ }
1349
+ return sanitizePerspectiveSettings(candidate) ?? {}
1350
+ }, [columnOrder, columnVisibility, sorting, filterValues, searchValue])
1351
+
1352
+ const applyPerspectiveSettings = React.useCallback((settings: PerspectiveSettings, nextId: string | null) => {
1353
+ const normalized = sanitizePerspectiveSettings(settings) ?? {}
1354
+ if (normalized.columnOrder && normalized.columnOrder.length) {
1355
+ setColumnOrder(normalized.columnOrder)
1356
+ } else {
1357
+ const ids = table.getAllLeafColumns().map((column) => column.id)
1358
+ if (ids.length) setColumnOrder(ids)
1359
+ }
1360
+ if (normalized.columnVisibility) setColumnVisibility(normalized.columnVisibility)
1361
+ else setColumnVisibility({})
1362
+ if (normalized.sorting) {
1363
+ const sortingState: SortingState = normalized.sorting.map((item) => ({
1364
+ id: item.id,
1365
+ desc: item.desc === true,
1366
+ }))
1367
+ setSorting(sortingState)
1368
+ onSortingChange?.(sortingState)
1369
+ } else {
1370
+ setSorting([])
1371
+ onSortingChange?.([])
1372
+ }
1373
+ if (onFiltersApply) {
1374
+ onFiltersApply((normalized.filters ?? {}) as FilterValues)
1375
+ }
1376
+ if (onSearchChange) {
1377
+ onSearchChange(normalized.searchValue ?? '')
1378
+ }
1379
+ setActivePerspectiveId(nextId)
1380
+ if (perspectiveTableId) {
1381
+ writePerspectiveCookie(perspectiveTableId, nextId)
1382
+ if (nextId) {
1383
+ const snapshot: PerspectiveSnapshot = { perspectiveId: nextId, settings: normalized, updatedAt: Date.now() }
1384
+ writePerspectiveSnapshot(perspectiveTableId, snapshot)
1385
+ initialSnapshotRef.current = snapshot
1386
+ } else {
1387
+ writePerspectiveSnapshot(perspectiveTableId, null)
1388
+ initialSnapshotRef.current = null
1389
+ }
1390
+ }
1391
+ }, [onFiltersApply, onSearchChange, onSortingChange, perspectiveTableId, table])
1392
+
1393
+ React.useLayoutEffect(() => {
1394
+ if (!perspectiveTableId) return
1395
+ if (snapshotHydratedTableRef.current === perspectiveTableId) return
1396
+ snapshotHydratedTableRef.current = perspectiveTableId
1397
+ const snapshot = readPerspectiveSnapshot(perspectiveTableId)
1398
+ if (!snapshot) return
1399
+ initialSnapshotRef.current = snapshot
1400
+ applyPerspectiveSettings(snapshot.settings, snapshot.perspectiveId ?? null)
1401
+ initialPerspectiveAppliedRef.current = true
1402
+ }, [perspectiveTableId, applyPerspectiveSettings])
1403
+
1404
+ type SavePerspectivePayload = {
1405
+ name: string
1406
+ isDefault: boolean
1407
+ applyToRoles: string[]
1408
+ setRoleDefault: boolean
1409
+ perspectiveId?: string | null
1410
+ settings?: PerspectiveSettings
1411
+ }
1412
+
1413
+ const perspectiveQueryKey: [string, string | null] = ['table-perspectives', perspectiveTableId]
1414
+ const savePerspectiveMutation = useMutation<PerspectiveSaveResponse, Error, SavePerspectivePayload>({
1415
+ mutationFn: async (input) => {
1416
+ if (!perspectiveTableId) throw new Error('Missing table id')
1417
+ const payload = {
1418
+ perspectiveId: input.perspectiveId ?? undefined,
1419
+ name: input.name,
1420
+ settings: input.settings ?? getCurrentSettings(),
1421
+ isDefault: input.isDefault,
1422
+ applyToRoles: input.applyToRoles,
1423
+ setRoleDefault: input.setRoleDefault,
1424
+ }
1425
+ if (process.env.NODE_ENV !== 'production') {
1426
+ // eslint-disable-next-line no-console
1427
+ console.debug('[DataTable] perspective payload', payload)
1428
+ }
1429
+ const call = await apiCall<PerspectiveSaveResponse>(
1430
+ `/api/perspectives/${encodeURIComponent(perspectiveTableId)}`,
1431
+ {
1432
+ method: 'POST',
1433
+ headers: { 'Content-Type': 'application/json' },
1434
+ body: JSON.stringify(payload),
1435
+ },
1436
+ )
1437
+ if (call.status === 404) {
1438
+ throw new Error(t('ui.dataTable.perspectives.error.apiUnavailable', 'Perspectives API is not available. Run `yarn generate` to regenerate module routes and restart the dev server.'))
1439
+ }
1440
+ if (!call.ok) {
1441
+ await raiseCrudError(call.response, t('ui.dataTable.perspectives.error.save', 'Failed to save perspective'))
1442
+ }
1443
+ const result = call.result
1444
+ if (!result) throw new Error(t('ui.dataTable.perspectives.error.save', 'Failed to save perspective'))
1445
+ return result
1446
+ },
1447
+ onSuccess: (data) => {
1448
+ if (perspectiveTableId) {
1449
+ void queryClient.invalidateQueries({ queryKey: perspectiveQueryKey })
1450
+ }
1451
+ if (data.perspective) {
1452
+ applyPerspectiveSettings(data.perspective.settings, data.perspective.id)
1453
+ }
1454
+ },
1455
+ })
1456
+
1457
+ const resolveColumnLabel = React.useCallback((column: TableColumn<T, unknown>): string => {
1458
+ const meta = (column.columnDef as any)?.meta
1459
+ if (typeof meta?.label === 'string' && meta.label.trim().length > 0) return meta.label.trim()
1460
+ if (typeof meta?.title === 'string' && meta.title.trim().length > 0) return meta.title.trim()
1461
+ const header = column.columnDef.header
1462
+ if (typeof header === 'string') return header
1463
+ if (typeof header === 'function') return normalizeLabel(column.id)
1464
+ return normalizeLabel(column.id)
1465
+ }, [])
1466
+
1467
+ const activePersonalPerspectiveId = React.useMemo(() => {
1468
+ if (!perspectiveData || !activePerspectiveId) return null
1469
+ const found = perspectiveData.perspectives.find((p) => p.id === activePerspectiveId)
1470
+ return found ? found.id : null
1471
+ }, [perspectiveData, activePerspectiveId])
1472
+
1473
+
1474
+ const deletePerspectiveMutation = useMutation<void, Error, { perspectiveId: string }>({
1475
+ mutationFn: async ({ perspectiveId }) => {
1476
+ if (!perspectiveTableId) throw new Error('Missing table id')
1477
+ const call = await apiCall(
1478
+ `/api/perspectives/${encodeURIComponent(perspectiveTableId)}/${encodeURIComponent(perspectiveId)}`,
1479
+ { method: 'DELETE' },
1480
+ )
1481
+ if (call.status === 404) throw new Error(t('ui.dataTable.perspectives.error.apiUnavailable', 'Perspectives API is not available. Run `yarn generate` and restart the dev server.'))
1482
+ if (!call.ok) {
1483
+ await raiseCrudError(call.response, t('ui.dataTable.perspectives.error.delete', 'Failed to delete perspective'))
1484
+ }
1485
+ },
1486
+ onMutate: ({ perspectiveId }) => {
1487
+ setDeletingIds((prev) => prev.includes(perspectiveId) ? prev : [...prev, perspectiveId])
1488
+ },
1489
+ onSettled: (_data, _error, variables) => {
1490
+ setDeletingIds((prev) => prev.filter((id) => id !== variables.perspectiveId))
1491
+ },
1492
+ onSuccess: (_data, variables) => {
1493
+ const removedActive = activePerspectiveId === variables.perspectiveId
1494
+ if (perspectiveTableId) {
1495
+ void queryClient.invalidateQueries({ queryKey: perspectiveQueryKey })
1496
+ if (removedActive) {
1497
+ setActivePerspectiveId(null)
1498
+ writePerspectiveCookie(perspectiveTableId, null)
1499
+ writePerspectiveSnapshot(perspectiveTableId, null)
1500
+ initialSnapshotRef.current = null
1501
+ initialPerspectiveAppliedRef.current = false
1502
+ }
1503
+ } else if (removedActive) {
1504
+ setActivePerspectiveId(null)
1505
+ initialPerspectiveAppliedRef.current = false
1506
+ }
1507
+ },
1508
+ })
1509
+
1510
+ const clearRoleMutation = useMutation<void, Error, { roleId: string }>({
1511
+ mutationFn: async ({ roleId }) => {
1512
+ if (!perspectiveTableId) throw new Error('Missing table id')
1513
+ const call = await apiCall(
1514
+ `/api/perspectives/${encodeURIComponent(perspectiveTableId)}/roles/${encodeURIComponent(roleId)}`,
1515
+ { method: 'DELETE' },
1516
+ )
1517
+ if (call.status === 404) throw new Error(t('ui.dataTable.perspectives.error.apiUnavailable', 'Perspectives API is not available. Run `yarn generate` and restart the dev server.'))
1518
+ if (!call.ok) {
1519
+ await raiseCrudError(call.response, t('ui.dataTable.perspectives.error.clearRoles', 'Failed to clear role perspectives'))
1520
+ }
1521
+ },
1522
+ onMutate: ({ roleId }) => {
1523
+ setRoleClearingIds((prev) => prev.includes(roleId) ? prev : [...prev, roleId])
1524
+ },
1525
+ onSettled: (_data, _error, variables) => {
1526
+ setRoleClearingIds((prev) => prev.filter((id) => id !== variables.roleId))
1527
+ },
1528
+ onSuccess: (_data, variables) => {
1529
+ if (perspectiveTableId) {
1530
+ void queryClient.invalidateQueries({ queryKey: perspectiveQueryKey })
1531
+ }
1532
+ if (activePerspectiveId) {
1533
+ const current = queryClient.getQueryData<PerspectivesIndexResponse>(perspectiveQueryKey)
1534
+ const match = current?.rolePerspectives.find((rp) => rp.id === activePerspectiveId)
1535
+ if (match && match.roleId === variables.roleId) {
1536
+ setActivePerspectiveId(null)
1537
+ if (perspectiveTableId) writePerspectiveCookie(perspectiveTableId, null)
1538
+ if (perspectiveTableId) writePerspectiveSnapshot(perspectiveTableId, null)
1539
+ initialSnapshotRef.current = null
1540
+ initialPerspectiveAppliedRef.current = false
1541
+ }
1542
+ }
1543
+ },
1544
+ })
1545
+
1546
+ const handlePerspectiveActivate = React.useCallback((item: PerspectiveDto | RolePerspectiveDto, _source?: 'personal' | 'role') => {
1547
+ applyPerspectiveSettings(item.settings, item.id)
1548
+ }, [applyPerspectiveSettings])
1549
+
1550
+ const handlePerspectiveSave = React.useCallback(async (input: { name: string; isDefault: boolean; applyToRoles: string[]; setRoleDefault: boolean; perspectiveId?: string | null; settings?: PerspectiveSettings }) => {
1551
+ const normalizedRoles = Array.from(new Set(input.applyToRoles))
1552
+ await savePerspectiveMutation.mutateAsync({
1553
+ name: input.name.trim(),
1554
+ isDefault: input.isDefault,
1555
+ applyToRoles: normalizedRoles,
1556
+ setRoleDefault: normalizedRoles.length > 0 ? input.setRoleDefault : false,
1557
+ perspectiveId: input.perspectiveId !== undefined ? input.perspectiveId : activePersonalPerspectiveId,
1558
+ settings: input.settings,
1559
+ })
1560
+ }, [savePerspectiveMutation, activePersonalPerspectiveId])
1561
+
1562
+ const handlePerspectiveDelete = React.useCallback(async (perspectiveId: string) => {
1563
+ await deletePerspectiveMutation.mutateAsync({ perspectiveId })
1564
+ }, [deletePerspectiveMutation])
1565
+
1566
+ const handleClearRole = React.useCallback(async (roleId: string) => {
1567
+ await clearRoleMutation.mutateAsync({ roleId })
1568
+ }, [clearRoleMutation])
1569
+
1570
+ const handleColumnChooserToggle = React.useCallback((key: string) => {
1571
+ const column = table.getColumn(key)
1572
+ if (!column) return
1573
+ const nextVisible = !column.getIsVisible()
1574
+ if (nextVisible) {
1575
+ setColumnOrder((prev) => (prev.includes(key) ? prev : [...prev, key]))
1576
+ }
1577
+ setColumnVisibility((prev) => {
1578
+ const next = { ...prev }
1579
+ if (nextVisible) delete next[key]
1580
+ else next[key] = false
1581
+ return next
1582
+ })
1583
+ column.toggleVisibility(nextVisible)
1584
+ }, [table])
1585
+
1586
+ const handleColumnChooserReorder = React.useCallback((newOrder: string[]) => {
1587
+ setColumnOrder(newOrder)
1588
+ table.setColumnOrder(newOrder)
1589
+ }, [table])
1590
+
1591
+ const dndSensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 5 } }))
1592
+ const enableHeaderDnd = Boolean(columnChooser)
1593
+ const stableDndContextId = React.useMemo(
1594
+ () => sanitizeDndContextId(
1595
+ extensionTableId
1596
+ ?? perspectiveTableId
1597
+ ?? resolvedReplacementHandle
1598
+ ?? (typeof title === 'string' && title.trim().length > 0 ? title : 'data-table'),
1599
+ ),
1600
+ [extensionTableId, perspectiveTableId, resolvedReplacementHandle, title],
1601
+ )
1602
+ const headerColumnIds = React.useMemo(() => {
1603
+ if (!enableHeaderDnd) return []
1604
+ return table.getHeaderGroups().flatMap((hg) => hg.headers.map((h) => h.id))
1605
+ }, [enableHeaderDnd, table, columnOrder])
1606
+
1607
+ const handleHeaderDragEnd = React.useCallback((event: DragEndEvent) => {
1608
+ const { active, over } = event
1609
+ if (!over || active.id === over.id) return
1610
+ const currentIds = columnOrder.length ? columnOrder : table.getAllLeafColumns().map((c) => c.id)
1611
+ const oldIdx = currentIds.indexOf(String(active.id))
1612
+ const newIdx = currentIds.indexOf(String(over.id))
1613
+ if (oldIdx === -1 || newIdx === -1) return
1614
+ const next = [...currentIds]
1615
+ const [moved] = next.splice(oldIdx, 1)
1616
+ next.splice(newIdx, 0, moved)
1617
+ setColumnOrder(next)
1618
+ table.setColumnOrder(next)
1619
+ }, [columnOrder, table])
1620
+
1621
+ const perspectiveApiWarning = perspectiveApiMissing && canUsePerspectives
1622
+ ? t('ui.dataTable.perspectives.warning.apiUnavailable', 'Perspectives API is not available yet. Run `yarn generate` to regenerate module routes, then restart the server.')
1623
+ : null
1624
+
1625
+ const loadStartRef = React.useRef<number | null>(null)
1626
+ const [measuredDurationMs, setMeasuredDurationMs] = React.useState<number | null>(null)
1627
+
1628
+ React.useEffect(() => {
1629
+ if (typeof isLoading !== 'boolean') return
1630
+ if (isLoading) {
1631
+ if (loadStartRef.current === null) {
1632
+ const now = typeof performance !== 'undefined' && typeof performance.now === 'function'
1633
+ ? performance.now()
1634
+ : Date.now()
1635
+ loadStartRef.current = now
1636
+ }
1637
+ return
1638
+ }
1639
+ if (loadStartRef.current !== null) {
1640
+ const now = typeof performance !== 'undefined' && typeof performance.now === 'function'
1641
+ ? performance.now()
1642
+ : Date.now()
1643
+ setMeasuredDurationMs(now - loadStartRef.current)
1644
+ loadStartRef.current = null
1645
+ }
1646
+ }, [isLoading])
1647
+
1648
+ React.useLayoutEffect(() => {
1649
+ if (!canUsePerspectives) return
1650
+ if (!perspectiveTableId) return
1651
+ if (initialSnapshotRef.current) return
1652
+ if (initialPerspectiveAppliedRef.current && activePerspectiveId != null) return
1653
+
1654
+ const source = perspectiveData ?? perspectiveConfig?.initialState?.response
1655
+ if (!source) return
1656
+
1657
+ const tryResolve = (id: string | null | undefined): PerspectiveDto | RolePerspectiveDto | undefined => {
1658
+ if (!id) return undefined
1659
+ return source.perspectives.find((p) => p.id === id)
1660
+ ?? source.rolePerspectives.find((p) => p.id === id)
1661
+ }
1662
+
1663
+ let target: PerspectiveDto | RolePerspectiveDto | undefined
1664
+ if (activePerspectiveId) {
1665
+ target = tryResolve(activePerspectiveId)
1666
+ }
1667
+ const cookieId = readPerspectiveCookie(perspectiveTableId)
1668
+ if (!target && cookieId) target = tryResolve(cookieId)
1669
+ if (!target && source.defaultPerspectiveId) {
1670
+ target = tryResolve(source.defaultPerspectiveId)
1671
+ }
1672
+ if (!target) {
1673
+ target = source.rolePerspectives.find((p) => p.isDefault)
1674
+ }
1675
+ if (!target) {
1676
+ target = source.perspectives[0]
1677
+ }
1678
+ if (target) {
1679
+ applyPerspectiveSettings(target.settings, target.id)
1680
+ }
1681
+ initialPerspectiveAppliedRef.current = true
1682
+ }, [canUsePerspectives, perspectiveData, perspectiveTableId, perspectiveConfig, applyPerspectiveSettings, activePerspectiveId])
1683
+
1684
+ const scrollTableIntoView = React.useCallback(() => {
1685
+ const rect = containerRef.current?.getBoundingClientRect()
1686
+ if (!rect || rect.top >= 0) return
1687
+ const behavior = window.matchMedia('(prefers-reduced-motion: reduce)').matches ? 'instant' : 'smooth'
1688
+ containerRef.current?.scrollIntoView({ behavior, block: 'start' })
1689
+ }, [])
1690
+
1691
+ const paginationNode = React.useMemo(() => {
1692
+ if (!pagination || pagination.total === 0) return null
1693
+
1694
+ const { page, totalPages, onPageChange, durationMs, cacheStatus } = pagination
1695
+ const startItem = (page - 1) * pagination.pageSize + 1
1696
+ const endItem = Math.min(page * pagination.pageSize, pagination.total)
1697
+ const effectiveDuration = (typeof durationMs === 'number' && Number.isFinite(durationMs) && durationMs >= 0)
1698
+ ? durationMs
1699
+ : measuredDurationMs ?? undefined
1700
+ const durationLabel = formatDurationLabel(effectiveDuration)
1701
+ const normalizedCacheStatus = cacheStatus === 'hit' || cacheStatus === 'miss' ? cacheStatus : null
1702
+ const cacheBadge = normalizedCacheStatus ? (
1703
+ <span
1704
+ className="inline-flex items-center justify-center"
1705
+ aria-label={t('ui.dataTable.pagination.cache.ariaLabel', 'Cache {status}', { status: normalizedCacheStatus.toUpperCase() })}
1706
+ title={t('ui.dataTable.pagination.cache.title', 'Cache {status}', { status: normalizedCacheStatus.toUpperCase() })}
1707
+ >
1708
+ <Circle
1709
+ className={`h-3.5 w-3.5 ${normalizedCacheStatus === 'hit' ? 'text-emerald-500' : 'text-amber-500'}`}
1710
+ strokeWidth={3}
1711
+ />
1712
+ <span className="sr-only">{t('ui.dataTable.pagination.cache.srOnly', 'Cache {status}', { status: normalizedCacheStatus.toUpperCase() })}</span>
1713
+ </span>
1714
+ ) : null
1715
+
1716
+ const pageSizeOptions = Array.isArray(pagination.pageSizeOptions)
1717
+ ? Array.from(new Set(
1718
+ [pagination.pageSize, ...pagination.pageSizeOptions]
1719
+ .filter((size): size is number => typeof size === 'number' && Number.isFinite(size) && size > 0)
1720
+ .map((size) => Math.max(1, Math.floor(size))),
1721
+ )).sort((left, right) => left - right)
1722
+ : []
1723
+ const pageSizeSelect = pageSizeOptions.length > 0 && pagination.onPageSizeChange ? (
1724
+ <span className="inline-flex items-center gap-1.5">
1725
+ <select
1726
+ className="rounded border bg-background pl-2 pr-7 py-0.5 text-sm min-w-[3.5rem]"
1727
+ value={pagination.pageSize}
1728
+ onChange={(event) => {
1729
+ pagination.onPageSizeChange!(Number(event.target.value))
1730
+ scrollTableIntoView()
1731
+ }}
1732
+ aria-label={t('ui.dataTable.pagination.rowsPerPage', 'Rows per page')}
1733
+ >
1734
+ {pageSizeOptions.map((size) => (
1735
+ <option key={size} value={size}>{size}</option>
1736
+ ))}
1737
+ </select>
1738
+ <span className="text-muted-foreground">{t('ui.dataTable.pagination.perPage', 'per page')}</span>
1739
+ </span>
1740
+ ) : null
1741
+
1742
+ return (
1743
+ <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 px-4 py-3 border-t">
1744
+ <div className="text-sm text-muted-foreground flex items-center justify-center sm:justify-start gap-2 flex-wrap">
1745
+ <span>
1746
+ {durationLabel
1747
+ ? t('ui.dataTable.pagination.resultsWithDuration', 'Showing {start} to {end} of {total} results in {duration}', { start: startItem, end: endItem, total: pagination.total, duration: durationLabel })
1748
+ : t('ui.dataTable.pagination.results', 'Showing {start} to {end} of {total} results', { start: startItem, end: endItem, total: pagination.total })
1749
+ }
1750
+ </span>
1751
+ {cacheBadge}
1752
+ {pageSizeSelect}
1753
+ </div>
1754
+ <div className="flex items-center justify-center sm:justify-end gap-2">
1755
+ <Button
1756
+ variant="outline"
1757
+ size="sm"
1758
+ onClick={() => { onPageChange(page - 1); scrollTableIntoView() }}
1759
+ disabled={page <= 1}
1760
+ >
1761
+ {t('ui.dataTable.pagination.previous', 'Previous')}
1762
+ </Button>
1763
+ <span className="text-sm whitespace-nowrap">
1764
+ {t('ui.dataTable.pagination.pageInfo', 'Page {page} of {totalPages}', { page, totalPages })}
1765
+ </span>
1766
+ <Button
1767
+ variant="outline"
1768
+ size="sm"
1769
+ onClick={() => { onPageChange(page + 1); scrollTableIntoView() }}
1770
+ disabled={page >= totalPages}
1771
+ >
1772
+ {t('ui.dataTable.pagination.next', 'Next')}
1773
+ </Button>
1774
+ </div>
1775
+ </div>
1776
+ )
1777
+ }, [pagination, measuredDurationMs, scrollTableIntoView, t])
1778
+
1779
+ // Auto filters: fetch custom field defs when requested
1780
+ const resolvedEntityIds = React.useMemo(() => {
1781
+ if (Array.isArray(entityIds) && entityIds.length) {
1782
+ const dedup = new Set<string>()
1783
+ const list: string[] = []
1784
+ entityIds.forEach((id) => {
1785
+ const trimmed = typeof id === 'string' ? id.trim() : ''
1786
+ if (!trimmed || dedup.has(trimmed)) return
1787
+ dedup.add(trimmed)
1788
+ list.push(trimmed)
1789
+ })
1790
+ return list
1791
+ }
1792
+ if (typeof entityId === 'string' && entityId.trim().length > 0) {
1793
+ return [entityId.trim()]
1794
+ }
1795
+ return []
1796
+ }, [entityId, entityIds])
1797
+ const entityKey = React.useMemo(() => (resolvedEntityIds.length ? resolvedEntityIds.join('|') : null), [resolvedEntityIds])
1798
+ const customFieldFilterExtrasSignature = React.useMemo(
1799
+ () => JSON.stringify(customFieldFilterKeyExtras ?? []),
1800
+ [customFieldFilterKeyExtras]
1801
+ )
1802
+
1803
+ const [cfFilterFieldsetsByEntity, setCfFilterFieldsetsByEntity] = React.useState<Record<string, CustomFieldsetDto[]>>({})
1804
+ const [cfFilterFieldsetSelection, setCfFilterFieldsetSelection] = React.useState<Record<string, string | null>>({})
1805
+
1806
+ React.useEffect(() => {
1807
+ if (!entityKey) {
1808
+ setCfFilterFieldsetsByEntity({})
1809
+ setCfFilterFieldsetSelection({})
1810
+ return
1811
+ }
1812
+ let cancelled = false
1813
+ const loadFieldsets = async () => {
1814
+ try {
1815
+ const payload = await fetchCustomFieldDefinitionsPayload(resolvedEntityIds)
1816
+ if (cancelled) return
1817
+ const fieldsets = payload.fieldsetsByEntity ?? {}
1818
+ setCfFilterFieldsetsByEntity(fieldsets)
1819
+ const selectionChanges: Array<[string, string | null]> = []
1820
+ let shouldNotify = false
1821
+ setCfFilterFieldsetSelection((prev) => {
1822
+ const next: Record<string, string | null> = {}
1823
+ let changed = false
1824
+ resolvedEntityIds.forEach((entityId) => {
1825
+ const list = fieldsets[entityId] ?? []
1826
+ if (!list.length) {
1827
+ if (prev[entityId] !== undefined) changed = true
1828
+ return
1829
+ }
1830
+ const existing = prev[entityId]
1831
+ const fallback = list[0]?.code ?? null
1832
+ const isValidExisting = existing ? list.some((entry) => entry.code === existing) : false
1833
+ const value = isValidExisting ? existing : fallback ?? null
1834
+ next[entityId] = value
1835
+ if (value !== existing) {
1836
+ changed = true
1837
+ selectionChanges.push([entityId, value])
1838
+ }
1839
+ })
1840
+ if (Object.keys(prev).length !== Object.keys(next).length) changed = true
1841
+ if (changed) {
1842
+ shouldNotify = true
1843
+ return next
1844
+ }
1845
+ return prev
1846
+ })
1847
+ if (shouldNotify && selectionChanges.length && onCustomFieldFilterFieldsetChange) {
1848
+ selectionChanges.forEach(([entityId, value]) => onCustomFieldFilterFieldsetChange(value, entityId))
1849
+ }
1850
+ } catch {
1851
+ if (!cancelled) {
1852
+ setCfFilterFieldsetsByEntity({})
1853
+ setCfFilterFieldsetSelection({})
1854
+ }
1855
+ }
1856
+ }
1857
+ loadFieldsets()
1858
+ return () => {
1859
+ cancelled = true
1860
+ }
1861
+ }, [customFieldFilterExtrasSignature, entityKey, onCustomFieldFilterFieldsetChange, resolvedEntityIds])
1862
+
1863
+ const supportsCustomFieldFilterFieldsets =
1864
+ resolvedEntityIds.length === 1 &&
1865
+ (cfFilterFieldsetsByEntity[resolvedEntityIds[0]]?.length ?? 0) > 0
1866
+ const activeCustomFieldFilterFieldset = supportsCustomFieldFilterFieldsets
1867
+ ? cfFilterFieldsetSelection[resolvedEntityIds[0]] ?? cfFilterFieldsetsByEntity[resolvedEntityIds[0]]?.[0]?.code ?? null
1868
+ : null
1869
+
1870
+ const handleCustomFieldFilterFieldsetChange = React.useCallback(
1871
+ (value: string) => {
1872
+ if (!supportsCustomFieldFilterFieldsets) return
1873
+ const entityId = resolvedEntityIds[0]
1874
+ const nextValue = value || null
1875
+ setCfFilterFieldsetSelection((prev) => {
1876
+ if (prev[entityId] === nextValue) return prev
1877
+ return { ...prev, [entityId]: nextValue }
1878
+ })
1879
+ if (onCustomFieldFilterFieldsetChange) {
1880
+ onCustomFieldFilterFieldsetChange(nextValue, entityId)
1881
+ }
1882
+ },
1883
+ [onCustomFieldFilterFieldsetChange, resolvedEntityIds, supportsCustomFieldFilterFieldsets],
1884
+ )
1885
+
1886
+ const { data: cfFilters = [] } = useCustomFieldFilterDefs(entityKey ? resolvedEntityIds : [], {
1887
+ enabled: !!entityKey,
1888
+ fieldset: supportsCustomFieldFilterFieldsets ? activeCustomFieldFilterFieldset ?? undefined : undefined,
1889
+ keyExtras: customFieldFilterKeyExtras,
1890
+ })
1891
+
1892
+ const isAutoAdvancedFilter = Boolean(advancedFilter?.auto)
1893
+ const isAutoColumnChooser = Boolean(columnChooser?.auto)
1894
+ const needsAutoDiscovery = isAutoAdvancedFilter || isAutoColumnChooser
1895
+ const { data: autoDiscoveryDefs = [] } = useCustomFieldDefs(
1896
+ needsAutoDiscovery && entityKey ? resolvedEntityIds : [],
1897
+ { enabled: needsAutoDiscovery && !!entityKey },
1898
+ )
1899
+ const autoDiscovered = useAutoDiscoveredFields({
1900
+ columns: needsAutoDiscovery ? mergedColumns : [],
1901
+ customFieldDefs: needsAutoDiscovery ? autoDiscoveryDefs : [],
1902
+ })
1903
+ const resolvedAdvancedFilterFields = isAutoAdvancedFilter
1904
+ ? autoDiscovered.advancedFilterFields
1905
+ : advancedFilter?.fields ?? []
1906
+ const resolvedColumnChooserFields = isAutoColumnChooser
1907
+ ? autoDiscovered.columnChooserFields
1908
+ : columnChooser?.availableColumns ?? []
1909
+
1910
+ const effectiveColumnChooserFields = React.useMemo<ColumnChooserField[]>(() => {
1911
+ if (resolvedColumnChooserFields.length > 0) return resolvedColumnChooserFields
1912
+ return table.getAllLeafColumns().map((col) => ({
1913
+ key: col.id,
1914
+ label: resolveColumnLabel(col),
1915
+ group: 'Columns',
1916
+ alwaysVisible: !col.getCanHide(),
1917
+ }))
1918
+ }, [resolvedColumnChooserFields, table, resolveColumnLabel, columns])
1919
+
1920
+ const visibleColumnKeys = React.useMemo(
1921
+ () => table.getAllLeafColumns().filter((c) => c.getIsVisible()).map((c) => c.id),
1922
+ [table, columnVisibility, columns],
1923
+ )
1924
+
1925
+ const selectedRows = React.useMemo<T[]>(() => {
1926
+ if (!hasInjectedBulkActions) return []
1927
+ return table.getSelectedRowModel().rows.map((row) => row.original as T)
1928
+ }, [hasInjectedBulkActions, table, rowSelection])
1929
+ const trackedBulkProgressJobIdsRef = React.useRef(new Set<string>())
1930
+
1931
+ const clearTrackedBulkProgressJob = React.useCallback((jobId: string | null): boolean => {
1932
+ if (!jobId) return false
1933
+ return trackedBulkProgressJobIdsRef.current.delete(jobId)
1934
+ }, [])
1935
+
1936
+ const refreshAfterBulkProgressCompletion = React.useCallback(() => {
1937
+ if (refreshButton?.onRefresh) {
1938
+ refreshButton.onRefresh()
1939
+ return
1940
+ }
1941
+ scheduleRouterRefresh(router)
1942
+ }, [refreshButton, router])
1943
+
1944
+ useAppEvent(
1945
+ 'progress.job.completed',
1946
+ (event) => {
1947
+ const payload = event.payload as { jobId?: unknown } | null | undefined
1948
+ const jobId = typeof payload?.jobId === 'string' ? payload.jobId : null
1949
+ if (!clearTrackedBulkProgressJob(jobId)) return
1950
+ refreshAfterBulkProgressCompletion()
1951
+ },
1952
+ [clearTrackedBulkProgressJob, refreshAfterBulkProgressCompletion],
1953
+ )
1954
+
1955
+ useAppEvent(
1956
+ 'progress.job.failed',
1957
+ (event) => {
1958
+ const payload = event.payload as { jobId?: unknown } | null | undefined
1959
+ const jobId = typeof payload?.jobId === 'string' ? payload.jobId : null
1960
+ clearTrackedBulkProgressJob(jobId)
1961
+ },
1962
+ [clearTrackedBulkProgressJob],
1963
+ )
1964
+
1965
+ useAppEvent(
1966
+ 'progress.job.cancelled',
1967
+ (event) => {
1968
+ const payload = event.payload as { jobId?: unknown } | null | undefined
1969
+ const jobId = typeof payload?.jobId === 'string' ? payload.jobId : null
1970
+ clearTrackedBulkProgressJob(jobId)
1971
+ },
1972
+ [clearTrackedBulkProgressJob],
1973
+ )
1974
+
1975
+ const runBulkAction = React.useCallback(
1976
+ async (action: InjectionBulkActionDefinition) => {
1977
+ if (action.requiresSelection !== false && !selectedRows.length) return
1978
+ try {
1979
+ const result = await action.onExecute(selectedRows, {
1980
+ tableId: extensionTableId,
1981
+ navigate: (href: string) => router.push(href),
1982
+ confirm,
1983
+ refresh: refreshButton?.onRefresh,
1984
+ injectionContext: resolvedInjectionContext,
1985
+ translate: t,
1986
+ })
1987
+ const normalized = result as BulkActionExecuteResult | void
1988
+ if (normalized && normalized.ok === false) {
1989
+ if (normalized.message === undefined) return
1990
+ flash(
1991
+ normalized.message
1992
+ ?? t('ui.dataTable.bulkAction.error', 'Bulk action failed.'),
1993
+ 'error',
1994
+ )
1995
+ return
1996
+ }
1997
+ if (normalized?.progressJobId) {
1998
+ trackedBulkProgressJobIdsRef.current.add(normalized.progressJobId)
1999
+ setRowSelection({})
2000
+ flash(
2001
+ normalized.message
2002
+ ?? t('ui.dataTable.bulkAction.started', 'Bulk action started. Track progress in the top bar.'),
2003
+ 'success',
2004
+ )
2005
+ return
2006
+ }
2007
+ flash(
2008
+ normalized?.message
2009
+ ?? t('ui.dataTable.bulkAction.success', 'Bulk action completed.'),
2010
+ 'success',
2011
+ )
2012
+ setRowSelection({})
2013
+ if (refreshButton?.onRefresh) {
2014
+ refreshButton.onRefresh()
2015
+ } else {
2016
+ scheduleRouterRefresh(router)
2017
+ }
2018
+ } catch (error) {
2019
+ flash(
2020
+ error instanceof Error
2021
+ ? error.message
2022
+ : t('ui.dataTable.bulkAction.error', 'Bulk action failed.'),
2023
+ 'error',
2024
+ )
2025
+ }
2026
+ },
2027
+ [confirm, extensionTableId, refreshButton, resolvedInjectionContext, router, selectedRows, t],
2028
+ )
2029
+
2030
+ const runPropBulkAction = React.useCallback(
2031
+ async (action: BulkAction<T>) => {
2032
+ const result = await action.onExecute(selectedRows)
2033
+ if (result !== false) {
2034
+ setRowSelection({})
2035
+ }
2036
+ },
2037
+ [selectedRows],
2038
+ )
2039
+
2040
+ const builtToolbar = React.useMemo(() => {
2041
+ if (toolbar) return toolbar
2042
+ const anySearch = onSearchChange != null
2043
+ const anyFilters = (baseFilters && baseFilters.length > 0) || (cfFilters && cfFilters.length > 0) || injectedFilters.length > 0
2044
+ const hasBulkButtons = hasInjectedBulkActions || hasPropBulkActions
2045
+ if (!anySearch && !anyFilters && !hasBulkButtons) return null
2046
+ // Merge base filters with CF filters, preferring base definitions when ids collide
2047
+ const baseList = baseFilters || []
2048
+ const existing = new Set(baseList.map((f) => f.id))
2049
+ const cfOnly = (cfFilters || []).filter((f) => !existing.has(f.id))
2050
+ const injectedOnly = injectedFilters.filter((f) => !existing.has(f.id) && !cfOnly.some((cf) => cf.id === f.id))
2051
+ const combined: FilterDef[] = [...baseList, ...cfOnly, ...injectedOnly]
2052
+ const perspectiveButton = canUsePerspectives ? (
2053
+ <ViewSwitcherDropdown
2054
+ activePerspectiveId={activePerspectiveId}
2055
+ perspectives={perspectiveData?.perspectives ?? []}
2056
+ rolePerspectives={perspectiveData?.rolePerspectives ?? []}
2057
+ onClear={() => applyPerspectiveSettings({}, null)}
2058
+ onActivate={handlePerspectiveActivate}
2059
+ onOpenSidebar={() => setPerspectiveOpen(true)}
2060
+ t={t}
2061
+ />
2062
+ ) : null
2063
+ const fieldsetSelector =
2064
+ supportsCustomFieldFilterFieldsets && resolvedEntityIds.length === 1
2065
+ ? (
2066
+ <div className="space-y-1">
2067
+ <div className="text-xs font-medium uppercase tracking-wide text-muted-foreground">
2068
+ {t('ui.dataTable.fieldset.label', 'Fieldset')}
2069
+ </div>
2070
+ <select
2071
+ className="w-full rounded border bg-background px-2 py-2 text-sm"
2072
+ value={activeCustomFieldFilterFieldset ?? ''}
2073
+ onChange={(event) => handleCustomFieldFilterFieldsetChange(event.target.value)}
2074
+ >
2075
+ {(cfFilterFieldsetsByEntity[resolvedEntityIds[0]] ?? []).map((fieldset) => (
2076
+ <option key={fieldset.code} value={fieldset.code}>
2077
+ {fieldset.label}
2078
+ </option>
2079
+ ))}
2080
+ </select>
2081
+ </div>
2082
+ )
2083
+ : null
2084
+ const leadingItems = perspectiveButton ? <div className="flex items-center gap-2">{perspectiveButton}</div> : null
2085
+ const trailingItems = hasBulkButtons ? (
2086
+ <div className="flex flex-wrap items-center gap-2">
2087
+ {selectedRows.length > 0 ? (
2088
+ <span className="text-sm text-muted-foreground">
2089
+ {t('ui.dataTable.bulkAction.selectedCount', '{count} selected', { count: selectedRows.length })}
2090
+ </span>
2091
+ ) : null}
2092
+ {injectedBulkActions.map((action) => {
2093
+ const label = t(action.label, action.label)
2094
+ const iconNode = resolveInjectedIcon(action.icon, 'h-4 w-4 shrink-0')
2095
+ return (
2096
+ <Button
2097
+ key={action.id}
2098
+ type="button"
2099
+ size="sm"
2100
+ variant="outline"
2101
+ title={label}
2102
+ aria-label={label}
2103
+ className={iconNode ? 'px-2 sm:px-3' : undefined}
2104
+ disabled={action.requiresSelection !== false && selectedRows.length === 0}
2105
+ onClick={() => void runBulkAction(action)}
2106
+ >
2107
+ {iconNode}
2108
+ <span className={iconNode ? 'hidden sm:inline' : undefined}>{label}</span>
2109
+ </Button>
2110
+ )
2111
+ })}
2112
+ {selectedRows.length > 0 ? (bulkActionsProp ?? []).map((action) => {
2113
+ const ActionIcon = action.icon
2114
+ return (
2115
+ <Button
2116
+ key={action.id}
2117
+ type="button"
2118
+ size="sm"
2119
+ variant={action.destructive ? 'destructive' : 'outline'}
2120
+ onClick={() => void runPropBulkAction(action)}
2121
+ >
2122
+ {ActionIcon ? <ActionIcon className="h-4 w-4 shrink-0" /> : null}
2123
+ <span>{action.label}</span>
2124
+ </Button>
2125
+ )
2126
+ }) : null}
2127
+ </div>
2128
+ ) : null
2129
+ return (
2130
+ <FilterBar
2131
+ searchValue={searchValue}
2132
+ onSearchChange={onSearchChange}
2133
+ searchPlaceholder={searchPlaceholder}
2134
+ searchAlign={searchAlign}
2135
+ filters={combined}
2136
+ values={filterValues}
2137
+ onApply={onFiltersApply}
2138
+ onClear={onFiltersClear}
2139
+ leadingItems={leadingItems}
2140
+ trailingItems={trailingItems}
2141
+ filtersExtraContent={fieldsetSelector}
2142
+ layout={embedded ? 'inline' : 'stacked'}
2143
+ className={embedded ? 'min-h-[2.25rem]' : undefined}
2144
+ />
2145
+ )
2146
+ }, [
2147
+ toolbar,
2148
+ searchValue,
2149
+ onSearchChange,
2150
+ searchPlaceholder,
2151
+ searchAlign,
2152
+ baseFilters,
2153
+ cfFilters,
2154
+ injectedFilters,
2155
+ filterValues,
2156
+ onFiltersApply,
2157
+ onFiltersClear,
2158
+ canUsePerspectives,
2159
+ embedded,
2160
+ supportsCustomFieldFilterFieldsets,
2161
+ resolvedEntityIds,
2162
+ activeCustomFieldFilterFieldset,
2163
+ handleCustomFieldFilterFieldsetChange,
2164
+ cfFilterFieldsetsByEntity,
2165
+ hasInjectedBulkActions,
2166
+ hasPropBulkActions,
2167
+ injectedBulkActions,
2168
+ bulkActionsProp,
2169
+ selectedRows.length,
2170
+ selectedRows,
2171
+ runBulkAction,
2172
+ runPropBulkAction,
2173
+ ])
2174
+
2175
+ const hasTitle = title != null
2176
+ const hasActions = actions !== undefined && actions !== null && actions !== false
2177
+ const shouldReserveActionsSpace = actions === null || actions === false
2178
+ const exportConfig = exporter === false ? null : exporter || null
2179
+ const resolvedExportSections = React.useMemo(() => resolveExportSections(exportConfig), [exportConfig])
2180
+ const hasExport = resolvedExportSections.length > 0
2181
+ const refreshButtonConfig = refreshButton
2182
+ const hasRefreshButton = Boolean(refreshButtonConfig)
2183
+ const hasToolbar = builtToolbar != null
2184
+ const hasToolbarInjection = Boolean(toolbarInjectionSpotId)
2185
+ const shouldRenderActionsWrapper = hasActions || hasRefreshButton || shouldReserveActionsSpace || hasExport || hasToolbarInjection || Boolean(advancedFilter)
2186
+ const renderToolbarInline = embedded && hasToolbar
2187
+ const shouldRenderToolbarBelow = hasToolbar && !renderToolbarInline
2188
+ const shouldRenderHeader = hasTitle || renderToolbarInline || shouldRenderActionsWrapper || shouldRenderToolbarBelow
2189
+ const containerClassName = embedded ? '' : 'rounded-lg border bg-card mx-1 sm:mx-2'
2190
+ const headerWrapperClassName = embedded ? 'pb-3' : 'px-4 py-3 border-b'
2191
+ const headerContentClassName = 'flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between'
2192
+ const toolbarWrapperClassName = embedded ? 'mt-2' : 'mt-3 pt-3 border-t'
2193
+ const tableScrollWrapperClassName = embedded ? '' : 'overflow-auto'
2194
+
2195
+ const virtualScrollRef = React.useRef<HTMLDivElement>(null)
2196
+ const allRows = table.getRowModel().rows
2197
+ const rowVirtualizer = virtualized
2198
+ ? useVirtualizer({
2199
+ count: allRows.length,
2200
+ getScrollElement: () => virtualScrollRef.current,
2201
+ estimateSize: () => 48,
2202
+ overscan: virtualizedOverscan,
2203
+ })
2204
+ : null
2205
+ const virtualMaxHeightStyle: React.CSSProperties | undefined = virtualized
2206
+ ? {
2207
+ maxHeight: typeof virtualizedMaxHeight === 'number'
2208
+ ? `${virtualizedMaxHeight}px`
2209
+ : virtualizedMaxHeight ?? 'calc(100vh - 300px)',
2210
+ overflow: 'auto',
2211
+ }
2212
+ : undefined
2213
+
2214
+ const titleContent = hasTitle ? (
2215
+ <div className="text-base font-semibold leading-tight min-h-[2.25rem] flex items-center">
2216
+ {typeof title === 'string' ? <h2 className="text-base font-semibold">{title}</h2> : title}
2217
+ </div>
2218
+ ) : <div className="min-h-[2.25rem]" />
2219
+
2220
+ return (
2221
+ <TooltipProvider delayDuration={300}>
2222
+ <div ref={containerRef} className={containerClassName} data-component-handle={resolvedReplacementHandle}>
2223
+ {shouldRenderHeader && (
2224
+ <div className={headerWrapperClassName}>
2225
+ {(hasTitle || shouldRenderActionsWrapper || renderToolbarInline) && (
2226
+ <div className={headerContentClassName}>
2227
+ <div className="flex-1 min-w-0">
2228
+ {renderToolbarInline ? builtToolbar : titleContent}
2229
+ </div>
2230
+ {shouldRenderActionsWrapper ? (
2231
+ <div className="flex flex-wrap items-center gap-2 min-h-[2.25rem]">
2232
+ {refreshButtonConfig ? (
2233
+ <Button
2234
+ type="button"
2235
+ variant="ghost"
2236
+ size="icon"
2237
+ onClick={refreshButtonConfig.onRefresh}
2238
+ aria-label={refreshButtonConfig.label}
2239
+ title={refreshButtonConfig.label}
2240
+ disabled={refreshButtonConfig.disabled || refreshButtonConfig.isRefreshing}
2241
+ >
2242
+ {refreshButtonConfig.isRefreshing ? (
2243
+ <Loader2 className="h-4 w-4 animate-spin" />
2244
+ ) : (
2245
+ <RefreshCw className="h-4 w-4" />
2246
+ )}
2247
+ <span className="sr-only">{refreshButtonConfig.label}</span>
2248
+ </Button>
2249
+ ) : null}
2250
+ {advancedFilter ? (
2251
+ <Button
2252
+ type="button"
2253
+ variant={advancedFilter.value.conditions.length > 0 ? 'secondary' : 'ghost'}
2254
+ size="icon"
2255
+ onClick={() => {
2256
+ const opening = !isAdvancedFilterOpen
2257
+ if (opening && advancedFilter.value.conditions.length === 0) {
2258
+ const newCondition = createEmptyCondition()
2259
+ if (resolvedAdvancedFilterFields.length > 0) {
2260
+ newCondition.field = resolvedAdvancedFilterFields[0].key
2261
+ newCondition.operator = getDefaultOperator(resolvedAdvancedFilterFields[0].type)
2262
+ }
2263
+ advancedFilter.onChange({ ...advancedFilter.value, conditions: [newCondition] })
2264
+ }
2265
+ setAdvancedFilterOpen(opening)
2266
+ }}
2267
+ aria-label={t('ui.advancedFilter.toggle', 'Advanced filters')}
2268
+ title={t('ui.advancedFilter.toggle', 'Advanced filters')}
2269
+ >
2270
+ <Filter className="h-4 w-4" />
2271
+ {advancedFilter.value.conditions.length > 0 ? (
2272
+ <span className="absolute -top-1 -right-1 flex h-4 w-4 items-center justify-center rounded-full bg-primary text-[10px] text-primary-foreground">
2273
+ {advancedFilter.value.conditions.length}
2274
+ </span>
2275
+ ) : null}
2276
+ </Button>
2277
+ ) : null}
2278
+ {canUsePerspectives ? (
2279
+ <Button
2280
+ type="button"
2281
+ variant="ghost"
2282
+ size="icon"
2283
+ onClick={() => setPerspectiveOpen(true)}
2284
+ aria-label={t('ui.dataTable.customizeColumns.ariaLabel', 'Customize columns')}
2285
+ title={t('ui.dataTable.customizeColumns.title', 'Customize columns')}
2286
+ >
2287
+ <MoreHorizontal className="h-4 w-4" />
2288
+ <span className="sr-only">{t('ui.dataTable.customizeColumns.srOnly', 'Customize columns')}</span>
2289
+ </Button>
2290
+ ) : null}
2291
+ {exportConfig && hasExport ? <ExportMenu config={exportConfig} sections={resolvedExportSections} /> : null}
2292
+ {toolbarInjectionSpotId ? (
2293
+ <InjectionSpot spotId={toolbarInjectionSpotId} context={resolvedInjectionContext} />
2294
+ ) : null}
2295
+ {hasActions ? actions : null}
2296
+ </div>
2297
+ ) : null}
2298
+ </div>
2299
+ )}
2300
+ {shouldRenderToolbarBelow ? <div className={toolbarWrapperClassName}>{builtToolbar}</div> : null}
2301
+ {headerInjectionSpotId ? (
2302
+ <div className={embedded ? 'mt-2' : 'mt-3'}>
2303
+ <InjectionSpot spotId={headerInjectionSpotId} context={resolvedInjectionContext} />
2304
+ </div>
2305
+ ) : null}
2306
+ </div>
2307
+ )}
2308
+ {advancedFilter && isAdvancedFilterOpen ? (
2309
+ <div className="border-b">
2310
+ <AdvancedFilterBuilder
2311
+ fields={resolvedAdvancedFilterFields}
2312
+ value={advancedFilter.value}
2313
+ onChange={advancedFilter.onChange}
2314
+ onApply={() => { advancedFilter.onApply(); setAdvancedFilterOpen(false) }}
2315
+ onClear={() => { advancedFilter.onClear(); setAdvancedFilterOpen(false) }}
2316
+ />
2317
+ </div>
2318
+ ) : null}
2319
+ {advancedFilter && advancedFilter.value.conditions.length > 0 && !isAdvancedFilterOpen ? (
2320
+ <div className="flex items-center gap-2 flex-wrap px-4 py-2 border-b text-sm">
2321
+ <span className="text-muted-foreground">
2322
+ {t('ui.advancedFilter.activeCount', '{count} active filters', { count: advancedFilter.value.conditions.length })}
2323
+ </span>
2324
+ <Button type="button" variant="ghost" size="sm" className="h-auto px-1 py-0.5 text-xs" onClick={() => setAdvancedFilterOpen(true)}>
2325
+ {t('ui.advancedFilter.edit', 'Edit')}
2326
+ </Button>
2327
+ <Button type="button" variant="ghost" size="sm" className="h-auto px-1 py-0.5 text-xs text-muted-foreground" onClick={advancedFilter.onClear}>
2328
+ {t('ui.advancedFilter.clearAll', 'Clear all')}
2329
+ </Button>
2330
+ </div>
2331
+ ) : null}
2332
+ <HeaderDndWrapper
2333
+ enabled={enableHeaderDnd}
2334
+ contextId={`${stableDndContextId}-headers`}
2335
+ sensors={dndSensors}
2336
+ columnIds={headerColumnIds}
2337
+ onDragEnd={handleHeaderDragEnd}
2338
+ >
2339
+ <div ref={virtualized ? virtualScrollRef : undefined} className={tableScrollWrapperClassName} style={virtualMaxHeightStyle}>
2340
+ <Table className="min-w-[640px] md:min-w-0">
2341
+ <TableHeader>
2342
+ {table.getHeaderGroups().map((hg) => (
2343
+ <TableRow key={hg.id}>
2344
+ {hasInjectedBulkActions ? (
2345
+ <TableHead className="w-8">
2346
+ <Checkbox
2347
+ checked={table.getIsAllPageRowsSelected()}
2348
+ onCheckedChange={(checked) => {
2349
+ table.toggleAllPageRowsSelected(Boolean(checked))
2350
+ }}
2351
+ aria-label={t('ui.dataTable.bulkAction.selectAll', 'Select all rows')}
2352
+ />
2353
+ </TableHead>
2354
+ ) : null}
2355
+ {hg.headers.map((header, headerIndex) => {
2356
+ const columnMeta = (header.column.columnDef as any)?.meta
2357
+ const priority = resolvePriority(header.column)
2358
+ const isFirstDataColumn = headerIndex === 0
2359
+ const stickyClass = stickyFirstColumn && isFirstDataColumn ? ' sticky left-0 z-10 bg-background' : ''
2360
+ const headerCellContent = header.isPlaceholder ? null : (
2361
+ <Button
2362
+ variant="ghost"
2363
+ type="button"
2364
+ className={`h-auto p-0 font-medium ${sortable && header.column.getCanSort?.() ? 'cursor-pointer select-none' : ''}`}
2365
+ onClick={() => sortable && header.column.toggleSorting?.(header.column.getIsSorted() === 'asc')}
2366
+ >
2367
+ {flexRender(header.column.columnDef.header, header.getContext())}
2368
+ {sortable && header.column.getCanSort?.() ? (
2369
+ <span className="ml-1 inline-flex flex-col text-[10px] leading-none gap-px">
2370
+ <span className={header.column.getIsSorted() === 'asc' ? 'text-foreground' : 'text-muted-foreground/40'}>▲</span>
2371
+ <span className={header.column.getIsSorted() === 'desc' ? 'text-foreground' : 'text-muted-foreground/40'}>▼</span>
2372
+ </span>
2373
+ ) : null}
2374
+ </Button>
2375
+ )
2376
+ return enableHeaderDnd ? (
2377
+ <SortableHeaderCell key={header.id} id={header.id} className={responsiveClass(priority, columnMeta?.hidden) + stickyClass}>
2378
+ {headerCellContent}
2379
+ </SortableHeaderCell>
2380
+ ) : (
2381
+ <TableHead key={header.id} className={responsiveClass(priority, columnMeta?.hidden) + stickyClass}>
2382
+ {headerCellContent}
2383
+ </TableHead>
2384
+ )
2385
+ })}
2386
+ {rowActions || injectedRowActions.length > 0 ? (
2387
+ <TableHead
2388
+ className={cn(
2389
+ 'w-0 text-right',
2390
+ stickyActionsColumn && 'sticky right-0 z-20 bg-background',
2391
+ )}
2392
+ >
2393
+ {t('ui.dataTable.actionsColumn', 'Actions')}
2394
+ </TableHead>
2395
+ ) : null}
2396
+ </TableRow>
2397
+ ))}
2398
+ </TableHeader>
2399
+ <TableBody>
2400
+ {isLoading ? (
2401
+ <TableRow>
2402
+ <TableCell colSpan={mergedColumns.length + (rowActions || injectedRowActions.length > 0 ? 1 : 0) + (hasInjectedBulkActions ? 1 : 0)} className="h-24 text-center">
2403
+ <div className="flex items-center justify-center gap-2">
2404
+ <Spinner size="md" />
2405
+ <span className="text-muted-foreground">{t('ui.dataTable.loading', 'Loading data...')}</span>
2406
+ </div>
2407
+ </TableCell>
2408
+ </TableRow>
2409
+ ) : error ? (
2410
+ <TableRow>
2411
+ <TableCell colSpan={mergedColumns.length + (rowActions || injectedRowActions.length > 0 ? 1 : 0) + (hasInjectedBulkActions ? 1 : 0)} className="h-24 text-center text-destructive">
2412
+ {error}
2413
+ </TableCell>
2414
+ </TableRow>
2415
+ ) : allRows.length ? (
2416
+ <>
2417
+ {virtualized && rowVirtualizer ? (
2418
+ <>
2419
+ {rowVirtualizer.getVirtualItems()[0]?.start > 0 ? (
2420
+ <tr style={{ height: `${rowVirtualizer.getVirtualItems()[0].start}px` }} />
2421
+ ) : null}
2422
+ </>
2423
+ ) : null}
2424
+ {(virtualized && rowVirtualizer
2425
+ ? rowVirtualizer.getVirtualItems().map((vi) => allRows[vi.index])
2426
+ : allRows
2427
+ ).map((row) => {
2428
+ const rowActionsElement = resolvedRowActions(row.original as T)
2429
+ const defaultRowAction = onRowClick ? null : pickDefaultRowAction(rowActionsElement, resolvedRowClickActionIds)
2430
+ const isClickable = !disableRowClick && (onRowClick || defaultRowAction)
2431
+
2432
+ return (
2433
+ <TableRow
2434
+ key={row.id}
2435
+ data-state={row.getIsSelected() && 'selected'}
2436
+ className={isClickable ? 'cursor-pointer hover:bg-muted/50 transition-colors' : ''}
2437
+ onClick={isClickable ? (e) => {
2438
+ // Don't trigger row click if clicking on actions cell
2439
+ if ((e.target as HTMLElement).closest('[data-actions-cell]')) {
2440
+ return
2441
+ }
2442
+
2443
+ if (onRowClick) {
2444
+ onRowClick(row.original as T)
2445
+ } else if (defaultRowAction) {
2446
+ if (defaultRowAction.href) {
2447
+ router.push(defaultRowAction.href)
2448
+ } else if (defaultRowAction.onSelect) {
2449
+ defaultRowAction.onSelect()
2450
+ }
2451
+ }
2452
+ } : undefined}
2453
+ >
2454
+ {hasInjectedBulkActions ? (
2455
+ <TableCell className="w-8">
2456
+ <Checkbox
2457
+ checked={row.getIsSelected()}
2458
+ onCheckedChange={(checked) => row.toggleSelected(Boolean(checked))}
2459
+ aria-label={t('ui.dataTable.bulkAction.selectRow', 'Select row')}
2460
+ onClick={(event) => event.stopPropagation()}
2461
+ />
2462
+ </TableCell>
2463
+ ) : null}
2464
+ {row.getVisibleCells().map((cell, cellIndex) => {
2465
+ const columnMeta = (cell.column.columnDef as any)?.meta
2466
+ const priority = resolvePriority(cell.column)
2467
+ const isStickyCell = stickyFirstColumn && cellIndex === 0
2468
+ const hasCustomCell = Boolean(cell.column.columnDef.cell)
2469
+ const columnId = String((cell.column as any).id || '')
2470
+ const accessorKey = String((cell.column.columnDef as any)?.accessorKey || '')
2471
+ const isDateCol = dateColumnIds ? dateColumnIds.has(columnId) : false
2472
+
2473
+ let content: React.ReactNode
2474
+ if (isDateCol) {
2475
+ const raw = cell.getValue() as any
2476
+ const d = tryParseDate(raw)
2477
+ content = d ? simpleFormat(d, DATE_FORMAT) : (raw as any)
2478
+ } else {
2479
+ content = flexRender(cell.column.columnDef.cell, cell.getContext())
2480
+ }
2481
+
2482
+ // Get truncation configuration for this column
2483
+ const skipTruncation = shouldSkipTruncation(columnId)
2484
+ // Get truncation configuration for this column
2485
+ const truncateConfig = getColumnTruncateConfig(columnId, accessorKey, columnMeta)
2486
+ const shouldTruncate = truncateConfig.truncate && !skipTruncation
2487
+ const maxWidth = truncateConfig.maxWidth
2488
+
2489
+ // Wrap content with TruncatedCell if truncation is enabled
2490
+ // Get raw cell value for tooltip - flexRender returns React elements
2491
+ // that cannot have their text extracted, so we pass the raw value directly
2492
+ // Check for custom tooltip content function in column meta for complex cells
2493
+ const cellValue = cell.getValue()
2494
+ const metaTooltipContent = columnMeta?.tooltipContent as ((row: unknown) => string | undefined) | undefined
2495
+ let tooltipText: string | undefined
2496
+ if (metaTooltipContent) {
2497
+ tooltipText = metaTooltipContent(row.original)
2498
+ } else if (isDateCol && cellValue != null) {
2499
+ const parsedDate = tryParseDate(cellValue)
2500
+ tooltipText = parsedDate ? simpleFormat(parsedDate, DATE_FORMAT) : String(cellValue)
2501
+ } else {
2502
+ tooltipText = cellValue != null ? String(cellValue) : undefined
2503
+ }
2504
+
2505
+ const wrappedContent = shouldTruncate ? (
2506
+ <TruncatedCell maxWidth={maxWidth} tooltipContent={tooltipText}>
2507
+ {content}
2508
+ </TruncatedCell>
2509
+ ) : content
2510
+
2511
+ return (
2512
+ <TableCell key={cell.id} className={responsiveClass(priority, columnMeta?.hidden) + (isStickyCell ? ' sticky left-0 z-10 bg-background' : '')}>
2513
+ {wrappedContent}
2514
+ </TableCell>
2515
+ )
2516
+ })}
2517
+ {rowActions || injectedRowActions.length > 0 ? (
2518
+ <TableCell
2519
+ className={cn(
2520
+ 'text-right whitespace-nowrap',
2521
+ stickyActionsColumn && 'sticky right-0 z-10 bg-background',
2522
+ )}
2523
+ data-actions-cell
2524
+ >
2525
+ {rowActionsElement}
2526
+ </TableCell>
2527
+ ) : null}
2528
+ </TableRow>
2529
+ )
2530
+ })}
2531
+ {virtualized && rowVirtualizer ? (() => {
2532
+ const virtualItems = rowVirtualizer.getVirtualItems()
2533
+ const lastItem = virtualItems[virtualItems.length - 1]
2534
+ const bottomPadding = lastItem ? rowVirtualizer.getTotalSize() - lastItem.end : 0
2535
+ return bottomPadding > 0 ? <tr style={{ height: `${bottomPadding}px` }} /> : null
2536
+ })() : null}
2537
+ </>
2538
+ ) : (
2539
+ <TableRow>
2540
+ <TableCell colSpan={mergedColumns.length + (rowActions || injectedRowActions.length > 0 ? 1 : 0) + (hasInjectedBulkActions ? 1 : 0)} className="h-24 text-center text-muted-foreground">
2541
+ {emptyState ?? t('ui.dataTable.emptyState.default', 'No results.')}
2542
+ </TableCell>
2543
+ </TableRow>
2544
+ )}
2545
+ </TableBody>
2546
+ </Table>
2547
+ </div>
2548
+ </HeaderDndWrapper>
2549
+ {footerInjectionSpotId ? (
2550
+ <div className={embedded ? 'mt-3' : 'px-4 py-3 border-t'}>
2551
+ <InjectionSpot spotId={footerInjectionSpotId} context={resolvedInjectionContext} />
2552
+ </div>
2553
+ ) : null}
2554
+ {paginationNode}
2555
+ {ConfirmDialogElement}
2556
+ {canUsePerspectives ? (
2557
+ <PerspectiveSidebar
2558
+ open={isPerspectiveOpen}
2559
+ onOpenChange={setPerspectiveOpen}
2560
+ loading={perspectiveQuery.isFetching && !perspectiveQuery.data}
2561
+ perspectives={perspectiveData?.perspectives ?? []}
2562
+ rolePerspectives={perspectiveData?.rolePerspectives ?? []}
2563
+ roles={perspectiveData?.roles ?? []}
2564
+ activePerspectiveId={activePerspectiveId}
2565
+ onActivatePerspective={handlePerspectiveActivate}
2566
+ onDeletePerspective={handlePerspectiveDelete}
2567
+ onClearRole={handleClearRole}
2568
+ onSave={handlePerspectiveSave}
2569
+ canApplyToRoles={Boolean(perspectiveData?.canApplyToRoles && canUseRoleDefaultsFeature)}
2570
+ availableColumns={effectiveColumnChooserFields}
2571
+ visibleColumnKeys={visibleColumnKeys}
2572
+ columnOrder={columnOrder}
2573
+ onToggleColumn={handleColumnChooserToggle}
2574
+ onReorderColumns={handleColumnChooserReorder}
2575
+ saving={savePerspectiveMutation.isPending}
2576
+ deletingIds={deletingIds}
2577
+ roleClearingIds={roleClearingIds}
2578
+ apiWarning={perspectiveApiWarning}
2579
+ />
2580
+ ) : null}
2581
+ </div>
2582
+ </TooltipProvider>
2583
+ )
2584
+ }