@antelopejs/dms 0.9.8 → 0.11.0

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 (451) hide show
  1. package/CHANGELOG.md +171 -0
  2. package/README.md +1 -1
  3. package/data/module-store/catalog.json +2 -2
  4. package/dist/attachments/table-view.js +2 -2
  5. package/dist/attachments/table-view.js.map +1 -1
  6. package/dist/automation/actions.d.ts +2 -0
  7. package/dist/automation/actions.js +116 -71
  8. package/dist/automation/actions.js.map +1 -1
  9. package/dist/automation/i18n.d.ts +4 -0
  10. package/dist/automation/i18n.js +19 -0
  11. package/dist/automation/i18n.js.map +1 -0
  12. package/dist/automation/send-notification.d.ts +21 -0
  13. package/dist/automation/send-notification.js +163 -0
  14. package/dist/automation/send-notification.js.map +1 -0
  15. package/dist/automation/triggers.js +19 -25
  16. package/dist/automation/triggers.js.map +1 -1
  17. package/dist/client-origin-cors.d.ts +1 -0
  18. package/dist/client-origin-cors.js +19 -0
  19. package/dist/client-origin-cors.js.map +1 -0
  20. package/dist/config.d.ts +3 -2
  21. package/dist/config.js +5 -7
  22. package/dist/config.js.map +1 -1
  23. package/dist/frontend-module.d.ts +4 -0
  24. package/dist/frontend-module.js +37 -0
  25. package/dist/frontend-module.js.map +1 -0
  26. package/dist/implementations/dms/client-ip.js +1 -1
  27. package/dist/implementations/dms/client-ip.js.map +1 -1
  28. package/dist/implementations/dms/dev-reload.d.ts +5 -0
  29. package/dist/implementations/dms/dev-reload.js +23 -3
  30. package/dist/implementations/dms/dev-reload.js.map +1 -1
  31. package/dist/implementations/dms/frontend-modules.d.ts +2 -4
  32. package/dist/implementations/dms/frontend-modules.js +38 -94
  33. package/dist/implementations/dms/frontend-modules.js.map +1 -1
  34. package/dist/implementations/dms/frontend-ready.d.ts +12 -2
  35. package/dist/implementations/dms/frontend-ready.js +34 -4
  36. package/dist/implementations/dms/frontend-ready.js.map +1 -1
  37. package/dist/implementations/dms/layout-banners.d.ts +2 -1
  38. package/dist/implementations/dms/layout-banners.js +9 -0
  39. package/dist/implementations/dms/layout-banners.js.map +1 -1
  40. package/dist/implementations/dms/manifest.d.ts +7 -2
  41. package/dist/implementations/dms/manifest.js +12 -7
  42. package/dist/implementations/dms/manifest.js.map +1 -1
  43. package/dist/implementations/dms/page.d.ts +4 -3
  44. package/dist/implementations/dms/page.js +49 -18
  45. package/dist/implementations/dms/page.js.map +1 -1
  46. package/dist/implementations/dms/permissions-resolver.d.ts +2 -2
  47. package/dist/implementations/dms/permissions-resolver.js +2 -2
  48. package/dist/implementations/dms/permissions-resolver.js.map +1 -1
  49. package/dist/implementations/dms/realtime.js +1 -0
  50. package/dist/implementations/dms/realtime.js.map +1 -1
  51. package/dist/implementations/dms-base/bulk-selection.d.ts +1 -1
  52. package/dist/implementations/dms-base/bulk-selection.js +23 -1
  53. package/dist/implementations/dms-base/bulk-selection.js.map +1 -1
  54. package/dist/implementations/dms-base/filter-tokens.js +5 -3
  55. package/dist/implementations/dms-base/filter-tokens.js.map +1 -1
  56. package/dist/implementations/dms-base/search-route.d.ts +1 -0
  57. package/dist/implementations/dms-base/search-route.js +35 -13
  58. package/dist/implementations/dms-base/search-route.js.map +1 -1
  59. package/dist/implementations/dms-base/table-view.js +11 -6
  60. package/dist/implementations/dms-base/table-view.js.map +1 -1
  61. package/dist/implementations/dms-notifications/index.d.ts +2 -2
  62. package/dist/implementations/dms-notifications/index.js +5 -5
  63. package/dist/implementations/dms-notifications/index.js.map +1 -1
  64. package/dist/index.js +32 -55
  65. package/dist/index.js.map +1 -1
  66. package/dist/pages/modules/index.d.ts +1 -0
  67. package/dist/pages/modules/index.js +1 -0
  68. package/dist/pages/modules/index.js.map +1 -1
  69. package/dist/pages/modules/modules-store.d.ts +9 -0
  70. package/dist/pages/modules/modules-store.js +30 -0
  71. package/dist/pages/modules/modules-store.js.map +1 -0
  72. package/dist/pages/settings/users/account-credentials.js +2 -2
  73. package/dist/pages/settings/users/account-credentials.js.map +1 -1
  74. package/dist/pages/settings/users/account-data-store.js +25 -8
  75. package/dist/pages/settings/users/account-data-store.js.map +1 -1
  76. package/dist/pages/settings/users/invite-extension-routes.js +9 -3
  77. package/dist/pages/settings/users/invite-extension-routes.js.map +1 -1
  78. package/dist/pages/settings/users/member-management.js +13 -14
  79. package/dist/pages/settings/users/member-management.js.map +1 -1
  80. package/dist/pages/settings/users/profile.d.ts +1 -4
  81. package/dist/pages/settings/users/profile.js +0 -35
  82. package/dist/pages/settings/users/profile.js.map +1 -1
  83. package/dist/pages/settings/users/role-editor-store.js +26 -18
  84. package/dist/pages/settings/users/role-editor-store.js.map +1 -1
  85. package/dist/pages/settings/users/role-editor.d.ts +0 -1
  86. package/dist/pages/settings/users/role-editor.js +1 -2
  87. package/dist/pages/settings/users/role-editor.js.map +1 -1
  88. package/dist/pages/settings/users/role-preview.js +1 -2
  89. package/dist/pages/settings/users/role-preview.js.map +1 -1
  90. package/dist/pages/settings/users/roles.js +2 -2
  91. package/dist/pages/settings/users/roles.js.map +1 -1
  92. package/dist/pages/settings/users/two-factor-operations.js.map +1 -1
  93. package/dist/realtime/broker.d.ts +6 -0
  94. package/dist/realtime/broker.js +8 -1
  95. package/dist/realtime/broker.js.map +1 -1
  96. package/dist/realtime/presence.d.ts +8 -3
  97. package/dist/realtime/presence.js +8 -4
  98. package/dist/realtime/presence.js.map +1 -1
  99. package/dist/realtime/sessions.d.ts +11 -1
  100. package/dist/realtime/sessions.js +21 -3
  101. package/dist/realtime/sessions.js.map +1 -1
  102. package/dist/realtime/table-view.d.ts +2 -2
  103. package/dist/realtime/table-view.js +4 -3
  104. package/dist/realtime/table-view.js.map +1 -1
  105. package/dist/routes/auth/client-ip.d.ts +1 -0
  106. package/dist/routes/auth/client-ip.js +18 -0
  107. package/dist/routes/auth/client-ip.js.map +1 -1
  108. package/dist/routes/auth/oauth/config.d.ts +2 -1
  109. package/dist/routes/auth/oauth/config.js +5 -1
  110. package/dist/routes/auth/oauth/config.js.map +1 -1
  111. package/dist/routes/auth/session-handoff.d.ts +0 -1
  112. package/dist/routes/auth/session-handoff.js +0 -2
  113. package/dist/routes/auth/session-handoff.js.map +1 -1
  114. package/dist/routes/auth.js +3 -2
  115. package/dist/routes/auth.js.map +1 -1
  116. package/dist/routes/index.d.ts +0 -1
  117. package/dist/routes/index.js +0 -1
  118. package/dist/routes/index.js.map +1 -1
  119. package/dist/routes/realtime.d.ts +13 -1
  120. package/dist/routes/realtime.js +114 -29
  121. package/dist/routes/realtime.js.map +1 -1
  122. package/dist/startup-config.d.ts +2 -0
  123. package/dist/startup-config.js +62 -0
  124. package/dist/startup-config.js.map +1 -0
  125. package/dist/store/interfaces.d.ts +3 -2
  126. package/dist/store/interfaces.js +16 -6
  127. package/dist/store/interfaces.js.map +1 -1
  128. package/dist/store/listing.js +25 -15
  129. package/dist/store/listing.js.map +1 -1
  130. package/dist/store/operations.js +5 -2
  131. package/dist/store/operations.js.map +1 -1
  132. package/dist/store/requirements.js +3 -2
  133. package/dist/store/requirements.js.map +1 -1
  134. package/dist/store/runtime.d.ts +10 -1
  135. package/dist/store/runtime.js +33 -6
  136. package/dist/store/runtime.js.map +1 -1
  137. package/dist/store/types.d.ts +10 -0
  138. package/dist/utils/ensure-default-tenant.d.ts +2 -1
  139. package/dist/utils/ensure-default-tenant.js +13 -1
  140. package/dist/utils/ensure-default-tenant.js.map +1 -1
  141. package/dist/utils/export-jobs.js +2 -1
  142. package/dist/utils/export-jobs.js.map +1 -1
  143. package/dist/validation/auth.schema.d.ts +2 -2
  144. package/dist/validation/member-invite.schema.d.ts +2 -10
  145. package/dist/validation/member-invite.schema.js +2 -15
  146. package/dist/validation/member-invite.schema.js.map +1 -1
  147. package/frontend-vue/dms.frontend.ts +24 -5
  148. package/frontend-vue/dms.server.ts +17 -0
  149. package/frontend-vue/layers/dms-auth/server/accounts.ts +3 -4
  150. package/frontend-vue/layers/dms-core/app/build/composables/renderNow.ts +33 -0
  151. package/frontend-vue/layers/dms-core/app/build/composables/user/tablePreferences.ts +5 -1
  152. package/frontend-vue/layers/dms-core/app/build/utils/crossTab.ts +199 -0
  153. package/frontend-vue/layers/dms-core/app/build/utils/sharedRealtimeConnection.ts +410 -0
  154. package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +77 -12
  155. package/frontend-vue/layers/dms-core/app/composables/data-types/useDataType.ts +26 -0
  156. package/frontend-vue/layers/dms-core/app/composables/live/livePauseSource.ts +29 -0
  157. package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +150 -9
  158. package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +6 -4
  159. package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +27 -1
  160. package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +256 -119
  161. package/frontend-vue/layers/dms-core/app/composables/translation/useComposedText.ts +21 -8
  162. package/frontend-vue/layers/dms-core/app/composables/translation/useTranslation.ts +25 -5
  163. package/frontend-vue/layers/dms-core/app/composables/useApiError.ts +3 -5
  164. package/frontend-vue/layers/dms-core/app/composables/useFieldErrors.ts +61 -26
  165. package/frontend-vue/layers/dms-core/app/plugins/dev-error-log.ts +16 -0
  166. package/frontend-vue/layers/dms-core/app/types/user.ts +6 -0
  167. package/frontend-vue/layers/dms-core/app/utils/composedText.ts +7 -2
  168. package/frontend-vue/layers/dms-core/app/utils/formatter.ts +134 -35
  169. package/frontend-vue/layers/dms-core/app/utils/url-interpolation.ts +18 -1
  170. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +136 -2
  171. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +136 -2
  172. package/frontend-vue/layers/dms-core/shared/types/app-config.ts +0 -2
  173. package/frontend-vue/layers/dms-layout/app/app.config.ts +0 -1
  174. package/frontend-vue/layers/dms-layout/app/assets/css/main.css +11 -1
  175. package/frontend-vue/layers/dms-layout/app/assets/fonts/Geist-Italic-Variable.woff2 +0 -0
  176. package/frontend-vue/layers/dms-layout/app/assets/fonts/Geist-Variable.woff2 +0 -0
  177. package/frontend-vue/layers/dms-layout/app/assets/fonts/GeistMono-Italic-Variable.woff2 +0 -0
  178. package/frontend-vue/layers/dms-layout/app/assets/fonts/GeistMono-Variable.woff2 +0 -0
  179. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +3 -2
  180. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +41 -21
  181. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardPageBody.vue +6 -2
  182. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +4 -19
  183. package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +11 -107
  184. package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +18 -7
  185. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +4 -0
  186. package/frontend-vue/layers/dms-layout/app/build/components/layout/RowSidePanel.vue +43 -82
  187. package/frontend-vue/layers/dms-layout/app/build/components/layout/SidebarUserMenu.vue +44 -3
  188. package/frontend-vue/layers/dms-layout/app/build/components/layout/UserIdentity.vue +11 -3
  189. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleCatalogTile.vue +132 -35
  190. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleManageMenu.vue +59 -0
  191. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleSourceLabel.vue +25 -0
  192. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreBanners.vue +73 -0
  193. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreOperation.vue +116 -0
  194. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +8 -3
  195. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +85 -207
  196. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +10 -3
  197. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTileSkeleton.vue +68 -0
  198. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +24 -8
  199. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxItem.vue +8 -5
  200. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/region/RegionTimeZoneInput.vue +1 -0
  201. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +19 -0
  202. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/useRoleEditor.ts +2 -1
  203. package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +31 -70
  204. package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +11 -1
  205. package/frontend-vue/layers/dms-layout/app/build/utils/dev-reload-stream.ts +81 -0
  206. package/frontend-vue/layers/dms-layout/app/build/utils/github-repository.ts +2 -2
  207. package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +232 -36
  208. package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +122 -14
  209. package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +32 -3
  210. package/frontend-vue/layers/dms-layout/app/composables/layout/useServedHeaderActions.ts +49 -11
  211. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleHistory.ts +3 -2
  212. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +59 -4
  213. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStoreActions.ts +194 -0
  214. package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +23 -3
  215. package/frontend-vue/layers/dms-layout/app/composables/page/usePageLayout.ts +4 -2
  216. package/frontend-vue/layers/dms-layout/app/composables/page/usePageRecordDeletion.ts +162 -0
  217. package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +6 -1
  218. package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +6 -2
  219. package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +6 -2
  220. package/frontend-vue/layers/dms-layout/app/custom-pages/modules-store.vue +291 -0
  221. package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +87 -474
  222. package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +3 -0
  223. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +13 -22
  224. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-navigation.client.ts +11 -10
  225. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-personal-pages.client.ts +22 -52
  226. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-quick-actions.client.ts +7 -7
  227. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-session.client.ts +11 -10
  228. package/frontend-vue/layers/dms-layout/app/plugins/dms-dev-reload.client.ts +14 -9
  229. package/frontend-vue/layers/dms-layout/app/theme/actions.ts +5 -4
  230. package/frontend-vue/layers/dms-layout/app/theme/form-controls.ts +9 -4
  231. package/frontend-vue/layers/dms-layout/app/theme/surfaces.ts +22 -6
  232. package/frontend-vue/layers/dms-layout/app/types/module-store.ts +9 -0
  233. package/frontend-vue/layers/dms-layout/app/types/page.ts +15 -1
  234. package/frontend-vue/layers/dms-layout/app/utils/account-menu.ts +11 -0
  235. package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +57 -0
  236. package/frontend-vue/layers/dms-layout/app/utils/pageQuery.ts +26 -0
  237. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +45 -18
  238. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +44 -17
  239. package/frontend-vue/layers/dms-ui/app/build/components/containers/TargetComponentTree.vue +31 -0
  240. package/frontend-vue/layers/dms-ui/app/build/components/containers/constants.ts +25 -0
  241. package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +244 -15
  242. package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +42 -25
  243. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowHeader.vue +153 -0
  244. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowNotFound.vue +44 -0
  245. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/drawerRowHeader.ts +35 -0
  246. package/frontend-vue/layers/dms-ui/app/build/components/form/FormEntries.vue +44 -9
  247. package/frontend-vue/layers/dms-ui/app/build/components/form/FormErrorSummary.vue +11 -4
  248. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldControl.vue +38 -5
  249. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldNotes.vue +29 -5
  250. package/frontend-vue/layers/dms-ui/app/build/components/form/FormLoadError.vue +5 -1
  251. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSectionNav.vue +3 -1
  252. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSections.vue +106 -27
  253. package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +12 -4
  254. package/frontend-vue/layers/dms-ui/app/build/components/table/Empty.vue +4 -2
  255. package/frontend-vue/layers/dms-ui/app/build/components/table/FiltersRow.vue +48 -1
  256. package/frontend-vue/layers/dms-ui/app/build/components/table/Pagination.vue +55 -54
  257. package/frontend-vue/layers/dms-ui/app/build/components/table/RowSelection.vue +2 -1
  258. package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +396 -288
  259. package/frontend-vue/layers/dms-ui/app/build/components/table/Tabs.vue +2 -1
  260. package/frontend-vue/layers/dms-ui/app/build/components/table/Views.vue +2 -1
  261. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardAvatar.vue +59 -0
  262. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardGridSkeleton.vue +19 -2
  263. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardRowActions.vue +38 -0
  264. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardsDisplay.vue +148 -104
  265. package/frontend-vue/layers/dms-ui/app/build/components/table-view/LiveHoldBanner.vue +17 -2
  266. package/frontend-vue/layers/dms-ui/app/build/composables/actions/bulkSelection.ts +43 -9
  267. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowComponentOptions.ts +76 -0
  268. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowNavigation.ts +172 -27
  269. package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +270 -46
  270. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/actionError.ts +6 -1
  271. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +2 -0
  272. package/frontend-vue/layers/dms-ui/app/build/composables/containers/closingLayers.ts +186 -0
  273. package/frontend-vue/layers/dms-ui/app/build/composables/containers/context.ts +14 -0
  274. package/frontend-vue/layers/dms-ui/app/build/composables/containers/drawerSize.ts +229 -0
  275. package/frontend-vue/layers/dms-ui/app/build/composables/containers/useResizableSize.ts +175 -0
  276. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/metricCells.ts +18 -35
  277. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/numberSuffix.ts +44 -0
  278. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +123 -27
  279. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerFieldTypes.ts +2 -1
  280. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationFilter.ts +75 -0
  281. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationRows.ts +92 -0
  282. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationValue.ts +39 -0
  283. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/twoLineCell.ts +3 -3
  284. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/useColumnValueRenderer.ts +6 -2
  285. package/frontend-vue/layers/dms-ui/app/build/composables/form/createFormFields.ts +15 -0
  286. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldSaved.ts +40 -0
  287. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldValueRule.ts +62 -0
  288. package/frontend-vue/layers/dms-ui/app/build/composables/form/formEntryContext.ts +5 -0
  289. package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +42 -7
  290. package/frontend-vue/layers/dms-ui/app/build/composables/form/formLayout.ts +106 -4
  291. package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +11 -5
  292. package/frontend-vue/layers/dms-ui/app/build/composables/form/formSections.ts +32 -0
  293. package/frontend-vue/layers/dms-ui/app/build/composables/form/formValidation.ts +36 -4
  294. package/frontend-vue/layers/dms-ui/app/build/composables/form/readonlyValue.ts +19 -0
  295. package/frontend-vue/layers/dms-ui/app/build/composables/form/registerFormFunctions.ts +27 -0
  296. package/frontend-vue/layers/dms-ui/app/build/composables/form/repeaterTable.ts +78 -0
  297. package/frontend-vue/layers/dms-ui/app/build/composables/form/serverFieldErrors.ts +49 -0
  298. package/frontend-vue/layers/dms-ui/app/build/composables/form/toasterClearance.ts +118 -0
  299. package/frontend-vue/layers/dms-ui/app/build/composables/form/useInstantForm.ts +12 -1
  300. package/frontend-vue/layers/dms-ui/app/build/composables/live/currentPageLive.ts +41 -3
  301. package/frontend-vue/layers/dms-ui/app/build/composables/live/livePoller.ts +10 -2
  302. package/frontend-vue/layers/dms-ui/app/build/composables/live/pageReadClock.ts +28 -0
  303. package/frontend-vue/layers/dms-ui/app/build/composables/live/shortcuts.ts +1 -1
  304. package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageLiveHost.ts +17 -3
  305. package/frontend-vue/layers/dms-ui/app/build/composables/live/useUpdatedLabel.ts +38 -0
  306. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/pageToolbarContext.ts +75 -0
  307. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/toolbarControlHeight.ts +29 -0
  308. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodComparison.ts +52 -0
  309. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +21 -0
  310. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodShortLabels.ts +28 -0
  311. package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodDraft.ts +1 -0
  312. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRelationFilterLabels.ts +97 -0
  313. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRowScrollAnchor.ts +106 -0
  314. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +9 -0
  315. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTableColumns.ts +1 -2
  316. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/archivedCount.ts +31 -0
  317. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/filterEditorValue.ts +38 -0
  318. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/formatFilterValue.ts +33 -2
  319. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/scrollEdges.ts +31 -0
  320. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useCardFields.ts +33 -0
  321. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useMobileDisplay.ts +48 -0
  322. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useScrolledPastTop.ts +41 -18
  323. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +3 -2
  324. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLinkedVisit.ts +78 -0
  325. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLiveHold.ts +135 -22
  326. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +56 -25
  327. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +90 -35
  328. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +86 -10
  329. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/bulkActions.ts +14 -4
  330. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/card.ts +127 -2
  331. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/compareValues.ts +13 -0
  332. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/expandedRows.ts +28 -0
  333. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/groupedRows.ts +114 -1
  334. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/linkedArrival.ts +308 -0
  335. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/locateRow.ts +106 -0
  336. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pageArrival.ts +34 -0
  337. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pinnedRows.ts +17 -0
  338. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/quickFilters.ts +41 -33
  339. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/recordLink.ts +24 -8
  340. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableCounts.ts +113 -0
  341. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableQuery.ts +27 -4
  342. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/views.ts +22 -6
  343. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/writeError.ts +21 -0
  344. package/frontend-vue/layers/dms-ui/app/build/utils/addressSuggestions.ts +49 -0
  345. package/frontend-vue/layers/dms-ui/app/build/utils/codeHighlight.ts +67 -29
  346. package/frontend-vue/layers/dms-ui/app/build/utils/eyebrow.ts +12 -0
  347. package/frontend-vue/layers/dms-ui/app/build/utils/fieldOptionsI18n.ts +51 -11
  348. package/frontend-vue/layers/dms-ui/app/build/utils/htmlGrammar.ts +79 -0
  349. package/frontend-vue/layers/dms-ui/app/build/utils/inputWidth.ts +53 -0
  350. package/frontend-vue/layers/dms-ui/app/build/utils/numberAdjustment.ts +6 -3
  351. package/frontend-vue/layers/dms-ui/app/build/utils/relativeDate.ts +19 -5
  352. package/frontend-vue/layers/dms-ui/app/build/utils/scrollWithin.ts +99 -0
  353. package/frontend-vue/layers/dms-ui/app/build/utils/shellParser.ts +1 -1
  354. package/frontend-vue/layers/dms-ui/app/build/utils/streamHighlight.ts +90 -0
  355. package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +36 -14
  356. package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
  357. package/frontend-vue/layers/dms-ui/app/components/RecursiveComponent.vue +9 -1
  358. package/frontend-vue/layers/dms-ui/app/components/blocks/LiveToggleBlock.vue +80 -32
  359. package/frontend-vue/layers/dms-ui/app/components/blocks/PageToolbarBlock.vue +137 -0
  360. package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +5 -0
  361. package/frontend-vue/layers/dms-ui/app/components/chart/ApexChartHost.vue +16 -2
  362. package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +24 -5
  363. package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +41 -23
  364. package/frontend-vue/layers/dms-ui/app/components/code-snippet/CodeSnippet.vue +23 -12
  365. package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +1 -1
  366. package/frontend-vue/layers/dms-ui/app/components/container-footer/ContainerFooter.vue +34 -0
  367. package/frontend-vue/layers/dms-ui/app/components/field-row/FieldRow.vue +25 -2
  368. package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +2 -2
  369. package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +119 -36
  370. package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +53 -22
  371. package/frontend-vue/layers/dms-ui/app/components/form/components/DatePicker.vue +20 -1
  372. package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +13 -1
  373. package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +8 -3
  374. package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +53 -5
  375. package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +29 -5
  376. package/frontend-vue/layers/dms-ui/app/components/form/components/InputNumber.vue +45 -8
  377. package/frontend-vue/layers/dms-ui/app/components/form/components/InputPercentage.vue +28 -5
  378. package/frontend-vue/layers/dms-ui/app/components/form/components/InputSecret.vue +5 -0
  379. package/frontend-vue/layers/dms-ui/app/components/form/components/InputText.vue +19 -2
  380. package/frontend-vue/layers/dms-ui/app/components/form/components/InputTime.vue +2 -1
  381. package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +226 -65
  382. package/frontend-vue/layers/dms-ui/app/components/form/components/Repeater.vue +80 -30
  383. package/frontend-vue/layers/dms-ui/app/components/form/components/RepeaterTable.vue +210 -0
  384. package/frontend-vue/layers/dms-ui/app/components/form/components/Select.vue +4 -2
  385. package/frontend-vue/layers/dms-ui/app/components/grid/GridRow.vue +11 -2
  386. package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +79 -70
  387. package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +117 -21
  388. package/frontend-vue/layers/dms-ui/app/components/meter/Meter.vue +2 -1
  389. package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +113 -34
  390. package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +14 -2
  391. package/frontend-vue/layers/dms-ui/app/components/section-header/Eyebrow.vue +6 -1
  392. package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +13 -0
  393. package/frontend-vue/layers/dms-ui/app/components/stat-group/StatGroup.vue +52 -3
  394. package/frontend-vue/layers/dms-ui/app/components/tab/Tab.vue +2 -1
  395. package/frontend-vue/layers/dms-ui/app/components/table-view/CascaderPath.vue +74 -25
  396. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +48 -44
  397. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanDisplay.vue +4 -1
  398. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +426 -97
  399. package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +33 -5
  400. package/frontend-vue/layers/dms-ui/app/composables/chart/apexCategoryLabels.ts +68 -0
  401. package/frontend-vue/layers/dms-ui/app/composables/chart/apexDatetimeBuckets.ts +306 -0
  402. package/frontend-vue/layers/dms-ui/app/composables/chart/formatValue.ts +4 -3
  403. package/frontend-vue/layers/dms-ui/app/composables/chart/heatmapColors.ts +93 -0
  404. package/frontend-vue/layers/dms-ui/app/composables/chart/localizeSeries.ts +62 -0
  405. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.ts +116 -8
  406. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.types.ts +7 -0
  407. package/frontend-vue/layers/dms-ui/app/composables/containers/types.ts +84 -1
  408. package/frontend-vue/layers/dms-ui/app/composables/containers/useDrawer.ts +69 -8
  409. package/frontend-vue/layers/dms-ui/app/composables/form/types/field-saved.ts +11 -0
  410. package/frontend-vue/layers/dms-ui/app/composables/form/types/field.ts +16 -0
  411. package/frontend-vue/layers/dms-ui/app/composables/form/types/functions.ts +1 -0
  412. package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +189 -28
  413. package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +29 -12
  414. package/frontend-vue/layers/dms-ui/app/composables/page-toolbar/usePageToolbarSize.ts +30 -0
  415. package/frontend-vue/layers/dms-ui/app/composables/table-view/kanban.ts +4 -2
  416. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +22 -3
  417. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +4 -0
  418. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/display.ts +24 -1
  419. package/frontend-vue/layers/dms-ui/app/plugins/closing-layers.client.ts +10 -0
  420. package/frontend-vue/layers/dms-ui/app/plugins/live-pause-source.ts +15 -0
  421. package/frontend-vue/layers/dms-ui/app/plugins/register.ts +2 -0
  422. package/frontend-vue/layers/dms-ui/app/types/input-number.ts +3 -2
  423. package/frontend-vue/layers/dms-ui/app/types/page-toolbar.ts +7 -0
  424. package/frontend-vue/layers/dms-ui/app/types/row-action.ts +0 -2
  425. package/frontend-vue/layers/dms-ui/app/types/row-navigation.ts +17 -0
  426. package/frontend-vue/layers/dms-ui/app/types/table/query.ts +2 -1
  427. package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +3 -1
  428. package/frontend-vue/layers/dms-ui/app/utils/cascader.ts +94 -3
  429. package/frontend-vue/layers/dms-ui/app/utils/componentTree.ts +24 -0
  430. package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +20 -5
  431. package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +20 -5
  432. package/frontend-vue/package.json +6 -2
  433. package/package.json +11 -10
  434. package/skills/dms/SKILL.md +1 -1
  435. package/skills/dms-auth/SKILL.md +5 -3
  436. package/skills/dms-best-practices/REFERENCE.md +2 -0
  437. package/skills/dms-best-practices/SKILL.md +4 -0
  438. package/skills/dms-module/SKILL.md +3 -2
  439. package/skills/dms-pages/REFERENCE.md +129 -16
  440. package/skills/dms-pages/SKILL.md +1 -1
  441. package/skills/dms-project/DEV-SERVERS.md +23 -1
  442. package/skills/dms-project/REFERENCE.md +1 -1
  443. package/skills/dms-project/SKILL.md +4 -1
  444. package/skills/dms-project/scripts/dms-dev.sh +205 -17
  445. package/dist/hooks/tenant-instances.d.ts +0 -1
  446. package/dist/hooks/tenant-instances.js +0 -13
  447. package/dist/hooks/tenant-instances.js.map +0 -1
  448. package/dist/routes/startup-gate.d.ts +0 -7
  449. package/dist/routes/startup-gate.js +0 -39
  450. package/dist/routes/startup-gate.js.map +0 -1
  451. package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +0 -42
