@conduction/nextcloud-vue 2.104.0 → 2.106.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 (224) hide show
  1. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  2. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -1
  4. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  5. package/dist/esm/components/CnCardGrid/CnCardGrid.vue.js +1 -1
  6. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +18 -0
  7. package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
  8. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js +3 -1
  9. package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
  10. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
  11. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +18 -0
  12. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
  13. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +1 -1
  14. package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
  15. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  16. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +93 -1
  17. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  18. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  19. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  20. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +6 -3
  21. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  22. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +3 -3
  23. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  24. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +112 -5
  25. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  26. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  27. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue.js +9 -0
  28. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue.js.map +1 -0
  29. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js +24 -0
  30. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue2.js.map +1 -0
  31. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js +49 -0
  32. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue3.js.map +1 -0
  33. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  34. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +36 -18
  35. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  36. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +17 -38
  37. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  38. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  39. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  40. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  41. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue.js +1 -1
  42. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +1 -1
  43. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
  44. package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
  45. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue.js +1 -1
  46. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js +2 -0
  47. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue2.js.map +1 -1
  48. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js +32 -29
  49. package/dist/esm/components/CnFeaturesBoard/CnFeaturesBoard.vue3.js.map +1 -1
  50. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  51. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +53 -2
  52. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  53. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +119 -111
  54. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  55. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue.js +1 -1
  56. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +62 -1
  57. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
  58. package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
  59. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue.js +1 -1
  60. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js +73 -4
  61. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js.map +1 -1
  62. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js +74 -27
  63. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js.map +1 -1
  64. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +208 -46
  65. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  66. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +121 -98
  67. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  68. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue.js +1 -1
  69. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js +24 -0
  70. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue2.js.map +1 -1
  71. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js +20 -4
  72. package/dist/esm/components/CnIndexPage/CnIndexPageWidgets.vue3.js.map +1 -1
  73. package/dist/esm/components/CnIndexPage/useSelfFetchList.js +25 -6
  74. package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
  75. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js +112 -1
  76. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue2.js.map +1 -1
  77. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js +100 -44
  78. package/dist/esm/components/CnIndexSidebar/CnIndexSidebar.vue3.js.map +1 -1
  79. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue.js +1 -1
  80. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js +67 -4
  81. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue2.js.map +1 -1
  82. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js +1 -1
  83. package/dist/esm/components/CnNcWidgetWidget/CnNcWidgetWidget.vue3.js.map +1 -1
  84. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue.js +9 -0
  85. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue.js.map +1 -0
  86. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue2.js +88 -0
  87. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue2.js.map +1 -0
  88. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue3.js +39 -0
  89. package/dist/esm/components/CnNoticeWidget/CnNoticeWidget.vue3.js.map +1 -0
  90. package/dist/esm/components/CnObjectCard/CnObjectCard.vue.js +1 -1
  91. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +295 -12
  92. package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
  93. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +267 -144
  94. package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
  95. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
  96. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +4 -0
  97. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  98. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +3 -1
  99. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  100. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  101. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +93 -1
  102. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  103. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js +322 -246
  104. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  105. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue.js +1 -1
  106. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js +122 -7
  107. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue2.js.map +1 -1
  108. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js +189 -93
  109. package/dist/esm/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue3.js.map +1 -1
  110. package/dist/esm/components/CnSettingsField/CnSettingsField.vue.js +1 -1
  111. package/dist/esm/components/CnSettingsField/CnSettingsField.vue2.js.map +1 -1
  112. package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js +3 -3
  113. package/dist/esm/components/CnSettingsField/CnSettingsField.vue3.js.map +1 -1
  114. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue.js +1 -1
  115. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue2.js.map +1 -1
  116. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js +16 -4
  117. package/dist/esm/components/CnSetupWizard/CnSetupWizard.vue3.js.map +1 -1
  118. package/dist/esm/components/CnTabs/CnTabs.vue.js +1 -1
  119. package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
  120. package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
  121. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  122. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +42 -0
  123. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  124. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js +38 -19
  125. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  126. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue.js +9 -0
  127. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue.js.map +1 -0
  128. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue2.js +258 -0
  129. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue2.js.map +1 -0
  130. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue3.js +69 -0
  131. package/dist/esm/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue3.js.map +1 -0
  132. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
  133. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js +11 -0
  134. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
  135. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +4 -4
  136. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
  137. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +16 -14
  138. package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
  139. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +12 -0
  140. package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
  141. package/dist/esm/index.js +2 -0
  142. package/dist/esm/index.js.map +1 -1
  143. package/dist/esm/l10n/en.json.js +24 -1
  144. package/dist/esm/l10n/en.json.js.map +1 -1
  145. package/dist/esm/l10n/nl.json.js +24 -1
  146. package/dist/esm/l10n/nl.json.js.map +1 -1
  147. package/dist/esm/library-style.js +1 -1
  148. package/dist/esm/nextcloud-vue.css +1369 -748
  149. package/dist/esm/utils/dueText.js +18 -3
  150. package/dist/esm/utils/dueText.js.map +1 -1
  151. package/dist/esm/utils/libraryWidgetKeys.js +2 -0
  152. package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
  153. package/dist/esm/utils/nextcloudApps.js +45 -0
  154. package/dist/esm/utils/nextcloudApps.js.map +1 -0
  155. package/dist/esm/utils/scopeListLayout.js +9 -2
  156. package/dist/esm/utils/scopeListLayout.js.map +1 -1
  157. package/dist/esm/utils/sortOrder.js +11 -1
  158. package/dist/esm/utils/sortOrder.js.map +1 -1
  159. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  160. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  161. package/dist/esm/utils/visibleWhen.js +12 -4
  162. package/dist/esm/utils/visibleWhen.js.map +1 -1
  163. package/dist/nextcloud-vue.cjs.js +8965 -6497
  164. package/dist/nextcloud-vue.cjs.js.map +2 -2
  165. package/dist/nextcloud-vue.css +1369 -748
  166. package/l10n/en.json +24 -1
  167. package/l10n/nl.json +24 -1
  168. package/package.json +1 -1
  169. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -1
  170. package/src/components/CnCardGrid/CnCardGrid.md +2 -0
  171. package/src/components/CnCardGrid/CnCardGrid.vue +20 -0
  172. package/src/components/CnCellRenderer/CnCellRenderer.vue +19 -1
  173. package/src/components/CnChartWidget/CnChartWidget.md +2 -0
  174. package/src/components/CnChartWidget/CnChartWidget.vue +93 -1
  175. package/src/components/CnDashboardPage/CnDashboardPage.vue +9 -6
  176. package/src/components/CnDetailPage/CnDetailPage.md +3 -2
  177. package/src/components/CnDetailPage/CnDetailPage.vue +112 -5
  178. package/src/components/CnDetailWidgetHost/CnDetailWidgetHeaderItems.vue +60 -0
  179. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +36 -29
  180. package/src/components/CnFactsWidget/CnFactsWidget.vue +4 -0
  181. package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -1
  182. package/src/components/CnFeaturesBoard/CnFeaturesBoard.vue +4 -1
  183. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +70 -4
  184. package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +62 -1
  185. package/src/components/CnFlowsPage/CnFlowBoard.vue +132 -8
  186. package/src/components/CnIndexPage/CnIndexPage.md +8 -5
  187. package/src/components/CnIndexPage/CnIndexPage.vue +236 -54
  188. package/src/components/CnIndexPage/CnIndexPageWidgets.vue +36 -1
  189. package/src/components/CnIndexPage/useSelfFetchList.js +25 -6
  190. package/src/components/CnIndexSidebar/CnIndexSidebar.md +2 -0
  191. package/src/components/CnIndexSidebar/CnIndexSidebar.vue +148 -1
  192. package/src/components/CnNcWidgetWidget/CnNcWidgetWidget.vue +68 -5
  193. package/src/components/CnNoticeWidget/CnNoticeWidget.vue +153 -0
  194. package/src/components/CnNoticeWidget/index.js +12 -0
  195. package/src/components/CnObjectCard/CnObjectCard.md +1 -0
  196. package/src/components/CnObjectCard/CnObjectCard.vue +354 -13
  197. package/src/components/CnObjectSidebar/CnFilesTab.vue +6 -0
  198. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +190 -4
  199. package/src/components/CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue +209 -9
  200. package/src/components/CnSettingsField/CnSettingsField.vue +4 -1
  201. package/src/components/CnSetupWizard/CnSetupWizard.vue +9 -1
  202. package/src/components/CnTabs/CnTabs.vue +11 -0
  203. package/src/components/CnTabsWidget/CnTabsWidget.vue +91 -18
  204. package/src/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.md +11 -0
  205. package/src/components/CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue +382 -0
  206. package/src/components/CnTeamWorkloadWidget/index.js +14 -0
  207. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +15 -4
  208. package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
  209. package/src/components/CnWidgetGrid/registerDashboardWidgets.js +12 -0
  210. package/src/components/index.js +2 -0
  211. package/src/css/dialog.css +20 -2
  212. package/src/css/index-page.css +14 -2
  213. package/src/css/look-board-detail.css +125 -8
  214. package/src/css/look-board-index.css +100 -4
  215. package/src/css/look-board.css +122 -14
  216. package/src/index.js +2 -1
  217. package/src/schemas/app-manifest-v2.schema.json +110 -8
  218. package/src/utils/dueText.js +18 -3
  219. package/src/utils/libraryWidgetKeys.js +2 -0
  220. package/src/utils/nextcloudApps.js +50 -0
  221. package/src/utils/scopeListLayout.js +9 -2
  222. package/src/utils/sortOrder.js +10 -0
  223. package/src/utils/validateManifestV2.compiled.js +3 -3
  224. package/src/utils/visibleWhen.js +12 -4
