@conduction/nextcloud-vue 2.83.0 → 2.85.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 (246) hide show
  1. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue.js +1 -1
  2. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js +131 -3
  3. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
  4. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +54 -18
  5. package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
  6. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue.js +1 -1
  7. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js +1 -1
  8. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue2.js.map +1 -1
  9. package/dist/esm/components/CnAuditTrailCard/CnAuditTrailCard.vue3.js.map +1 -1
  10. package/dist/esm/components/CnBoardView/CnBoardView.vue.js +1 -1
  11. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +232 -4
  12. package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
  13. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +95 -51
  14. package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
  15. package/dist/esm/components/CnBodySections/CnBodySections.vue.js +1 -1
  16. package/dist/esm/components/CnBodySections/CnBodySections.vue2.js +22 -1
  17. package/dist/esm/components/CnBodySections/CnBodySections.vue2.js.map +1 -1
  18. package/dist/esm/components/CnBodySections/CnBodySections.vue3.js.map +1 -1
  19. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue.js +1 -1
  20. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js +47 -4
  21. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue2.js.map +1 -1
  22. package/dist/esm/components/CnCountdownWidget/CnCountdownWidget.vue3.js.map +1 -1
  23. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue.js +1 -1
  24. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +239 -3
  25. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
  26. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +5 -5
  27. package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
  28. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue.js +1 -1
  29. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +50 -0
  30. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
  31. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +22 -12
  32. package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
  33. package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue.js +1 -1
  34. package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue2.js +1 -1
  35. package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue2.js.map +1 -1
  36. package/dist/esm/components/CnDeltaWidget/CnDeltaWidget.vue3.js.map +1 -1
  37. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +226 -8
  38. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  39. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  40. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  41. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +3 -1
  42. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  43. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  44. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
  45. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +18 -5
  46. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
  47. package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
  48. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue.js +1 -1
  49. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js +38 -1
  50. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue2.js.map +1 -1
  51. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js +45 -5
  52. package/dist/esm/components/CnFieldListWidget/CnFieldListWidget.vue3.js.map +1 -1
  53. package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue.js +1 -1
  54. package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue2.js +1 -1
  55. package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue2.js.map +1 -1
  56. package/dist/esm/components/CnGaugeWidget/CnGaugeWidget.vue3.js.map +1 -1
  57. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +548 -77
  58. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  59. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +191 -82
  60. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  61. package/dist/esm/components/CnIndexPage/useSelfFetchList.js +11 -2
  62. package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
  63. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js +29 -6
  64. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
  65. package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
  66. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
  67. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +126 -2
  68. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
  69. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js +24 -2
  70. package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
  71. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue.js +1 -1
  72. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js +106 -3
  73. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue2.js.map +1 -1
  74. package/dist/esm/components/CnRecordListWidget/CnRecordListWidget.vue3.js.map +1 -1
  75. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue.js +1 -1
  76. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js +67 -5
  77. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue2.js.map +1 -1
  78. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js +181 -124
  79. package/dist/esm/components/CnRegisterMapping/CnRegisterMapping.vue3.js.map +1 -1
  80. package/dist/esm/components/CnReportsPage/CnReportsPage.vue.js +1 -1
  81. package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js +135 -2
  82. package/dist/esm/components/CnReportsPage/CnReportsPage.vue2.js.map +1 -1
  83. package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js +95 -43
  84. package/dist/esm/components/CnReportsPage/CnReportsPage.vue3.js.map +1 -1
  85. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue.js +1 -1
  86. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js +5 -1
  87. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue2.js.map +1 -1
  88. package/dist/esm/components/CnSettingsPage/CnSettingsPage.vue3.js.map +1 -1
  89. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue.js +1 -1
  90. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js +72 -5
  91. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
  92. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js +33 -8
  93. package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
  94. package/dist/esm/components/CnStatWidget/CnStatWidget.vue.js +1 -1
  95. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +135 -11
  96. package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
  97. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +6 -2
  98. package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
  99. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue.js +1 -1
  100. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +17 -2
  101. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
  102. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +1 -1
  103. package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
  104. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue.js +1 -1
  105. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js +39 -6
  106. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue2.js.map +1 -1
  107. package/dist/esm/components/CnTabsWidget/CnTabsWidget.vue3.js.map +1 -1
  108. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue.js +1 -1
  109. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js +140 -7
  110. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue2.js.map +1 -1
  111. package/dist/esm/components/CnToggleTableWidget/CnToggleTableWidget.vue3.js.map +1 -1
  112. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue.js +1 -1
  113. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js +37 -9
  114. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue2.js.map +1 -1
  115. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js +6 -5
  116. package/dist/esm/components/CnVersionInfoCard/CnVersionInfoCard.vue3.js.map +1 -1
  117. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue.js +1 -1
  118. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js +16 -0
  119. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue2.js.map +1 -1
  120. package/dist/esm/components/CnWalkthrough/CnWalkthrough.vue3.js.map +1 -1
  121. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue.js +1 -1
  122. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +2 -1
  123. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -1
  124. package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -1
  125. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue2.js +53 -1
  126. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue2.js.map +1 -1
  127. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue3.js +3 -2
  128. package/dist/esm/components/CnWidgetCardGrid/CnWidgetCardGrid.vue3.js.map +1 -1
  129. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue.js +1 -1
  130. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js +135 -0
  131. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue2.js.map +1 -1
  132. package/dist/esm/components/CnWidgetObjectTable/CnWidgetObjectTable.vue3.js.map +1 -1
  133. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue.js +1 -1
  134. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js +19 -0
  135. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue2.js.map +1 -1
  136. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js +37 -19
  137. package/dist/esm/components/CnWidgetWrapper/CnWidgetWrapper.vue3.js.map +1 -1
  138. package/dist/esm/composables/useListView.js +21 -0
  139. package/dist/esm/composables/useListView.js.map +1 -1
  140. package/dist/esm/dialogs/CnTransitionInputDialog.vue.js +1 -1
  141. package/dist/esm/dialogs/CnTransitionInputDialog.vue2.js +68 -6
  142. package/dist/esm/dialogs/CnTransitionInputDialog.vue2.js.map +1 -1
  143. package/dist/esm/dialogs/CnTransitionInputDialog.vue3.js +133 -105
  144. package/dist/esm/dialogs/CnTransitionInputDialog.vue3.js.map +1 -1
  145. package/dist/esm/index.js +1 -0
  146. package/dist/esm/index.js.map +1 -1
  147. package/dist/esm/l10n/en.json.js +38 -1
  148. package/dist/esm/l10n/en.json.js.map +1 -1
  149. package/dist/esm/l10n/nl.json.js +78 -1
  150. package/dist/esm/l10n/nl.json.js.map +1 -1
  151. package/dist/esm/library-style.js +1 -1
  152. package/dist/esm/nextcloud-vue.css +1030 -533
  153. package/dist/esm/store/useObjectStore.js +16 -2
  154. package/dist/esm/store/useObjectStore.js.map +1 -1
  155. package/dist/esm/utils/boardColumns.js +8 -1
  156. package/dist/esm/utils/boardColumns.js.map +1 -1
  157. package/dist/esm/utils/boardReferenceColumns.js +278 -0
  158. package/dist/esm/utils/boardReferenceColumns.js.map +1 -0
  159. package/dist/esm/utils/buildManifest.js +41 -4
  160. package/dist/esm/utils/buildManifest.js.map +1 -1
  161. package/dist/esm/utils/detailWidgetVisibility.js +149 -0
  162. package/dist/esm/utils/detailWidgetVisibility.js.map +1 -0
  163. package/dist/esm/utils/formatMetric.js +39 -3
  164. package/dist/esm/utils/formatMetric.js.map +1 -1
  165. package/dist/esm/utils/gridStack.js +40 -1
  166. package/dist/esm/utils/gridStack.js.map +1 -1
  167. package/dist/esm/utils/humaniseKey.js +41 -0
  168. package/dist/esm/utils/humaniseKey.js.map +1 -0
  169. package/dist/esm/utils/notificationRuleLabel.js +6 -23
  170. package/dist/esm/utils/notificationRuleLabel.js.map +1 -1
  171. package/dist/esm/utils/resolveManifestTokens.js +1 -0
  172. package/dist/esm/utils/resolveManifestTokens.js.map +1 -1
  173. package/dist/esm/utils/sentinelTokens.js +9 -0
  174. package/dist/esm/utils/sentinelTokens.js.map +1 -1
  175. package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
  176. package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
  177. package/dist/esm/utils/widgetDispatch.js +8 -2
  178. package/dist/esm/utils/widgetDispatch.js.map +1 -1
  179. package/dist/nextcloud-vue.cjs.js +17159 -13422
  180. package/dist/nextcloud-vue.cjs.js.map +2 -2
  181. package/dist/nextcloud-vue.css +1030 -533
  182. package/l10n/en.json +38 -1
  183. package/l10n/nl.json +78 -1
  184. package/package.json +1 -1
  185. package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +160 -8
  186. package/src/components/CnAuditTrailCard/CnAuditTrailCard.vue +1 -1
  187. package/src/components/CnBoardView/CnBoardView.vue +262 -6
  188. package/src/components/CnBodySections/CnBodySections.vue +22 -1
  189. package/src/components/CnCountdownWidget/CnCountdownWidget.vue +47 -4
  190. package/src/components/CnDashboardGrid/CnDashboardGrid.vue +244 -8
  191. package/src/components/CnDashboardPage/CnDashboardPage.vue +62 -2
  192. package/src/components/CnDeltaWidget/CnDeltaWidget.vue +1 -1
  193. package/src/components/CnDetailPage/CnDetailPage.md +3 -1
  194. package/src/components/CnDetailPage/CnDetailPage.vue +226 -8
  195. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +3 -1
  196. package/src/components/CnFactsWidget/CnFactsWidget.vue +18 -5
  197. package/src/components/CnFieldListWidget/CnFieldListWidget.vue +63 -1
  198. package/src/components/CnGaugeWidget/CnGaugeWidget.vue +1 -1
  199. package/src/components/CnIndexPage/CnIndexPage.vue +586 -25
  200. package/src/components/CnIndexPage/useSelfFetchList.js +10 -1
  201. package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +29 -6
  202. package/src/components/CnObjectDataWidget/CnObjectDataWidget.md +1 -1
  203. package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +154 -5
  204. package/src/components/CnRecordListWidget/CnRecordListWidget.vue +106 -3
  205. package/src/components/CnRegisterMapping/CnRegisterMapping.md +5 -0
  206. package/src/components/CnRegisterMapping/CnRegisterMapping.vue +231 -96
  207. package/src/components/CnReportsPage/CnReportsPage.vue +179 -1
  208. package/src/components/CnSettingsPage/CnSettingsPage.vue +5 -1
  209. package/src/components/CnStagesWidget/CnStagesWidget.vue +103 -6
  210. package/src/components/CnStatWidget/CnStatWidget.vue +137 -12
  211. package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +34 -3
  212. package/src/components/CnTabsWidget/CnTabsWidget.vue +39 -6
  213. package/src/components/CnToggleTableWidget/CnToggleTableWidget.vue +140 -7
  214. package/src/components/CnVersionInfoCard/CnVersionInfoCard.md +2 -1
  215. package/src/components/CnVersionInfoCard/CnVersionInfoCard.vue +42 -13
  216. package/src/components/CnWalkthrough/CnWalkthrough.vue +16 -0
  217. package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +2 -1
  218. package/src/components/CnWidgetCardGrid/CnWidgetCardGrid.vue +55 -2
  219. package/src/components/CnWidgetObjectTable/CnWidgetObjectTable.vue +135 -0
  220. package/src/components/CnWidgetWrapper/CnWidgetWrapper.md +10 -0
  221. package/src/components/CnWidgetWrapper/CnWidgetWrapper.vue +44 -1
  222. package/src/composables/useListView.js +21 -0
  223. package/src/css/board.css +65 -4
  224. package/src/css/dialog.css +4 -2
  225. package/src/css/form-field.css +14 -0
  226. package/src/css/form-widget.css +11 -0
  227. package/src/css/index-page.css +49 -0
  228. package/src/css/kpi-card.css +13 -0
  229. package/src/css/look-board-reports.css +58 -0
  230. package/src/css/look-board.css +108 -7
  231. package/src/dialogs/CnTransitionInputDialog.vue +87 -7
  232. package/src/index.js +2 -0
  233. package/src/schemas/app-manifest-v2.schema.json +137 -6
  234. package/src/store/useObjectStore.js +16 -2
  235. package/src/utils/boardColumns.js +8 -1
  236. package/src/utils/boardReferenceColumns.js +274 -0
  237. package/src/utils/buildManifest.js +40 -3
  238. package/src/utils/detailWidgetVisibility.js +145 -0
  239. package/src/utils/formatMetric.js +38 -2
  240. package/src/utils/gridStack.js +39 -0
  241. package/src/utils/humaniseKey.js +38 -0
  242. package/src/utils/notificationRuleLabel.js +5 -23
  243. package/src/utils/resolveManifestTokens.js +1 -0
  244. package/src/utils/sentinelTokens.js +9 -0
  245. package/src/utils/validateManifestV2.compiled.js +3 -3
  246. package/src/utils/widgetDispatch.js +8 -2
