@conduction/nextcloud-vue 2.68.0 → 2.70.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.
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js +25 -0
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +155 -152
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js +77 -0
- package/dist/esm/components/CnAppNav/CnAppNav.vue2.js.map +1 -1
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js +8 -8
- package/dist/esm/components/CnAppNav/CnAppNav.vue3.js.map +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue.js +1 -0
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue.js.map +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js +28 -0
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue2.js.map +1 -1
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js +4 -2
- package/dist/esm/components/CnBreadcrumbs/CnBreadcrumbs.vue3.js.map +1 -1
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js +25 -1
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue3.js +32 -17
- package/dist/esm/components/CnCalendarWidget/CnCalendarWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue2.js +14 -1
- package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue2.js.map +1 -1
- package/dist/esm/components/CnCapabilityTable/CnCapabilityTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js +43 -2
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js +3 -3
- package/dist/esm/components/CnDashboardGrid/CnDashboardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js +45 -35
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js +19 -1
- package/dist/esm/components/CnDashboardPage/CnDashboardPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +35 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +94 -6
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +64 -19
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +28 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +3 -2
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js +12 -0
- package/dist/esm/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js +9 -0
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js +54 -7
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js +26 -8
- package/dist/esm/components/CnHeaderWidget/CnHeaderWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +143 -0
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +68 -25
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue2.js.map +1 -1
- package/dist/esm/components/CnNextStepCard/CnNextStepCard.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +4 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +17 -3
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +25 -24
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStagesWidget/CnStagesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js +29 -2
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js +7 -0
- package/dist/esm/components/CnStatWidget/CnStatWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js +24 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js +1 -1
- package/dist/esm/components/CnStatsBlock/CnStatsBlock.vue3.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js +81 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js +25 -3
- package/dist/esm/components/CnStatsBlockWidget/CnStatsBlockWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js +15 -2
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js +31 -32
- package/dist/esm/components/CnWeekStripWidget/CnWeekStripWidget.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +3 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +3 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/mixins/pageViews.js +278 -11
- package/dist/esm/mixins/pageViews.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +145 -0
- package/dist/esm/store/plugins/dashboardLayouts.js +35 -1
- package/dist/esm/store/plugins/dashboardLayouts.js.map +1 -1
- package/dist/esm/utils/capabilityComparison.js +29 -1
- package/dist/esm/utils/capabilityComparison.js.map +1 -1
- package/dist/esm/utils/formatMetric.js +137 -16
- package/dist/esm/utils/formatMetric.js.map +1 -1
- package/dist/esm/utils/validateManifestV2.compiled.js +2 -2
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +2840 -1477
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +145 -0
- package/l10n/en.json +3 -0
- package/l10n/nl.json +3 -0
- package/package.json +1 -1
- package/src/components/CnActionsBar/CnActionsBar.md +2 -0
- package/src/components/CnActionsBar/CnActionsBar.vue +28 -2
- package/src/components/CnAppNav/CnAppNav.vue +85 -8
- package/src/components/CnBreadcrumbs/CnBreadcrumbs.vue +45 -0
- package/src/components/CnCalendarWidget/CnCalendarWidget.vue +32 -4
- package/src/components/CnCapabilityTable/CnCapabilityTable.vue +14 -0
- package/src/components/CnDashboardGrid/CnDashboardGrid.md +2 -0
- package/src/components/CnDashboardGrid/CnDashboardGrid.vue +54 -2
- package/src/components/CnDashboardPage/CnDashboardPage.md +2 -0
- package/src/components/CnDashboardPage/CnDashboardPage.vue +59 -35
- package/src/components/CnDataTable/CnDataTable.vue +35 -1
- package/src/components/CnDetailPage/CnDetailPage.md +8 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +124 -8
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +30 -1
- package/src/components/CnFeaturesAndRoadmapPage/CnFeaturesAndRoadmapPage.vue +12 -0
- package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +9 -0
- package/src/components/CnHeaderWidget/CnHeaderWidget.vue +95 -8
- package/src/components/CnIndexPage/CnIndexPage.vue +171 -6
- package/src/components/CnNextStepCard/CnNextStepCard.vue +11 -0
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +4 -1
- package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +34 -7
- package/src/components/CnStagesWidget/CnStagesWidget.vue +19 -0
- package/src/components/CnStatWidget/CnStatWidget.vue +38 -2
- package/src/components/CnStatsBlock/CnStatsBlock.md +2 -0
- package/src/components/CnStatsBlock/CnStatsBlock.vue +25 -2
- package/src/components/CnStatsBlockWidget/CnStatsBlockWidget.vue +95 -3
- package/src/components/CnWeekStripWidget/CnWeekStripWidget.vue +24 -3
- package/src/css/detail-page.css +10 -0
- package/src/css/index-page.css +12 -0
- package/src/css/kpi-card.css +15 -0
- package/src/mixins/pageViews.js +277 -10
- package/src/schemas/app-manifest-v2.schema.json +73 -6
- package/src/store/plugins/dashboardLayouts.js +34 -0
- package/src/utils/capabilityComparison.js +28 -0
- package/src/utils/formatMetric.js +143 -15
- package/src/utils/validateManifestV2.compiled.js +2 -2
|
@@ -111,6 +111,15 @@
|
|
|
111
111
|
data-testid="cn-stat-widget-caption">
|
|
112
112
|
{{ resolvedCaption }}
|
|
113
113
|
</span>
|
|
114
|
+
<!-- A second caption line (`content.note`), as LpStart's "3
|
|
115
|
+
deadlines this week" under the count. Static text for now. -->
|
|
116
|
+
<span
|
|
117
|
+
v-if="!displayLoading && !displayError && resolvedNote"
|
|
118
|
+
class="cn-kpi-card__label cn-stat-widget__note cn-kpi-card__caption-line"
|
|
119
|
+
:class="noteVariantClass"
|
|
120
|
+
data-testid="cn-stat-widget-note">
|
|
121
|
+
{{ resolvedNote }}
|
|
122
|
+
</span>
|
|
114
123
|
</div>
|
|
115
124
|
</component>
|
|
116
125
|
</template>
|
|
@@ -663,6 +672,33 @@ export default {
|
|
|
663
672
|
return rules.find((r) => r && this.matchesRule(current, r)) || null
|
|
664
673
|
},
|
|
665
674
|
|
|
675
|
+
/**
|
|
676
|
+
* The second caption line: `content.note` through the host translate
|
|
677
|
+
* function, or '' without it.
|
|
678
|
+
*
|
|
679
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stat-tile-can-carry-a-second-caption-line
|
|
680
|
+
* @return {string}
|
|
681
|
+
*/
|
|
682
|
+
resolvedNote() {
|
|
683
|
+
const note = this.content && this.content.note
|
|
684
|
+
return typeof note === 'string' && note !== '' ? this.effectiveTranslate(note) : ''
|
|
685
|
+
},
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* The colour class of the second caption line (`content.noteVariant`,
|
|
689
|
+
* the same names as `captionVariant`), or '' for the muted default.
|
|
690
|
+
*
|
|
691
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stat-tile-can-carry-a-second-caption-line
|
|
692
|
+
* @return {string}
|
|
693
|
+
*/
|
|
694
|
+
noteVariantClass() {
|
|
695
|
+
const variant = this.content && this.content.noteVariant
|
|
696
|
+
if (!variant || variant === 'default' || !Object.hasOwn(VARIANT_COLORS, variant)) {
|
|
697
|
+
return ''
|
|
698
|
+
}
|
|
699
|
+
return `cn-kpi-card__label--${variant === 'danger' ? 'error' : variant}`
|
|
700
|
+
},
|
|
701
|
+
|
|
666
702
|
/**
|
|
667
703
|
* The caption's colour class: the matching override's
|
|
668
704
|
* `captionVariant`, else `content.captionVariant`, as
|
|
@@ -1312,7 +1348,7 @@ export default {
|
|
|
1312
1348
|
* @return {string}
|
|
1313
1349
|
*/
|
|
1314
1350
|
formattedValue() {
|
|
1315
|
-
return formatMetricValue(this.displayValue, this.content.format, this.configCtx)
|
|
1351
|
+
return formatMetricValue(this.displayValue, this.content.format, this.configCtx, this.objectCtx?.object || null)
|
|
1316
1352
|
},
|
|
1317
1353
|
|
|
1318
1354
|
/**
|
|
@@ -1346,7 +1382,7 @@ export default {
|
|
|
1346
1382
|
return ''
|
|
1347
1383
|
}
|
|
1348
1384
|
const { prefix, suffix, ...rest } = (this.content.format || {})
|
|
1349
|
-
return formatMetricValue(this.limitValue, rest, this.configCtx)
|
|
1385
|
+
return formatMetricValue(this.limitValue, rest, this.configCtx, this.objectCtx?.object || null)
|
|
1350
1386
|
},
|
|
1351
1387
|
|
|
1352
1388
|
/**
|
|
@@ -151,3 +151,5 @@ export default {
|
|
|
151
151
|
| Slot | Scope | Description |
|
|
152
152
|
|---|---|---|
|
|
153
153
|
| `value` | `{ count, formatted }` | Override the prominently-displayed value — render a pre-formatted string (currency, percent, a "—" placeholder, …). `count` is the raw `count` prop; `formatted` is the locale-formatted string the default slot renders. Useful for currency / unit prefixes (e.g. `€{{ formatted }}`). |
|
|
154
|
+
|
|
155
|
+
`layout` (String, default empty): `stacked` is the board's tile, as CnStatWidget `content.layout: "stacked"`: no icon circle, a 14px muted title, the number at 34px/700 and the unit on a muted line under it.
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
v-bind="componentAttrs"
|
|
20
20
|
@click="onClick">
|
|
21
21
|
<!-- Icon -->
|
|
22
|
-
<div v-if="hasIcon" class="cn-kpi-card__icon cn-stats-block__icon" :class="iconClasses">
|
|
22
|
+
<div v-if="hasIcon && !isStacked" class="cn-kpi-card__icon cn-stats-block__icon" :class="iconClasses">
|
|
23
23
|
<slot name="icon">
|
|
24
24
|
<component :is="icon" v-if="icon" :size="iconSize" />
|
|
25
25
|
</slot>
|
|
@@ -249,6 +249,18 @@ export default {
|
|
|
249
249
|
default: false,
|
|
250
250
|
},
|
|
251
251
|
|
|
252
|
+
/**
|
|
253
|
+
* `stacked` is the board's tile, as CnStatWidget `content.layout:
|
|
254
|
+
* "stacked"`: no icon circle, a 14px muted title, the number at
|
|
255
|
+
* 34px/700 in the text colour. Empty (the default) keeps the KPI card.
|
|
256
|
+
*
|
|
257
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look
|
|
258
|
+
*/
|
|
259
|
+
layout: {
|
|
260
|
+
type: String,
|
|
261
|
+
default: '',
|
|
262
|
+
},
|
|
263
|
+
|
|
252
264
|
/**
|
|
253
265
|
* Lay the icon left of the content. No longer needed — this is the
|
|
254
266
|
* canonical card's own layout — and kept so existing callers that pass
|
|
@@ -366,13 +378,24 @@ export default {
|
|
|
366
378
|
return {}
|
|
367
379
|
},
|
|
368
380
|
|
|
381
|
+
/**
|
|
382
|
+
* Whether the block takes the stacked board look (`layout`).
|
|
383
|
+
*
|
|
384
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look
|
|
385
|
+
* @return {boolean}
|
|
386
|
+
*/
|
|
387
|
+
isStacked() {
|
|
388
|
+
return this.layout === 'stacked'
|
|
389
|
+
},
|
|
390
|
+
|
|
369
391
|
rootClasses() {
|
|
370
392
|
return {
|
|
371
393
|
// Canonical + legacy, in pairs: the canonical class carries the
|
|
372
394
|
// look, the legacy one keeps existing app CSS matching.
|
|
373
395
|
// The canonical card is already horizontal, so the legacy pair
|
|
374
396
|
// is emitted for app CSS but carries no look of its own.
|
|
375
|
-
'cn-kpi-card--horizontal': this.horizontal || !this.vertical,
|
|
397
|
+
'cn-kpi-card--horizontal': !this.isStacked && (this.horizontal || !this.vertical),
|
|
398
|
+
'cn-kpi-card--stacked': this.isStacked,
|
|
376
399
|
'cn-stats-block--horizontal': this.horizontal || !this.vertical,
|
|
377
400
|
'cn-kpi-card--vertical': this.vertical,
|
|
378
401
|
'cn-stats-block--vertical': this.vertical,
|
|
@@ -32,9 +32,16 @@
|
|
|
32
32
|
:showZeroCount="showZeroCount"
|
|
33
33
|
:horizontal="horizontal"
|
|
34
34
|
:vertical="vertical"
|
|
35
|
+
:layout="layout"
|
|
35
36
|
:filled="filled"
|
|
36
37
|
:clickable="!!view.route"
|
|
37
|
-
:route="view.route || null"
|
|
38
|
+
:route="view.route || null">
|
|
39
|
+
<!-- An entry with a `format` (or `currency` / `currencyField`)
|
|
40
|
+
shows its value formatted, money in its own currency. -->
|
|
41
|
+
<template v-if="view.formatted !== null" #value>
|
|
42
|
+
{{ view.formatted }}
|
|
43
|
+
</template>
|
|
44
|
+
</CnStatsBlock>
|
|
38
45
|
</template>
|
|
39
46
|
|
|
40
47
|
<!-- Single-source mode (pre-existing interface, unchanged). -->
|
|
@@ -48,9 +55,14 @@
|
|
|
48
55
|
:showZeroCount="showZeroCount"
|
|
49
56
|
:horizontal="horizontal"
|
|
50
57
|
:vertical="vertical"
|
|
58
|
+
:layout="layout"
|
|
51
59
|
:filled="filled"
|
|
52
60
|
:clickable="!!route"
|
|
53
|
-
:route="route"
|
|
61
|
+
:route="route">
|
|
62
|
+
<template v-if="formattedSingle !== null" #value>
|
|
63
|
+
{{ formattedSingle }}
|
|
64
|
+
</template>
|
|
65
|
+
</CnStatsBlock>
|
|
54
66
|
</div>
|
|
55
67
|
</template>
|
|
56
68
|
|
|
@@ -58,6 +70,7 @@
|
|
|
58
70
|
import { subscribe, unsubscribe } from '@nextcloud/event-bus'
|
|
59
71
|
import CnStatsBlock from '../CnStatsBlock/CnStatsBlock.vue'
|
|
60
72
|
import { useDataSource } from '../../composables/useDataSource.js'
|
|
73
|
+
import { formatMetricValue, normalizeMetricFormat, unwrapAppConfig } from '../../utils/formatMetric.js'
|
|
61
74
|
import { dropOptionalUnresolved, hasUnresolvedTokens, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'
|
|
62
75
|
|
|
63
76
|
// The canonical KPI scale (`--cn-kpi-*`) lives in one stylesheet. Imported
|
|
@@ -85,6 +98,15 @@ const PAGE_REFRESH_BUS_CHANNEL = 'cn:page:refresh'
|
|
|
85
98
|
* `field`, token-resolved `filter`) plus per-entry presentation
|
|
86
99
|
* (`title`, `variant`, `countLabel`), an optional `route` deep link, and
|
|
87
100
|
* `hideWhenZero` (the entry is omitted when its resolved count is 0).
|
|
101
|
+
*
|
|
102
|
+
* An entry formats its value with `format` (a style name such as
|
|
103
|
+
* `"currency"`, or `{ style, currency, currencyField, decimals, prefix,
|
|
104
|
+
* suffix }`), as a `type:"stat"` widget's `content.format` does. A money
|
|
105
|
+
* value takes the currency from `currencyField` (a field of the detail
|
|
106
|
+
* page's object, e.g. a contract's own `currency`), then `currency` (a code
|
|
107
|
+
* or `@config.currency`), then the app's reporting currency, then EUR, and
|
|
108
|
+
* is formatted in the user's locale. `currency` / `currencyField` may sit on
|
|
109
|
+
* the entry itself and imply `format: "currency"`.
|
|
88
110
|
* Exactly one of `dataSource` / `entries` must be provided — when
|
|
89
111
|
* `entries` is absent, the single-`dataSource` path renders exactly as
|
|
90
112
|
* before.
|
|
@@ -140,6 +162,11 @@ export default {
|
|
|
140
162
|
* `filter`. Null on pages that don't provide it.
|
|
141
163
|
*/
|
|
142
164
|
cnWorkspaceContext: { default: null },
|
|
165
|
+
/**
|
|
166
|
+
* Page-level app config provided by the page — the reporting
|
|
167
|
+
* currency (`currency`) and `@config.<key>` tokens in `format`.
|
|
168
|
+
*/
|
|
169
|
+
cnAppConfig: { default: () => ({}) },
|
|
143
170
|
},
|
|
144
171
|
|
|
145
172
|
props: {
|
|
@@ -179,6 +206,20 @@ export default {
|
|
|
179
206
|
default: () => [],
|
|
180
207
|
},
|
|
181
208
|
|
|
209
|
+
/**
|
|
210
|
+
* How the single-source value is shown: a style name (`'currency'`,
|
|
211
|
+
* `'number'`, `'percent'`, `'decimal'`, `'duration-hours'`) or
|
|
212
|
+
* `{ style, currency, currencyField, decimals, prefix, suffix }`.
|
|
213
|
+
* Empty (the default) shows the plain localized count. Entries carry
|
|
214
|
+
* their own `format`.
|
|
215
|
+
*
|
|
216
|
+
* @type {string|object|null}
|
|
217
|
+
*/
|
|
218
|
+
format: {
|
|
219
|
+
type: [String, Object],
|
|
220
|
+
default: null,
|
|
221
|
+
},
|
|
222
|
+
|
|
182
223
|
/** Block title (manifest `widgetDef.title`). */
|
|
183
224
|
title: {
|
|
184
225
|
type: String,
|
|
@@ -221,6 +262,17 @@ export default {
|
|
|
221
262
|
default: false,
|
|
222
263
|
},
|
|
223
264
|
|
|
265
|
+
/**
|
|
266
|
+
* `stacked` gives every block the board look (see CnStatsBlock
|
|
267
|
+
* `layout`). Empty (the default) keeps the KPI card.
|
|
268
|
+
*
|
|
269
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-stats-block-can-take-the-stacked-board-look
|
|
270
|
+
*/
|
|
271
|
+
layout: {
|
|
272
|
+
type: String,
|
|
273
|
+
default: '',
|
|
274
|
+
},
|
|
275
|
+
|
|
224
276
|
/**
|
|
225
277
|
* Draw each tile's own grey box. Off by default — the widget is
|
|
226
278
|
* rendered inside a CnWidgetWrapper that already draws a card.
|
|
@@ -329,6 +381,41 @@ export default {
|
|
|
329
381
|
return (typeof c === 'object' && 'value' in c) ? c.value : c
|
|
330
382
|
},
|
|
331
383
|
|
|
384
|
+
/**
|
|
385
|
+
* The page-level app config map (reporting currency, `@config` keys).
|
|
386
|
+
*
|
|
387
|
+
* @return {object}
|
|
388
|
+
*/
|
|
389
|
+
configCtx() {
|
|
390
|
+
return unwrapAppConfig(this.cnAppConfig)
|
|
391
|
+
},
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* The detail page's object, or null on a dashboard. Its fields feed
|
|
395
|
+
* an entry's `currencyField`.
|
|
396
|
+
*
|
|
397
|
+
* @return {object|null}
|
|
398
|
+
*/
|
|
399
|
+
pageObject() {
|
|
400
|
+
const object = this.objectCtx && this.objectCtx.object
|
|
401
|
+
return object && typeof object === 'object' ? object : null
|
|
402
|
+
},
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* The single-source value formatted per `format`, or null to keep
|
|
406
|
+
* the plain count.
|
|
407
|
+
*
|
|
408
|
+
* @return {string|null}
|
|
409
|
+
* @spec openspec/changes/stat-currency-from-object/specs/dashboard-page/spec.md#requirement-a-stats-block-entry-formats-its-value
|
|
410
|
+
*/
|
|
411
|
+
formattedSingle() {
|
|
412
|
+
const fmt = normalizeMetricFormat({ format: this.format })
|
|
413
|
+
if (!fmt || this.loading) {
|
|
414
|
+
return null
|
|
415
|
+
}
|
|
416
|
+
return formatMetricValue(this.resolvedCount, fmt, this.configCtx, this.pageObject)
|
|
417
|
+
},
|
|
418
|
+
|
|
332
419
|
/**
|
|
333
420
|
* Token-resolution context for entry filters, merged from the
|
|
334
421
|
* detail-page object context and the page-level workspace bag.
|
|
@@ -346,14 +433,19 @@ export default {
|
|
|
346
433
|
* its fetched count and loading flag, with `hideWhenZero` entries
|
|
347
434
|
* whose resolved count is 0 omitted.
|
|
348
435
|
*
|
|
349
|
-
* @return {Array<{entry: object, key: string, count: number, loading: boolean}>}
|
|
436
|
+
* @return {Array<{entry: object, key: string, count: number, loading: boolean, formatted: (string|null)}>}
|
|
437
|
+
* @spec openspec/changes/stat-currency-from-object/specs/dashboard-page/spec.md#requirement-a-stats-block-entry-formats-its-value
|
|
350
438
|
*/
|
|
351
439
|
entryViews() {
|
|
352
440
|
return (this.entries || [])
|
|
353
441
|
.map((entry, i) => {
|
|
354
442
|
const raw = this.entryCounts[i]
|
|
355
443
|
const resolved = typeof raw === 'number'
|
|
444
|
+
const fmt = normalizeMetricFormat(entry)
|
|
356
445
|
return {
|
|
446
|
+
// The value as the entry's `format` says, money in the
|
|
447
|
+
// currency the entry names; null keeps the plain count.
|
|
448
|
+
formatted: fmt && resolved ? formatMetricValue(raw, fmt, this.configCtx, this.pageObject) : null,
|
|
357
449
|
entry: entry || {},
|
|
358
450
|
key: `${(entry && entry.register) || ''}/${(entry && entry.schema) || ''}/${i}`,
|
|
359
451
|
count: resolved ? raw : 0,
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
SPDX-License-Identifier: EUPL-1.2
|
|
4
4
|
-->
|
|
5
5
|
<template>
|
|
6
|
-
<div class="cn-week-strip" data-testid="cn-week-strip">
|
|
6
|
+
<div class="cn-week-strip" :class="{ 'cn-week-strip--inset': inset }" data-testid="cn-week-strip">
|
|
7
7
|
<div v-if="loading" class="cn-week-strip__state" role="status">
|
|
8
8
|
<NcLoadingIcon :size="20" />
|
|
9
9
|
<span class="cn-week-strip__state-text">{{ loadingLabel }}</span>
|
|
@@ -118,9 +118,11 @@ export default {
|
|
|
118
118
|
* today), `lateField` a boolean field that marks a record late.
|
|
119
119
|
* `items` are static entries `{ title, meta?, date, route?, href?, late? }`.
|
|
120
120
|
* `emptyText` is the text of a day without items. `weekOffset` moves
|
|
121
|
-
* the strip whole weeks from the current one.
|
|
121
|
+
* the strip whole weeks from the current one. `inset: true` draws the
|
|
122
|
+
* strip inside the board's inset (16px above, 24px at the sides, 22px
|
|
123
|
+
* below) instead of from card edge to card edge.
|
|
122
124
|
*
|
|
123
|
-
* @type {{days?: (5|7), source?: {register?: string, schema?: string, filter?: object, limit?: number}, dateField?: string, titleField?: string, metaFields?: Array<string>, itemRoute?: string, lateWhen?: {op?: string, value?: number}, lateField?: string, items?: Array<{title: string, meta?: string, date: string, route?: (string|object), href?: string, late?: boolean}>, emptyText?: string, weekOffset?: number}}
|
|
125
|
+
* @type {{days?: (5|7), inset?: boolean, source?: {register?: string, schema?: string, filter?: object, limit?: number}, dateField?: string, titleField?: string, metaFields?: Array<string>, itemRoute?: string, lateWhen?: {op?: string, value?: number}, lateField?: string, items?: Array<{title: string, meta?: string, date: string, route?: (string|object), href?: string, late?: boolean}>, emptyText?: string, weekOffset?: number}}
|
|
124
126
|
*/
|
|
125
127
|
content: {
|
|
126
128
|
type: Object,
|
|
@@ -159,6 +161,17 @@ export default {
|
|
|
159
161
|
},
|
|
160
162
|
|
|
161
163
|
computed: {
|
|
164
|
+
/**
|
|
165
|
+
* Whether the widget draws the board's inset (`content.inset`). Off
|
|
166
|
+
* by default, which keeps it edge to edge as before.
|
|
167
|
+
*
|
|
168
|
+
* @spec openspec/changes/zuiddrecht-pixel-gaps-3/specs/zuiddrecht-pixel-gaps-3/spec.md#requirement-a-strip-and-a-stacked-bar-can-take-the-board-inset
|
|
169
|
+
* @return {boolean}
|
|
170
|
+
*/
|
|
171
|
+
inset() {
|
|
172
|
+
return Boolean(this.content && this.content.inset === true)
|
|
173
|
+
},
|
|
174
|
+
|
|
162
175
|
/**
|
|
163
176
|
* The translate function in use.
|
|
164
177
|
*
|
|
@@ -542,6 +555,14 @@ export default {
|
|
|
542
555
|
width: 100%;
|
|
543
556
|
}
|
|
544
557
|
|
|
558
|
+
/* `content.inset: true`: the board's inset inside the card (zuiddrecht-pixel-
|
|
559
|
+
gaps-3). A dashboard renders its widgets flush, so without it the strip ran
|
|
560
|
+
from card edge to card edge. Theme hook: --cn-widget-board-inset. */
|
|
561
|
+
.cn-week-strip--inset {
|
|
562
|
+
box-sizing: border-box;
|
|
563
|
+
padding: var(--cn-widget-board-inset, 16px 24px 22px);
|
|
564
|
+
}
|
|
565
|
+
|
|
545
566
|
.cn-week-strip__state {
|
|
546
567
|
display: flex;
|
|
547
568
|
align-items: center;
|
package/src/css/detail-page.css
CHANGED
|
@@ -46,6 +46,16 @@
|
|
|
46
46
|
row-gap: var(--cn-detail-header-row-gap, 22px);
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
/* That wrap must not take the actions off the title row. With `flex-basis:
|
|
50
|
+
auto` a long title claimed its whole text width, so the actions wrapped
|
|
51
|
+
under it. From a zero basis the title takes what the actions leave and
|
|
52
|
+
wraps (two lines, then an ellipsis) inside it; only on a page too narrow
|
|
53
|
+
for 16rem of title beside the actions do the actions drop a row. */
|
|
54
|
+
.cn-detail-page__header--with-widget > :first-child {
|
|
55
|
+
flex: 1 1 0;
|
|
56
|
+
min-width: min(100%, 16rem);
|
|
57
|
+
}
|
|
58
|
+
|
|
49
59
|
.cn-detail-page__header-widget {
|
|
50
60
|
flex: 1 0 100%;
|
|
51
61
|
min-width: 0;
|
package/src/css/index-page.css
CHANGED
|
@@ -10,6 +10,18 @@
|
|
|
10
10
|
padding: calc(5 * var(--default-grid-baseline));
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
/* The board's header buttons (`headerButtons`) at the end of the title row,
|
|
14
|
+
centred on the title and its count line. They wrap under the title on a
|
|
15
|
+
narrow page. */
|
|
16
|
+
.cn-index-page__header-buttons {
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-wrap: wrap;
|
|
19
|
+
align-items: center;
|
|
20
|
+
align-self: center;
|
|
21
|
+
gap: calc(2 * var(--default-grid-baseline));
|
|
22
|
+
margin-inline-start: auto;
|
|
23
|
+
}
|
|
24
|
+
|
|
13
25
|
.cn-index-page__below-header {
|
|
14
26
|
margin-bottom: calc(4 * var(--default-grid-baseline));
|
|
15
27
|
}
|
package/src/css/kpi-card.css
CHANGED
|
@@ -414,6 +414,21 @@ a.cn-kpi-card--stacked:focus-visible {
|
|
|
414
414
|
text-decoration: none !important;
|
|
415
415
|
}
|
|
416
416
|
|
|
417
|
+
/* A stacked stats block (CnStatsBlock `layout: "stacked"`): its unit
|
|
418
|
+
("decisions") goes on a muted line of its own under the number, where the
|
|
419
|
+
stat tile puts its caption (zuiddrecht-pixel-gaps-3). */
|
|
420
|
+
.cn-kpi-card--stacked .cn-stats-block__count {
|
|
421
|
+
flex-direction: column;
|
|
422
|
+
align-items: flex-start;
|
|
423
|
+
gap: var(--cn-kpi-stacked-gap, 6px);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.cn-kpi-card--stacked .cn-stats-block__count-label {
|
|
427
|
+
font-size: var(--cn-kpi-label-size, 13px);
|
|
428
|
+
font-weight: 500;
|
|
429
|
+
color: var(--color-text-maxcontrast);
|
|
430
|
+
}
|
|
431
|
+
|
|
417
432
|
/* Filled: the card draws its own grey box, for a KPI that is NOT already
|
|
418
433
|
inside a wrapper that draws one. Opt-in via `content.flat: false`. */
|
|
419
434
|
.cn-kpi-card--filled {
|