@@ -164,24 +164,37 @@
164
164
  @selectObject="onSelectObject" />
165
165
  </div>
166
166
  </div>
167
- <CnDetailWidgetHost
168
- v-else-if="entry.widget"
169
- :widget="entry.widget"
170
- chrome="bare"
171
- :availableWidgets="availableWidgets"
172
- :objectId="objectId"
173
- :object="objectData"
174
- :objectType="objectType"
175
- :schemaObject="schemaObject"
176
- :register="register"
177
- :schema="schema"
178
- :store="store"
179
- :surface="surface"
180
- :integrationContext="integrationContext"
181
- :cnRegistry="cnRegistry"
182
- @geoSaved="onGeoSaved"
183
- @openIntegration="onOpenIntegration"
184
- @selectObject="onSelectObject" />
167
+ <template v-else-if="entry.widget">
168
+ <!-- A single widget with a title of its own heads its panel
169
+ (DqZaakDocumenten); a title the tab already shows is not
170
+ repeated. -->
171
+ <div
172
+ v-if="panelHeading(entry)"
173
+ class="cn-tabs-widget__panel-heading"
174
+ data-testid="cn-tabs-widget-panel-heading">
175
+ <h2 class="cn-tabs-widget__panel-title">
176
+ {{ panelHeading(entry).title }}
177
+ </h2>
178
+ <span v-if="panelHeading(entry).subtitle" class="cn-tabs-widget__panel-subtitle">{{ panelHeading(entry).subtitle }}</span>
179
+ </div>
180
+ <CnDetailWidgetHost
181
+ :widget="entry.widget"
182
+ chrome="bare"
183
+ :availableWidgets="availableWidgets"
184
+ :objectId="objectId"
185
+ :object="objectData"
186
+ :objectType="objectType"
187
+ :schemaObject="schemaObject"
188
+ :register="register"
189
+ :schema="schema"
190
+ :store="store"
191
+ :surface="surface"
192
+ :integrationContext="integrationContext"
193
+ :cnRegistry="cnRegistry"
194
+ @geoSaved="onGeoSaved"
195
+ @openIntegration="onOpenIntegration"
196
+ @selectObject="onSelectObject" />
197
+ </template>
185
198
  <CnEmptyContent v-else :name="missingLabel(entry)" />
