@antelopejs/dms 0.9.8 → 0.10.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 (420) hide show
  1. package/CHANGELOG.md +102 -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 -0
  21. package/dist/config.js +5 -3
  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 +22 -89
  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 +1 -2
  41. package/dist/implementations/dms/manifest.js +2 -0
  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 +28 -48
  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 +24 -13
  129. package/dist/store/listing.js.map +1 -1
  130. package/dist/store/operations.js +4 -0
  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/validation/auth.schema.d.ts +2 -2
  139. package/dist/validation/member-invite.schema.d.ts +2 -10
  140. package/dist/validation/member-invite.schema.js +2 -15
  141. package/dist/validation/member-invite.schema.js.map +1 -1
  142. package/frontend-vue/dms.frontend.ts +24 -5
  143. package/frontend-vue/dms.server.ts +17 -0
  144. package/frontend-vue/layers/dms-auth/server/accounts.ts +3 -4
  145. package/frontend-vue/layers/dms-core/app/build/composables/renderNow.ts +34 -0
  146. package/frontend-vue/layers/dms-core/app/build/composables/user/tablePreferences.ts +5 -1
  147. package/frontend-vue/layers/dms-core/app/build/utils/crossTab.ts +199 -0
  148. package/frontend-vue/layers/dms-core/app/build/utils/sharedRealtimeConnection.ts +410 -0
  149. package/frontend-vue/layers/dms-core/app/composables/auth/useApiData.ts +77 -12
  150. package/frontend-vue/layers/dms-core/app/composables/data-types/useDataType.ts +26 -0
  151. package/frontend-vue/layers/dms-core/app/composables/hydration/isHydratingComponent.ts +29 -0
  152. package/frontend-vue/layers/dms-core/app/composables/live/livePauseSource.ts +29 -0
  153. package/frontend-vue/layers/dms-core/app/composables/period/compareResolvers.ts +150 -9
  154. package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +6 -4
  155. package/frontend-vue/layers/dms-core/app/composables/period/usePeriodScope.ts +27 -1
  156. package/frontend-vue/layers/dms-core/app/composables/realtime/useUserRealtime.ts +256 -119
  157. package/frontend-vue/layers/dms-core/app/composables/translation/useComposedText.ts +21 -8
  158. package/frontend-vue/layers/dms-core/app/composables/translation/useTranslation.ts +22 -5
  159. package/frontend-vue/layers/dms-core/app/plugins/dev-error-log.ts +16 -0
  160. package/frontend-vue/layers/dms-core/app/types/user.ts +6 -0
  161. package/frontend-vue/layers/dms-core/app/utils/composedText.ts +7 -2
  162. package/frontend-vue/layers/dms-core/app/utils/formatter.ts +166 -35
  163. package/frontend-vue/layers/dms-core/app/utils/url-interpolation.ts +18 -1
  164. package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +134 -2
  165. package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +134 -2
  166. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardBanners.vue +3 -2
  167. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +41 -21
  168. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardPageBody.vue +9 -0
  169. package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardSidebar.vue +4 -19
  170. package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +11 -3
  171. package/frontend-vue/layers/dms-layout/app/build/components/layout/GithubStarPrompt.vue +18 -7
  172. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionBar.vue +8 -0
  173. package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +14 -0
  174. package/frontend-vue/layers/dms-layout/app/build/components/layout/RowSidePanel.vue +43 -82
  175. package/frontend-vue/layers/dms-layout/app/build/components/layout/SidebarUserMenu.vue +44 -3
  176. package/frontend-vue/layers/dms-layout/app/build/components/layout/UserIdentity.vue +11 -3
  177. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleCatalogTile.vue +132 -35
  178. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleManageMenu.vue +59 -0
  179. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleSourceLabel.vue +25 -0
  180. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreBanners.vue +73 -0
  181. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreOperation.vue +116 -0
  182. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreRequirements.vue +8 -3
  183. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleStoreTile.vue +85 -207
  184. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTile.vue +10 -3
  185. package/frontend-vue/layers/dms-layout/app/build/components/pages/modules/ModuleTileSkeleton.vue +68 -0
  186. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxDisplay.vue +24 -8
  187. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/notification/NotificationInboxItem.vue +8 -5
  188. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/region/RegionTimeZoneInput.vue +1 -0
  189. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/role-permissions.ts +19 -0
  190. package/frontend-vue/layers/dms-layout/app/build/components/pages/settings/roles/useRoleEditor.ts +2 -1
  191. package/frontend-vue/layers/dms-layout/app/build/composables/layout/useSidePanelWidth.ts +31 -70
  192. package/frontend-vue/layers/dms-layout/app/build/composables/notification/useNotificationGroupsOpen.ts +11 -1
  193. package/frontend-vue/layers/dms-layout/app/build/utils/dev-reload-stream.ts +81 -0
  194. package/frontend-vue/layers/dms-layout/app/build/utils/module-store-catalog.ts +232 -36
  195. package/frontend-vue/layers/dms-layout/app/build/utils/modules-catalog.ts +122 -14
  196. package/frontend-vue/layers/dms-layout/app/composables/layout/useLayoutBanners.ts +32 -3
  197. package/frontend-vue/layers/dms-layout/app/composables/layout/usePageRecord.ts +50 -0
  198. package/frontend-vue/layers/dms-layout/app/composables/layout/useServedHeaderActions.ts +25 -7
  199. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleHistory.ts +3 -2
  200. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStore.ts +51 -4
  201. package/frontend-vue/layers/dms-layout/app/composables/page/useModuleStoreActions.ts +194 -0
  202. package/frontend-vue/layers/dms-layout/app/composables/page/usePageComponentPreload.ts +23 -3
  203. package/frontend-vue/layers/dms-layout/app/composables/page/usePageLayout.ts +4 -2
  204. package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +6 -1
  205. package/frontend-vue/layers/dms-layout/app/custom-layouts/DefaultLayout.vue +9 -0
  206. package/frontend-vue/layers/dms-layout/app/custom-layouts/SettingsLayout.vue +9 -0
  207. package/frontend-vue/layers/dms-layout/app/custom-pages/modules-store.vue +291 -0
  208. package/frontend-vue/layers/dms-layout/app/custom-pages/modules.vue +87 -474
  209. package/frontend-vue/layers/dms-layout/app/middleware/module-routing.global.ts +3 -0
  210. package/frontend-vue/layers/dms-layout/app/pages/[...slug].vue +6 -22
  211. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-navigation.client.ts +2 -1
  212. package/frontend-vue/layers/dms-layout/app/plugins/command-palette-personal-pages.client.ts +14 -44
  213. package/frontend-vue/layers/dms-layout/app/plugins/dms-dev-reload.client.ts +14 -9
  214. package/frontend-vue/layers/dms-layout/app/theme/form-controls.ts +9 -4
  215. package/frontend-vue/layers/dms-layout/app/theme/surfaces.ts +22 -6
  216. package/frontend-vue/layers/dms-layout/app/types/module-store.ts +9 -0
  217. package/frontend-vue/layers/dms-layout/app/types/page.ts +15 -1
  218. package/frontend-vue/layers/dms-layout/app/utils/account-menu.ts +11 -0
  219. package/frontend-vue/layers/dms-layout/app/utils/breadcrumb.ts +57 -0
  220. package/frontend-vue/layers/dms-layout/app/utils/pageQuery.ts +26 -0
  221. package/frontend-vue/layers/dms-layout/app/utils/pageRecord.ts +71 -0
  222. package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +39 -14
  223. package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +38 -13
  224. package/frontend-vue/layers/dms-ui/app/build/components/containers/TargetComponentTree.vue +31 -0
  225. package/frontend-vue/layers/dms-ui/app/build/components/containers/constants.ts +25 -0
  226. package/frontend-vue/layers/dms-ui/app/build/components/containers/drawer/DynamicDrawer.vue +244 -15
  227. package/frontend-vue/layers/dms-ui/app/build/components/containers/modal/DynamicModal.vue +42 -25
  228. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowHeader.vue +153 -0
  229. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/RowNotFound.vue +44 -0
  230. package/frontend-vue/layers/dms-ui/app/build/components/containers/row/drawerRowHeader.ts +35 -0
  231. package/frontend-vue/layers/dms-ui/app/build/components/form/FormEntries.vue +44 -9
  232. package/frontend-vue/layers/dms-ui/app/build/components/form/FormErrorSummary.vue +11 -4
  233. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldControl.vue +38 -5
  234. package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldNotes.vue +29 -5
  235. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSectionNav.vue +3 -1
  236. package/frontend-vue/layers/dms-ui/app/build/components/form/FormSections.vue +106 -27
  237. package/frontend-vue/layers/dms-ui/app/build/components/table/Actions.vue +12 -4
  238. package/frontend-vue/layers/dms-ui/app/build/components/table/Empty.vue +4 -2
  239. package/frontend-vue/layers/dms-ui/app/build/components/table/FiltersRow.vue +38 -1
  240. package/frontend-vue/layers/dms-ui/app/build/components/table/Pagination.vue +55 -54
  241. package/frontend-vue/layers/dms-ui/app/build/components/table/RowSelection.vue +2 -1
  242. package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +396 -288
  243. package/frontend-vue/layers/dms-ui/app/build/components/table/Tabs.vue +2 -1
  244. package/frontend-vue/layers/dms-ui/app/build/components/table/Views.vue +2 -1
  245. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardAvatar.vue +59 -0
  246. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardGridSkeleton.vue +19 -2
  247. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardRowActions.vue +38 -0
  248. package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardsDisplay.vue +148 -104
  249. package/frontend-vue/layers/dms-ui/app/build/components/table-view/LiveHoldBanner.vue +17 -2
  250. package/frontend-vue/layers/dms-ui/app/build/composables/actions/bulkSelection.ts +43 -9
  251. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowComponentOptions.ts +76 -0
  252. package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowNavigation.ts +172 -27
  253. package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +270 -46
  254. package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +2 -0
  255. package/frontend-vue/layers/dms-ui/app/build/composables/containers/closingLayers.ts +186 -0
  256. package/frontend-vue/layers/dms-ui/app/build/composables/containers/context.ts +14 -0
  257. package/frontend-vue/layers/dms-ui/app/build/composables/containers/drawerSize.ts +229 -0
  258. package/frontend-vue/layers/dms-ui/app/build/composables/containers/useResizableSize.ts +175 -0
  259. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/metricCells.ts +18 -35
  260. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/numberSuffix.ts +47 -0
  261. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +99 -24
  262. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerFieldTypes.ts +2 -1
  263. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationFilter.ts +75 -0
  264. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationRows.ts +71 -0
  265. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/relationValue.ts +39 -0
  266. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/twoLineCell.ts +3 -3
  267. package/frontend-vue/layers/dms-ui/app/build/composables/data-types/useColumnValueRenderer.ts +6 -2
  268. package/frontend-vue/layers/dms-ui/app/build/composables/form/createFormFields.ts +15 -0
  269. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldSaved.ts +40 -0
  270. package/frontend-vue/layers/dms-ui/app/build/composables/form/fieldValueRule.ts +62 -0
  271. package/frontend-vue/layers/dms-ui/app/build/composables/form/formEntryContext.ts +5 -0
  272. package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +42 -7
  273. package/frontend-vue/layers/dms-ui/app/build/composables/form/formLayout.ts +106 -4
  274. package/frontend-vue/layers/dms-ui/app/build/composables/form/formSections.ts +32 -0
  275. package/frontend-vue/layers/dms-ui/app/build/composables/form/readonlyValue.ts +19 -0
  276. package/frontend-vue/layers/dms-ui/app/build/composables/form/registerFormFunctions.ts +27 -0
  277. package/frontend-vue/layers/dms-ui/app/build/composables/form/repeaterTable.ts +65 -0
  278. package/frontend-vue/layers/dms-ui/app/build/composables/form/serverFieldErrors.ts +49 -0
  279. package/frontend-vue/layers/dms-ui/app/build/composables/form/toasterClearance.ts +118 -0
  280. package/frontend-vue/layers/dms-ui/app/build/composables/form/useInstantForm.ts +12 -1
  281. package/frontend-vue/layers/dms-ui/app/build/composables/live/currentPageLive.ts +41 -3
  282. package/frontend-vue/layers/dms-ui/app/build/composables/live/livePoller.ts +10 -2
  283. package/frontend-vue/layers/dms-ui/app/build/composables/live/pageReadClock.ts +28 -0
  284. package/frontend-vue/layers/dms-ui/app/build/composables/live/shortcuts.ts +1 -1
  285. package/frontend-vue/layers/dms-ui/app/build/composables/live/usePageLiveHost.ts +17 -3
  286. package/frontend-vue/layers/dms-ui/app/build/composables/live/useUpdatedLabel.ts +38 -0
  287. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/pageToolbarContext.ts +75 -0
  288. package/frontend-vue/layers/dms-ui/app/build/composables/page-toolbar/toolbarControlHeight.ts +29 -0
  289. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodComparison.ts +52 -0
  290. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +21 -0
  291. package/frontend-vue/layers/dms-ui/app/build/composables/period/periodShortLabels.ts +28 -0
  292. package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodDraft.ts +1 -0
  293. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRelationFilterLabels.ts +73 -0
  294. package/frontend-vue/layers/dms-ui/app/build/composables/table/useRowScrollAnchor.ts +106 -0
  295. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +9 -0
  296. package/frontend-vue/layers/dms-ui/app/build/composables/table/useTableColumns.ts +1 -2
  297. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/archivedCount.ts +31 -0
  298. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/filterEditorValue.ts +38 -0
  299. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/formatFilterValue.ts +33 -2
  300. package/frontend-vue/layers/dms-ui/app/build/composables/table/utils/scrollEdges.ts +31 -0
  301. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useCardFields.ts +33 -0
  302. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useMobileDisplay.ts +48 -0
  303. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useScrolledPastTop.ts +41 -18
  304. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableFooter.ts +3 -2
  305. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLinkedVisit.ts +78 -0
  306. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableLiveHold.ts +135 -22
  307. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +56 -25
  308. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +77 -32
  309. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViews.ts +86 -10
  310. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/bulkActions.ts +14 -4
  311. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/card.ts +127 -2
  312. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/compareValues.ts +13 -0
  313. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/expandedRows.ts +28 -0
  314. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/groupedRows.ts +114 -1
  315. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/linkedArrival.ts +308 -0
  316. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/locateRow.ts +106 -0
  317. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pageArrival.ts +34 -0
  318. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/pinnedRows.ts +17 -0
  319. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/quickFilters.ts +41 -33
  320. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/recordLink.ts +24 -8
  321. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableCounts.ts +113 -0
  322. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/tableQuery.ts +27 -4
  323. package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/views.ts +22 -6
  324. package/frontend-vue/layers/dms-ui/app/build/utils/addressSuggestions.ts +49 -0
  325. package/frontend-vue/layers/dms-ui/app/build/utils/eyebrow.ts +12 -0
  326. package/frontend-vue/layers/dms-ui/app/build/utils/fieldOptionsI18n.ts +51 -11
  327. package/frontend-vue/layers/dms-ui/app/build/utils/inputWidth.ts +53 -0
  328. package/frontend-vue/layers/dms-ui/app/build/utils/numberAdjustment.ts +6 -3
  329. package/frontend-vue/layers/dms-ui/app/build/utils/relativeDate.ts +19 -5
  330. package/frontend-vue/layers/dms-ui/app/build/utils/scrollWithin.ts +99 -0
  331. package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +36 -14
  332. package/frontend-vue/layers/dms-ui/app/components/PasswordStrength.vue +1 -1
  333. package/frontend-vue/layers/dms-ui/app/components/RecursiveComponent.vue +9 -1
  334. package/frontend-vue/layers/dms-ui/app/components/blocks/LiveToggleBlock.vue +80 -32
  335. package/frontend-vue/layers/dms-ui/app/components/blocks/PageToolbarBlock.vue +137 -0
  336. package/frontend-vue/layers/dms-ui/app/components/blocks/StatGroupBlock.vue +5 -0
  337. package/frontend-vue/layers/dms-ui/app/components/chart/ApexChartHost.vue +16 -2
  338. package/frontend-vue/layers/dms-ui/app/components/chart/Chart.vue +24 -5
  339. package/frontend-vue/layers/dms-ui/app/components/chart/ChartCard.vue +41 -23
  340. package/frontend-vue/layers/dms-ui/app/components/confirm/ConfirmModal.vue +1 -1
  341. package/frontend-vue/layers/dms-ui/app/components/container-footer/ContainerFooter.vue +34 -0
  342. package/frontend-vue/layers/dms-ui/app/components/field-row/FieldRow.vue +25 -2
  343. package/frontend-vue/layers/dms-ui/app/components/flow-canvas/FlowCanvas.vue +2 -2
  344. package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +118 -36
  345. package/frontend-vue/layers/dms-ui/app/components/form/components/DatePicker.vue +20 -1
  346. package/frontend-vue/layers/dms-ui/app/components/form/components/Display.vue +13 -1
  347. package/frontend-vue/layers/dms-ui/app/components/form/components/Image.vue +8 -3
  348. package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +53 -5
  349. package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +29 -5
  350. package/frontend-vue/layers/dms-ui/app/components/form/components/InputNumber.vue +45 -8
  351. package/frontend-vue/layers/dms-ui/app/components/form/components/InputPercentage.vue +28 -5
  352. package/frontend-vue/layers/dms-ui/app/components/form/components/InputSecret.vue +5 -0
  353. package/frontend-vue/layers/dms-ui/app/components/form/components/InputText.vue +19 -2
  354. package/frontend-vue/layers/dms-ui/app/components/form/components/InputTime.vue +2 -1
  355. package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +205 -65
  356. package/frontend-vue/layers/dms-ui/app/components/form/components/Repeater.vue +80 -30
  357. package/frontend-vue/layers/dms-ui/app/components/form/components/RepeaterTable.vue +203 -0
  358. package/frontend-vue/layers/dms-ui/app/components/form/components/Select.vue +4 -2
  359. package/frontend-vue/layers/dms-ui/app/components/grid/GridRow.vue +11 -2
  360. package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +79 -70
  361. package/frontend-vue/layers/dms-ui/app/components/kpi/KpiCard.vue +117 -21
  362. package/frontend-vue/layers/dms-ui/app/components/meter/Meter.vue +2 -1
  363. package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +113 -34
  364. package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +14 -2
  365. package/frontend-vue/layers/dms-ui/app/components/section-header/Eyebrow.vue +6 -1
  366. package/frontend-vue/layers/dms-ui/app/components/segmented/Segmented.vue +13 -0
  367. package/frontend-vue/layers/dms-ui/app/components/stat-group/StatGroup.vue +52 -3
  368. package/frontend-vue/layers/dms-ui/app/components/tab/Tab.vue +2 -1
  369. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanBoard.vue +45 -35
  370. package/frontend-vue/layers/dms-ui/app/components/table-view/KanbanDisplay.vue +4 -1
  371. package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +410 -96
  372. package/frontend-vue/layers/dms-ui/app/components/top-list/TopListCard.vue +33 -5
  373. package/frontend-vue/layers/dms-ui/app/composables/chart/apexCategoryLabels.ts +68 -0
  374. package/frontend-vue/layers/dms-ui/app/composables/chart/apexDatetimeBuckets.ts +306 -0
  375. package/frontend-vue/layers/dms-ui/app/composables/chart/formatValue.ts +4 -3
  376. package/frontend-vue/layers/dms-ui/app/composables/chart/heatmapColors.ts +93 -0
  377. package/frontend-vue/layers/dms-ui/app/composables/chart/localizeSeries.ts +62 -0
  378. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.ts +116 -8
  379. package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.types.ts +7 -0
  380. package/frontend-vue/layers/dms-ui/app/composables/containers/types.ts +84 -1
  381. package/frontend-vue/layers/dms-ui/app/composables/containers/useDrawer.ts +69 -8
  382. package/frontend-vue/layers/dms-ui/app/composables/form/types/field-saved.ts +11 -0
  383. package/frontend-vue/layers/dms-ui/app/composables/form/types/field.ts +4 -0
  384. package/frontend-vue/layers/dms-ui/app/composables/form/types/functions.ts +1 -0
  385. package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +142 -21
  386. package/frontend-vue/layers/dms-ui/app/composables/live/usePageLive.ts +11 -4
  387. package/frontend-vue/layers/dms-ui/app/composables/page-toolbar/usePageToolbarSize.ts +30 -0
  388. package/frontend-vue/layers/dms-ui/app/composables/table-view/kanban.ts +4 -2
  389. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +22 -3
  390. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +4 -0
  391. package/frontend-vue/layers/dms-ui/app/composables/table-view/types/display.ts +24 -1
  392. package/frontend-vue/layers/dms-ui/app/plugins/closing-layers.client.ts +10 -0
  393. package/frontend-vue/layers/dms-ui/app/plugins/live-pause-source.ts +15 -0
  394. package/frontend-vue/layers/dms-ui/app/plugins/register.ts +2 -0
  395. package/frontend-vue/layers/dms-ui/app/types/input-number.ts +3 -2
  396. package/frontend-vue/layers/dms-ui/app/types/page-toolbar.ts +7 -0
  397. package/frontend-vue/layers/dms-ui/app/types/row-action.ts +0 -2
  398. package/frontend-vue/layers/dms-ui/app/types/row-navigation.ts +17 -0
  399. package/frontend-vue/layers/dms-ui/app/types/table/query.ts +2 -1
  400. package/frontend-vue/layers/dms-ui/app/utils/blockRefresh.ts +3 -1
  401. package/frontend-vue/layers/dms-ui/app/utils/componentTree.ts +24 -0
  402. package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +16 -4
  403. package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +16 -4
  404. package/frontend-vue/package.json +2 -2
  405. package/package.json +7 -6
  406. package/skills/dms/SKILL.md +1 -1
  407. package/skills/dms-auth/SKILL.md +5 -3
  408. package/skills/dms-best-practices/REFERENCE.md +2 -0
  409. package/skills/dms-best-practices/SKILL.md +4 -0
  410. package/skills/dms-module/SKILL.md +3 -2
  411. package/skills/dms-pages/REFERENCE.md +125 -16
  412. package/skills/dms-pages/SKILL.md +1 -1
  413. package/skills/dms-project/DEV-SERVERS.md +23 -1
  414. package/skills/dms-project/REFERENCE.md +1 -1
  415. package/skills/dms-project/SKILL.md +4 -1
  416. package/skills/dms-project/scripts/dms-dev.sh +205 -17
  417. package/dist/routes/startup-gate.d.ts +0 -7
  418. package/dist/routes/startup-gate.js +0 -39
  419. package/dist/routes/startup-gate.js.map +0 -1
  420. package/frontend-vue/layers/dms-core/app/composables/period/periodRenderNow.ts +0 -42