@@ -34,7 +34,31 @@
34
34
  <template>
35
35
  <div class="cn-reports-page" :class="{ 'cn-reports-page--board': isBoard }">
36
36
  <div class="cn-reports-page__header">
37
- <h2 class="cn-reports-page__title">
37
+ <!-- The header buttons (`headerButtons`) beside the title, as the
38
+ board's "Downloaden" and the buildiq square (board DcRapportages).
39
+ Without them, and outside the board look, the title stands alone
40
+ as before. -->
41
+ <div v-if="headerButtonsRow.length > 0" class="cn-reports-page__heading-row">
42
+ <h2 class="cn-reports-page__title">
43
+ {{ resolvedTitle }}
44
+ </h2>
45
+ <div class="cn-reports-page__header-buttons" data-testid="cn-reports-header-buttons">
46
+ <template v-for="button in headerButtonsRow" :key="button.key">
47
+ <CnBuildiqEditButton v-if="button.action === '__buildiq'" />
48
+ <NcButton
49
+ v-else
50
+ :variant="button.variant"
51
+ :data-testid="`cn-reports-header-button-${button.key}`"
52
+ @click="onHeaderButton(button)">
53
+ <template v-if="button.icon" #icon>
54
+ <CnIcon :name="button.icon" :size="20" />
55
+ </template>
56
+ {{ button.label }}
57
+ </NcButton>
58
+ </template>
59
+ </div>
60
+ </div>
61
+ <h2 v-else class="cn-reports-page__title">
38
62
  {{ resolvedTitle }}