186
199
  </CnTab>
187
200
  </CnTabs>
@@ -361,6 +374,11 @@ export default {
361
374
  * label written in the manifest reads in the user's language.
362
375
  */
363
376
  cnTranslate: { default: () => (key) => key },
377
+ /**
378
+ * CnDetailPage's report of the open body tab, so its side column can
379
+ * follow the tab (a side entry's `tabs`). Null on any other page.
380
+ */
381
+ cnDetailReportTab: { default: null },
364
382
  },
365
383
 
366
384
  props: {
@@ -804,6 +822,16 @@ export default {
804
822
  this.activeKey = this.resolvedTabs[index]?.key || ''
805
823
  },
806
824
 
825
+ // Tell the detail page which tab is open (side cards per tab).
826
+ 'activeTab.id': {
827
+ immediate: true,
828
+ handler(id) {
829
+ if (typeof this.cnDetailReportTab === 'function') {
830
+ this.cnDetailReportTab(id || '')
831
+ }
832
+ },
833
+ },
834
+
807
835
  // A tab appeared or went. Keep the same tab active; when it is the active
808
836
  // tab that went, the first visible one takes over and the hash follows.
809
837
  visibleKeys() {
@@ -996,6 +1024,33 @@ export default {
996
1024
  return [tab?.widgetId]
997
1025
  },
998
1026
 
1027
+ /**
1028
+ * The title row a single widget's panel draws: the widget's own title
1029
+ * and its description, through the app's translate. None when the
1030
+ * widget has no title or no description, or the tab already shows the
1031
+ * title: a title alone names what the tab already names, so the
1032
+ * widget stays bare in its panel.
1033
+ *
1034
+ * @param {object} entry A resolved tab.
1035
+ * @return {{title: string, subtitle: string}|null} The heading, or null.
1036
+ */
1037
+ panelHeading(entry) {
1038
+ const widget = entry && entry.widget
1039
+ const raw = widgetTitleOf(widget)
1040
+ if (!raw) {
1041
+ return null
1042
+ }
1043
+ const translate = typeof this.cnTranslate === 'function' ? this.cnTranslate : (key) => key
1044
+ const title = translate(raw)
1045
+ if (title === entry.label) {
1046
+ return null
1047
+ }
1048
+ if (!widget || typeof widget.description !== 'string' || widget.description === '') {
1049
+ return null
1050
+ }
1051
+ return { title, subtitle: translate(widget.description) }
1052
+ },
1053
+
999
1054
  /**
1000
1055
  * The label a tab shows: the authored `label`, else (board look) History
1001
1056
  * for the activity widget, else the child widget's title, else its id.
@@ -1254,6 +1309,24 @@ export default {
1254
1309
 
1255
1310
  <style scoped>
1256
1311
  /* A stack (`widgetIds`): the panel's widget, then cards 20px apart. */
1312
+ .cn-tabs-widget__panel-heading {
1313
+ display: flex;
1314
+ flex-direction: column;
1315
+ gap: 4px;
1316
+ margin-bottom: 16px;
1317
+ }
1318
+
1319
+ .cn-tabs-widget__panel-title {
1320
+ margin: 0;
1321
+ font-size: 17px;
1322
+ font-weight: 700;
1323
+ }
1324
+
1325
+ .cn-tabs-widget__panel-subtitle {
1326
+ font-size: 14px;
1327
+ color: var(--color-text-maxcontrast);
1328
+ }
1329
+
1257
1330
  .cn-tabs-widget__stack {
1258
1331
  display: flex;
1259
1332
  flex-direction: column;
@@ -0,0 +1,11 @@
1
+ The team today from an app's workload endpoint, with each person's Nextcloud user status.
2
+
3
+ ```vue
4
+ <CnTeamWorkloadWidget
5
+ :content="{
6
+ source: { url: '/apps/dossiq/api/work-queue/workload', responsePath: 'handlers', userField: 'handler', countField: 'openCaseCount' },
7
+ capacity: 12,
8
+ }" />
9
+ ```
10
+
11
+ Props: `content` holds the source, the capacity and the labels.
@@ -0,0 +1,382 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ SPDX-License-Identifier: EUPL-1.2
4
+ -->
5
+ <template>
6
+ <div class="cn-team-workload" data-testid="cn-team-workload">
7
+ <p v-if="loading && members.length === 0" class="cn-team-workload__state-line" role="status">
8
+ {{ loadingLabel }}
9
+ </p>
10
+ <p v-else-if="members.length === 0" class="cn-team-workload__state-line">
11
+ {{ emptyLabel }}
12
+ </p>
13
+ <ul v-else class="cn-team-workload__list">
14
+ <li
15
+ v-for="member in members"
16
+ :key="member.id"
17
+ class="cn-team-workload__member"
18
+ data-testid="cn-team-workload-member">
19
+ <div class="cn-team-workload__row">
20
+ <span class="cn-team-workload__avatar" aria-hidden="true">{{ member.initials }}</span>
21
+ <span class="cn-team-workload__who">
22
+ <strong class="cn-team-workload__name">{{ member.name }}</strong>
23
+ <span
24
+ class="cn-team-workload__state"
25
+ :class="{ 'cn-team-workload__state--away': !member.available }">{{ member.line }}</span>
26
+ </span>
27
+ <span class="cn-team-workload__count">{{ member.countText }}</span>
28
+ </div>
29
+ <span class="cn-team-workload__bar" aria-hidden="true">
30
+ <span
31
+ class="cn-team-workload__bar-fill"
32
+ :class="{ 'cn-team-workload__bar-fill--hot': member.hot }"
33
+ :style="{ width: member.percent + '%' }" />
34
+ </span>
35
+ </li>
36
+ </ul>
37
+ </div>
38
+ </template>
39
+
40
+ <script>
41
+ import { getCurrentUser } from '@nextcloud/auth'
42
+ import { translatePlural as n, translate as t } from '@nextcloud/l10n'
43
+ import { generateUrl } from '@nextcloud/router'
44
+ import { fetchStatuses } from '../../utils/nextcloudDirectory.js'
45
+ import { readPath } from '../../utils/readPath.js'
46
+ import { userLocale } from '../../utils/userLocale.js'
47
+
48
+ /** The share of the capacity from which a load bar reads as full. */
49
+ const HOT_PERCENT = 90
50
+
51
+ /**
52
+ * CnTeamWorkloadWidget: the `team-workload` widget ("Team vandaag" on board
53
+ * DqTeamwachtrij). Each person of the team with their initials, name, an
54
+ * availability line, their open count and a load bar; the card's header
55
+ * note says how many are present ("4 of 5 present").
56
+ *
57
+ * The counts come from the app (`content.source`, an endpoint that answers a
58
+ * row per person). The availability comes from Nextcloud's user status, read
59
+ * on the client through the OCS user status API: the person's own message
60
+ * with the time it ends ("In a meeting until 14:00"), else Available, Away or
61
+ * Busy, and Absent today without a status. Present means any status but
62
+ * offline.
63
+ *
64
+ * `content`:
65
+ * - `source`: `{ url, responsePath?, userField?, nameField?, countField? }`;
66
+ * the url goes through `generateUrl`, the rows are read at `responsePath`
67
+ * (else the body), each with the user id at `userField` (default
68
+ * `userId`), the name at `nameField` (default `displayName`) and the count
69
+ * at `countField` (default `count`).
70
+ * - `capacity`: the count at a full bar; without it the highest count is.
71
+ * - `countLabel`: `{ one, other }` with `{count}`, through the app's
72
+ * translate; without it the count reads "{count} open".
73
+ * - `emptyText`: shown without anyone to list.
74
+ */
75
+ export default {
76
+ name: 'CnTeamWorkloadWidget',
77
+
78
+ inject: {
79
+ cnTranslate: { default: () => (key) => key },
80
+ },
81
+
82
+ props: {
83
+ /** The widget's content: `source`, `capacity`, `countLabel`, `emptyText`. */
84
+ content: {
85
+ type: Object,
86
+ default: () => ({}),
87
+ },
88
+ },
89
+
90
+ emits: ['header'],
91
+
92
+ data() {
93
+ return {
94
+ /** The rows the source answered. */
95
+ rows: [],
96
+ /** The user statuses by user id. */
97
+ statuses: {},
98
+ /** Whether the source is being read. */
99
+ loading: false,
100
+ }
101
+ },
102
+
103
+ computed: {
104
+ /** @return {string} The signed-in user's id. */
105
+ viewerId() {
106
+ return String(getCurrentUser()?.uid || '')
107
+ },
108
+
109
+ /**
110
+ * The people to list, the viewer first and the rest by name, each with
111
+ * the line, count and bar to draw.
112
+ *
113
+ * @return {Array<object>} The members.
114
+ */
115
+ members() {
116
+ const source = this.content?.source || {}
117
+ const userField = source.userField || 'userId'
118
+ const nameField = source.nameField || 'displayName'
119
+ const countField = source.countField || 'count'
120
+ const people = this.rows
121
+ .filter((row) => row && typeof row === 'object' && readPath(row, userField))
122
+ .map((row) => {
123
+ const id = String(readPath(row, userField))
124
+ const name = String(readPath(row, nameField) || id)
125
+ return { id, name, count: Number(readPath(row, countField)) || 0 }
126
+ })
127
+ const capacity = Number(this.content?.capacity) > 0
128
+ ? Number(this.content.capacity)
129
+ : Math.max(1, ...people.map((p) => p.count))
130
+ return people
131
+ .sort((a, b) => (Number(b.id === this.viewerId) - Number(a.id === this.viewerId)) || a.name.localeCompare(b.name))
132
+ .map((person) => {
133
+ const status = this.statuses[person.id] || null
134
+ const percent = Math.min(100, Math.round((person.count / capacity) * 100))
135
+ return {
136
+ ...person,
137
+ name: person.id === this.viewerId ? t('nextcloud-vue', '{name} (you)', { name: person.name }) : person.name,
138
+ initials: initialsOf(person.name),
139
+ line: this.lineOf(status),
140
+ available: !!status && status.status === 'online',
141
+ present: isPresent(status),
142
+ countText: this.countText(person.count),
143
+ percent,
144
+ hot: percent >= HOT_PERCENT,
145
+ }
146
+ })
147
+ },
148
+
149
+ /** @return {string} The header note: how many of the team are present. */
150
+ headerNote() {
151
+ const total = this.members.length
152
+ if (total === 0) {
153
+ return ''
154
+ }
155
+ const present = this.members.filter((member) => member.present).length
156
+ return t('nextcloud-vue', '{present} of {total} present', { present, total })
157
+ },
158
+
159
+ /** @return {string} What the card says while it reads. */
160
+ loadingLabel() {
161
+ return t('nextcloud-vue', 'Loading…')
162
+ },
163
+
164
+ /** @return {string} What the card says without anyone to list. */
165
+ emptyLabel() {
166
+ return this.content?.emptyText ? this.cnTranslate(this.content.emptyText) : t('nextcloud-vue', 'No one in the team')
167
+ },
168
+ },
169
+
170
+ watch: {
171
+ headerNote: {
172
+ immediate: true,
173
+ handler(note) {
174
+ /**
175
+ * @event header The card header's parts: `{ link, note, action, pill }`, the note being how many are present.
176
+ */
177
+ this.$emit('header', { link: null, note, action: null, pill: null })
178
+ },
179
+ },
180
+
181
+ 'content.source': {
182
+ deep: true,
183
+ handler() {
184
+ this.load()
185
+ },
186
+ },
187
+ },
188
+
189
+ mounted() {
190
+ this.load()
191
+ },
192
+
193
+ methods: {
194
+ /**
195
+ * Read the counts and the user statuses. A failed read leaves the card
196
+ * empty; a failed status read leaves everyone without a status.
197
+ *
198
+ * @return {Promise<void>}
199
+ */
200
+ async load() {
201
+ const source = this.content?.source
202
+ if (!source || typeof source.url !== 'string' || source.url === '') {
203
+ this.rows = []
204
+ return
205
+ }
206
+ this.loading = true
207
+ try {
208
+ const { default: axios } = await import('@nextcloud/axios')
209
+ const [answer, statuses] = await Promise.all([
210
+ axios.get(generateUrl(source.url)),
211
+ fetchStatuses(axios),
212
+ ])
213
+ const body = answer && answer.data
214
+ const rows = source.responsePath ? readPath(body, source.responsePath) : body
215
+ this.rows = Array.isArray(rows) ? rows : []
216
+ this.statuses = statuses || {}
217
+ } catch {
218
+ this.rows = []
219
+ } finally {
220
+ this.loading = false
221
+ }
222
+ },
223
+
224
+ /**
225
+ * The availability line of a user status.
226
+ *
227
+ * @param {object|null} status The user status, or null without one.
228
+ * @return {string} The line.
229
+ */
230
+ lineOf(status) {
231
+ if (!status) {
232
+ return t('nextcloud-vue', 'Absent today')
233
+ }
234
+ const message = typeof status.message === 'string' ? status.message.trim() : ''
235
+ if (message !== '') {
236
+ const clearAt = Number(status.clearAt)
237
+ if (!Number.isFinite(clearAt) || clearAt <= 0) {
238
+ return message
239
+ }
240
+ const time = new Date(clearAt * 1000).toLocaleTimeString(userLocale() || undefined, { hour: '2-digit', minute: '2-digit' })
241
+ return t('nextcloud-vue', '{message} until {time}', { message, time })
242
+ }
243
+ switch (status.status) {
244
+ case 'online':
245
+ return t('nextcloud-vue', 'Available')
246
+ case 'away':
247
+ return t('nextcloud-vue', 'Away')
248
+ case 'dnd':
249
+ case 'busy':
250
+ return t('nextcloud-vue', 'Busy')
251
+ default:
252
+ return t('nextcloud-vue', 'Absent today')
253
+ }
254
+ },
255
+
256
+ /**
257
+ * The count as words: the content's `countLabel`, else "{count} open".
258
+ *
259
+ * @param {number} count The open count.
260
+ * @return {string} The text.
261
+ */
262
+ countText(count) {
263
+ const label = this.content?.countLabel
264
+ if (label && typeof label === 'object' && typeof label.one === 'string' && typeof label.other === 'string') {
265
+ return this.cnTranslate(count === 1 ? label.one : label.other).replace('{count}', String(count))
266
+ }
267
+ return n('nextcloud-vue', '{count} open', '{count} open', count, { count })
268
+ },
269
+ },
270
+ }
271
+
272
+ /**
273
+ * Up to two initials of a name.
274
+ *
275
+ * @param {string} name The name.
276
+ * @return {string} The initials, upper case.
277
+ */
278
+ function initialsOf(name) {
279
+ const parts = String(name || '').trim().split(/\s+/).filter(Boolean)
280
+ if (parts.length === 0) {
281
+ return ''
282
+ }
283
+ const last = parts.length > 1 ? parts[parts.length - 1].charAt(0) : ''
284
+ return (parts[0].charAt(0) + last).toUpperCase()
285
+ }
286
+
287
+ /**
288
+ * Whether a person is at work today: any status but offline or invisible.
289
+ *
290
+ * @param {object|null} status The user status.
291
+ * @return {boolean} True when present.
292
+ */
293
+ function isPresent(status) {
294
+ return !!status && status.status !== 'offline' && status.status !== 'invisible'
295
+ }
296
+ </script>
297
+
298
+ <style scoped>
299
+ .cn-team-workload__list {
300
+ display: flex;
301
+ flex-direction: column;
302
+ gap: 14px;
303
+ margin: 0;
304
+ padding: 0;
305
+ list-style: none;
306
+ }
307
+
308
+ .cn-team-workload__member {
309
+ display: flex;
310
+ flex-direction: column;
311
+ gap: 6px;
312
+ }
313
+
314
+ .cn-team-workload__row {
315
+ display: flex;
316
+ align-items: center;
317
+ gap: 10px;
318
+ }
319
+
320
+ .cn-team-workload__avatar {
321
+ display: flex;
322
+ flex: none;
323
+ align-items: center;
324
+ justify-content: center;
325
+ width: 32px;
326
+ height: 32px;
327
+ border-radius: 50%;
328
+ background: var(--color-background-dark);
329
+ color: var(--color-main-text);
330
+ font-size: 12px;
331
+ font-weight: 700;
332
+ }
333
+
334
+ .cn-team-workload__who {
335
+ display: flex;
336
+ flex: 1;
337
+ flex-direction: column;
338
+ min-width: 0;
339
+ }
340
+
341
+ .cn-team-workload__name {
342
+ font-size: 14px;
343
+ }
344
+
345
+ .cn-team-workload__state {
346
+ font-size: 13px;
347
+ color: var(--color-text-maxcontrast);
348
+ }
349
+
350
+ .cn-team-workload__state--away {
351
+ color: var(--color-warning-text, var(--color-text-maxcontrast));
352
+ }
353
+
354
+ .cn-team-workload__count {
355
+ font-size: 13px;
356
+ font-weight: 600;
357
+ white-space: nowrap;
358
+ }
359
+
360
+ .cn-team-workload__bar {
361
+ display: block;
362
+ height: 6px;
363
+ overflow: hidden;
364
+ border-radius: 3px;
365
+ background: var(--color-border);
366
+ }
367
+
368
+ .cn-team-workload__bar-fill {
369
+ display: block;
370
+ height: 100%;
371
+ background: var(--color-primary-element);
372
+ }
373
+
374
+ .cn-team-workload__bar-fill--hot {
375
+ background: var(--color-error);
376
+ }
377
+
378
+ .cn-team-workload__state-line {
379
+ margin: 0;
380
+ color: var(--color-text-maxcontrast);
381
+ }
382
+ </style>
@@ -0,0 +1,14 @@
1
+ /**
2
+ * SPDX-FileCopyrightText: 2026 Conduction B.V.
3
+ * SPDX-License-Identifier: EUPL-1.2
4
+ *
5
+ * CnTeamWorkloadWidget: the `team-workload` widget (the team today, with
6
+ * each person's availability and open count). It is registered in
7
+ * `CnWidgetGrid/registerDashboardWidgets.js`, inline, because a bare
8
+ * side-effect import of this module may be tree-shaken away.
9
+ */
10
+
11
+ import CnTeamWorkloadWidget from './CnTeamWorkloadWidget.vue'
12
+
13
+ export { CnTeamWorkloadWidget }
14
+ export default CnTeamWorkloadWidget
@@ -35,13 +35,13 @@
35
35
  <div class="cn-version-info__details">
36
36
  <!-- Application Name (the board card can leave it to its description: `showAppName`) -->
37
37
  <div v-if="!isBoard || (showAppName && !compact)" class="cn-version-info__item" data-testid="cn-version-info-app-name">
38
- <span class="cn-version-info__label">{{ fieldLabels.appName }}:</span>
38
+ <span class="cn-version-info__label">{{ fieldLabels.appName }}{{ labelColon }}</span>
39
39
  <span class="cn-version-info__value">{{ appName }}</span>
40
40
  </div>
41
41
 
42
42
  <!-- Version -->
43
43
  <div class="cn-version-info__item">
44
- <span class="cn-version-info__label">{{ fieldLabels.version }}:</span>
44
+ <span class="cn-version-info__label">{{ fieldLabels.version }}{{ labelColon }}</span>
45
45
  <span class="cn-version-info__value">{{ appVersion }}</span>
46
46
  </div>
47
47
 
@@ -49,13 +49,13 @@
49
49
  which draws the row ("Unknown" until the app reports it) with the
50
50
  up-to-date state inline ("0.4.47, bijgewerkt"). -->
51
51
  <div v-if="configuredVersion || isBoard" class="cn-version-info__item" data-testid="cn-version-info-configured">
52
- <span class="cn-version-info__label">{{ fieldLabels.configuredVersion }}:</span>
52
+ <span class="cn-version-info__label">{{ fieldLabels.configuredVersion }}{{ labelColon }}</span>
53
53
  <span class="cn-version-info__value">{{ isBoard ? boardConfiguredText : configuredVersion }}</span>
54
54
  </div>
55
55
 
56
56
  <!-- Additional items -->
57
57
  <div v-for="item in additionalItems" :key="item.label" class="cn-version-info__item">
58
- <span class="cn-version-info__label">{{ item.label }}:</span>
58
+ <span class="cn-version-info__label">{{ item.label }}{{ labelColon }}</span>
59
59
  <span class="cn-version-info__value" :class="item.statusClass">{{ item.value }}</span>
60
60
  </div>
61
61
 
@@ -320,6 +320,17 @@ export default {
320
320
  },
321
321
 
322
322
  computed: {
323
+ /**
324
+ * The colon after each label: none on the compact board card, which
325
+ * sets each label beside its value ("Versie 0.4.47", DqInstellingenAlgemeen);
326
+ * one otherwise, PqBeheer's card included.
327
+ *
328
+ * @return {string} The colon, or ''.
329
+ */
330
+ labelColon() {
331
+ return this.isBoard && this.compact ? '' : ':'
332
+ },
333
+
323
334
  /**
324
335
  * The field labels: the app's, else the library's in the reader's language.
325
336
  *
@@ -21,6 +21,7 @@ import CnExportsWidget from '../CnExportsWidget/CnExportsWidget.vue'
21
21
  import CnHeaderWidget from '../CnHeaderWidget/CnHeaderWidget.vue'
22
22
  import CnIntegrationWidget from '../CnIntegrationWidget/CnIntegrationWidget.vue'
23
23
  import CnNavCardGrid from '../CnNavCardGrid/CnNavCardGrid.vue'
24
+ import CnNoticeWidget from '../CnNoticeWidget/CnNoticeWidget.vue'
24
25
  import CnObjectDataWidget from '../CnObjectDataWidget/CnObjectDataWidget.vue'
25
26
  import CnObjectGeoWidget from '../CnObjectGeoWidget/CnObjectGeoWidget.vue'
26
27
  import CnObjectMetadataWidget from '../CnObjectMetadataWidget/CnObjectMetadataWidget.vue'
@@ -86,6 +87,7 @@ export const BUILT_IN_WIDGETS = {
86
87
  related: CnRelatedObjectsWidget,
87
88
  integration: CnIntegrationWidget,
88
89
  banner: CnBannerWidget,
90
+ notice: CnNoticeWidget,
89
91
  'audit-trail': CnAuditTrailWidget,
90
92
  timeline: CnTimelineWidget,
91
93
  header: CnHeaderWidget,
@@ -40,6 +40,7 @@ import CnStatsBlockWidget from '../CnStatsBlockWidget/CnStatsBlockWidget.vue'
40
40
  import CnStatsBlockWidgetForm from '../CnStatsBlockWidgetForm/CnStatsBlockWidgetForm.vue'
41
41
  import CnTabsWidget from '../CnTabsWidget/CnTabsWidget.vue'
42
42
  import CnTabsWidgetForm from '../CnTabsWidgetForm/CnTabsWidgetForm.vue'
43
+ import CnTeamWorkloadWidget from '../CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue'
43
44
  import CnWeekStripWidget from '../CnWeekStripWidget/CnWeekStripWidget.vue'
44
45
  import CnWeekStripWidgetForm from '../CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue'
45
46
  // Typed widgets registered with an explicit renderer + config FORM. chart /
@@ -381,6 +382,17 @@ registerDashboardWidget('stacked-bar', {
381
382
  icon: 'ChartBar',
382
383
  })
383
384
 
385
+ // The team today: availability from the Nextcloud user status and each
386
+ // person's open count from the app (DqTeamwachtrij "Team vandaag"). Placed
387
+ // from the manifest: there is no config form yet.
388
+ registerDashboardWidget('team-workload', {
389
+ renderer: CnTeamWorkloadWidget,
390
+ form: null,
391
+ defaultContent: { source: { url: '', responsePath: '', userField: 'userId', nameField: 'displayName', countField: 'count' } },
392
+ displayName: 'Team workload',
393
+ icon: 'AccountGroup',
394
+ })
395
+
384
396
  /**
385
397
  * Explicit no-op that guarantees this module (and therefore every widget's
386
398
  * self-registration side effect) is evaluated. Now optional: `CnDashboardPage`
@@ -131,6 +131,7 @@ export { CnContactPicker } from './CnContactPicker/index.js'
131
131
  export { CnContactCreate } from './CnContactCreate/index.js'
132
132
  export { CnResourceSelect } from './CnResourceSelect/index.js'
133
133
  export { CnSearchSelect } from './CnSearchSelect/index.js'
134
+ export { CnNoticeWidget } from './CnNoticeWidget/index.js'
134
135
  export { CnPropertySourceField } from './CnPropertySourceField/index.js'
135
136
  export { CnDurationField } from './CnDurationField/index.js'
136
137
  export { CnFavouriteToggle } from './CnFavouriteToggle/index.js'
@@ -425,6 +426,7 @@ export { CnGuardianHome } from './CnGuardianHome/index.js'
425
426
  export { CnWeekStripWidget } from './CnWeekStripWidget/index.js'
426
427
  export { CnWeekStripWidgetForm } from './CnWeekStripWidgetForm/index.js'
427
428
  export { CnStackedBarWidget } from './CnStackedBarWidget/index.js'
429
+ export { CnTeamWorkloadWidget } from './CnTeamWorkloadWidget/index.js'
428
430
  export { CnStackedBarWidgetForm } from './CnStackedBarWidgetForm/index.js'
429
431
  export { CnSegmentedControl } from './CnSegmentedControl/index.js'
430
432
  export { CnBrandStripe } from './CnBrandStripe/index.js'