@@ -20,8 +20,6 @@ const TIME_UNITS = {
20
20
  ms: 1,
21
21
  } as const;
22
22
 
23
- const TIME_UNIT_ORDER = ["y", "M", "w", "d", "h", "m", "s", "ms"] as const;
24
-
25
23
  type TranslateFunction = (
26
24
  key: string,
27
25
  params?: Record<string, unknown>,
@@ -229,9 +227,56 @@ export function formatDateTime(
229
227
  });
230
228
  }
231
229
 
230
+ // The narrow no-break space French (and a few other locales) groups digits
231
+ // with: the interface's font draws it a fraction of a digit wide, so "1 234"
232
+ // read as "1234" beside a column's "12 345". Groups take a full no-break space.
233
+ const NARROW_GROUP_SEPARATOR = " ";
234
+ const GROUP_SEPARATOR = " ";
235
+
236
+ /** `Intl.NumberFormat`, its digit groups always shown and visible. */
237
+ export interface ReadableNumberFormat {
238
+ format(value: number): string;
239
+ formatToParts(value: number): Intl.NumberFormatPart[];
240
+ resolvedOptions(): Intl.ResolvedNumberFormatOptions;
241
+ }
242
+
243
+ function readablePart(part: Intl.NumberFormatPart): Intl.NumberFormatPart {
244
+ return part.type === "group" && part.value === NARROW_GROUP_SEPARATOR
245
+ ? { ...part, value: GROUP_SEPARATOR }
246
+ : part;
247
+ }
248
+
249
+ /**
250
+ * `new Intl.NumberFormat(locale, options)` the way the interface writes a
251
+ * figure: a four-digit one grouped like a longer one ("1 234" next to
252
+ * "12 345", where Spanish or Polish would leave "1234" by default), with a
253
+ * group separator wide enough to see. `options.useGrouping` still decides.
254
+ * Throws as `Intl.NumberFormat` does on options it refuses.
255
+ */
256
+ export function readableNumberFormat(
257
+ locale?: string,
258
+ options?: Intl.NumberFormatOptions,
259
+ ): ReadableNumberFormat {
260
+ const format = new Intl.NumberFormat(locale, {
261
+ useGrouping: true,
262
+ ...options,
263
+ });
264
+ const formatToParts = (value: number) =>
265
+ format.formatToParts(value).map(readablePart);
266
+ return {
267
+ format: (value) =>
268
+ formatToParts(value)
269
+ .map((part) => part.value)
270
+ .join(""),
271
+ formatToParts,
272
+ resolvedOptions: () => format.resolvedOptions(),
273
+ };
274
+ }
275
+
232
276
  /**
233
- * A number written for `locale`; a value that is not a number is returned as
234
- * is. Options Intl refuses (an unknown currency) write the bare number.
277
+ * A number written for `locale` (see {@link readableNumberFormat}); a value
278
+ * that is not a number is returned as is. Options Intl refuses (an unknown
279
+ * currency) write the bare number.
235
280
  */