39
63
  </h2>
40
64
  <p v-if="resolvedDescription" class="cn-reports-page__description">
@@ -134,9 +158,12 @@
134
158
  * The Reports page: one surface listing an app's reports as cards.
135
159
  */
136
160
  import { translate as t } from '@nextcloud/l10n'
161
+ import { NcButton } from '@nextcloud/vue'
137
162
  import { defineAsyncComponent } from 'vue'
163
+ import CnBuildiqEditButton from '../CnBuildiqEditButton/CnBuildiqEditButton.vue'
138
164
  import CnIcon from '../CnIcon/CnIcon.vue'
139
165
  import { useLook } from '../../composables/useLook.js'
166
+ import { resolveRegisteredHandler } from '../../utils/actionsDispatcher.js'
140
167
  import { followLinkClick } from '../../utils/linkNavigation.js'
141
168
 
142
169
  export default {
@@ -146,10 +173,18 @@ export default {
146
173
  // Loaded only for a page that declares widgets: a cards-only reports
147
174
  // page does not pull the dashboard in.
148
175
  CnDashboardPage: defineAsyncComponent(() => import('../CnDashboardPage/CnDashboardPage.vue').then((m) => m.default)),
176
+ CnBuildiqEditButton,
149
177
  CnIcon,
178
+ NcButton,
150
179
  },
151
180
 
152
181
  inject: {
182
+ /**
183
+ * The v2 registry and the legacy customComponents map, provided by
184
+ * CnAppRoot: a header action's named handler resolves against them.
185
+ */
186
+ cnRegistry: { default: () => ({}) },
187
+ cnCustomComponents: { default: () => ({}) },
153
188
  /**
154
189
  * The consuming app's translate function, provided by CnAppRoot.
155
190
  * Defaults to identity so the page still renders outside one.
@@ -268,6 +303,38 @@ export default {
268
303
  default: null,
269
304
  },
270
305
 
306
+ /**
307
+ * Buttons beside the title (`config.headerButtons`), as the board's
308
+ * "Downloaden". Each is `{ label, action, variant?, icon?, id? }`:
309
+ * `action` is the id of a `headerActions` entry, which says what the
310
+ * click does. Under the board look the buildiq square sits between the
311
+ * secondary buttons and the primary one. Empty (the default) changes
312
+ * nothing.
313
+ *
314
+ * @spec openspec/changes/screens-reports-header-buttons/specs/reports-page/spec.md#requirement-a-reports-page-carries-header-buttons
315
+ * @type {Array<{label: string, action: string, variant?: ('primary'|'secondary'), icon?: string, id?: string}>|null}
316
+ */
317
+ headerButtons: {
318
+ type: Array,
319
+ default: null,
320
+ },
321
+
322
+ /**
323
+ * The page's header actions (`config.headerActions`), in the shape of
324
+ * an index page's: a header button names one by id. `handler` is a
325
+ * function, `navigate` (with `route`, and `params`), `emit`, `none`,
326
+ * or a registry name; `type: "navigate"` with a `target` URL opens
327
+ * that URL in a new tab. Every click also emits `header-action`
328
+ * `{ action, id }`, except for `handler: "none"`.
329
+ *
330
+ * @spec openspec/changes/screens-reports-header-buttons/specs/reports-page/spec.md#requirement-a-reports-page-carries-header-buttons
331
+ * @type {Array<object>|null}
332
+ */
333
+ headerActions: {
334
+ type: Array,
335
+ default: null,
336
+ },
337
+
271
338
  /**
272
339
  * The look to draw in: `board` or `nextcloud`. Absent: the look
273
340
  * CnAppRoot provides (`cnLook`), else `nextcloud`.
@@ -290,6 +357,8 @@ export default {
290
357
  },
291
358
  },
292
359
 
360
+ emits: ['header-action'],
361
+
293
362
  setup(props) {
294
363
  const { isBoard } = useLook(props)
295
364
  return { isBoard }
@@ -339,6 +408,8 @@ export default {
339
408
  'emptyLabel',
340
409
  'widgets',
341
410
  'layout',
411
+ 'headerButtons',
412
+ 'headerActions',
342
413
  ]) {
343
414
  if (this[key] !== null && this[key] !== undefined) {
344
415
  fromProps[key] = this[key]
@@ -378,6 +449,51 @@ export default {
378
449
  })
379
450
  },
380
451
 
452
+ /**
453
+ * The declared header buttons that have a label and an action,
454
+ * translated, each with a stable key and a variant.
455
+ *
456
+ * @spec openspec/changes/screens-reports-header-buttons/specs/reports-page/spec.md#requirement-a-reports-page-carries-header-buttons
457
+ * @return {Array<{key: string, label: string, action: string, variant: string, icon: string}>} The buttons.
458
+ */
459
+ resolvedHeaderButtons() {
460
+ const declared = Array.isArray(this.config.headerButtons) ? this.config.headerButtons : []
461
+ return declared
462
+ .filter((button) => button && typeof button === 'object'
463
+ && typeof button.action === 'string' && button.action !== ''
464
+ && typeof button.label === 'string' && button.label !== '')
465
+ .map((button) => ({
466
+ key: String(button.id || button.action),
467
+ label: this.tr(button.label),
468
+ action: button.action,
469
+ variant: button.variant === 'primary' ? 'primary' : 'secondary',
470
+ icon: typeof button.icon === 'string' ? button.icon : '',
471
+ }))
472
+ },
473
+
474
+ /**
475
+ * The header row in the order it renders. Without the board look:
476
+ * the declared buttons in the declared order. Under the board look:
477
+ * the secondary buttons, the buildiq square, then the primary ones,
478
+ * as the board draws "Downloaden" and the square. No declared button
479
+ * leaves the header as it was, without the square.
480
+ *
481
+ * @spec openspec/changes/screens-reports-header-buttons/specs/reports-page/spec.md#requirement-a-reports-page-carries-header-buttons
482
+ * @return {Array<object>} The buttons; the square is `{ key: '__buildiq', action: '__buildiq' }`.
483
+ */
484
+ headerButtonsRow() {
485
+ const buttons = this.resolvedHeaderButtons
486
+ if (buttons.length === 0 || !this.isBoard) {
487
+ return buttons
488
+ }
489
+ const square = { key: '__buildiq', action: '__buildiq', variant: 'secondary', label: '', icon: '' }
490
+ return [
491
+ ...buttons.filter((button) => button.variant !== 'primary'),
492
+ square,
493
+ ...buttons.filter((button) => button.variant === 'primary'),
494
+ ]
495
+ },
496
+
381
497
  /**
382
498
  * The heading.
383
499
  *
@@ -567,6 +683,47 @@ export default {
567
683
  }
568
684
  },
569
685
 
686
+ /**
687
+ * A header button was clicked: run the `headerActions` entry it names
688
+ * and emit `header-action`, as an index page's header button does.
689
+ *
690
+ * @spec openspec/changes/screens-reports-header-buttons/specs/reports-page/spec.md#requirement-a-reports-page-carries-header-buttons
691
+ * @param {{action: string}} button The resolved button.
692
+ * @return {void}
693
+ */
694
+ onHeaderButton(button) {
695
+ const id = button.action
696
+ const actions = Array.isArray(this.config.headerActions) ? this.config.headerActions : []
697
+ const entry = actions.find((action) => action && action.id === id) || null
698
+ const handler = entry ? entry.handler : undefined
699
+ if (handler === 'none') {
700
+ return
701
+ }
702
+ if (typeof handler === 'function') {
703
+ handler({ actionId: id })
704
+ } else if (handler === 'navigate' && entry.route) {
705
+ const params = (entry.params && typeof entry.params === 'object') ? entry.params : null
706
+ if (this.$router && typeof this.$router.push === 'function') {
707
+ this.$router.push(params ? { name: entry.route, params } : { name: entry.route })
708
+ }
709
+ } else if (entry && entry.type === 'navigate' && typeof entry.target === 'string' && entry.target !== '') {
710
+ window.open(entry.target, '_blank', 'noopener,noreferrer')
711
+ } else if (typeof handler === 'string' && handler !== 'emit') {
712
+ const resolved = resolveRegisteredHandler(handler, this.cnRegistry, this.cnCustomComponents)
713
+ if (typeof resolved === 'function') {
714
+ resolved({ actionId: id })
715
+ }
716
+ }
717
+ /**
718
+ * A header button was clicked (`headerButtons`), after its
719
+ * `headerActions` entry ran; not for `handler: "none"`.
720
+ *
721
+ * @event header-action
722
+ * @property {{action: string, id: string}} payload The id of the `headerActions` entry the button names, as `action` and `id`.
723
+ */
724
+ this.$emit('header-action', { action: id, id })
725
+ },
726
+
570
727
  /**
571
728
  * Navigate by route NAME on a plain click; a modified or middle click
572
729
  * is left to the browser so it can open the card's href elsewhere.
@@ -598,6 +755,27 @@ export default {
598
755
  margin: 0 0 4px;
599
756
  }
600
757
 
758
+ .cn-reports-page__heading-row {
759
+ display: flex;
760
+ flex-wrap: wrap;
761
+ align-items: flex-start;
762
+ justify-content: space-between;
763
+ gap: 12px 24px;
764
+ }
765
+
766
+ .cn-reports-page__heading-row .cn-reports-page__title {
767
+ flex: 1 1 320px;
768
+ min-width: 0;
769
+ }
770
+
771
+ .cn-reports-page__header-buttons {
772
+ display: flex;
773
+ flex: none;
774
+ flex-wrap: wrap;
775
+ align-items: center;
776
+ gap: 10px;
777
+ }
778
+
601
779
  .cn-reports-page__description {
602
780
  margin: 0 0 16px;
603
781
  color: var(--color-text-maxcontrast);
@@ -303,6 +303,7 @@ import {
303
303
  import ContentSave from 'vue-material-design-icons/ContentSave.vue'
304
304
  import CnBuildiqEditButton from '../CnBuildiqEditButton/CnBuildiqEditButton.vue'
305
305
  import CnRegisterMapping from '../CnRegisterMapping/CnRegisterMapping.vue'
306
+ import CnToggleTableWidget from '../CnToggleTableWidget/CnToggleTableWidget.vue'
306
307
  import CnVersionInfoCard from '../CnVersionInfoCard/CnVersionInfoCard.vue'
307
308
  import { useLook } from '../../composables/useLook.js'
308
309
  import { prefixUrl } from '../../utils/headers.js'
@@ -340,6 +341,8 @@ const COMPONENT_DISCRIMINATOR = Symbol('cn-settings-component-widget')
340
341
  const BUILTIN_SETTINGS_WIDGETS = Object.freeze({
341
342
  'version-info': CnVersionInfoCard,
342
343
  'register-mapping': CnRegisterMapping,
344
+ // Rows with a switch: objects (`bind.mode: 'each'`) or a config map (`'map'`).
345
+ 'toggle-table': CnToggleTableWidget,
343
346
  component: COMPONENT_DISCRIMINATOR,
344
347
  })
345
348
 
@@ -367,7 +370,8 @@ const BUILTIN_SETTINGS_WIDGETS = Object.freeze({
367
370
  * - `widgets[]` — ordered list of widgets. Each entry has a `type`
368
371
  * string and optional `props`. Built-in widget types resolve
369
372
  * first (`version-info` → `CnVersionInfoCard`, `register-mapping`
370
- * → `CnRegisterMapping`); unknown types fall back to the
373
+ * → `CnRegisterMapping`, `toggle-table` → `CnToggleTableWidget`);
374
+ * unknown types fall back to the
371
375
  * `customComponents` registry.
372
376
  *
373
377
  * Widget events (e.g. CnRegisterMapping's `@save`, CnVersionInfoCard's
@@ -59,8 +59,15 @@
59
59
  class="cn-stages-widget__bar-label"
60
60
  :title="stage.hint || stage.label"
61
61
  :aria-disabled="stage.disabled && stage.clickable ? 'true' : null"
62
+ :aria-busy="movingTo === stage.id ? 'true' : null"
62
63
  :data-testid="`cn-stages-widget-stage-${stage.id}`"
63
64
  @click="onBarClick(stage)">
65
+ <NcLoadingIcon
66
+ v-if="movingTo === stage.id"
67
+ class="cn-stages-widget__moving"
68
+ :size="16"
69
+ :name="movingMessage"
70
+ data-testid="cn-stages-widget-moving" />
64
71
  {{ stage.label }}<span v-if="stage.state === 'current'" class="cn-stages-widget__sr-only"> ({{ tr('current step') }})</span>
65
72
  </component>
66
73
  <span v-if="stage.date" class="cn-stages-widget__bar-date">{{ stage.date }}</span>
@@ -83,7 +90,19 @@
83
90
  @stageClick="onStageClick"
84
91
  @stageBlocked="onStageBlocked">
85
92
  <template #label="{ stage }">
86
- <span class="cn-timeline-stages__label" :data-testid="`cn-stages-widget-stage-${stage.id}`">
93
+ <span
94
+ class="cn-timeline-stages__label"
95
+ :aria-busy="movingTo === stage.id ? 'true' : null"
96
+ :data-testid="`cn-stages-widget-stage-${stage.id}`">
97
+ <!-- The move in flight. A transition can take seconds, and
98
+ a strip that shows nothing until it returns reads as
99
+ one that never moved. -->
100
+ <NcLoadingIcon
101
+ v-if="movingTo === stage.id"
102
+ class="cn-stages-widget__moving"
103
+ :size="16"
104
+ :name="movingMessage"
105
+ data-testid="cn-stages-widget-moving" />
87
106
  {{ stage.label }}
88
107
  </span>
89
108
  <span v-if="stage.subtitle" class="cn-timeline-stages__subtitle">
@@ -156,8 +175,11 @@
156
175
  v-if="pendingAction"
157
176
  :transition="pendingAction"
158
177
  :schema="schema"
178
+ :error="inputError"
179
+ :fieldErrors="inputFieldErrors"
180
+ :busy="busy"
159
181
  @confirm="onInputConfirm"
160
- @close="pendingAction = null" />
182
+ @close="closeInputDialog" />
161
183
  </div>
162
184
  </template>
163
185
 
@@ -177,6 +199,7 @@ import {
177
199
  performTransition,
178
200
  readAvailableActions,
179
201
  transitionError,
202
+ transitionFieldErrors,
180
203
  } from '../../composables/useLifecycleTransitions.js'
181
204
  import { normalizeLook } from '../../composables/useLook.js'
182
205
  import { useObjectStore } from '../../store/useObjectStore.js'
@@ -534,6 +557,17 @@ export default {
534
557
  pendingAction: null,
535
558
  /** @type {boolean} Whether a move is running. */
536
559
  busy: false,
560
+ /**
561
+ * @type {string} The stage a running move leads to, or ''. Drives
562
+ * the loading indicator on that one stage.
563
+ */
564
+ movingTo: '',
565
+ /** @type {string} A refusal shown inside the open input dialog. */
566
+ inputError: '',
567
+ /** @type {string[]} The input keys that refusal named. */
568
+ inputFieldErrors: [],
569
+ /** @type {string[]} The input keys the last refused move named. */
570
+ lastFieldErrors: [],
537
571
  /** @type {string} Why the last move failed, or ''. */
538
572
  moveError: '',
539
573
  /**
@@ -766,6 +800,20 @@ export default {
766
800
  return this.interactive && !this.busy
767
801
  },
768
802
 
803
+ /**
804
+ * What the stage a move is running to says while it runs: the
805
+ * loading indicator's name and the live region's sentence.
806
+ *
807
+ * @return {string} The sentence, or '' when nothing is moving.
808
+ */
809
+ movingMessage() {
810
+ if (!this.movingTo) {
811
+ return ''
812
+ }
813
+ const stage = this.stages.find((s) => s.id === this.movingTo)
814
+ return this.tr('Moving to {stage}', { stage: stage ? stage.label : this.movingTo })
815
+ },
816
+
769
817
  /**
770
818
  * The allowed moves by the stage they lead to, or null while they are
771
819
  * unknown.
@@ -1225,13 +1273,44 @@ export default {
1225
1273
  * @param {object} data The collected input values.
1226
1274
  * @return {void}
1227
1275
  */
1228
- onInputConfirm(data) {
1276
+ async onInputConfirm(data) {
1229
1277
  const pending = this.pendingAction
1230
- this.pendingAction = null
1231
1278
  if (!pending || !pending.__request) {
1279
+ this.closeInputDialog()
1280
+ return
1281
+ }
1282
+ this.inputError = ''
1283
+ this.inputFieldErrors = []
1284
+ // THE DIALOG STAYS OPEN WHILE THE MOVE RUNS, with its confirm
1285
+ // button busy. Closing it first left the person looking at an
1286
+ // unchanged strip for as long as the transition took, which on a
1287
+ // live case was long enough to reload the page thinking it had
1288
+ // not been sent.
1289
+ const moved = await this.performMove(pending.__request, data)
1290
+ if (this.pendingAction !== pending) {
1291
+ // Closed while the move ran; nothing left to update.
1232
1292
  return
1233
1293
  }
1234
- this.performMove(pending.__request, data)
1294
+ if (moved) {
1295
+ this.closeInputDialog()
1296
+ return
1297
+ }
1298
+ // Refused. The sentence goes into the dialog the person is looking
1299
+ // at, with what they typed still in it.
1300
+ this.inputError = this.moveError
1301
+ this.inputFieldErrors = this.lastFieldErrors
1302
+ this.moveError = ''
1303
+ },
1304
+
1305
+ /**
1306
+ * Close the input dialog and forget the refusal it was showing.
1307
+ *
1308
+ * @return {void}
1309
+ */
1310
+ closeInputDialog() {
1311
+ this.pendingAction = null
1312
+ this.inputError = ''
1313
+ this.inputFieldErrors = []
1235
1314
  },
1236
1315
 
1237
1316
  /**
@@ -1239,11 +1318,17 @@ export default {
1239
1318
  *
1240
1319
  * @param {{stage: {id: string, label: string}, action: string}} request The move.
1241
1320
  * @param {object} [data] The collected transition inputs, when any.
1242
- * @return {Promise<void>}
1321
+ * @return {Promise<boolean>} Whether the move was made.
1243
1322
  */
1244
1323
  async performMove(request, data) {
1245
1324
  this.busy = true
1325
+ this.movingTo = request.stage.id
1246
1326
  this.moveError = ''
1327
+ this.lastFieldErrors = []
1328
+ // Said as the move starts, not only when it lands: a screen reader
1329
+ // user otherwise hears nothing for as long as the server takes.
1330
+ this.statusMessage = this.movingMessage
1331
+ let moved = false
1247
1332
  try {
1248
1333
  if (this.lifecycleMode) {
1249
1334
  await performTransition(this.recordId, request.action, data)
@@ -1270,11 +1355,16 @@ export default {
1270
1355
  if (this.lifecycleMode) {
1271
1356
  await this.loadActions()
1272
1357
  }
1358
+ moved = true
1273
1359
  } catch (error) {
1274
1360
  this.moveError = transitionError(error, this.tr('The move could not be made'))
1361
+ this.lastFieldErrors = transitionFieldErrors(error)
1362
+ this.statusMessage = this.moveError
1275
1363
  } finally {
1276
1364
  this.busy = false
1365
+ this.movingTo = ''
1277
1366
  }
1367
+ return moved
1278
1368
  },
1279
1369
 
1280
1370
  /**
@@ -1501,6 +1591,13 @@ export default {
1501
1591
  padding: 8px 0;
1502
1592
  }
1503
1593
 
1594
+ /* The stage a running move leads to: a small spinner before its label. */
1595
+ .cn-stages-widget__moving {
1596
+ display: inline-flex;
1597
+ vertical-align: middle;
1598
+ margin-inline-end: 4px;
1599
+ }
1600
+
1504
1601
  .cn-stages-widget__notice {
1505
1602
  margin: 0;
1506
1603
  color: var(--color-text-maxcontrast);