@@ -1,4 +1,7 @@
1
1
  <script setup lang="ts" generic="T extends Data">
2
+ import { get } from "@nuxt/ui/runtime/utils/index.js";
3
+ import DmsBlockStatus from "../../build/components/blocks/BlockStatus.vue";
4
+ import { readFetchNotFound } from "../../build/utils/fetchNotFound";
2
5
  import type {
3
6
  ColumnSizingState,
4
7
  ExpandedState,
@@ -52,15 +55,15 @@ import {
52
55
  KANBAN_DISPLAY_BRIDGE_KEY,
53
56
  } from "../../composables/table-view/kanban";
54
57
  import { useTableRowActions } from "../../build/composables/table-view/useTableViewRowActions";
58
+ import { describeTableWriteError } from "../../build/composables/table-view/utils/writeError";
55
59
  import {
56
60
  buildTableQuery,
57
61
  isFilterEffective,
58
62
  } from "../../build/composables/table-view/utils/tableQuery";
59
63
  import { buildTableViewShortcuts } from "../../composables/table-view/shortcuts";
60
64
  import {
61
- defaultExpandedRows,
65
+ expandedRowsOnListing,
62
66
  type ExpandedRowMap,
63
- keepListedRows,
64
67
  nextExpandedRows,
65
68
  } from "../../build/composables/table-view/utils/expandedRows";
66
69
  import { resolveTableChrome } from "../../build/composables/table-view/utils/chrome";
@@ -128,16 +131,32 @@ import {
128
131
  type WatchSource,
129
132
  } from "vue";
130
133
  import { useTableViewConfig } from "../../build/composables/table-view/useTableViewConfig";
131
- import { useTableViews } from "../../build/composables/table-view/useTableViews";
134
+ import {
135
+ keptViewId,
136
+ useTableViews,
137
+ } from "../../build/composables/table-view/useTableViews";
132
138
  import { useGroupedRows } from "../../build/composables/table-view/useGroupedRows";
133
139
  import { useTableFooter } from "../../build/composables/table-view/useTableFooter";
134
140
  import { useAccumulatedPages } from "../../build/composables/table-view/useAccumulatedPages";
141
+ import type { RowStep } from "../../build/composables/actions/rowNavigation";
142
+ import { rowLocator } from "../../build/composables/table-view/utils/locateRow";
135
143
  import { usePersistedPagination } from "../../build/composables/table-view/usePersistedPagination";
136
144
  import { useTablePreferences } from "../../build/composables/table-view/useTablePreferences";
145
+ import {
146
+ isLinkedArrival,
147
+ linkedArrivalPreferences,
148
+ linkFollowingPreferences,
149
+ linkKeyScope,
150
+ urlFilterScope,
151
+ } from "../../build/composables/table-view/utils/linkedArrival";
152
+ import { pageArrivalKind } from "../../build/composables/table-view/utils/pageArrival";
153
+ import { useTableLinkedVisit } from "../../build/composables/table-view/useTableLinkedVisit";
154
+ import { useMobileDisplay } from "../../build/composables/table-view/useMobileDisplay";
137
155
  import type { Ref } from "vue";
138
156
  import type { TableField } from "#dms-core/app/build/composables/user/tablePreferences";
139
157
  import { useTableRows } from "../../build/composables/table-view/useTableRows";
140
158
  import { useRefreshTriggers } from "../../build/composables/blocks/useRefreshTriggers";
159
+ import { refreshPageBlocks } from "../../utils/blockRefresh";
141
160
  import { useTableLiveHold } from "../../build/composables/table-view/useTableLiveHold";
142
161
  import type { TableViewLiveHoldOptions } from "../../types/live";
143
162
  import { usePageLive } from "../../composables/live/usePageLive";
@@ -145,13 +164,24 @@ import LiveHoldBanner from "../../build/components/table-view/LiveHoldBanner.vue
145
164
  import { useTableReorder } from "../../build/composables/table-view/useTableReorder";
146
165
  import {
147
166
  groupedOptionsFor,
148
- groupedSorting,
167
+ listTableSorting,
168
+ orderGroupedRows,
169
+ pickedTableSorting,
170
+ type TableSortingInput,
149
171
  isGroupableColumn,
150
172
  } from "../../build/composables/table-view/utils/groupedRows";
151
173
  import {
152
174
  readTableUrlTab,
153
175
  useTableUrlTab,
154
176
  } from "../../build/composables/table-view/useTableUrlTab";
177
+ import {
178
+ buildTableCountQueries,
179
+ isCountNarrowed,
180
+ navBadgeCount,
181
+ VIEW_COUNT_ID_PREFIX,
182
+ type TableCountState,
183
+ } from "../../build/composables/table-view/utils/tableCounts";
184
+ import { viewFilters } from "../../build/composables/table-view/utils/views";
155
185
  import { useTableRecordLink } from "../../build/composables/table-view/useTableRecordLink";
156
186
  import TableViews, {
157
187
  type TableViewItem,
@@ -187,6 +217,7 @@ interface RealtimePresenceActor {
187
217
  type RealtimePresenceMap = Record<string, RealtimePresenceActor[]>;
188
218
 
189
219
  /** A kept field of the table, its state, and its default. */
220
+ // The default may move once mounted (a phone's display): read when written.
190
221
  type TableStatePreference = [TableField, Ref<unknown>, unknown];
191
222
 
192
223
  // The backend options of a table view, declared here as well so the
@@ -194,6 +225,8 @@ type TableStatePreference = [TableField, Ref<unknown>, unknown];
194
225
  interface TableViewProps<T extends Data> extends TableViewConfig<T> {
195
226
  /** Expandable rows: caret column + detail band (backend `expandable`). */
196
227
  expandable?: TableViewExpandableConfig;
228
+ /** Caption count while the archived rows show (backend `archivedCountLabel`). */
229
+ archivedCountLabel?: string;
197
230
  /** Placeholder of the search field. */
198
231
  searchPlaceholder?: string;
199
232
  /** Key opening the search (`defineShortcuts` notation); `shift_f` else. */
@@ -254,7 +287,51 @@ const {
254
287
  defaultDisplay,
255
288
  } = useTableViewConfig<T>(props);
256
289
 
257
- const tablePreferences = useTablePreferences(componentId, pageId);
290
+ const route = useDmsRoute();
291
+ const urlScope = {
292
+ tableId: props.tableId,
293
+ isSoleTableView: props.isSoleTableView,
294
+ };
295
+ const ALL_TAB_ID = "all";
296
+
297
+ const keptPreferences = useTablePreferences(componentId, pageId);
298
+ // A link naming a view, a tab, a record or a URL filter opens on the table's
299
+ // defaults plus what it names, never over the state kept from the last visit
300
+ // (which stays kept for the next plain visit), until the user makes the
301
+ // state shown their own: a view, a tab, a filter or a sort they pick. Only a
302
+ // reload (or Back) of the URL the table wrote for that state restores it.
303
+ const isLinked = isLinkedArrival({
304
+ query: route.query,
305
+ urlScope,
306
+ queryParamFilters,
307
+ isVisit: pageArrivalKind() === "visit",
308
+ rememberedTab: keptPreferences.get<string>("activeTab", ALL_TAB_ID),
309
+ keptLinkScope: keptPreferences.get<string | undefined>(
310
+ "linkScope",
311
+ undefined,
312
+ ),
313
+ shownLink: keptPreferences.get<string | undefined>("shownLink", undefined),
314
+ });
315
+ const tablePreferences = linkFollowingPreferences(
316
+ isLinked ? linkedArrivalPreferences(keptPreferences) : keptPreferences,
317
+ {
318
+ isLinkedArrival: isLinked,
319
+ urlScope,
320
+ linkScope: () => urlFilterScope(route.query, queryParamFilters),
321
+ linkKeys: () => linkKeyScope(route.query, urlScope, queryParamFilters),
322
+ // Read once the table is mounted, past the declarations below. A page
323
+ // kept without its scope is restored as it is (`usePersistedPagination`).
324
+ listingState: () => [
325
+ ["activeView", keptViewId(activeViewId.value), undefined],
326
+ ["activeTab", activeTabId.value, ALL_TAB_ID],
327
+ ["columnFilters", columnFilters.value, []],
328
+ ["sorting", sorting.value, defaultSortState],
329
+ ["pagination", paginationState.value, DEFAULT_PAGINATION],
330
+ ["paginationScope", undefined, undefined],
331
+ ],
332
+ },
333
+ );
334
+ onMounted(() => void tablePreferences.settleArrival());
258
335
  const { sendComponentEvent } = useComponentEvent(componentId);
259
336
  const { processI18n } = useTranslation();
260
337
 
@@ -394,8 +471,6 @@ const resolvedChrome = resolveTableChrome(props.layout, {
394
471
  });
395
472
  const GLOBAL_FILTER_DEBOUNCE_MS = 400;
396
473
 
397
- const route = useDmsRoute();
398
-
399
474
  const DEFAULT_FILTER_MODE = "is";
400
475
 
401
476
  const queryParamHiddenFilters = computed<TableFilter[]>(() => {
@@ -511,12 +586,16 @@ const defaultSortKind = tableDefaultSort
511
586
  ?.type?.id,
512
587
  )
513
588
  : undefined;
589
+ // A hand-ordered table has no sort to pick: a sort saved before it was
590
+ // ordered by hand is not read back.
514
591
  const sorting = ref<SortingState>(
515
- sortingOrDefault(
516
- tablePreferences.get<SortingState>("sorting", defaultSortState),
517
- declaredSortableIds,
518
- defaultSortState,
519
- ),
592
+ reorderConfig
593
+ ? defaultSortState
594
+ : sortingOrDefault(
595
+ tablePreferences.get<SortingState>("sorting", defaultSortState),
596
+ declaredSortableIds,
597
+ defaultSortState,
598
+ ),
520
599
  );
521
600
 
522
601
  const savedFilters = tablePreferences.get<TableFilter[]>("columnFilters", []);
@@ -538,6 +617,16 @@ const manualFilters = savedFilters
538
617
  .filter((f) => !pinnedKeys.has(f.accessorKey))
539
618
  .map((f) => ({ ...f, pinned: false }));
540
619
  const initialFilters = [...buildPinnedFilters(), ...manualFilters];
620
+ // The pinned filters at their declared value: what a view leaves out of them.
621
+ const pinnedDefaultFilters: TableFilter[] = (defaultFilters || []).map(
622
+ (filter) => ({
623
+ accessorKey: filter.accessorKey,
624
+ mode: filter.mode,
625
+ value: filter.value,
626
+ pinned: true,
627
+ initialValue: filter.value,
628
+ }),
629
+ );
541
630
 
542
631
  const columnFilters = ref<TableFilter[]>(initialFilters);
543
632
  const columnVisibility = ref<VisibilityState>(
@@ -559,18 +648,21 @@ const globalFilterDebounced = refDebounced(
559
648
  );
560
649
  const showArchived = ref(false);
561
650
 
562
- const urlScope = {
563
- tableId: props.tableId,
564
- isSoleTableView: props.isSoleTableView,
565
- };
651
+ // The display the table counts as its default: the phone's `mobileDisplay`
652
+ // once applied (see `useMobileDisplay` below).
653
+ const displayDefault = ref(
654
+ offeredDisplayIds.has(defaultDisplay) ? defaultDisplay : fallbackDisplayId,
655
+ );
566
656
 
567
657
  // Views: named states of the whole table, the module's and the user's own.
568
658
  const {
569
659
  items: tableViewItems,
570
660
  activeViewId,
661
+ activeView,
571
662
  isModified: isViewModified,
572
663
  canSaveViews,
573
- openView,
664
+ selectView,
665
+ openLinkedView,
574
666
  resetView,
575
667
  saveView,
576
668
  saveAsNewView,
@@ -578,21 +670,16 @@ const {
578
670
  } = useTableViews({
579
671
  views: props.views,
580
672
  defaults: {
581
- pinnedFilters: (defaultFilters || []).map((filter) => ({
582
- accessorKey: filter.accessorKey,
583
- mode: filter.mode,
584
- value: filter.value,
585
- pinned: true,
586
- initialValue: filter.value,
587
- })),
673
+ pinnedFilters: pinnedDefaultFilters,
588
674
  sorting: defaultSortState,
589
675
  visibility: initialVisibility,
590
676
  columnOrder: listableColumns.map(
591
677
  (column) => column.id ?? column.accessorKey,
592
678
  ),
593
- display: offeredDisplayIds.has(defaultDisplay)
594
- ? defaultDisplay
595
- : fallbackDisplayId,
679
+ // Read when a view opens: a phone's `mobileDisplay` once applied.
680
+ get display() {
681
+ return displayDefault.value;
682
+ },
596
683
  density: props.density ?? DEFAULT_DENSITY,
597
684
  displays: offeredDisplayIds,
598
685
  },
@@ -610,9 +697,22 @@ const {
610
697
  urlScope,
611
698
  });
612
699
 
613
- const ALL_TAB_ID = "all";
700
+ // A phone opens on the table's `mobileDisplay` (cards rather than a grid cut
701
+ // after its first columns) unless a display was chosen: saved, fixed by the
702
+ // open view, or the only way in.
703
+ useMobileDisplay({
704
+ mobileDisplay: props.mobileDisplay,
705
+ defaultDisplay: displayDefault,
706
+ offered: offeredDisplayIds,
707
+ display: activeDisplayId,
708
+ activeView,
709
+ isDisplayChosen: () =>
710
+ isDefaultDisplayStandalone ||
711
+ tablePreferences.get<string | undefined>("viewMode", undefined) !==
712
+ undefined,
713
+ });
614
714
 
615
- const { t, te } = useI18n();
715
+ const { t, te, locale } = useI18n();
616
716
 
617
717
  // A tab named by the URL wins over the one kept from the last visit.
618
718
  const activeTabId = ref<string>(
@@ -668,12 +768,33 @@ const activeTabFilters = computed<TableFilter[]>(() => {
668
768
  return tab?.filters || [];
669
769
  });
670
770
 
771
+ const isOpenableTab = (tabId: string) =>
772
+ resolvedTabs.value.some((tab) => !tab.to && tab.id === tabId);
773
+
671
774
  useTableUrlTab({
672
775
  route,
673
776
  urlScope,
674
777
  activeTabId,
675
- isOpenable: (tabId) =>
676
- resolvedTabs.value.some((tab) => !tab.to && tab.id === tabId),
778
+ isOpenable: isOpenableTab,
779
+ });
780
+
781
+ // A link to this page while it is shown opens like a linked arrival: on the
782
+ // table's defaults plus what it names, leaving the kept state as it was.
783
+ useTableLinkedVisit({
784
+ route,
785
+ urlScope,
786
+ queryParamFilters,
787
+ activeView: () => activeViewId.value,
788
+ activeTab: () => activeTabId.value,
789
+ canOpenView: (id) => tableViewItems.value.some((view) => view.id === id),
790
+ canOpenTab: isOpenableTab,
791
+ follow: (query) => {
792
+ void tablePreferences.followLink(() => {
793
+ openLinkedView(query);
794
+ const tab = readTableUrlTab(query, urlScope);
795
+ activeTabId.value = tab && isOpenableTab(tab) ? tab : ALL_TAB_ID;
796
+ });
797
+ },
677
798
  });
678
799
 
679
800
  watch(resolvedTabs, (next) => {
@@ -700,6 +821,7 @@ const tableRows = useTableRows<T>({
700
821
  // Quick filters: dropdowns of the toolbar over a column's values (a select's
701
822
  // items, a boolean, the rows a relation points to), writing that column's
702
823
  // filter like the filters row does.
824
+ const { getDataType } = useDataTypes();
703
825
  const relationQuickFilterValues = ref<Record<string, QuickFilterItem[]>>({});
704
826
  // Relation values load after mount: until then their buttons hold their place.
705
827
  const relationQuickFiltersLoaded = ref(false);
@@ -712,7 +834,7 @@ const quickFilterDefinitions = (props.quickFilters ?? []).map((filter) => {
712
834
  column,
713
835
  mode: quickFilterMode(column, filter.mode),
714
836
  items: staticQuickFilterItems(column, processI18n),
715
- source: relationQuickFilterSource(column),
837
+ source: relationQuickFilterSource(column, getDataType),
716
838
  };
717
839
  });
718
840
 
@@ -726,7 +848,9 @@ const loadRelationQuickFilters = async () => {
726
848
  try {
727
849
  const response = await $authFetch<
728
850
  TableViewListResponse<Record<string, unknown>>
729
- >(source!.url, { query: { limit: RELATION_QUICK_FILTER_LIMIT } });
851
+ >(source!.searchUrl, {
852
+ query: { limit: RELATION_QUICK_FILTER_LIMIT },
853
+ });
730
854
  relationQuickFilterValues.value = {
731
855
  ...relationQuickFilterValues.value,
732
856
  [filter.field]: relationQuickFilterItems(response.results, source!),
@@ -800,17 +924,22 @@ const effectiveGlobalFilter = computed(() =>
800
924
  const isGroupedDisplay = computed(
801
925
  () => activeDisplayId.value === GROUPED_DISPLAY_ID && !!groupedOptions,
802
926
  );
927
+ const sortingInput = computed<TableSortingInput>(() => ({
928
+ reorderField: reorderConfig?.field,
929
+ grouped: isGroupedDisplay.value ? activeGroupedOptions.value : undefined,
930
+ canSort: activeCapabilities.value.sorting,
931
+ sorting: sorting.value,
932
+ defaultSorting: defaultSortState,
933
+ }));
934
+ // The order the rows are wanted in; the grouped display lists them on the
935
+ // grouped column and orders each group by it (`shownResults`).
936
+ const pickedSorting = computed(() => pickedTableSorting(sortingInput.value));
803
937
  const effectiveSorting = computed<SortingState>(() => {
804
- if (reorderConfig) return [{ id: reorderConfig.field, desc: false }];
805
- const picked = activeCapabilities.value.sorting
806
- ? sorting.value
807
- : defaultSortState;
808
- return sanitizeSorting(
809
- isGroupedDisplay.value && activeGroupedOptions.value
810
- ? groupedSorting(picked, activeGroupedOptions.value)
811
- : picked,
812
- sortableIds.value,
813
- );
938
+ const listed = listTableSorting(sortingInput.value);
939
+ // The hand order is kept whole: its field need not be a sortable column.
940
+ return reorderConfig && !sortingInput.value.grouped
941
+ ? listed
942
+ : sanitizeSorting(listed, sortableIds.value);
814
943
  });
815
944
 
816
945
  // A feed grows by pages (`loadMore`, `infinite`) rather than paging.
@@ -852,6 +981,7 @@ const openRowId = computed(() =>
852
981
 
853
982
  // The gate is set up with the other refresh triggers below; the hold only
854
983
  // reads it once the table is on screen.
984
+ const NEW_ROWS_COUNT_ID = "liveHold:new";
855
985
  const tableRoot = useTemplateRef<ComponentPublicInstance>("tableRoot");
856
986
  const liveHold = useTableLiveHold({
857
987
  liveHold: props.liveHold,
@@ -863,6 +993,32 @@ const liveHold = useTableLiveHold({
863
993
  isSorted: () => !isSameSorting(effectiveSorting.value, defaultSortState),
864
994
  tableElement: () => tableRoot.value?.$el ?? null,
865
995
  },
996
+ // The pill counts the rows the table would list, on its own count route:
997
+ // the rows its tab, search and filters match now, less those it holds.
998
+ newRows: {
999
+ heldList: () => data.value,
1000
+ countNow: async (signal) => {
1001
+ const counts = await tableRows.countBatch(
1002
+ [
1003
+ {
1004
+ id: NEW_ROWS_COUNT_ID,
1005
+ query: {
1006
+ ...buildTableQuery({
1007
+ pagination: { pageIndex: 0, pageSize: 0 },
1008
+ sorting: [],
1009
+ columnFilters: effectiveColumnFilters.value,
1010
+ hiddenFilters: hiddenFilters.value,
1011
+ globalFilter: effectiveGlobalFilter.value,
1012
+ }),
1013
+ ...archiveQuery.value,
1014
+ },
1015
+ },
1016
+ ],
1017
+ signal,
1018
+ );
1019
+ return counts[NEW_ROWS_COUNT_ID];
1020
+ },
1021
+ },
866
1022
  });
867
1023
 
868
1024
  // The list, its counters and its footer figures load in parallel: a server
@@ -911,6 +1067,11 @@ const listLoadError = computed(() => {
911
1067
  : "error.500";
912
1068
  });
913
1069
 
1070
+ // A list answering 404 names a resource that does not exist (the workspace
1071
+ // its rows belong to was deleted): no retry fixes it, so the table gives way
1072
+ // to the not-found state blocks show, with what the 404 said.
1073
+ const listNotFound = computed(() => readFetchNotFound(settledError.value));
1074
+
914
1075
  // useDmsAsyncData keeps the previous result when a refetch fails, so rows kept
915
1076
  // on screen would masquerade as the newly requested page/filter. Hide them
916
1077
  // while the latest query is in error; the error panel takes their place.
@@ -919,7 +1080,13 @@ const listLoadError = computed(() => {
919
1080
  const shownData = computed(() =>
920
1081
  listLoadError.value ? undefined : data.value,
921
1082
  );
922
- const shownResults = computed(() => shownData.value?.results);
1083
+ const shownResults = computed(() => {
1084
+ const results = shownData.value?.results;
1085
+ const grouped = activeGroupedOptions.value;
1086
+ return results && isGroupedDisplay.value && grouped
1087
+ ? orderGroupedRows(results, grouped, pickedSorting.value, locale.value)
1088
+ : results;
1089
+ });
923
1090
 
924
1091
  // Rows selected before the failure can no longer be seen or verified, yet the
925
1092
  // selection toolbar would keep offering delete/archive/export on them.
@@ -1003,28 +1170,46 @@ watch(
1003
1170
  },
1004
1171
  );
1005
1172
 
1006
- // A view's counter shares the tab counters' request, under its own id.
1007
- const VIEW_COUNT_ID_PREFIX = "view:";
1008
- const countQuery = (filters: TableFilter[], search?: string) =>
1009
- buildTableQuery({
1010
- pagination: { pageIndex: 0, pageSize: 0 },
1011
- sorting: [],
1012
- columnFilters: filters,
1013
- hiddenFilters: queryParamHiddenFilters.value,
1014
- globalFilter: search,
1015
- });
1016
-
1017
- const tabCountsQuery = computed(() => [
1018
- ...resolvedTabs.value
1019
- .filter((tab) => !tab.to)
1020
- .map((tab) => ({ id: tab.id, query: countQuery(tab.filters) })),
1021
- ...tableViewItems.value
1022
- .filter((view) => view.count)
1023
- .map((view) => ({
1024
- id: `${VIEW_COUNT_ID_PREFIX}${view.id}`,
1025
- query: countQuery(view.filters ?? [], view.search),
1026
- })),
1173
+ const scopeFilters = computed<TableFilter[]>(() => [
1174
+ ...queryParamHiddenFilters.value,
1175
+ ...routeParamHiddenFilters.value,
1027
1176
  ]);
1177
+ const countState = computed<TableCountState>(() => ({
1178
+ scopeFilters: scopeFilters.value,
1179
+ tabFilters: activeTabFilters.value,
1180
+ columnFilters: effectiveColumnFilters.value,
1181
+ search: effectiveGlobalFilter.value,
1182
+ }));
1183
+ const isCountStateNarrowed = computed(() => isCountNarrowed(countState.value));
1184
+
1185
+ // Each counter counts what a click on it would list, so the counters follow
1186
+ // the filters, the search, the view and the tab: picks made in a row (quick
1187
+ // filters toggled one after the other) are counted once, when they settle.
1188
+ const COUNTS_DEBOUNCE_MS = 200;
1189
+ const debouncedCountsQuery = refDebounced(
1190
+ computed(() =>
1191
+ buildTableCountQueries({
1192
+ ...countState.value,
1193
+ tabs: resolvedTabs.value
1194
+ .filter((tab) => !tab.to)
1195
+ .map((tab) => ({
1196
+ id: tab.id,
1197
+ filters: tab.filters,
1198
+ hasNavBadge: !!tab.navBadge,
1199
+ })),
1200
+ views: tableViewItems.value
1201
+ .filter((view) => view.count)
1202
+ .map((view) => ({
1203
+ id: view.id,
1204
+ filters: viewFilters(view, pinnedDefaultFilters),
1205
+ search: view.search,
1206
+ })),
1207
+ activeViewId: activeViewId.value,
1208
+ }),
1209
+ ),
1210
+ COUNTS_DEBOUNCE_MS,
1211
+ );
1212
+ const tabCountsQuery = computed(() => debouncedCountsQuery.value);
1028
1213
 
1029
1214
  // No default: `null` until the counts arrive, so the tabs draw placeholders
1030
1215
  // instead of a count of nothing.
@@ -1095,6 +1280,83 @@ const hasMoreRows = computed(
1095
1280
  const loadMoreRows = () => {
1096
1281
  if (hasMoreRows.value && !isListLoading.value) loadNextPage();
1097
1282
  };
1283
+ // A row's drawer or side panel steps through every row the table lists: past
1284
+ // the rows loaded, it turns the table's page (or grows its feed) and steps
1285
+ // onto the rows that arrive. A display reading its own rows keeps to them.
1286
+ const ADJACENT_ROWS_TIMEOUT_MS = 15_000;
1287
+ const listsServerPages = computed(() => !isActiveDisplaySelfManaged.value);
1288
+ // What a display reading its own rows shows (`setShownRows`), in its order:
1289
+ // the rows its row's container steps through. Another display, another set.
1290
+ const selfManagedRows = shallowRef<Data[]>([]);
1291
+ const setSelfManagedRows = (rows: T[]) => {
1292
+ selfManagedRows.value = rows as Data[];
1293
+ };
1294
+ watch(activeDisplayId, () => {
1295
+ selfManagedRows.value = [];
1296
+ });
1297
+ const navigableRows = (): Data[] =>
1298
+ listsServerPages.value
1299
+ ? ((shownResults.value ?? []) as Data[])
1300
+ : selfManagedRows.value;
1301
+ const loadedRowsOffset = () => {
1302
+ if (isAccumulating || !listsServerPages.value) return 0;
1303
+ const { pageIndex, pageSize } = paginationState.value;
1304
+ return pageSize > 0 ? pageIndex * pageSize : 0;
1305
+ };
1306
+ const turnPage = (step: RowStep): boolean => {
1307
+ if (isAccumulating) {
1308
+ if (step < 0 || !hasMoreRows.value) return false;
1309
+ loadNextPage();
1310
+ return true;
1311
+ }
1312
+ const { pageIndex, pageSize } = paginationState.value;
1313
+ const next = pageIndex + step;
1314
+ const total = data.value?.total ?? 0;
1315
+ if (pageSize <= 0 || next < 0 || next * pageSize >= total) return false;
1316
+ paginationState.value = { ...paginationState.value, pageIndex: next };
1317
+ return true;
1318
+ };
1319
+ let adjacentRowsLoad: Promise<boolean> | undefined;
1320
+ const loadAdjacentRows = (step: RowStep): Promise<boolean> => {
1321
+ // A second J while the page loads waits for the same page.
1322
+ if (adjacentRowsLoad) return adjacentRowsLoad;
1323
+ if (!listsServerPages.value || isListLoading.value || !turnPage(step)) {
1324
+ return Promise.resolve(false);
1325
+ }
1326
+ const before = shownResults.value;
1327
+ adjacentRowsLoad = until(
1328
+ () => shownResults.value !== before && !isListLoading.value,
1329
+ )
1330
+ .toBe(true, { timeout: ADJACENT_ROWS_TIMEOUT_MS })
1331
+ .then(() => !listLoadError.value && shownResults.value !== before)
1332
+ .finally(() => {
1333
+ adjacentRowsLoad = undefined;
1334
+ });
1335
+ return adjacentRowsLoad;
1336
+ };
1337
+ // A row past the rows loaded (a deep link, a row live rows pushed out) is
1338
+ // found in the table's whole result, under the query the list reads: its
1339
+ // position, and its neighbours for the steps. Its rows read in the order
1340
+ // the table shows them: pinned rows lead each page it reads, not the result.
1341
+ const locateRow = rowLocator({
1342
+ list: async (offset, limit) => {
1343
+ if (!listsServerPages.value) return null;
1344
+ const page = await tableRows.listAsShown(
1345
+ {
1346
+ ...queryRequest.value,
1347
+ ...archiveQuery.value,
1348
+ ...liveHold.heldQuery(),
1349
+ offset,
1350
+ limit,
1351
+ },
1352
+ requestedPagination.value.pageSize,
1353
+ );
1354
+ return page && { results: page.results as Data[], total: page.total };
1355
+ },
1356
+ rowIdKey: props.rowIdKey ?? ROW_ID_DEFAULT_KEY,
1357
+ isWholeRead: () => !!props.source && !props.source.capabilities.paginate,
1358
+ });
1359
+
1098
1360
  const accumulation = computed<TableAccumulation | undefined>(() =>
1099
1361
  isAccumulating
1100
1362
  ? {
@@ -1130,7 +1392,7 @@ const { reorderState } = useTableReorder<T>({
1130
1392
  onError: (failure) =>
1131
1393
  toast.add({
1132
1394
  title: t("dms.table.reorder.error"),
1133
- description: resolveApiErrorMessage(failure),
1395
+ description: describeTableWriteError(failure, t, allColumns, processI18n),
1134
1396
  color: Color.error,
1135
1397
  icon: "i-ph-warning-circle",
1136
1398
  }),
@@ -1160,6 +1422,7 @@ provide(KANBAN_DISPLAY_BRIDGE_KEY, {
1160
1422
  groupByField: kanbanGroupBy,
1161
1423
  editAction: tableProps.value.rowActions?.edit,
1162
1424
  deleteAction: tableProps.value.rowActions?.delete,
1425
+ onCardMoved: () => void refreshCounts(),
1163
1426
  });
1164
1427
 
1165
1428
  // A link tab counts the rows of the list it opens; a list the caller may not
@@ -1198,14 +1461,19 @@ const refreshLinkTabCounts = async () => {
1198
1461
  // event) or a refresh: what summarises them elsewhere (a navigation count)
1199
1462
  // is told to read its figure again.
1200
1463
  const { notifyTableDataChanged } = useTableDataChanges();
1201
- const refreshAll = async () => {
1464
+ const refreshCounts = async () => {
1202
1465
  notifyTableDataChanged(location);
1203
1466
  await Promise.all([
1204
- refresh(),
1205
1467
  refreshTabCounts(),
1206
1468
  refreshLinkTabCounts(),
1207
1469
  refreshGroupCounts(),
1208
1470
  refreshSummaries(),
1471
+ ]);
1472
+ };
1473
+ const refreshAll = async () => {
1474
+ await Promise.all([
1475
+ refresh(),
1476
+ refreshCounts(),
1209
1477
  activeDisplayRef.value?.refresh?.(),
1210
1478
  ]);
1211
1479
  };
@@ -1222,6 +1490,11 @@ const refreshByUser = () => {
1222
1490
  liveGate.acknowledge();
1223
1491
  return refreshAll();
1224
1492
  };
1493
+ // A write of the user's (a row or bulk action, a form saved) can move what
1494
+ // the rest of the page shows of the same records — a KPI, a tab's badge,
1495
+ // another list: every block of the page reads its data again, this table
1496
+ // among them, at once even on a paused live page.
1497
+ const refreshAfterWrite = () => refreshPageBlocks();
1225
1498
 
1226
1499
  // "Preview as role": a link tab to a page the previewed role could not open
1227
1500
  // is drawn locked, one to a page it opens partially is drawn partially
@@ -1259,19 +1532,27 @@ const tabsWithCount = computed(() =>
1259
1532
 
1260
1533
  // A tab declared with `navBadge` keeps the navigation badge the server
1261
1534
  // counted for the page it stands for (the linked page, or this one) up to
1262
- // date with its own counter; an empty badge stands for zero.
1535
+ // date: with the tab's own counter while nothing narrows it, else with a
1536
+ // count of the tab alone, since the badge stands for the whole page. An
1537
+ // empty badge stands for zero.
1263
1538
  const { setNavBadge } = useNavBadges();
1539
+ const tabNavBadgeCount = (tab: ResolvedTab, count: number | undefined) =>
1540
+ tab.to
1541
+ ? count
1542
+ : navBadgeCount(tabCountsData.value, tab.id, isCountStateNarrowed.value);
1264
1543
  watch(
1265
1544
  tabsWithCount,
1266
1545
  (next) => {
1267
1546
  for (const tab of next) {
1268
1547
  const source = resolvedTabs.value.find((entry) => entry.id === tab.id);
1269
- if (!source?.navBadge || tab.count === undefined) continue;
1548
+ if (!source?.navBadge) continue;
1549
+ const count = tabNavBadgeCount(source, tab.count);
1550
+ if (count === undefined) continue;
1270
1551
  const fullId = source.to ? source.toPage : pageId;
1271
1552
  const tone =
1272
1553
  typeof source.navBadge === "object" ? source.navBadge.tone : undefined;
1273
1554
  if (fullId) {
1274
- setNavBadge(fullId, tab.count > 0 ? String(tab.count) : "", tone);
1555
+ setNavBadge(fullId, count > 0 ? String(count) : "", tone);
1275
1556
  }
1276
1557
  }
1277
1558
  },
@@ -1296,10 +1577,11 @@ const {
1296
1577
  } = useTableRowActions<T>({
1297
1578
  api: $authFetch,
1298
1579
  location,
1580
+ columns: allColumns,
1299
1581
  caption,
1300
1582
  labelKey,
1301
1583
  rowIdKey: props.rowIdKey,
1302
- refreshCallback: refreshByUser,
1584
+ refreshCallback: refreshAfterWrite,
1303
1585
  formComponents,
1304
1586
  formContainer,
1305
1587
  formPages,
@@ -1308,9 +1590,16 @@ const {
1308
1590
  pageId: pageId!,
1309
1591
  queryParamFilters,
1310
1592
  rowNavigation: {
1311
- rows: () => (shownResults.value ?? []) as Data[],
1593
+ rows: navigableRows,
1312
1594
  rowIdKey: props.rowIdKey ?? ROW_ID_DEFAULT_KEY,
1313
1595
  getRow: (id) => tableRows.getRow(id) as Promise<Data | undefined>,
1596
+ total: () =>
1597
+ listsServerPages.value
1598
+ ? (shownData.value?.total ?? undefined)
1599
+ : undefined,
1600
+ offset: loadedRowsOffset,
1601
+ loadAdjacent: loadAdjacentRows,
1602
+ locate: locateRow,
1314
1603
  },
1315
1604
  recordScope: urlScope,
1316
1605
  onRecordShown: (id) => recordLink?.shown(id),
@@ -1382,12 +1671,14 @@ const deselectRows = (ids: string[]) => {
1382
1671
  };
1383
1672
 
1384
1673
  // From the archive, a delete is a permanent one: its confirmation says so.
1385
- const deleteRows = async (ids: string[]) => {
1674
+ // A display holding the rows (a kanban card) hands them for its wording.
1675
+ const deleteRows = async (ids: string[], rows?: T[]) => {
1386
1676
  const done = await deleteRowsAction(
1387
1677
  ids,
1388
1678
  tableProps.value.rowActions?.delete,
1389
1679
  {
1390
1680
  permanently: !!archiveMode && showArchived.value,
1681
+ rows,
1391
1682
  },
1392
1683
  );
1393
1684
  if (done) deselectRows(ids);
@@ -1414,12 +1705,18 @@ const rowActionConfig = (key: "details" | "duplicate" | "add" | "edit") =>
1414
1705
  tableProps.value.rowActions?.[key];
1415
1706
  const handleRowClick = (item: T) =>
1416
1707
  runConfirmedBuiltIn(rowActionConfig("details"), item, () => openRow(item));
1417
- const handleRowDuplicate = (itemId: string) =>
1418
- runConfirmedBuiltIn(
1708
+ // Its dialog words the row shown (`{name}`), else only its id.
1709
+ const handleRowDuplicate = (itemId: string) => {
1710
+ const idKey = props.rowIdKey ?? ROW_ID_DEFAULT_KEY;
1711
+ const shown = shownResults.value?.find(
1712
+ (row) => String(get(row, idKey)) === itemId,
1713
+ );
1714
+ return runConfirmedBuiltIn(
1419
1715
  rowActionConfig("duplicate"),
1420
- { [props.rowIdKey ?? ROW_ID_DEFAULT_KEY]: itemId },
1716
+ shown ?? { [idKey]: itemId },
1421
1717
  () => duplicateRow(itemId),
1422
1718
  );
1719
+ };
1423
1720
  const handleRowAdd = () =>
1424
1721
  runConfirmedBuiltIn(rowActionConfig("add"), undefined, () =>
1425
1722
  newRow(queryParamDefaults.value),
@@ -1484,12 +1781,14 @@ const realtimeEventHandlers: Record<
1484
1781
  return refreshAll();
1485
1782
  },
1486
1783
  [REALTIME_EVENT_TYPE.CREATED]: () => refreshAll(),
1784
+ // An edited row can change tab, group or column: the counts are read again
1785
+ // with it.
1487
1786
  [REALTIME_EVENT_TYPE.UPDATED]: async (ids) => {
1488
1787
  if (isActiveDisplaySelfManaged.value) {
1489
- await activeDisplayRef.value?.refresh?.();
1788
+ await Promise.all([activeDisplayRef.value?.refresh?.(), refreshCounts()]);
1490
1789
  return;
1491
1790
  }
1492
- await Promise.all(ids.map((id) => patchRowLocal(id)));
1791
+ await Promise.all([...ids.map((id) => patchRowLocal(id)), refreshCounts()]);
1493
1792
  },
1494
1793
  };
1495
1794
 
@@ -1636,22 +1935,20 @@ const expandedModel = computed<ExpandedState>({
1636
1935
  },
1637
1936
  });
1638
1937
 
1639
- let expandedQueryKey: string | undefined;
1938
+ let expandedListedKey: string | undefined;
1640
1939
  if (expandableConfig) {
1641
1940
  watch(
1642
1941
  shownResults,
1643
1942
  (rows) => {
1644
- const queryKey = JSON.stringify([queryRequest.value, archiveQuery.value]);
1645
- if (queryKey === expandedQueryKey) {
1646
- expanded.value = keepListedRows(expanded.value, rowIdsOf(rows));
1647
- return;
1648
- }
1649
- expandedQueryKey = queryKey;
1650
- expanded.value = defaultExpandedRows(
1651
- rowIdsOf(rows),
1943
+ const next = expandedRowsOnListing(
1944
+ { expanded: expanded.value, listedKey: expandedListedKey },
1945
+ rows ? rowIdsOf(rows) : undefined,
1946
+ JSON.stringify([queryRequest.value, archiveQuery.value]),
1652
1947
  expandableConfig.defaultExpanded,
1653
1948
  expandableConfig.single,
1654
1949
  );
1950
+ expandedListedKey = next.listedKey;
1951
+ expanded.value = next.expanded;
1655
1952
  },
1656
1953
  { immediate: true },
1657
1954
  );
@@ -1896,6 +2193,7 @@ const displayContext = computed(
1896
2193
  pageId,
1897
2194
  options: optionsForDisplay(activeDisplayId.value),
1898
2195
  refresh: refreshByUser,
2196
+ setShownRows: setSelfManagedRows,
1899
2197
  reorder: reorderState.value,
1900
2198
  table: displayTable.value,
1901
2199
  }),
@@ -1993,13 +2291,16 @@ defineShortcuts(
1993
2291
  );
1994
2292
 
1995
2293
  // Each kept field with its default: a value back to it is forgotten.
2294
+ // A hand-ordered table keeps no sort.
1996
2295
  const tableStatePreferences: TableStatePreference[] = [
1997
- ["sorting", sorting, defaultSortState],
2296
+ ...(reorderConfig
2297
+ ? []
2298
+ : [["sorting", sorting, defaultSortState] as TableStatePreference]),
1998
2299
  ["columnFilters", columnFilters, []],
1999
2300
  ["columnVisibility", columnVisibility, initialVisibility],
2000
2301
  ["filtersOpen", filtersRowOpen, hasEmptyDefaultFilter],
2001
2302
  ["activeTab", activeTabId, ALL_TAB_ID],
2002
- ["viewMode", activeDisplayId, defaultDisplay],
2303
+ ["viewMode", activeDisplayId, displayDefault],
2003
2304
  ["columnOrder", columnOrder, []],
2004
2305
  ["kanbanGroupBy", kanbanGroupBy, defaultKanbanGroupBy],
2005
2306
  ["groupedGroupBy", groupedGroupBy, defaultGroupedGroupBy],
@@ -2010,7 +2311,7 @@ tableStatePreferences.forEach(([field, state, defaultValue]) => {
2010
2311
  watch(
2011
2312
  state,
2012
2313
  (newValue) => {
2013
- tablePreferences.set(field, newValue, defaultValue);
2314
+ tablePreferences.set(field, newValue, unref(defaultValue));
2014
2315
  },
2015
2316
  { deep: true },
2016
2317
  );
@@ -2099,7 +2400,13 @@ onMounted(() => {
2099
2400
  </script>
2100
2401
 
2101
2402
  <template>
2403
+ <DmsBlockStatus
2404
+ v-if="listNotFound"
2405
+ state="not-found"
2406
+ :not-found="listNotFound"
2407
+ />
2102
2408
  <UTable
2409
+ v-else
2103
2410
  ref="tableRoot"
2104
2411
  v-bind="tableProps"
2105
2412
  v-model:pagination="paginationState"
@@ -2132,6 +2439,7 @@ onMounted(() => {
2132
2439
  :url-filters="shownUrlFilters"
2133
2440
  :default-page-size="props.pageSize"
2134
2441
  :archive-toggle="canToggleArchived"
2442
+ :archived-count-label="props.archivedCountLabel"
2135
2443
  :displays="availableDisplays"
2136
2444
  :active-capabilities="activeCapabilities"
2137
2445
  :kanban-group-by-options="kanbanGroupByOptions"
@@ -2143,7 +2451,10 @@ onMounted(() => {
2143
2451
  :load-error="listLoadError"
2144
2452
  :data="shownResults || []"
2145
2453
  :pagination-options="{ manualPagination: true, rowCount: data?.total }"
2146
- :sorting-options="{ manualSorting: true }"
2454
+ :sorting-options="{
2455
+ manualSorting: true,
2456
+ enableSorting: activeCapabilities.sorting,
2457
+ }"
2147
2458
  :default-sort="tableDefaultSort"
2148
2459
  :default-sort-label="defaultSortLabel"
2149
2460
  :default-sort-kind="defaultSortKind"
@@ -2171,10 +2482,7 @@ onMounted(() => {
2171
2482
  @remove-url-filter="removeUrlFilter"
2172
2483
  @edit="handleRowEdit"
2173
2484
  >
2174
- <template
2175
- v-if="liveHold.newCount.value > 0 || liveHold.reason.value"
2176
- #notice
2177
- >
2485
+ <template v-if="liveHold.reason.value" #notice>
2178
2486
  <LiveHoldBanner
2179
2487
  :count="liveHold.newCount.value"
2180
2488
  :reason="liveHold.reason.value"
@@ -2183,6 +2491,27 @@ onMounted(() => {
2183
2491
  @show="liveHold.showNew"
2184
2492
  />
2185
2493
  </template>
2494
+ <!-- What came in while the rows were read or the page paused: a pill
2495
+ over the first rows, under the column header band (the table sets
2496
+ its height; a sticky one stays put above a scrolled area, so the pill
2497
+ stays under it), taking no room, so the rows stay where they are.
2498
+ Above the pinned columns and the presence rail it overlaps. -->
2499
+ <template v-else-if="liveHold.newCount.value > 0" #notice>
2500
+ <div class="relative z-30 h-0">
2501
+ <div
2502
+ class="pointer-events-none absolute inset-x-0 top-[calc(var(--dms-table-head-height,0px)+0.5rem)] flex justify-center px-4"
2503
+ >
2504
+ <LiveHoldBanner
2505
+ floating
2506
+ class="pointer-events-auto"
2507
+ :count="liveHold.newCount.value"
2508
+ :missed-label="liveHold.missedLabel"
2509
+ :detail="liveHold.missedDetail.value"
2510
+ @show="liveHold.showNew"
2511
+ />
2512
+ </div>
2513
+ </div>
2514
+ </template>
2186
2515
  <template v-if="hasViews" #views>
2187
2516
  <TableViews
2188
2517
  :items="viewItems"
@@ -2191,7 +2520,7 @@ onMounted(() => {
2191
2520
  :modified="isViewModified"
2192
2521
  :can-save-as="canSaveViews"
2193
2522
  :divided="tabsWithCount.length === 0"
2194
- @open="openView"
2523
+ @open="selectView"
2195
2524
  @reset="resetView"
2196
2525
  @save="saveView"
2197
2526
  @save-as="saveAsNewView"