236
281
  export function formatNumber(
237
282
  value: number,
@@ -250,7 +295,7 @@ export function formatNumber(
250
295
  ): unknown {
251
296
  if (!isNumber(value)) return value;
252
297
  try {
253
- return new Intl.NumberFormat(locale, options).format(value);
298
+ return readableNumberFormat(locale, options).format(value);
254
299
  } catch {
255
300
  return String(value);
256
301
  }
@@ -312,16 +357,19 @@ const TIME_SPAN_UNITS: Record<string, keyof typeof TIME_UNITS> = {
312
357
  w: "w",
313
358
  d: "d",
314
359
  h: "h",
360
+ min: "m",
315
361
  m: "m",
316
362
  s: "s",
317
363
  ms: "ms",
318
364
  };
319
365
 
320
- // `01:30`, `26:00:30`: hours, then minutes and seconds under 60.
366
+ // `01:30`, `8:15`, `26:00:30`: hours, then minutes and seconds under 60.
321
367
  const CLOCK_SPAN = /^(\d+):([0-5]\d)(?::([0-5]\d))?$/;
322
- // `1h30m`, `1h 30m`, `1h:30m` (what `formatTimeSpan` writes), `1.5h`.
323
- const UNIT_SPAN = /^(?:\d+(?:\.\d+)?(?:ms|mo|[Mmwydhs])[\s:]*)+$/;
324
- const UNIT_SPAN_PART = /(\d+(?:\.\d+)?)(ms|mo|[Mmwydhs])/g;
368
+ // `8h15`, `8 h 05`: hours, then bare minutes, the way a time is often written.
369
+ const HOUR_MINUTE_SPAN = /^(\d+)\s*h\s*([0-5]?\d)$/i;
370
+ // `1h30m`, `1h 30m`, `7 h 30 min`, `1h:30m` (older displays), `1.5h`.
371
+ const UNIT_SPAN = /^(?:\d+(?:[.,]\d+)?\s*(?:ms|mo|min|[Mmwydhs])[\s:]*)+$/;
372
+ const UNIT_SPAN_PART = /(\d+(?:[.,]\d+)?)\s*(ms|mo|min|[Mmwydhs])/g;
325
373
 
326
374
  function readClockSpan(text: string): number | undefined {
327
375
  const match = CLOCK_SPAN.exec(text);
@@ -334,24 +382,38 @@ function readClockSpan(text: string): number | undefined {
334
382
  );
335
383
  }
336
384
 
385
+ function readHourMinuteSpan(text: string): number | undefined {
386
+ const match = HOUR_MINUTE_SPAN.exec(text);
387
+ if (!match) return undefined;
388
+ const [, hours, minutes] = match;
389
+ return Number(hours) * TIME_UNITS.h + Number(minutes) * TIME_UNITS.m;
390
+ }
391
+
337
392
  function readUnitSpan(text: string): number | undefined {
338
393
  if (!UNIT_SPAN.test(text)) return undefined;
339
394
  let total = 0;
340
395
  for (const [, amount, unit] of text.matchAll(UNIT_SPAN_PART)) {
341
- total += Number.parseFloat(amount!) * TIME_UNITS[TIME_SPAN_UNITS[unit!]!];
396
+ const value = Number.parseFloat(amount!.replace(",", "."));
397
+ total += value * TIME_UNITS[TIME_SPAN_UNITS[unit!]!];
342
398
  }
343
399
  return total;
344
400
  }
345
401
 
346
402
  /**
347
- * A duration typed in, in milliseconds: a clock duration (`01:30`,
348
- * `26:00:30`) or units (`1h30m`, `1h 30m`, `1.5h`), or `undefined` for a
349
- * text that is neither.
403
+ * A duration typed in, in milliseconds: a clock duration (`01:30`, `8:15`,
404
+ * `26:00:30`), hours then minutes (`8h15`, `8h`) or units (`1h30m`,
405
+ * `1h 30m`, `7 h 30 min`, `1.5h`), a `-` before any of them for a negative
406
+ * one, or `undefined` for a text that is neither. It reads all
407
+ * {@link formatDuration} writes.
350
408
  */
351
409
  export function readTimeSpan(value: string): number | undefined {
352
- const text = value.trim();
410
+ const trimmed = value.trim();
411
+ const isNegative = trimmed.startsWith("-");
412
+ const text = isNegative ? trimmed.slice(1).trimStart() : trimmed;
353
413
  if (!text) return undefined;
354
- return readClockSpan(text) ?? readUnitSpan(text);
414
+ const span =
415
+ readClockSpan(text) ?? readHourMinuteSpan(text) ?? readUnitSpan(text);
416
+ return span !== undefined && isNegative ? -span : span;
355
417
  }
356
418
 
357
419
  export function parseTimeSpan(value: string | number): number {
@@ -359,30 +421,99 @@ export function parseTimeSpan(value: string | number): number {
359
421
  return readTimeSpan(value) ?? 0;
360
422
  }
361
423
 
362
- const DEFAULT_SEPARATOR = ":";
363
- const ZERO_MS = "0ms";
424
+ // Durations count in hours at most: a day or a month is a calendar span, not
425
+ // a number of hours.
426
+ const DURATION_UNITS = [
427
+ { symbol: "h", ms: TIME_UNITS.h, fractionDigits: 0 },
428
+ { symbol: "min", ms: TIME_UNITS.m, fractionDigits: 0 },
429
+ // The seconds carry the milliseconds, so nothing is rounded away.
430
+ { symbol: "s", ms: TIME_UNITS.s, fractionDigits: 3 },
431
+ ] as const;
432
+ const MS_FRACTION_DIGITS = 3;
364
433
 
365
- export function formatTimeSpan(
366
- ms: unknown,
367
- separator: string = DEFAULT_SEPARATOR,
368
- ): string {
369
- if (!isNumber(ms)) return String(ms);
434
+ /**
435
+ * How exact a written duration is: `exact` (the default) keeps every
436
+ * millisecond, so a field shows the value it holds and {@link readTimeSpan}
437
+ * reads it back; `rounded` keeps two units at most, the smaller one rounded
438
+ * ("2.1 s", "4 min 12 s", "7 h 30 min"), for a cell read at a glance.
439
+ */
440
+ export type DurationPrecision = "exact" | "rounded";
441
+
442
+ export interface DurationFormatOptions {
443
+ precision?: DurationPrecision;
444
+ }
370
445
 
371
- const totalMs = Number(ms);
372
- if (totalMs === 0) return ZERO_MS;
446
+ const ROUNDED_SECONDS_FRACTION_DIGITS = 1;
373
447
 
448
+ function durationFigure(
449
+ locale: string,
450
+ value: number,
451
+ symbol: string,
452
+ fractionDigits: number,
453
+ ): string {
454
+ const figure = readableNumberFormat(locale, {
455
+ maximumFractionDigits: fractionDigits,
456
+ useGrouping: false,
457
+ numberingSystem: "latn",
458
+ }).format(value);
459
+ return `${figure} ${symbol}`;
460
+ }
461
+
462
+ function exactDuration(ms: number, locale: string): string {
463
+ let remaining = ms;
464
+ if (remaining < TIME_UNITS.s) {
465
+ return durationFigure(locale, remaining, "ms", MS_FRACTION_DIGITS);
466
+ }
374
467
  const parts: string[] = [];
375
- let remaining = Math.abs(totalMs);
376
-
377
- for (const unit of TIME_UNIT_ORDER) {
378
- const unitValue = TIME_UNITS[unit];
379
- if (remaining >= unitValue) {
380
- const value = Math.floor(remaining / unitValue);
381
- parts.push(`${value}${unit}`);
382
- remaining %= unitValue;
383
- }
468
+ for (const { symbol, ms: unitMs, fractionDigits } of DURATION_UNITS) {
469
+ const amount = fractionDigits
470
+ ? remaining / unitMs
471
+ : Math.floor(remaining / unitMs);
472
+ remaining -= amount * unitMs;
473
+ if (amount)
474
+ parts.push(durationFigure(locale, amount, symbol, fractionDigits));
384
475
  }
476
+ return parts.join(" ");
477
+ }
478
+
479
+ function roundedDuration(ms: number, locale: string): string {
480
+ if (ms < TIME_UNITS.s) return durationFigure(locale, Math.round(ms), "ms", 0);
481
+ if (ms < TIME_UNITS.m) {
482
+ return durationFigure(
483
+ locale,
484
+ ms / TIME_UNITS.s,
485
+ "s",
486
+ ROUNDED_SECONDS_FRACTION_DIGITS,
487
+ );
488
+ }
489
+ const [major, majorSymbol, minor, minorSymbol] =
490
+ ms < TIME_UNITS.h
491
+ ? ([TIME_UNITS.m, "min", TIME_UNITS.s, "s"] as const)
492
+ : ([TIME_UNITS.h, "h", TIME_UNITS.m, "min"] as const);
493
+ // Rounded as a whole first, so 1 min 59.7 s reads "2 min", not "1 min 60 s".
494
+ const total = Math.round(ms / minor) * minor;
495
+ const whole = Math.floor(total / major);
496
+ const rest = (total - whole * major) / minor;
497
+ const head = durationFigure(locale, whole, majorSymbol, 0);
498
+ return rest
499
+ ? `${head} ${durationFigure(locale, rest, minorSymbol, 0)}`
500
+ : head;
501
+ }
385
502
 
386
- const formatted = parts.join(separator);
387
- return totalMs < 0 ? `-${formatted}` : formatted;
503
+ /**
504
+ * A duration in milliseconds the way people write it, its figures in the
505
+ * reader's locale: "910 ms", "2.149 s", "4 min 12 s", "7 h 30 min". The one
506
+ * writer of a duration, for a field and a cell alike. Exact by default (hours
507
+ * and minutes whole, the seconds carrying the milliseconds), its figures
508
+ * neither grouped nor in another script, so {@link readTimeSpan} reads back
509
+ * the very value it wrote; `precision: "rounded"` for a cell.
510
+ */
511
+ export function formatDuration(
512
+ ms: number,
513
+ locale: string,
514
+ { precision = "exact" }: DurationFormatOptions = {},
515
+ ): string {
516
+ const sign = ms < 0 ? "-" : "";
517
+ const write = precision === "rounded" ? roundedDuration : exactDuration;
518
+ return sign + write(Math.abs(ms), locale);
388
519
  }
@@ -12,8 +12,25 @@ const WHOLE_ROW_TOKEN = new RegExp(`^\\s*${ROW_TOKEN.source}\\s*$`);
12
12
  export const namesRowField = (template: string): boolean =>
13
13
  new RegExp(ROW_TOKEN.source).test(template);
14
14
 
15
+ // A relation field is listed as the related row (a join): it stands for that
16
+ // row's id, as the field stores it, rather than "[object Object]". A dotted
17
+ // path (`{{row.customer.name}}`) still reaches into the row.
18
+ const RELATED_ROW_ID_KEYS = ["_id", "id"] as const;
19
+
20
+ const isPlainObject = (value: unknown): value is Record<string, unknown> =>
21
+ Object.prototype.toString.call(value) === "[object Object]";
22
+
23
+ const relatedRowId = (value: Record<string, unknown>): unknown =>
24
+ RELATED_ROW_ID_KEYS.map((key) => value[key]).find(
25
+ (id) => id !== undefined && id !== null,
26
+ );
27
+
28
+ const scalarOf = (value: unknown): unknown =>
29
+ isPlainObject(value) ? relatedRowId(value) : value;
30
+
15
31
  const rowValue = (data: Record<string, unknown>, key: string): string => {
16
- const value: unknown = get(data, key);
32
+ const field: unknown = get(data, key);
33
+ const value = Array.isArray(field) ? field.map(scalarOf) : scalarOf(field);
17
34
  return value === undefined || value === null ? "" : String(value);
18
35
  };
19
36
 
@@ -438,7 +438,8 @@
438
438
  "chart": {
439
439
  "no_data": "No data",
440
440
  "legend_period": "Period",
441
- "legend_comparison": "Comparison"
441
+ "legend_comparison": "Comparison",
442
+ "versus_value": "vs {value}"
442
443
  },
443
444
  "period": {
444
445
  "aria_label": "Period selector",
@@ -463,12 +464,33 @@
463
464
  "all": "All time",
464
465
  "custom": "Custom"
465
466
  },
467
+ "short_presets": {
468
+ "last-hour": "1H",
469
+ "last-24h": "24H",
470
+ "today": "1D",
471
+ "yesterday": "-1D",
472
+ "last-7-days": "7D",
473
+ "last-30-days": "30D",
474
+ "last-90-days": "90D",
475
+ "this-month": "MTD",
476
+ "last-month": "-1M",
477
+ "this-quarter": "QTD",
478
+ "last-quarter": "-1Q",
479
+ "ytd": "YTD",
480
+ "last-year": "-1Y",
481
+ "all": "ALL"
482
+ },
466
483
  "comparisons": {
467
484
  "none": "No comparison",
468
485
  "previous-period": "vs previous period",
469
486
  "previous-year": "vs previous year",
470
487
  "custom": "Custom"
471
488
  },
489
+ "compare_labels": {
490
+ "previous-period": "vs previous period",
491
+ "previous-year": "vs previous year",
492
+ "custom": "vs comparison period"
493
+ },
472
494
  "compare_options": {
473
495
  "none": "No comparison",
474
496
  "previous-period": "Previous period",
@@ -494,7 +516,117 @@
494
516
  },
495
517
  "a11y": {
496
518
  "breadcrumb": "Breadcrumb",
497
- "loading": "Loading"
519
+ "close": "Close",
520
+ "loading": "Loading",
521
+ "show_options": "Show options"
522
+ },
523
+ "automation": {
524
+ "triggers": {
525
+ "memberAdded": {
526
+ "name": "Member added",
527
+ "description": "Fires when a user becomes a member of a workspace"
528
+ },
529
+ "memberRemoved": {
530
+ "name": "Member removed",
531
+ "description": "Fires when members are removed from a workspace"
532
+ },
533
+ "inviteCreated": {
534
+ "name": "Invite created",
535
+ "description": "Fires when a user invite is created on a workspace"
536
+ },
537
+ "userRegistered": {
538
+ "name": "User registered",
539
+ "description": "Fires when a new user completes signup"
540
+ },
541
+ "recordCreated": {
542
+ "name": "Record created",
543
+ "description": "Fires when rows are created in a TableView-backed table"
544
+ },
545
+ "recordUpdated": {
546
+ "name": "Record updated",
547
+ "description": "Fires when rows are updated in a TableView-backed table"
548
+ },
549
+ "recordDeleted": {
550
+ "name": "Record deleted",
551
+ "description": "Fires when rows are deleted in a TableView-backed table"
552
+ },
553
+ "record": {
554
+ "fields": {
555
+ "location": "Data controller location to watch, e.g. \"/api/tables/members\". Leave empty to fire for every table."
556
+ }
557
+ }
558
+ },
559
+ "actions": {
560
+ "sendEmail": {
561
+ "name": "Send email",
562
+ "description": "Send an HTML email through the configured email provider, written out or rendered from a registered HTML template",
563
+ "fields": {
564
+ "template": "Name of a registered HTML template to render instead of the HTML",
565
+ "props": "Props the template is rendered with",
566
+ "language": "Language the template is rendered in; defaults to the recipient's when the address belongs to a user"
567
+ }
568
+ },
569
+ "sendNotification": {
570
+ "name": "Send notification",
571
+ "description": "Send an in-app notification to specific users and/or every user holding one of the given roles",
572
+ "fields": {
573
+ "userId": "One recipient, e.g. a trigger's user ID",
574
+ "tenantId": "Workspace whose members receive the role notification; leave empty for the default workspace",
575
+ "title": "Literal text, or a \"$\" translation key",
576
+ "description": "Literal text, or a \"$\" translation key",
577
+ "params": "Interpolation params of the title and description translations",
578
+ "idempotencyKey": "Stable event key, e.g. a trigger's delivery ID: a replayed event is delivered once"
579
+ }
580
+ },
581
+ "inviteUser": {
582
+ "name": "Invite user",
583
+ "description": "Invite an email address to a workspace: existing users are added as members directly, otherwise an invite is created (and its signup email sent unless Send the email is off)",
584
+ "fields": {
585
+ "tenantId": "Workspace to invite into; defaults to the default workspace",
586
+ "language": "Language of the invitation email and of the account it creates; defaults to en",
587
+ "inviterName": "Who the invitation email says it comes from"
588
+ }
589
+ }
590
+ },
591
+ "fields": {
592
+ "actorId": "Actor ID",
593
+ "actorName": "Actor name",
594
+ "asTenantOwner": "Workspace owner",
595
+ "at": "Date",
596
+ "deliveryId": "Delivery ID",
597
+ "description": "Description",
598
+ "email": "Email",
599
+ "error": "Error",
600
+ "firstname": "First name",
601
+ "html": "HTML",
602
+ "icon": "Icon",
603
+ "idempotencyKey": "Replay key",
604
+ "ids": "Record IDs",
605
+ "inviteId": "Invite ID",
606
+ "inviterName": "Inviter name",
607
+ "isTenantOwner": "Workspace owner",
608
+ "kind": "Result",
609
+ "language": "Language",
610
+ "lastname": "Last name",
611
+ "linkTo": "Link",
612
+ "location": "Table",
613
+ "messageId": "Message ID",
614
+ "name": "Name",
615
+ "ok": "Sent",
616
+ "params": "Parameters",
617
+ "props": "Template props",
618
+ "roleIds": "Roles",
619
+ "sendEmail": "Send the email",
620
+ "subject": "Subject",
621
+ "success": "Success",
622
+ "template": "Template",
623
+ "tenantId": "Workspace",
624
+ "title": "Title",
625
+ "to": "To",
626
+ "tone": "Tone",
627
+ "userId": "User",
628
+ "userIds": "Users"
629
+ }
498
630
  }
499
631
  }
500
632
  }
@@ -438,7 +438,8 @@
438
438
  "chart": {
439
439
  "no_data": "Aucune donnée",
440
440
  "legend_period": "Période",
441
- "legend_comparison": "Comparaison"
441
+ "legend_comparison": "Comparaison",
442
+ "versus_value": "vs {value}"
442
443
  },
443
444
  "period": {
444
445
  "aria_label": "Sélecteur de période",
@@ -463,12 +464,33 @@
463
464
  "all": "Toute la période",
464
465
  "custom": "Personnalisé"
465
466
  },
467
+ "short_presets": {
468
+ "last-hour": "1 h",
469
+ "last-24h": "24 h",
470
+ "today": "Auj.",
471
+ "yesterday": "Hier",
472
+ "last-7-days": "7 j",
473
+ "last-30-days": "30 j",
474
+ "last-90-days": "90 j",
475
+ "this-month": "Mois",
476
+ "last-month": "M-1",
477
+ "this-quarter": "Trim.",
478
+ "last-quarter": "T-1",
479
+ "ytd": "Année",
480
+ "last-year": "A-1",
481
+ "all": "Tout"
482
+ },
466
483
  "comparisons": {
467
484
  "none": "Sans comparaison",
468
485
  "previous-period": "vs période précédente",
469
486
  "previous-year": "vs année précédente",
470
487
  "custom": "Personnalisé"
471
488
  },
489
+ "compare_labels": {
490
+ "previous-period": "vs période précédente",
491
+ "previous-year": "vs année précédente",
492
+ "custom": "vs période de comparaison"
493
+ },
472
494
  "compare_options": {
473
495
  "none": "Aucune comparaison",
474
496
  "previous-period": "Période précédente",
@@ -494,7 +516,117 @@
494
516
  },
495
517
  "a11y": {
496
518
  "breadcrumb": "Fil d'Ariane",
497
- "loading": "Chargement"
519
+ "close": "Fermer",
520
+ "loading": "Chargement",
521
+ "show_options": "Afficher les options"
522
+ },
523
+ "automation": {
524
+ "triggers": {
525
+ "memberAdded": {
526
+ "name": "Membre ajouté",
527
+ "description": "Se déclenche quand un utilisateur devient membre d'un espace de travail"
528
+ },
529
+ "memberRemoved": {
530
+ "name": "Membre retiré",
531
+ "description": "Se déclenche quand des membres sont retirés d'un espace de travail"
532
+ },
533
+ "inviteCreated": {
534
+ "name": "Invitation créée",
535
+ "description": "Se déclenche quand une invitation est créée dans un espace de travail"
536
+ },
537
+ "userRegistered": {
538
+ "name": "Utilisateur inscrit",
539
+ "description": "Se déclenche quand un nouvel utilisateur termine son inscription"
540
+ },
541
+ "recordCreated": {
542
+ "name": "Enregistrement créé",
543
+ "description": "Se déclenche quand des lignes sont créées dans une table adossée à une TableView"
544
+ },
545
+ "recordUpdated": {
546
+ "name": "Enregistrement modifié",
547
+ "description": "Se déclenche quand des lignes sont modifiées dans une table adossée à une TableView"
548
+ },
549
+ "recordDeleted": {
550
+ "name": "Enregistrement supprimé",
551
+ "description": "Se déclenche quand des lignes sont supprimées dans une table adossée à une TableView"
552
+ },
553
+ "record": {
554
+ "fields": {
555
+ "location": "Emplacement du contrôleur de données à surveiller, par ex. \"/api/tables/members\". Laissez vide pour réagir à toutes les tables."
556
+ }
557
+ }
558
+ },
559
+ "actions": {
560
+ "sendEmail": {
561
+ "name": "Envoyer un e-mail",
562
+ "description": "Envoie un e-mail HTML via le fournisseur d'e-mail configuré, rédigé tel quel ou généré à partir d'un modèle HTML enregistré",
563
+ "fields": {
564
+ "template": "Nom d'un modèle HTML enregistré à générer à la place du HTML",
565
+ "props": "Propriétés avec lesquelles le modèle est généré",
566
+ "language": "Langue dans laquelle le modèle est généré ; par défaut celle du destinataire quand l'adresse appartient à un utilisateur"
567
+ }
568
+ },
569
+ "sendNotification": {
570
+ "name": "Envoyer une notification",
571
+ "description": "Envoie une notification dans l'application à des utilisateurs précis et/ou à tous les utilisateurs ayant l'un des rôles indiqués",
572
+ "fields": {
573
+ "userId": "Un destinataire, par ex. l'ID utilisateur d'un déclencheur",
574
+ "tenantId": "Espace de travail dont les membres reçoivent la notification par rôle ; laissez vide pour l'espace par défaut",
575
+ "title": "Texte littéral, ou clé de traduction \"$\"",
576
+ "description": "Texte littéral, ou clé de traduction \"$\"",
577
+ "params": "Paramètres d'interpolation des traductions du titre et de la description",
578
+ "idempotencyKey": "Clé stable de l'événement, par ex. l'ID de livraison d'un déclencheur : un événement rejoué n'est livré qu'une fois"
579
+ }
580
+ },
581
+ "inviteUser": {
582
+ "name": "Inviter un utilisateur",
583
+ "description": "Invite une adresse e-mail dans un espace de travail : un utilisateur existant y est ajouté directement comme membre, sinon une invitation est créée (et son e-mail d'inscription envoyé, sauf si Envoyer l'e-mail est désactivé)",
584
+ "fields": {
585
+ "tenantId": "Espace de travail où inviter ; par défaut l'espace par défaut",
586
+ "language": "Langue de l'e-mail d'invitation et du compte qu'il crée ; en par défaut",
587
+ "inviterName": "Nom de l'expéditeur indiqué dans l'e-mail d'invitation"
588
+ }
589
+ }
590
+ },
591
+ "fields": {
592
+ "actorId": "ID de l'auteur",
593
+ "actorName": "Nom de l'auteur",
594
+ "asTenantOwner": "Propriétaire de l'espace",
595
+ "at": "Date",
596
+ "deliveryId": "ID de livraison",
597
+ "description": "Description",
598
+ "email": "E-mail",
599
+ "error": "Erreur",
600
+ "firstname": "Prénom",
601
+ "html": "HTML",
602
+ "icon": "Icône",
603
+ "idempotencyKey": "Clé de rejeu",
604
+ "ids": "ID des enregistrements",
605
+ "inviteId": "ID de l'invitation",
606
+ "inviterName": "Nom de l'invitant",
607
+ "isTenantOwner": "Propriétaire de l'espace",
608
+ "kind": "Résultat",
609
+ "language": "Langue",
610
+ "lastname": "Nom",
611
+ "linkTo": "Lien",
612
+ "location": "Table",
613
+ "messageId": "ID du message",
614
+ "name": "Nom",
615
+ "ok": "Envoyée",
616
+ "params": "Paramètres",
617
+ "props": "Propriétés du modèle",
618
+ "roleIds": "Rôles",
619
+ "sendEmail": "Envoyer l'e-mail",
620
+ "subject": "Objet",
621
+ "success": "Succès",
622
+ "template": "Modèle",
623
+ "tenantId": "Espace de travail",
624
+ "title": "Titre",
625
+ "to": "Destinataire",
626
+ "tone": "Ton",
627
+ "userId": "Utilisateur",
628
+ "userIds": "Utilisateurs"
629
+ }
498
630
  }
499
631
  }
500
632
  }
@@ -3,8 +3,9 @@ import DmsBlockActions from "#dms-ui/app/build/components/blocks/BlockActions.vu
3
3
  import { useComposedText } from "#dms-core/app/composables/translation/useComposedText";
4
4
 
5
5
  /**
6
- * Stacks the global layout banners under the dashboard header, on every page
7
- * of the dashboard layout. Each banner carries its own live-region role, so a
6
+ * Stacks the global layout banners under the dashboard header, on the pages
7
+ * each banner's scope covers (the application's pages unless it says
8
+ * otherwise). Each banner carries its own live-region role, so a
8
9
  * banner appearing after a navigation is announced as well. A text is a
9
10
  * string or a composed text, written in the reader's language, followed by
10
11
  * the banner's link buttons.