@conduction/nextcloud-vue 2.139.0 → 2.140.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/CnAppRoot/CnAppRoot.vue2.js +9 -2
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +17 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +2 -2
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js +52 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js +23 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue.js +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js +106 -0
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js +30 -7
- package/dist/esm/components/CnConversationThread/CnConversationWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +59 -3
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js +44 -34
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +28 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js +7 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowser.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js +5 -0
- package/dist/esm/components/CnFilesBrowser/filesBrowserColumns.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +30 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js +28 -3
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectDataWidget/CnObjectDataWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue.js +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js +10 -0
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue2.js.map +1 -1
- package/dist/esm/components/CnRecordListWidget/CnRecordNoteRows.vue3.js.map +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue.js +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js +12 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js +1 -1
- package/dist/esm/components/CnStackedBarWidget/CnStackedBarWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue.js +1 -1
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue2.js +23 -1
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue2.js.map +1 -1
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue3.js +43 -13
- package/dist/esm/components/CnTransitionInputFields/CnTransitionInputFields.vue3.js.map +1 -1
- package/dist/esm/composables/indexSources.js +5 -2
- package/dist/esm/composables/indexSources.js.map +1 -1
- package/dist/esm/composables/useTaskInboxStore.js +4 -3
- package/dist/esm/composables/useTaskInboxStore.js.map +1 -1
- package/dist/esm/l10n/en.json.js +2 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +5 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +466 -371
- package/dist/esm/utils/builtInFormatters.js +23 -1
- package/dist/esm/utils/builtInFormatters.js.map +1 -1
- package/dist/esm/utils/fillTemplate.js +7 -1
- package/dist/esm/utils/fillTemplate.js.map +1 -1
- package/dist/esm/utils/myTeam.js +24 -4
- package/dist/esm/utils/myTeam.js.map +1 -1
- package/dist/esm/utils/resolveFilterTokens.js +5 -4
- package/dist/esm/utils/resolveFilterTokens.js.map +1 -1
- package/dist/esm/utils/transitionInputs.js +43 -2
- package/dist/esm/utils/transitionInputs.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +771 -222
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +466 -371
- package/l10n/en.json +2 -1
- package/l10n/nl.json +5 -1
- package/package.json +1 -1
- package/src/components/CnAppRoot/CnAppRoot.vue +9 -2
- package/src/components/CnCellRenderer/CnCellRenderer.vue +19 -3
- package/src/components/CnChartWidget/CnChartWidget.md +1 -0
- package/src/components/CnChartWidget/CnChartWidget.vue +72 -1
- package/src/components/CnConversationThread/CnConversationWidget.vue +126 -1
- package/src/components/CnFactsWidget/CnFactsWidget.vue +72 -5
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +28 -1
- package/src/components/CnFilesBrowser/filesBrowser.js +7 -1
- package/src/components/CnFilesBrowser/filesBrowserColumns.js +5 -0
- package/src/components/CnIndexPage/CnIndexPage.vue +30 -2
- package/src/components/CnObjectDataWidget/CnObjectDataWidget.vue +28 -3
- package/src/components/CnRecordListWidget/CnRecordNoteRows.vue +10 -0
- package/src/components/CnStackedBarWidget/CnStackedBarWidget.vue +68 -2
- package/src/components/CnTransitionInputFields/CnTransitionInputFields.vue +43 -1
- package/src/composables/indexSources.js +5 -2
- package/src/composables/useTaskInboxStore.js +4 -3
- package/src/css/look-board.css +10 -2
- package/src/css/table.css +13 -0
- package/src/utils/builtInFormatters.js +22 -0
- package/src/utils/fillTemplate.js +7 -1
- package/src/utils/myTeam.js +24 -4
- package/src/utils/resolveFilterTokens.js +5 -4
- package/src/utils/transitionInputs.js +42 -1
|
@@ -12,6 +12,13 @@ import { readPath } from '../../utils/readPath.js';
|
|
|
12
12
|
/** The bus channel every endpoint-bound widget on the page refetches on. */
|
|
13
13
|
const PAGE_REFRESH_CHANNEL = 'cn:page:refresh';
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* The document event a recorded note sends, so a row's leaf (humaniq's
|
|
17
|
+
* hours) can record what it holds for the same object at the same moment.
|
|
18
|
+
* `detail` is the row's leaf props plus `note`.
|
|
19
|
+
*/
|
|
20
|
+
const NOTE_RECORDED_EVENT = 'cn-record-notes:recorded';
|
|
21
|
+
|
|
15
22
|
/**
|
|
16
23
|
* The rows of a record list drawn as notes to record (`presentation:
|
|
17
24
|
* "notes"`, DqDagAfsluiten "Nog open van vandaag"): per row its pill, title,
|
|
@@ -246,6 +253,9 @@ var script = {
|
|
|
246
253
|
try {
|
|
247
254
|
const { default: axios } = await import('@nextcloud/axios');
|
|
248
255
|
await axios.post(prefixUrl(url), { [this.action.bodyField || 'message']: text });
|
|
256
|
+
if (this.leaf) {
|
|
257
|
+
document.dispatchEvent(new CustomEvent(NOTE_RECORDED_EVENT, { detail: { ...this.leafProps(item.row), note: text } }));
|
|
258
|
+
}
|
|
249
259
|
const { [item.key]: done, ...rest } = this.drafts;
|
|
250
260
|
this.drafts = rest;
|
|
251
261
|
if (refresh) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnRecordNoteRows.vue2.js","sources":["../../../../src/components/CnRecordListWidget/CnRecordNoteRows.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-record-notes\" data-testid=\"cn-record-list-notes\">\n\t\t<p class=\"cn-record-notes__ready\" data-testid=\"cn-record-list-notes-ready\">\n\t\t\t<span class=\"cn-record-notes__ready-figure\">{{ readyCount }}</span>\n\t\t\t<span class=\"cn-record-notes__ready-label\">{{ readyLabel }}</span>\n\t\t</p>\n\t\t<div\n\t\t\tv-for=\"item in items\"\n\t\t\t:key=\"item.key\"\n\t\t\tclass=\"cn-record-notes__row\"\n\t\t\tdata-testid=\"cn-record-list-item\">\n\t\t\t<div class=\"cn-record-notes__head\">\n\t\t\t\t<CnStatusBadge\n\t\t\t\t\tv-if=\"item.pill\"\n\t\t\t\t\t:label=\"item.pill.label\"\n\t\t\t\t\t:variant=\"item.pill.variant\"\n\t\t\t\t\tsize=\"small\" />\n\t\t\t\t<component\n\t\t\t\t\t:is=\"item.tag\"\n\t\t\t\t\tv-if=\"item.title\"\n\t\t\t\t\tclass=\"cn-record-notes__title\"\n\t\t\t\t\t:class=\"{ 'cn-record-notes__title--link': item.tag !== 'div' }\"\n\t\t\t\t\tv-bind=\"item.attrs\">\n\t\t\t\t\t{{ item.title }}\n\t\t\t\t</component>\n\t\t\t\t<span v-if=\"item.meta\" class=\"cn-record-notes__meta\">{{ item.meta }}</span>\n\t\t\t\t<span class=\"cn-record-notes__spacer\" />\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"item.due\"\n\t\t\t\t\tclass=\"cn-record-notes__due\"\n\t\t\t\t\t:class=\"`cn-record-notes__due--${dueToneOf(item.due)}`\"\n\t\t\t\t\tdata-testid=\"cn-record-list-due\">{{ item.due.text }}</span>\n\t\t\t</div>\n\t\t\t<div class=\"cn-record-notes__fields\">\n\t\t\t\t<NcTextField\n\t\t\t\t\tclass=\"cn-record-notes__field\"\n\t\t\t\t\t:modelValue=\"drafts[item.key] || ''\"\n\t\t\t\t\t:label=\"fieldLabel\"\n\t\t\t\t\t:labelOutside=\"false\"\n\t\t\t\t\t:placeholder=\"placeholder\"\n\t\t\t\t\t:disabled=\"busy\"\n\t\t\t\t\t:data-testid=\"`cn-record-list-note-${item.key}`\"\n\t\t\t\t\t@update:modelValue=\"setDraft(item.key, $event)\" />\n\t\t\t\t<div v-if=\"leaf && leafProvider\" class=\"cn-record-notes__leaf\" :data-testid=\"`cn-record-list-leaf-${item.key}`\">\n\t\t\t\t\t<CnLeafMountHost\n\t\t\t\t\t\tv-if=\"leafIsMount\"\n\t\t\t\t\t\t:provider=\"leafProvider\"\n\t\t\t\t\t\t:mountProps=\"leafProps(item.row)\" />\n\t\t\t\t\t<component :is=\"leafComponent\" v-else-if=\"leafComponent\" v-bind=\"leafProps(item.row)\" />\n\t\t\t\t</div>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t:disabled=\"busy || !hasDraft(item.key)\"\n\t\t\t\t\t:data-testid=\"`cn-record-list-record-${item.key}`\"\n\t\t\t\t\t@click=\"record(item)\">\n\t\t\t\t\t{{ recordLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t\t<p\n\t\t\t\tv-if=\"leafUnavailable\"\n\t\t\t\tclass=\"cn-record-notes__leaf-missing\"\n\t\t\t\t:data-testid=\"`cn-record-list-leaf-missing-${item.key}`\">\n\t\t\t\t{{ leafUnavailableText }}\n\t\t\t</p>\n\t\t</div>\n\t\t<p v-if=\"error\"\n\t\t\tclass=\"cn-record-notes__error\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-record-list-notes-error\">\n\t\t\t{{ error }}\n\t\t</p>\n\t\t<div v-if=\"recordAll\" class=\"cn-record-notes__all\" data-testid=\"cn-record-list-record-all\">\n\t\t\t<div class=\"cn-record-notes__all-text\">\n\t\t\t\t<h3 v-if=\"recordAll.title\" class=\"cn-record-notes__all-title\">\n\t\t\t\t\t{{ recordAll.title }}\n\t\t\t\t</h3>\n\t\t\t\t<p v-if=\"recordAll.text\" class=\"cn-record-notes__all-sentence\">\n\t\t\t\t\t{{ recordAll.text }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t\t<div class=\"cn-record-notes__all-actions\">\n\t\t\t\t<NcButton v-if=\"recordAll.cancel\" variant=\"secondary\" :to=\"recordAll.cancel.to\">\n\t\t\t\t\t{{ recordAll.cancel.label }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t:disabled=\"busy || readyCount === 0\"\n\t\t\t\t\tdata-testid=\"cn-record-list-record-all-button\"\n\t\t\t\t\t@click=\"recordEvery\">\n\t\t\t\t\t<template v-if=\"recordAll.icon\" #icon>\n\t\t\t\t\t\t<CnIcon :name=\"recordAll.icon\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ recordAll.label }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { emit } from '@nextcloud/event-bus'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcTextField } from '@nextcloud/vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport CnLeafMountHost from '../CnLeafMountHost/CnLeafMountHost.vue'\nimport CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'\nimport { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'\nimport { isAppInstalled } from '../../utils/appInstalled.js'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { readPath } from '../../utils/readPath.js'\n\n/** The bus channel every endpoint-bound widget on the page refetches on. */\nconst PAGE_REFRESH_CHANNEL = 'cn:page:refresh'\n\n/**\n * The rows of a record list drawn as notes to record (`presentation:\n * \"notes\"`, DqDagAfsluiten \"Nog open van vandaag\"): per row its pill, title,\n * meta and due text, a field for what happened, an optional integration leaf\n * (humaniq's hours) and a button that records the note on the row's own\n * notes endpoint. What is typed and not yet recorded stays in this browser\n * (session storage) and is counted as the \"ready to record\" figure; one\n * button records every filled row. After a note is recorded the page's\n * endpoint widgets refetch, so the row moves to whatever lists the server\n * keeps of recorded work.\n *\n * @spec openspec/changes/record-list-notes/specs/record-list-notes/spec.md#requirement-a-record-list-can-collect-and-record-a-note-per-row\n */\nexport default {\n\tname: 'CnRecordNoteRows',\n\n\tcomponents: { CnIcon, CnLeafMountHost, CnStatusBadge, NcButton, NcTextField },\n\n\tprops: {\n\t\t/** The list's rows as the record list builds them (`key`, `row`, `tag`, `attrs`, `title`, `meta`, `pill`, `due`). */\n\t\titems: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * `noteAction`, already translated: `{ urlField?, url?, bodyField?,\n\t\t * label, fieldLabel, placeholder?, readyLabel, draftsKey?, errorText? }`.\n\t\t */\n\t\taction: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** `recordAll`, already translated: `{ label, title?, text?, icon?, cancel?: { label, to } }`, or null. */\n\t\trecordAll: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** `rowLeaf`: `{ id, app?, register?, objectIdField, schemaField?, schema?, unavailableText? }`, or null. */\n\t\tleaf: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tsetup() {\n\t\tconst { getById } = useIntegrationRegistry()\n\t\treturn { getProvider: getById }\n\t},\n\n\tdata() {\n\t\treturn { drafts: this.readDrafts(), busy: false, error: '' }\n\t},\n\n\tcomputed: {\n\t\t/** @return {string} The session storage key of this list's drafts. */\n\t\tdraftsKey() {\n\t\t\treturn `cn-record-list-notes:${this.action.draftsKey || 'default'}`\n\t\t},\n\n\t\t/** @return {number} The rows with a note typed and not yet recorded. */\n\t\treadyCount() {\n\t\t\treturn this.items.filter((item) => this.hasDraft(item.key)).length\n\t\t},\n\n\t\t/** @return {string} The label of the ready figure. */\n\t\treadyLabel() {\n\t\t\treturn this.action.readyLabel || t('nextcloud-vue', 'filled in, not recorded yet')\n\t\t},\n\n\t\t/** @return {string} */\n\t\trecordLabel() {\n\t\t\treturn this.action.label || t('nextcloud-vue', 'Record')\n\t\t},\n\n\t\t/** @return {string} */\n\t\tfieldLabel() {\n\t\t\treturn this.action.fieldLabel || t('nextcloud-vue', 'Note')\n\t\t},\n\n\t\t/** @return {string} */\n\t\tplaceholder() {\n\t\t\treturn this.action.placeholder || ''\n\t\t},\n\n\t\t/** @return {object|null} The leaf's integration provider, when one is registered. */\n\t\tleafProvider() {\n\t\t\treturn this.leaf && this.leaf.id ? (this.getProvider(this.leaf.id) || null) : null\n\t\t},\n\n\t\t/** @return {boolean} Whether the leaf mounts itself (`renderMode: \"mount\"`). */\n\t\tleafIsMount() {\n\t\t\tconst provider = this.leafProvider\n\t\t\treturn Boolean(provider) && provider.renderMode === 'mount' && typeof provider.mount === 'function'\n\t\t},\n\n\t\t/** @return {object|null} The leaf's component, in component mode. */\n\t\tleafComponent() {\n\t\t\tconst provider = this.leafProvider\n\t\t\treturn provider ? (provider.tab || provider.widget || null) : null\n\t\t},\n\n\t\t/**\n\t\t * Whether the leaf's app is installed but the leaf is not available, so\n\t\t * the row says so instead of silently showing no field.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tleafUnavailable() {\n\t\t\treturn Boolean(this.leaf && this.leaf.app && !this.leafProvider && isAppInstalled(this.leaf.app))\n\t\t},\n\n\t\t/** @return {string} */\n\t\tleafUnavailableText() {\n\t\t\treturn this.leaf?.unavailableText || t('nextcloud-vue', 'This field is not available here.')\n\t\t},\n\t},\n\n\twatch: {\n\t\tdrafts: {\n\t\t\tdeep: true,\n\t\t\thandler() {\n\t\t\t\tthis.saveDrafts()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * The drafts this browser session kept, by row key.\n\t\t *\n\t\t * @return {Object<string, string>}\n\t\t */\n\t\treadDrafts() {\n\t\t\ttry {\n\t\t\t\tconst raw = window.sessionStorage.getItem(`cn-record-list-notes:${this.action?.draftsKey || 'default'}`)\n\t\t\t\tconst parsed = raw ? JSON.parse(raw) : {}\n\t\t\t\treturn parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}\n\t\t\t} catch {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t},\n\n\t\t/** @return {void} */\n\t\tsaveDrafts() {\n\t\t\ttry {\n\t\t\t\twindow.sessionStorage.setItem(this.draftsKey, JSON.stringify(this.drafts))\n\t\t\t} catch {\n\t\t\t\t// Without session storage the drafts live as long as the page.\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The colour of a due text: a term that ended or ends today in the\n\t\t * error colour, one still to come in the warning colour.\n\t\t *\n\t\t * @param {{tone: string}} due The due text.\n\t\t * @return {string} `error`, `warning` or `muted`.\n\t\t */\n\t\tdueToneOf(due) {\n\t\t\tif (due.tone === 'danger' || due.tone === 'today') {\n\t\t\t\treturn 'error'\n\t\t\t}\n\t\t\treturn due.tone === 'upcoming' ? 'warning' : 'muted'\n\t\t},\n\n\t\t/**\n\t\t * @param {string} key The row key.\n\t\t * @param {string} value The typed note.\n\t\t * @return {void}\n\t\t */\n\t\tsetDraft(key, value) {\n\t\t\tthis.drafts = { ...this.drafts, [key]: String(value ?? '') }\n\t\t},\n\n\t\t/**\n\t\t * @param {string} key The row key.\n\t\t * @return {boolean} Whether the row has a note typed.\n\t\t */\n\t\thasDraft(key) {\n\t\t\treturn String(this.drafts[key] ?? '').trim() !== ''\n\t\t},\n\n\t\t/**\n\t\t * The props the row's leaf gets: the row's object, register and schema.\n\t\t *\n\t\t * @param {object} row The row.\n\t\t * @return {object}\n\t\t */\n\t\tleafProps(row) {\n\t\t\tconst leaf = this.leaf || {}\n\t\t\treturn {\n\t\t\t\tobjectId: String(readPath(row, leaf.objectIdField || 'id') ?? ''),\n\t\t\t\tregister: leaf.register || '',\n\t\t\t\tschema: leaf.schemaField ? String(readPath(row, leaf.schemaField) ?? '') : (leaf.schema || ''),\n\t\t\t\tsurface: 'row',\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The row's notes endpoint: the row's `urlField`, else `url` with\n\t\t * `{field}` filled from the row.\n\t\t *\n\t\t * @param {object} row The row.\n\t\t * @return {string} The URL, or ''.\n\t\t */\n\t\turlOf(row) {\n\t\t\tif (this.action.urlField) {\n\t\t\t\treturn String(readPath(row, this.action.urlField) ?? '')\n\t\t\t}\n\t\t\tconst template = String(this.action.url || '')\n\t\t\treturn template.replace(/\\{([A-Za-z0-9_.]+)\\}/g, (_, path) => encodeURIComponent(String(readPath(row, path) ?? '')))\n\t\t},\n\n\t\t/**\n\t\t * Record one row's note, then let the page refetch.\n\t\t *\n\t\t * @param {object} item The row item.\n\t\t * @param {boolean} [refresh] Whether to refresh the page after it.\n\t\t * @return {Promise<boolean>} Whether it was recorded.\n\t\t */\n\t\tasync record(item, refresh = true) {\n\t\t\tconst text = String(this.drafts[item.key] ?? '').trim()\n\t\t\tconst url = this.urlOf(item.row)\n\t\t\tif (text === '' || url === '') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tthis.busy = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\t\t\t\tawait axios.post(prefixUrl(url), { [this.action.bodyField || 'message']: text })\n\t\t\t\tconst { [item.key]: done, ...rest } = this.drafts\n\t\t\t\tthis.drafts = rest\n\t\t\t\tif (refresh) {\n\t\t\t\t\temit(PAGE_REFRESH_CHANNEL, {})\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t} catch {\n\t\t\t\tthis.error = this.action.errorText || t('nextcloud-vue', 'The note could not be recorded. It is kept here; try again.')\n\t\t\t\treturn false\n\t\t\t} finally {\n\t\t\t\tthis.busy = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Record every row with a note, one after another, then refresh once.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync recordEvery() {\n\t\t\tconst filled = this.items.filter((item) => this.hasDraft(item.key))\n\t\t\tlet any = false\n\t\t\tfor (const item of filled) {\n\t\t\t\tany = (await this.record(item, false)) || any\n\t\t\t}\n\t\t\tif (any) {\n\t\t\t\temit(PAGE_REFRESH_CHANNEL, {})\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-record-notes {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 0;\n}\n\n.cn-record-notes__ready {\n\tdisplay: flex;\n\talign-items: baseline;\n\tgap: 6px;\n\tmargin: 0 0 8px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-record-notes__ready-figure {\n\tfont-weight: 700;\n\tcolor: var(--color-main-text);\n}\n\n.cn-record-notes__row {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 10px;\n\tpadding: 14px 0;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-record-notes__head {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-record-notes__title {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-record-notes__title--link {\n\ttext-decoration: underline;\n}\n\n.cn-record-notes__meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-record-notes__spacer {\n\tflex: 1 1 auto;\n}\n\n.cn-record-notes__due {\n\tfont-weight: 600;\n}\n\n.cn-record-notes__due--error {\n\tcolor: var(--color-error-text);\n}\n\n.cn-record-notes__due--warning {\n\tcolor: var(--color-warning-text);\n}\n\n.cn-record-notes__due--muted {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: 400;\n}\n\n.cn-record-notes__fields {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: flex-end;\n\tgap: 10px;\n}\n\n.cn-record-notes__field {\n\tflex: 1 1 260px;\n}\n\n.cn-record-notes__leaf {\n\tflex: 0 0 auto;\n}\n\n.cn-record-notes__leaf-missing,\n.cn-record-notes__error {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-record-notes__error {\n\tcolor: var(--color-error-text);\n}\n\n.cn-record-notes__all {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tmargin-top: 8px;\n\tpadding-top: 14px;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-record-notes__all-title {\n\tmargin: 0;\n\tfont-size: 16px;\n}\n\n.cn-record-notes__all-sentence {\n\tmargin: 4px 0 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-record-notes__all-actions {\n\tdisplay: flex;\n\tgap: 8px;\n}\n</style>\n"],"names":["CnIcon","CnLeafMountHost","CnStatusBadge","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnRecordNoteRows.vue2.js","sources":["../../../../src/components/CnRecordListWidget/CnRecordNoteRows.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-record-notes\" data-testid=\"cn-record-list-notes\">\n\t\t<p class=\"cn-record-notes__ready\" data-testid=\"cn-record-list-notes-ready\">\n\t\t\t<span class=\"cn-record-notes__ready-figure\">{{ readyCount }}</span>\n\t\t\t<span class=\"cn-record-notes__ready-label\">{{ readyLabel }}</span>\n\t\t</p>\n\t\t<div\n\t\t\tv-for=\"item in items\"\n\t\t\t:key=\"item.key\"\n\t\t\tclass=\"cn-record-notes__row\"\n\t\t\tdata-testid=\"cn-record-list-item\">\n\t\t\t<div class=\"cn-record-notes__head\">\n\t\t\t\t<CnStatusBadge\n\t\t\t\t\tv-if=\"item.pill\"\n\t\t\t\t\t:label=\"item.pill.label\"\n\t\t\t\t\t:variant=\"item.pill.variant\"\n\t\t\t\t\tsize=\"small\" />\n\t\t\t\t<component\n\t\t\t\t\t:is=\"item.tag\"\n\t\t\t\t\tv-if=\"item.title\"\n\t\t\t\t\tclass=\"cn-record-notes__title\"\n\t\t\t\t\t:class=\"{ 'cn-record-notes__title--link': item.tag !== 'div' }\"\n\t\t\t\t\tv-bind=\"item.attrs\">\n\t\t\t\t\t{{ item.title }}\n\t\t\t\t</component>\n\t\t\t\t<span v-if=\"item.meta\" class=\"cn-record-notes__meta\">{{ item.meta }}</span>\n\t\t\t\t<span class=\"cn-record-notes__spacer\" />\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"item.due\"\n\t\t\t\t\tclass=\"cn-record-notes__due\"\n\t\t\t\t\t:class=\"`cn-record-notes__due--${dueToneOf(item.due)}`\"\n\t\t\t\t\tdata-testid=\"cn-record-list-due\">{{ item.due.text }}</span>\n\t\t\t</div>\n\t\t\t<div class=\"cn-record-notes__fields\">\n\t\t\t\t<NcTextField\n\t\t\t\t\tclass=\"cn-record-notes__field\"\n\t\t\t\t\t:modelValue=\"drafts[item.key] || ''\"\n\t\t\t\t\t:label=\"fieldLabel\"\n\t\t\t\t\t:labelOutside=\"false\"\n\t\t\t\t\t:placeholder=\"placeholder\"\n\t\t\t\t\t:disabled=\"busy\"\n\t\t\t\t\t:data-testid=\"`cn-record-list-note-${item.key}`\"\n\t\t\t\t\t@update:modelValue=\"setDraft(item.key, $event)\" />\n\t\t\t\t<div v-if=\"leaf && leafProvider\" class=\"cn-record-notes__leaf\" :data-testid=\"`cn-record-list-leaf-${item.key}`\">\n\t\t\t\t\t<CnLeafMountHost\n\t\t\t\t\t\tv-if=\"leafIsMount\"\n\t\t\t\t\t\t:provider=\"leafProvider\"\n\t\t\t\t\t\t:mountProps=\"leafProps(item.row)\" />\n\t\t\t\t\t<component :is=\"leafComponent\" v-else-if=\"leafComponent\" v-bind=\"leafProps(item.row)\" />\n\t\t\t\t</div>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t:disabled=\"busy || !hasDraft(item.key)\"\n\t\t\t\t\t:data-testid=\"`cn-record-list-record-${item.key}`\"\n\t\t\t\t\t@click=\"record(item)\">\n\t\t\t\t\t{{ recordLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t\t<p\n\t\t\t\tv-if=\"leafUnavailable\"\n\t\t\t\tclass=\"cn-record-notes__leaf-missing\"\n\t\t\t\t:data-testid=\"`cn-record-list-leaf-missing-${item.key}`\">\n\t\t\t\t{{ leafUnavailableText }}\n\t\t\t</p>\n\t\t</div>\n\t\t<p v-if=\"error\"\n\t\t\tclass=\"cn-record-notes__error\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-record-list-notes-error\">\n\t\t\t{{ error }}\n\t\t</p>\n\t\t<div v-if=\"recordAll\" class=\"cn-record-notes__all\" data-testid=\"cn-record-list-record-all\">\n\t\t\t<div class=\"cn-record-notes__all-text\">\n\t\t\t\t<h3 v-if=\"recordAll.title\" class=\"cn-record-notes__all-title\">\n\t\t\t\t\t{{ recordAll.title }}\n\t\t\t\t</h3>\n\t\t\t\t<p v-if=\"recordAll.text\" class=\"cn-record-notes__all-sentence\">\n\t\t\t\t\t{{ recordAll.text }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t\t<div class=\"cn-record-notes__all-actions\">\n\t\t\t\t<NcButton v-if=\"recordAll.cancel\" variant=\"secondary\" :to=\"recordAll.cancel.to\">\n\t\t\t\t\t{{ recordAll.cancel.label }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t:disabled=\"busy || readyCount === 0\"\n\t\t\t\t\tdata-testid=\"cn-record-list-record-all-button\"\n\t\t\t\t\t@click=\"recordEvery\">\n\t\t\t\t\t<template v-if=\"recordAll.icon\" #icon>\n\t\t\t\t\t\t<CnIcon :name=\"recordAll.icon\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ recordAll.label }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { emit } from '@nextcloud/event-bus'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcTextField } from '@nextcloud/vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport CnLeafMountHost from '../CnLeafMountHost/CnLeafMountHost.vue'\nimport CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'\nimport { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'\nimport { isAppInstalled } from '../../utils/appInstalled.js'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { readPath } from '../../utils/readPath.js'\n\n/** The bus channel every endpoint-bound widget on the page refetches on. */\nconst PAGE_REFRESH_CHANNEL = 'cn:page:refresh'\n\n/**\n * The document event a recorded note sends, so a row's leaf (humaniq's\n * hours) can record what it holds for the same object at the same moment.\n * `detail` is the row's leaf props plus `note`.\n */\nconst NOTE_RECORDED_EVENT = 'cn-record-notes:recorded'\n\n/**\n * The rows of a record list drawn as notes to record (`presentation:\n * \"notes\"`, DqDagAfsluiten \"Nog open van vandaag\"): per row its pill, title,\n * meta and due text, a field for what happened, an optional integration leaf\n * (humaniq's hours) and a button that records the note on the row's own\n * notes endpoint. What is typed and not yet recorded stays in this browser\n * (session storage) and is counted as the \"ready to record\" figure; one\n * button records every filled row. After a note is recorded the page's\n * endpoint widgets refetch, so the row moves to whatever lists the server\n * keeps of recorded work.\n *\n * @spec openspec/changes/record-list-notes/specs/record-list-notes/spec.md#requirement-a-record-list-can-collect-and-record-a-note-per-row\n */\nexport default {\n\tname: 'CnRecordNoteRows',\n\n\tcomponents: { CnIcon, CnLeafMountHost, CnStatusBadge, NcButton, NcTextField },\n\n\tprops: {\n\t\t/** The list's rows as the record list builds them (`key`, `row`, `tag`, `attrs`, `title`, `meta`, `pill`, `due`). */\n\t\titems: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * `noteAction`, already translated: `{ urlField?, url?, bodyField?,\n\t\t * label, fieldLabel, placeholder?, readyLabel, draftsKey?, errorText? }`.\n\t\t */\n\t\taction: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** `recordAll`, already translated: `{ label, title?, text?, icon?, cancel?: { label, to } }`, or null. */\n\t\trecordAll: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** `rowLeaf`: `{ id, app?, register?, objectIdField, schemaField?, schema?, unavailableText? }`, or null. */\n\t\tleaf: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tsetup() {\n\t\tconst { getById } = useIntegrationRegistry()\n\t\treturn { getProvider: getById }\n\t},\n\n\tdata() {\n\t\treturn { drafts: this.readDrafts(), busy: false, error: '' }\n\t},\n\n\tcomputed: {\n\t\t/** @return {string} The session storage key of this list's drafts. */\n\t\tdraftsKey() {\n\t\t\treturn `cn-record-list-notes:${this.action.draftsKey || 'default'}`\n\t\t},\n\n\t\t/** @return {number} The rows with a note typed and not yet recorded. */\n\t\treadyCount() {\n\t\t\treturn this.items.filter((item) => this.hasDraft(item.key)).length\n\t\t},\n\n\t\t/** @return {string} The label of the ready figure. */\n\t\treadyLabel() {\n\t\t\treturn this.action.readyLabel || t('nextcloud-vue', 'filled in, not recorded yet')\n\t\t},\n\n\t\t/** @return {string} */\n\t\trecordLabel() {\n\t\t\treturn this.action.label || t('nextcloud-vue', 'Record')\n\t\t},\n\n\t\t/** @return {string} */\n\t\tfieldLabel() {\n\t\t\treturn this.action.fieldLabel || t('nextcloud-vue', 'Note')\n\t\t},\n\n\t\t/** @return {string} */\n\t\tplaceholder() {\n\t\t\treturn this.action.placeholder || ''\n\t\t},\n\n\t\t/** @return {object|null} The leaf's integration provider, when one is registered. */\n\t\tleafProvider() {\n\t\t\treturn this.leaf && this.leaf.id ? (this.getProvider(this.leaf.id) || null) : null\n\t\t},\n\n\t\t/** @return {boolean} Whether the leaf mounts itself (`renderMode: \"mount\"`). */\n\t\tleafIsMount() {\n\t\t\tconst provider = this.leafProvider\n\t\t\treturn Boolean(provider) && provider.renderMode === 'mount' && typeof provider.mount === 'function'\n\t\t},\n\n\t\t/** @return {object|null} The leaf's component, in component mode. */\n\t\tleafComponent() {\n\t\t\tconst provider = this.leafProvider\n\t\t\treturn provider ? (provider.tab || provider.widget || null) : null\n\t\t},\n\n\t\t/**\n\t\t * Whether the leaf's app is installed but the leaf is not available, so\n\t\t * the row says so instead of silently showing no field.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tleafUnavailable() {\n\t\t\treturn Boolean(this.leaf && this.leaf.app && !this.leafProvider && isAppInstalled(this.leaf.app))\n\t\t},\n\n\t\t/** @return {string} */\n\t\tleafUnavailableText() {\n\t\t\treturn this.leaf?.unavailableText || t('nextcloud-vue', 'This field is not available here.')\n\t\t},\n\t},\n\n\twatch: {\n\t\tdrafts: {\n\t\t\tdeep: true,\n\t\t\thandler() {\n\t\t\t\tthis.saveDrafts()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * The drafts this browser session kept, by row key.\n\t\t *\n\t\t * @return {Object<string, string>}\n\t\t */\n\t\treadDrafts() {\n\t\t\ttry {\n\t\t\t\tconst raw = window.sessionStorage.getItem(`cn-record-list-notes:${this.action?.draftsKey || 'default'}`)\n\t\t\t\tconst parsed = raw ? JSON.parse(raw) : {}\n\t\t\t\treturn parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}\n\t\t\t} catch {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t},\n\n\t\t/** @return {void} */\n\t\tsaveDrafts() {\n\t\t\ttry {\n\t\t\t\twindow.sessionStorage.setItem(this.draftsKey, JSON.stringify(this.drafts))\n\t\t\t} catch {\n\t\t\t\t// Without session storage the drafts live as long as the page.\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The colour of a due text: a term that ended or ends today in the\n\t\t * error colour, one still to come in the warning colour.\n\t\t *\n\t\t * @param {{tone: string}} due The due text.\n\t\t * @return {string} `error`, `warning` or `muted`.\n\t\t */\n\t\tdueToneOf(due) {\n\t\t\tif (due.tone === 'danger' || due.tone === 'today') {\n\t\t\t\treturn 'error'\n\t\t\t}\n\t\t\treturn due.tone === 'upcoming' ? 'warning' : 'muted'\n\t\t},\n\n\t\t/**\n\t\t * @param {string} key The row key.\n\t\t * @param {string} value The typed note.\n\t\t * @return {void}\n\t\t */\n\t\tsetDraft(key, value) {\n\t\t\tthis.drafts = { ...this.drafts, [key]: String(value ?? '') }\n\t\t},\n\n\t\t/**\n\t\t * @param {string} key The row key.\n\t\t * @return {boolean} Whether the row has a note typed.\n\t\t */\n\t\thasDraft(key) {\n\t\t\treturn String(this.drafts[key] ?? '').trim() !== ''\n\t\t},\n\n\t\t/**\n\t\t * The props the row's leaf gets: the row's object, register and schema.\n\t\t *\n\t\t * @param {object} row The row.\n\t\t * @return {object}\n\t\t */\n\t\tleafProps(row) {\n\t\t\tconst leaf = this.leaf || {}\n\t\t\treturn {\n\t\t\t\tobjectId: String(readPath(row, leaf.objectIdField || 'id') ?? ''),\n\t\t\t\tregister: leaf.register || '',\n\t\t\t\tschema: leaf.schemaField ? String(readPath(row, leaf.schemaField) ?? '') : (leaf.schema || ''),\n\t\t\t\tsurface: 'row',\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The row's notes endpoint: the row's `urlField`, else `url` with\n\t\t * `{field}` filled from the row.\n\t\t *\n\t\t * @param {object} row The row.\n\t\t * @return {string} The URL, or ''.\n\t\t */\n\t\turlOf(row) {\n\t\t\tif (this.action.urlField) {\n\t\t\t\treturn String(readPath(row, this.action.urlField) ?? '')\n\t\t\t}\n\t\t\tconst template = String(this.action.url || '')\n\t\t\treturn template.replace(/\\{([A-Za-z0-9_.]+)\\}/g, (_, path) => encodeURIComponent(String(readPath(row, path) ?? '')))\n\t\t},\n\n\t\t/**\n\t\t * Record one row's note, then let the page refetch.\n\t\t *\n\t\t * @param {object} item The row item.\n\t\t * @param {boolean} [refresh] Whether to refresh the page after it.\n\t\t * @return {Promise<boolean>} Whether it was recorded.\n\t\t */\n\t\tasync record(item, refresh = true) {\n\t\t\tconst text = String(this.drafts[item.key] ?? '').trim()\n\t\t\tconst url = this.urlOf(item.row)\n\t\t\tif (text === '' || url === '') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tthis.busy = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\t\t\t\tawait axios.post(prefixUrl(url), { [this.action.bodyField || 'message']: text })\n\t\t\t\tif (this.leaf) {\n\t\t\t\t\tdocument.dispatchEvent(new CustomEvent(NOTE_RECORDED_EVENT, { detail: { ...this.leafProps(item.row), note: text } }))\n\t\t\t\t}\n\t\t\t\tconst { [item.key]: done, ...rest } = this.drafts\n\t\t\t\tthis.drafts = rest\n\t\t\t\tif (refresh) {\n\t\t\t\t\temit(PAGE_REFRESH_CHANNEL, {})\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t} catch {\n\t\t\t\tthis.error = this.action.errorText || t('nextcloud-vue', 'The note could not be recorded. It is kept here; try again.')\n\t\t\t\treturn false\n\t\t\t} finally {\n\t\t\t\tthis.busy = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Record every row with a note, one after another, then refresh once.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync recordEvery() {\n\t\t\tconst filled = this.items.filter((item) => this.hasDraft(item.key))\n\t\t\tlet any = false\n\t\t\tfor (const item of filled) {\n\t\t\t\tany = (await this.record(item, false)) || any\n\t\t\t}\n\t\t\tif (any) {\n\t\t\t\temit(PAGE_REFRESH_CHANNEL, {})\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-record-notes {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 0;\n}\n\n.cn-record-notes__ready {\n\tdisplay: flex;\n\talign-items: baseline;\n\tgap: 6px;\n\tmargin: 0 0 8px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-record-notes__ready-figure {\n\tfont-weight: 700;\n\tcolor: var(--color-main-text);\n}\n\n.cn-record-notes__row {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 10px;\n\tpadding: 14px 0;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-record-notes__head {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-record-notes__title {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-record-notes__title--link {\n\ttext-decoration: underline;\n}\n\n.cn-record-notes__meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-record-notes__spacer {\n\tflex: 1 1 auto;\n}\n\n.cn-record-notes__due {\n\tfont-weight: 600;\n}\n\n.cn-record-notes__due--error {\n\tcolor: var(--color-error-text);\n}\n\n.cn-record-notes__due--warning {\n\tcolor: var(--color-warning-text);\n}\n\n.cn-record-notes__due--muted {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: 400;\n}\n\n.cn-record-notes__fields {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: flex-end;\n\tgap: 10px;\n}\n\n.cn-record-notes__field {\n\tflex: 1 1 260px;\n}\n\n.cn-record-notes__leaf {\n\tflex: 0 0 auto;\n}\n\n.cn-record-notes__leaf-missing,\n.cn-record-notes__error {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-record-notes__error {\n\tcolor: var(--color-error-text);\n}\n\n.cn-record-notes__all {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tmargin-top: 8px;\n\tpadding-top: 14px;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-record-notes__all-title {\n\tmargin: 0;\n\tfont-size: 16px;\n}\n\n.cn-record-notes__all-sentence {\n\tmargin: 4px 0 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-record-notes__all-actions {\n\tdisplay: flex;\n\tgap: 8px;\n}\n</style>\n"],"names":["CnIcon","CnLeafMountHost","CnStatusBadge","t"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnRecordNoteRows.vue3.js","sources":["../../../../src/components/CnRecordListWidget/CnRecordNoteRows.vue?vue&type=template&id=6d7ff53b&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-record-notes\" data-testid=\"cn-record-list-notes\">\n\t\t<p class=\"cn-record-notes__ready\" data-testid=\"cn-record-list-notes-ready\">\n\t\t\t<span class=\"cn-record-notes__ready-figure\">{{ readyCount }}</span>\n\t\t\t<span class=\"cn-record-notes__ready-label\">{{ readyLabel }}</span>\n\t\t</p>\n\t\t<div\n\t\t\tv-for=\"item in items\"\n\t\t\t:key=\"item.key\"\n\t\t\tclass=\"cn-record-notes__row\"\n\t\t\tdata-testid=\"cn-record-list-item\">\n\t\t\t<div class=\"cn-record-notes__head\">\n\t\t\t\t<CnStatusBadge\n\t\t\t\t\tv-if=\"item.pill\"\n\t\t\t\t\t:label=\"item.pill.label\"\n\t\t\t\t\t:variant=\"item.pill.variant\"\n\t\t\t\t\tsize=\"small\" />\n\t\t\t\t<component\n\t\t\t\t\t:is=\"item.tag\"\n\t\t\t\t\tv-if=\"item.title\"\n\t\t\t\t\tclass=\"cn-record-notes__title\"\n\t\t\t\t\t:class=\"{ 'cn-record-notes__title--link': item.tag !== 'div' }\"\n\t\t\t\t\tv-bind=\"item.attrs\">\n\t\t\t\t\t{{ item.title }}\n\t\t\t\t</component>\n\t\t\t\t<span v-if=\"item.meta\" class=\"cn-record-notes__meta\">{{ item.meta }}</span>\n\t\t\t\t<span class=\"cn-record-notes__spacer\" />\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"item.due\"\n\t\t\t\t\tclass=\"cn-record-notes__due\"\n\t\t\t\t\t:class=\"`cn-record-notes__due--${dueToneOf(item.due)}`\"\n\t\t\t\t\tdata-testid=\"cn-record-list-due\">{{ item.due.text }}</span>\n\t\t\t</div>\n\t\t\t<div class=\"cn-record-notes__fields\">\n\t\t\t\t<NcTextField\n\t\t\t\t\tclass=\"cn-record-notes__field\"\n\t\t\t\t\t:modelValue=\"drafts[item.key] || ''\"\n\t\t\t\t\t:label=\"fieldLabel\"\n\t\t\t\t\t:labelOutside=\"false\"\n\t\t\t\t\t:placeholder=\"placeholder\"\n\t\t\t\t\t:disabled=\"busy\"\n\t\t\t\t\t:data-testid=\"`cn-record-list-note-${item.key}`\"\n\t\t\t\t\t@update:modelValue=\"setDraft(item.key, $event)\" />\n\t\t\t\t<div v-if=\"leaf && leafProvider\" class=\"cn-record-notes__leaf\" :data-testid=\"`cn-record-list-leaf-${item.key}`\">\n\t\t\t\t\t<CnLeafMountHost\n\t\t\t\t\t\tv-if=\"leafIsMount\"\n\t\t\t\t\t\t:provider=\"leafProvider\"\n\t\t\t\t\t\t:mountProps=\"leafProps(item.row)\" />\n\t\t\t\t\t<component :is=\"leafComponent\" v-else-if=\"leafComponent\" v-bind=\"leafProps(item.row)\" />\n\t\t\t\t</div>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t:disabled=\"busy || !hasDraft(item.key)\"\n\t\t\t\t\t:data-testid=\"`cn-record-list-record-${item.key}`\"\n\t\t\t\t\t@click=\"record(item)\">\n\t\t\t\t\t{{ recordLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t\t<p\n\t\t\t\tv-if=\"leafUnavailable\"\n\t\t\t\tclass=\"cn-record-notes__leaf-missing\"\n\t\t\t\t:data-testid=\"`cn-record-list-leaf-missing-${item.key}`\">\n\t\t\t\t{{ leafUnavailableText }}\n\t\t\t</p>\n\t\t</div>\n\t\t<p v-if=\"error\"\n\t\t\tclass=\"cn-record-notes__error\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-record-list-notes-error\">\n\t\t\t{{ error }}\n\t\t</p>\n\t\t<div v-if=\"recordAll\" class=\"cn-record-notes__all\" data-testid=\"cn-record-list-record-all\">\n\t\t\t<div class=\"cn-record-notes__all-text\">\n\t\t\t\t<h3 v-if=\"recordAll.title\" class=\"cn-record-notes__all-title\">\n\t\t\t\t\t{{ recordAll.title }}\n\t\t\t\t</h3>\n\t\t\t\t<p v-if=\"recordAll.text\" class=\"cn-record-notes__all-sentence\">\n\t\t\t\t\t{{ recordAll.text }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t\t<div class=\"cn-record-notes__all-actions\">\n\t\t\t\t<NcButton v-if=\"recordAll.cancel\" variant=\"secondary\" :to=\"recordAll.cancel.to\">\n\t\t\t\t\t{{ recordAll.cancel.label }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t:disabled=\"busy || readyCount === 0\"\n\t\t\t\t\tdata-testid=\"cn-record-list-record-all-button\"\n\t\t\t\t\t@click=\"recordEvery\">\n\t\t\t\t\t<template v-if=\"recordAll.icon\" #icon>\n\t\t\t\t\t\t<CnIcon :name=\"recordAll.icon\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ recordAll.label }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { emit } from '@nextcloud/event-bus'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcTextField } from '@nextcloud/vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport CnLeafMountHost from '../CnLeafMountHost/CnLeafMountHost.vue'\nimport CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'\nimport { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'\nimport { isAppInstalled } from '../../utils/appInstalled.js'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { readPath } from '../../utils/readPath.js'\n\n/** The bus channel every endpoint-bound widget on the page refetches on. */\nconst PAGE_REFRESH_CHANNEL = 'cn:page:refresh'\n\n/**\n * The rows of a record list drawn as notes to record (`presentation:\n * \"notes\"`, DqDagAfsluiten \"Nog open van vandaag\"): per row its pill, title,\n * meta and due text, a field for what happened, an optional integration leaf\n * (humaniq's hours) and a button that records the note on the row's own\n * notes endpoint. What is typed and not yet recorded stays in this browser\n * (session storage) and is counted as the \"ready to record\" figure; one\n * button records every filled row. After a note is recorded the page's\n * endpoint widgets refetch, so the row moves to whatever lists the server\n * keeps of recorded work.\n *\n * @spec openspec/changes/record-list-notes/specs/record-list-notes/spec.md#requirement-a-record-list-can-collect-and-record-a-note-per-row\n */\nexport default {\n\tname: 'CnRecordNoteRows',\n\n\tcomponents: { CnIcon, CnLeafMountHost, CnStatusBadge, NcButton, NcTextField },\n\n\tprops: {\n\t\t/** The list's rows as the record list builds them (`key`, `row`, `tag`, `attrs`, `title`, `meta`, `pill`, `due`). */\n\t\titems: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * `noteAction`, already translated: `{ urlField?, url?, bodyField?,\n\t\t * label, fieldLabel, placeholder?, readyLabel, draftsKey?, errorText? }`.\n\t\t */\n\t\taction: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** `recordAll`, already translated: `{ label, title?, text?, icon?, cancel?: { label, to } }`, or null. */\n\t\trecordAll: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** `rowLeaf`: `{ id, app?, register?, objectIdField, schemaField?, schema?, unavailableText? }`, or null. */\n\t\tleaf: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tsetup() {\n\t\tconst { getById } = useIntegrationRegistry()\n\t\treturn { getProvider: getById }\n\t},\n\n\tdata() {\n\t\treturn { drafts: this.readDrafts(), busy: false, error: '' }\n\t},\n\n\tcomputed: {\n\t\t/** @return {string} The session storage key of this list's drafts. */\n\t\tdraftsKey() {\n\t\t\treturn `cn-record-list-notes:${this.action.draftsKey || 'default'}`\n\t\t},\n\n\t\t/** @return {number} The rows with a note typed and not yet recorded. */\n\t\treadyCount() {\n\t\t\treturn this.items.filter((item) => this.hasDraft(item.key)).length\n\t\t},\n\n\t\t/** @return {string} The label of the ready figure. */\n\t\treadyLabel() {\n\t\t\treturn this.action.readyLabel || t('nextcloud-vue', 'filled in, not recorded yet')\n\t\t},\n\n\t\t/** @return {string} */\n\t\trecordLabel() {\n\t\t\treturn this.action.label || t('nextcloud-vue', 'Record')\n\t\t},\n\n\t\t/** @return {string} */\n\t\tfieldLabel() {\n\t\t\treturn this.action.fieldLabel || t('nextcloud-vue', 'Note')\n\t\t},\n\n\t\t/** @return {string} */\n\t\tplaceholder() {\n\t\t\treturn this.action.placeholder || ''\n\t\t},\n\n\t\t/** @return {object|null} The leaf's integration provider, when one is registered. */\n\t\tleafProvider() {\n\t\t\treturn this.leaf && this.leaf.id ? (this.getProvider(this.leaf.id) || null) : null\n\t\t},\n\n\t\t/** @return {boolean} Whether the leaf mounts itself (`renderMode: \"mount\"`). */\n\t\tleafIsMount() {\n\t\t\tconst provider = this.leafProvider\n\t\t\treturn Boolean(provider) && provider.renderMode === 'mount' && typeof provider.mount === 'function'\n\t\t},\n\n\t\t/** @return {object|null} The leaf's component, in component mode. */\n\t\tleafComponent() {\n\t\t\tconst provider = this.leafProvider\n\t\t\treturn provider ? (provider.tab || provider.widget || null) : null\n\t\t},\n\n\t\t/**\n\t\t * Whether the leaf's app is installed but the leaf is not available, so\n\t\t * the row says so instead of silently showing no field.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tleafUnavailable() {\n\t\t\treturn Boolean(this.leaf && this.leaf.app && !this.leafProvider && isAppInstalled(this.leaf.app))\n\t\t},\n\n\t\t/** @return {string} */\n\t\tleafUnavailableText() {\n\t\t\treturn this.leaf?.unavailableText || t('nextcloud-vue', 'This field is not available here.')\n\t\t},\n\t},\n\n\twatch: {\n\t\tdrafts: {\n\t\t\tdeep: true,\n\t\t\thandler() {\n\t\t\t\tthis.saveDrafts()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * The drafts this browser session kept, by row key.\n\t\t *\n\t\t * @return {Object<string, string>}\n\t\t */\n\t\treadDrafts() {\n\t\t\ttry {\n\t\t\t\tconst raw = window.sessionStorage.getItem(`cn-record-list-notes:${this.action?.draftsKey || 'default'}`)\n\t\t\t\tconst parsed = raw ? JSON.parse(raw) : {}\n\t\t\t\treturn parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}\n\t\t\t} catch {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t},\n\n\t\t/** @return {void} */\n\t\tsaveDrafts() {\n\t\t\ttry {\n\t\t\t\twindow.sessionStorage.setItem(this.draftsKey, JSON.stringify(this.drafts))\n\t\t\t} catch {\n\t\t\t\t// Without session storage the drafts live as long as the page.\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The colour of a due text: a term that ended or ends today in the\n\t\t * error colour, one still to come in the warning colour.\n\t\t *\n\t\t * @param {{tone: string}} due The due text.\n\t\t * @return {string} `error`, `warning` or `muted`.\n\t\t */\n\t\tdueToneOf(due) {\n\t\t\tif (due.tone === 'danger' || due.tone === 'today') {\n\t\t\t\treturn 'error'\n\t\t\t}\n\t\t\treturn due.tone === 'upcoming' ? 'warning' : 'muted'\n\t\t},\n\n\t\t/**\n\t\t * @param {string} key The row key.\n\t\t * @param {string} value The typed note.\n\t\t * @return {void}\n\t\t */\n\t\tsetDraft(key, value) {\n\t\t\tthis.drafts = { ...this.drafts, [key]: String(value ?? '') }\n\t\t},\n\n\t\t/**\n\t\t * @param {string} key The row key.\n\t\t * @return {boolean} Whether the row has a note typed.\n\t\t */\n\t\thasDraft(key) {\n\t\t\treturn String(this.drafts[key] ?? '').trim() !== ''\n\t\t},\n\n\t\t/**\n\t\t * The props the row's leaf gets: the row's object, register and schema.\n\t\t *\n\t\t * @param {object} row The row.\n\t\t * @return {object}\n\t\t */\n\t\tleafProps(row) {\n\t\t\tconst leaf = this.leaf || {}\n\t\t\treturn {\n\t\t\t\tobjectId: String(readPath(row, leaf.objectIdField || 'id') ?? ''),\n\t\t\t\tregister: leaf.register || '',\n\t\t\t\tschema: leaf.schemaField ? String(readPath(row, leaf.schemaField) ?? '') : (leaf.schema || ''),\n\t\t\t\tsurface: 'row',\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The row's notes endpoint: the row's `urlField`, else `url` with\n\t\t * `{field}` filled from the row.\n\t\t *\n\t\t * @param {object} row The row.\n\t\t * @return {string} The URL, or ''.\n\t\t */\n\t\turlOf(row) {\n\t\t\tif (this.action.urlField) {\n\t\t\t\treturn String(readPath(row, this.action.urlField) ?? '')\n\t\t\t}\n\t\t\tconst template = String(this.action.url || '')\n\t\t\treturn template.replace(/\\{([A-Za-z0-9_.]+)\\}/g, (_, path) => encodeURIComponent(String(readPath(row, path) ?? '')))\n\t\t},\n\n\t\t/**\n\t\t * Record one row's note, then let the page refetch.\n\t\t *\n\t\t * @param {object} item The row item.\n\t\t * @param {boolean} [refresh] Whether to refresh the page after it.\n\t\t * @return {Promise<boolean>} Whether it was recorded.\n\t\t */\n\t\tasync record(item, refresh = true) {\n\t\t\tconst text = String(this.drafts[item.key] ?? '').trim()\n\t\t\tconst url = this.urlOf(item.row)\n\t\t\tif (text === '' || url === '') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tthis.busy = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\t\t\t\tawait axios.post(prefixUrl(url), { [this.action.bodyField || 'message']: text })\n\t\t\t\tconst { [item.key]: done, ...rest } = this.drafts\n\t\t\t\tthis.drafts = rest\n\t\t\t\tif (refresh) {\n\t\t\t\t\temit(PAGE_REFRESH_CHANNEL, {})\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t} catch {\n\t\t\t\tthis.error = this.action.errorText || t('nextcloud-vue', 'The note could not be recorded. It is kept here; try again.')\n\t\t\t\treturn false\n\t\t\t} finally {\n\t\t\t\tthis.busy = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Record every row with a note, one after another, then refresh once.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync recordEvery() {\n\t\t\tconst filled = this.items.filter((item) => this.hasDraft(item.key))\n\t\t\tlet any = false\n\t\t\tfor (const item of filled) {\n\t\t\t\tany = (await this.record(item, false)) || any\n\t\t\t}\n\t\t\tif (any) {\n\t\t\t\temit(PAGE_REFRESH_CHANNEL, {})\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-record-notes {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 0;\n}\n\n.cn-record-notes__ready {\n\tdisplay: flex;\n\talign-items: baseline;\n\tgap: 6px;\n\tmargin: 0 0 8px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-record-notes__ready-figure {\n\tfont-weight: 700;\n\tcolor: var(--color-main-text);\n}\n\n.cn-record-notes__row {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 10px;\n\tpadding: 14px 0;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-record-notes__head {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-record-notes__title {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-record-notes__title--link {\n\ttext-decoration: underline;\n}\n\n.cn-record-notes__meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-record-notes__spacer {\n\tflex: 1 1 auto;\n}\n\n.cn-record-notes__due {\n\tfont-weight: 600;\n}\n\n.cn-record-notes__due--error {\n\tcolor: var(--color-error-text);\n}\n\n.cn-record-notes__due--warning {\n\tcolor: var(--color-warning-text);\n}\n\n.cn-record-notes__due--muted {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: 400;\n}\n\n.cn-record-notes__fields {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: flex-end;\n\tgap: 10px;\n}\n\n.cn-record-notes__field {\n\tflex: 1 1 260px;\n}\n\n.cn-record-notes__leaf {\n\tflex: 0 0 auto;\n}\n\n.cn-record-notes__leaf-missing,\n.cn-record-notes__error {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-record-notes__error {\n\tcolor: var(--color-error-text);\n}\n\n.cn-record-notes__all {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tmargin-top: 8px;\n\tpadding-top: 14px;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-record-notes__all-title {\n\tmargin: 0;\n\tfont-size: 16px;\n}\n\n.cn-record-notes__all-sentence {\n\tmargin: 4px 0 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-record-notes__all-actions {\n\tdisplay: flex;\n\tgap: 8px;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_Fragment","_renderList","_createBlock","_resolveDynamicComponent","_mergeProps","_createTextVNode","_createVNode"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnRecordNoteRows.vue3.js","sources":["../../../../src/components/CnRecordListWidget/CnRecordNoteRows.vue?vue&type=template&id=56a223c4&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n\n<template>\n\t<div class=\"cn-record-notes\" data-testid=\"cn-record-list-notes\">\n\t\t<p class=\"cn-record-notes__ready\" data-testid=\"cn-record-list-notes-ready\">\n\t\t\t<span class=\"cn-record-notes__ready-figure\">{{ readyCount }}</span>\n\t\t\t<span class=\"cn-record-notes__ready-label\">{{ readyLabel }}</span>\n\t\t</p>\n\t\t<div\n\t\t\tv-for=\"item in items\"\n\t\t\t:key=\"item.key\"\n\t\t\tclass=\"cn-record-notes__row\"\n\t\t\tdata-testid=\"cn-record-list-item\">\n\t\t\t<div class=\"cn-record-notes__head\">\n\t\t\t\t<CnStatusBadge\n\t\t\t\t\tv-if=\"item.pill\"\n\t\t\t\t\t:label=\"item.pill.label\"\n\t\t\t\t\t:variant=\"item.pill.variant\"\n\t\t\t\t\tsize=\"small\" />\n\t\t\t\t<component\n\t\t\t\t\t:is=\"item.tag\"\n\t\t\t\t\tv-if=\"item.title\"\n\t\t\t\t\tclass=\"cn-record-notes__title\"\n\t\t\t\t\t:class=\"{ 'cn-record-notes__title--link': item.tag !== 'div' }\"\n\t\t\t\t\tv-bind=\"item.attrs\">\n\t\t\t\t\t{{ item.title }}\n\t\t\t\t</component>\n\t\t\t\t<span v-if=\"item.meta\" class=\"cn-record-notes__meta\">{{ item.meta }}</span>\n\t\t\t\t<span class=\"cn-record-notes__spacer\" />\n\t\t\t\t<span\n\t\t\t\t\tv-if=\"item.due\"\n\t\t\t\t\tclass=\"cn-record-notes__due\"\n\t\t\t\t\t:class=\"`cn-record-notes__due--${dueToneOf(item.due)}`\"\n\t\t\t\t\tdata-testid=\"cn-record-list-due\">{{ item.due.text }}</span>\n\t\t\t</div>\n\t\t\t<div class=\"cn-record-notes__fields\">\n\t\t\t\t<NcTextField\n\t\t\t\t\tclass=\"cn-record-notes__field\"\n\t\t\t\t\t:modelValue=\"drafts[item.key] || ''\"\n\t\t\t\t\t:label=\"fieldLabel\"\n\t\t\t\t\t:labelOutside=\"false\"\n\t\t\t\t\t:placeholder=\"placeholder\"\n\t\t\t\t\t:disabled=\"busy\"\n\t\t\t\t\t:data-testid=\"`cn-record-list-note-${item.key}`\"\n\t\t\t\t\t@update:modelValue=\"setDraft(item.key, $event)\" />\n\t\t\t\t<div v-if=\"leaf && leafProvider\" class=\"cn-record-notes__leaf\" :data-testid=\"`cn-record-list-leaf-${item.key}`\">\n\t\t\t\t\t<CnLeafMountHost\n\t\t\t\t\t\tv-if=\"leafIsMount\"\n\t\t\t\t\t\t:provider=\"leafProvider\"\n\t\t\t\t\t\t:mountProps=\"leafProps(item.row)\" />\n\t\t\t\t\t<component :is=\"leafComponent\" v-else-if=\"leafComponent\" v-bind=\"leafProps(item.row)\" />\n\t\t\t\t</div>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t:disabled=\"busy || !hasDraft(item.key)\"\n\t\t\t\t\t:data-testid=\"`cn-record-list-record-${item.key}`\"\n\t\t\t\t\t@click=\"record(item)\">\n\t\t\t\t\t{{ recordLabel }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t\t<p\n\t\t\t\tv-if=\"leafUnavailable\"\n\t\t\t\tclass=\"cn-record-notes__leaf-missing\"\n\t\t\t\t:data-testid=\"`cn-record-list-leaf-missing-${item.key}`\">\n\t\t\t\t{{ leafUnavailableText }}\n\t\t\t</p>\n\t\t</div>\n\t\t<p v-if=\"error\"\n\t\t\tclass=\"cn-record-notes__error\"\n\t\t\trole=\"alert\"\n\t\t\tdata-testid=\"cn-record-list-notes-error\">\n\t\t\t{{ error }}\n\t\t</p>\n\t\t<div v-if=\"recordAll\" class=\"cn-record-notes__all\" data-testid=\"cn-record-list-record-all\">\n\t\t\t<div class=\"cn-record-notes__all-text\">\n\t\t\t\t<h3 v-if=\"recordAll.title\" class=\"cn-record-notes__all-title\">\n\t\t\t\t\t{{ recordAll.title }}\n\t\t\t\t</h3>\n\t\t\t\t<p v-if=\"recordAll.text\" class=\"cn-record-notes__all-sentence\">\n\t\t\t\t\t{{ recordAll.text }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t\t<div class=\"cn-record-notes__all-actions\">\n\t\t\t\t<NcButton v-if=\"recordAll.cancel\" variant=\"secondary\" :to=\"recordAll.cancel.to\">\n\t\t\t\t\t{{ recordAll.cancel.label }}\n\t\t\t\t</NcButton>\n\t\t\t\t<NcButton\n\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t:disabled=\"busy || readyCount === 0\"\n\t\t\t\t\tdata-testid=\"cn-record-list-record-all-button\"\n\t\t\t\t\t@click=\"recordEvery\">\n\t\t\t\t\t<template v-if=\"recordAll.icon\" #icon>\n\t\t\t\t\t\t<CnIcon :name=\"recordAll.icon\" :size=\"20\" />\n\t\t\t\t\t</template>\n\t\t\t\t\t{{ recordAll.label }}\n\t\t\t\t</NcButton>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</template>\n\n<script>\nimport { emit } from '@nextcloud/event-bus'\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton, NcTextField } from '@nextcloud/vue'\nimport CnIcon from '../CnIcon/CnIcon.vue'\nimport CnLeafMountHost from '../CnLeafMountHost/CnLeafMountHost.vue'\nimport CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'\nimport { useIntegrationRegistry } from '../../composables/useIntegrationRegistry.js'\nimport { isAppInstalled } from '../../utils/appInstalled.js'\nimport { prefixUrl } from '../../utils/headers.js'\nimport { readPath } from '../../utils/readPath.js'\n\n/** The bus channel every endpoint-bound widget on the page refetches on. */\nconst PAGE_REFRESH_CHANNEL = 'cn:page:refresh'\n\n/**\n * The document event a recorded note sends, so a row's leaf (humaniq's\n * hours) can record what it holds for the same object at the same moment.\n * `detail` is the row's leaf props plus `note`.\n */\nconst NOTE_RECORDED_EVENT = 'cn-record-notes:recorded'\n\n/**\n * The rows of a record list drawn as notes to record (`presentation:\n * \"notes\"`, DqDagAfsluiten \"Nog open van vandaag\"): per row its pill, title,\n * meta and due text, a field for what happened, an optional integration leaf\n * (humaniq's hours) and a button that records the note on the row's own\n * notes endpoint. What is typed and not yet recorded stays in this browser\n * (session storage) and is counted as the \"ready to record\" figure; one\n * button records every filled row. After a note is recorded the page's\n * endpoint widgets refetch, so the row moves to whatever lists the server\n * keeps of recorded work.\n *\n * @spec openspec/changes/record-list-notes/specs/record-list-notes/spec.md#requirement-a-record-list-can-collect-and-record-a-note-per-row\n */\nexport default {\n\tname: 'CnRecordNoteRows',\n\n\tcomponents: { CnIcon, CnLeafMountHost, CnStatusBadge, NcButton, NcTextField },\n\n\tprops: {\n\t\t/** The list's rows as the record list builds them (`key`, `row`, `tag`, `attrs`, `title`, `meta`, `pill`, `due`). */\n\t\titems: {\n\t\t\ttype: Array,\n\t\t\tdefault: () => [],\n\t\t},\n\n\t\t/**\n\t\t * `noteAction`, already translated: `{ urlField?, url?, bodyField?,\n\t\t * label, fieldLabel, placeholder?, readyLabel, draftsKey?, errorText? }`.\n\t\t */\n\t\taction: {\n\t\t\ttype: Object,\n\t\t\trequired: true,\n\t\t},\n\n\t\t/** `recordAll`, already translated: `{ label, title?, text?, icon?, cancel?: { label, to } }`, or null. */\n\t\trecordAll: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** `rowLeaf`: `{ id, app?, register?, objectIdField, schemaField?, schema?, unavailableText? }`, or null. */\n\t\tleaf: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tsetup() {\n\t\tconst { getById } = useIntegrationRegistry()\n\t\treturn { getProvider: getById }\n\t},\n\n\tdata() {\n\t\treturn { drafts: this.readDrafts(), busy: false, error: '' }\n\t},\n\n\tcomputed: {\n\t\t/** @return {string} The session storage key of this list's drafts. */\n\t\tdraftsKey() {\n\t\t\treturn `cn-record-list-notes:${this.action.draftsKey || 'default'}`\n\t\t},\n\n\t\t/** @return {number} The rows with a note typed and not yet recorded. */\n\t\treadyCount() {\n\t\t\treturn this.items.filter((item) => this.hasDraft(item.key)).length\n\t\t},\n\n\t\t/** @return {string} The label of the ready figure. */\n\t\treadyLabel() {\n\t\t\treturn this.action.readyLabel || t('nextcloud-vue', 'filled in, not recorded yet')\n\t\t},\n\n\t\t/** @return {string} */\n\t\trecordLabel() {\n\t\t\treturn this.action.label || t('nextcloud-vue', 'Record')\n\t\t},\n\n\t\t/** @return {string} */\n\t\tfieldLabel() {\n\t\t\treturn this.action.fieldLabel || t('nextcloud-vue', 'Note')\n\t\t},\n\n\t\t/** @return {string} */\n\t\tplaceholder() {\n\t\t\treturn this.action.placeholder || ''\n\t\t},\n\n\t\t/** @return {object|null} The leaf's integration provider, when one is registered. */\n\t\tleafProvider() {\n\t\t\treturn this.leaf && this.leaf.id ? (this.getProvider(this.leaf.id) || null) : null\n\t\t},\n\n\t\t/** @return {boolean} Whether the leaf mounts itself (`renderMode: \"mount\"`). */\n\t\tleafIsMount() {\n\t\t\tconst provider = this.leafProvider\n\t\t\treturn Boolean(provider) && provider.renderMode === 'mount' && typeof provider.mount === 'function'\n\t\t},\n\n\t\t/** @return {object|null} The leaf's component, in component mode. */\n\t\tleafComponent() {\n\t\t\tconst provider = this.leafProvider\n\t\t\treturn provider ? (provider.tab || provider.widget || null) : null\n\t\t},\n\n\t\t/**\n\t\t * Whether the leaf's app is installed but the leaf is not available, so\n\t\t * the row says so instead of silently showing no field.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tleafUnavailable() {\n\t\t\treturn Boolean(this.leaf && this.leaf.app && !this.leafProvider && isAppInstalled(this.leaf.app))\n\t\t},\n\n\t\t/** @return {string} */\n\t\tleafUnavailableText() {\n\t\t\treturn this.leaf?.unavailableText || t('nextcloud-vue', 'This field is not available here.')\n\t\t},\n\t},\n\n\twatch: {\n\t\tdrafts: {\n\t\t\tdeep: true,\n\t\t\thandler() {\n\t\t\t\tthis.saveDrafts()\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * The drafts this browser session kept, by row key.\n\t\t *\n\t\t * @return {Object<string, string>}\n\t\t */\n\t\treadDrafts() {\n\t\t\ttry {\n\t\t\t\tconst raw = window.sessionStorage.getItem(`cn-record-list-notes:${this.action?.draftsKey || 'default'}`)\n\t\t\t\tconst parsed = raw ? JSON.parse(raw) : {}\n\t\t\t\treturn parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}\n\t\t\t} catch {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t},\n\n\t\t/** @return {void} */\n\t\tsaveDrafts() {\n\t\t\ttry {\n\t\t\t\twindow.sessionStorage.setItem(this.draftsKey, JSON.stringify(this.drafts))\n\t\t\t} catch {\n\t\t\t\t// Without session storage the drafts live as long as the page.\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The colour of a due text: a term that ended or ends today in the\n\t\t * error colour, one still to come in the warning colour.\n\t\t *\n\t\t * @param {{tone: string}} due The due text.\n\t\t * @return {string} `error`, `warning` or `muted`.\n\t\t */\n\t\tdueToneOf(due) {\n\t\t\tif (due.tone === 'danger' || due.tone === 'today') {\n\t\t\t\treturn 'error'\n\t\t\t}\n\t\t\treturn due.tone === 'upcoming' ? 'warning' : 'muted'\n\t\t},\n\n\t\t/**\n\t\t * @param {string} key The row key.\n\t\t * @param {string} value The typed note.\n\t\t * @return {void}\n\t\t */\n\t\tsetDraft(key, value) {\n\t\t\tthis.drafts = { ...this.drafts, [key]: String(value ?? '') }\n\t\t},\n\n\t\t/**\n\t\t * @param {string} key The row key.\n\t\t * @return {boolean} Whether the row has a note typed.\n\t\t */\n\t\thasDraft(key) {\n\t\t\treturn String(this.drafts[key] ?? '').trim() !== ''\n\t\t},\n\n\t\t/**\n\t\t * The props the row's leaf gets: the row's object, register and schema.\n\t\t *\n\t\t * @param {object} row The row.\n\t\t * @return {object}\n\t\t */\n\t\tleafProps(row) {\n\t\t\tconst leaf = this.leaf || {}\n\t\t\treturn {\n\t\t\t\tobjectId: String(readPath(row, leaf.objectIdField || 'id') ?? ''),\n\t\t\t\tregister: leaf.register || '',\n\t\t\t\tschema: leaf.schemaField ? String(readPath(row, leaf.schemaField) ?? '') : (leaf.schema || ''),\n\t\t\t\tsurface: 'row',\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The row's notes endpoint: the row's `urlField`, else `url` with\n\t\t * `{field}` filled from the row.\n\t\t *\n\t\t * @param {object} row The row.\n\t\t * @return {string} The URL, or ''.\n\t\t */\n\t\turlOf(row) {\n\t\t\tif (this.action.urlField) {\n\t\t\t\treturn String(readPath(row, this.action.urlField) ?? '')\n\t\t\t}\n\t\t\tconst template = String(this.action.url || '')\n\t\t\treturn template.replace(/\\{([A-Za-z0-9_.]+)\\}/g, (_, path) => encodeURIComponent(String(readPath(row, path) ?? '')))\n\t\t},\n\n\t\t/**\n\t\t * Record one row's note, then let the page refetch.\n\t\t *\n\t\t * @param {object} item The row item.\n\t\t * @param {boolean} [refresh] Whether to refresh the page after it.\n\t\t * @return {Promise<boolean>} Whether it was recorded.\n\t\t */\n\t\tasync record(item, refresh = true) {\n\t\t\tconst text = String(this.drafts[item.key] ?? '').trim()\n\t\t\tconst url = this.urlOf(item.row)\n\t\t\tif (text === '' || url === '') {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tthis.busy = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst { default: axios } = await import('@nextcloud/axios')\n\t\t\t\tawait axios.post(prefixUrl(url), { [this.action.bodyField || 'message']: text })\n\t\t\t\tif (this.leaf) {\n\t\t\t\t\tdocument.dispatchEvent(new CustomEvent(NOTE_RECORDED_EVENT, { detail: { ...this.leafProps(item.row), note: text } }))\n\t\t\t\t}\n\t\t\t\tconst { [item.key]: done, ...rest } = this.drafts\n\t\t\t\tthis.drafts = rest\n\t\t\t\tif (refresh) {\n\t\t\t\t\temit(PAGE_REFRESH_CHANNEL, {})\n\t\t\t\t}\n\t\t\t\treturn true\n\t\t\t} catch {\n\t\t\t\tthis.error = this.action.errorText || t('nextcloud-vue', 'The note could not be recorded. It is kept here; try again.')\n\t\t\t\treturn false\n\t\t\t} finally {\n\t\t\t\tthis.busy = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Record every row with a note, one after another, then refresh once.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync recordEvery() {\n\t\t\tconst filled = this.items.filter((item) => this.hasDraft(item.key))\n\t\t\tlet any = false\n\t\t\tfor (const item of filled) {\n\t\t\t\tany = (await this.record(item, false)) || any\n\t\t\t}\n\t\t\tif (any) {\n\t\t\t\temit(PAGE_REFRESH_CHANNEL, {})\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-record-notes {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 0;\n}\n\n.cn-record-notes__ready {\n\tdisplay: flex;\n\talign-items: baseline;\n\tgap: 6px;\n\tmargin: 0 0 8px;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-record-notes__ready-figure {\n\tfont-weight: 700;\n\tcolor: var(--color-main-text);\n}\n\n.cn-record-notes__row {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 10px;\n\tpadding: 14px 0;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-record-notes__head {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-record-notes__title {\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n}\n\n.cn-record-notes__title--link {\n\ttext-decoration: underline;\n}\n\n.cn-record-notes__meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-record-notes__spacer {\n\tflex: 1 1 auto;\n}\n\n.cn-record-notes__due {\n\tfont-weight: 600;\n}\n\n.cn-record-notes__due--error {\n\tcolor: var(--color-error-text);\n}\n\n.cn-record-notes__due--warning {\n\tcolor: var(--color-warning-text);\n}\n\n.cn-record-notes__due--muted {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-weight: 400;\n}\n\n.cn-record-notes__fields {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: flex-end;\n\tgap: 10px;\n}\n\n.cn-record-notes__field {\n\tflex: 1 1 260px;\n}\n\n.cn-record-notes__leaf {\n\tflex: 0 0 auto;\n}\n\n.cn-record-notes__leaf-missing,\n.cn-record-notes__error {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-record-notes__error {\n\tcolor: var(--color-error-text);\n}\n\n.cn-record-notes__all {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tmargin-top: 8px;\n\tpadding-top: 14px;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-record-notes__all-title {\n\tmargin: 0;\n\tfont-size: 16px;\n}\n\n.cn-record-notes__all-sentence {\n\tmargin: 4px 0 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-record-notes__all-actions {\n\tdisplay: flex;\n\tgap: 8px;\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_createElementVNode","_toDisplayString","_Fragment","_renderList","_createBlock","_resolveDynamicComponent","_mergeProps","_createTextVNode","_createVNode"],"mappings":";;;AAWK,EAAA,KAAA,EAAA,iBAAA;AAAA,EAAA,aAAA,EAAA,sBAAA;;;AACA,EAAA,KAAK,EAAC,wBAAY;AAAA,EAAA,aAAA,EAAA,4BAAA;;AACd,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,+BAAE,GAAA;AACR,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,8BAAe,GAAA;AAOtB,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,uBAAK,GAAA;;;AAcQ,EAAA,KAAK,EAAC,uBAAuB;;AAQhD,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,yBAAW,GAAA;;;;;AAiCtB,EAAA,KAAK,EAAC,wBAAwB;AAC9B,EAAA,IAAK,EAAA,OAAO;eACD,EAAC,4BAA4B;;;;AAGnB,EAAA,KAAK,EAAC,sBAAsB;AAAC,EAAA,aAAW,EAAC,2BAAC;;AAC1D,MAAA,WAAA,GAAA,EAAA,KAAK,EAAC,2BAAU,GAAA;;;AACjB,EAAA,KAAA,EAAA,4BAAA;;;;AAGsB,EAAA,KAAK,EAAC,+BAA+B;;AAI1D,MAAA,WAAA,GAAA,EAAA,KAAK,EAAC,8BAAO,GAAA;;;;;;;;;AA/EnB,EAAA,QAAAA,SAAA,EAAA,EAAAC,kBAAA,QAAA;AACC,IAAAC,kBAAA,CAGG,KAHH,UAGG,EAAA;MAFHA,kBAAc,CAAA,MAAA,EAAd,UAAc,EAAAC,eAAA,CAAA,QAAA,CAAA,UAAA,CAAA,EAAA,CAAA,YAAA;MACdD,kBAA2B,CAAA,MAAA,EAA3B,UAA2B,EAAAC,eAAA,CAAA,QAAA,CAAA,UAAA,CAAA,EAAA,CAAA,YAAA;;sBAE3BF,kBA0DK,CAAAG,QAAA,EAAA,IAAA,EAAAC,UAAA,CAzDS,MAAA,CAAA,KAAA,EAAA,CAAP,IAAI,KAAA;2BADXJ,kBA0DK,CAAA,KAAA,EAAA;QAxDH,GAAE,EAAE,IAAI,CAAC,GAAG;aACT,EAAC,sBAAmB;qBACd,EAAC,qBAAiB;;2BAsBvB,iBAAA,EAAA;AAnBE,UAAA,CAAA,IAAI,CAAC,IAAE;4BADdK,WAIgB,CAAA,wBAAA,EAAA;;AAFd,gBAAA,KAAK,EAAE,IAAI,CAAC,IAAG,CAAA,KAAA;AACf,gBAAA,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,OAAO;AAC3B,gBAAA,IAAI,EAAC,OAAO;;;AAGN,UAAA,CAAA,IAAI,CAAA,KAAA;AAFV,eAAAN,SAAA,EAAA,EAAAM,WAAA,CAOWC,uBANN,CAAA,IAAI,CAAC,GAAE,GADZC,UAOW,CAAA;;AAJX,gBAAA,KAAK,EAAC,CAAA,wBAAwB,EACQ,EAAA,8BAAA,EAAA,IAAA,CAAA,GAAA,KAAA,KAAA,EAAA,CAAA;AACnC,eAAA,EAAA,EAAA,OAAA,EAAA,IAAA,EAAA,EAAA,IAAA,CAAA,KAAA,CAAA,EAAA;iCACH,MAAc;AAAX,kBAAAC,eAAA,CAAAN,eAAA,CAAA,IAAI,CAAC,KAAK,CAAA,EAAA,CAAA,YAAA;;;;;AAEF,UAAA,CAAA,IAAI,CAAC,IAAI;+CAAkC,kBAAA,EAAAA,eAAA,CAAA,IAAA,CAAA,IAAA,CAAA,EAAA,CAAA,YAAA;;sDACf,CAAA,MAAA,EAAA,EAAlC,KAAK,EAAC,yBAAyB,EAAA,EAAA,IAAA,EAAA,CAAA,CAAA,cAAA,CAAA;AAE9B,UAAA,CAAA,IAAI,CAAC,GAAG;4BADfF,kBAIoC,CAAA,MAAA,EAAA;;AAFnC,gBAAA,KAAK,kBAAC,sBAAI,EAAA,CAAA,sBAAA,EACmB,kBAAA,CAAA,IAAA,CAAA,GAAA,CAAA,CAAA,CAAA,CAAA,CAAA;AAC7B,gBAAA,aAAW,EAAC,oBAAmB;iCAAI,IAAA,CAAA,GAAA,CAAA,IAAA,CAAA,EAAA,CAAA,mBAAA;;;AAEnC,QAAAC,kBAAA,CAwBI,OAxBJ,UAwBI,EAAA;qBAfgC,CAAA,sBAAA,EAAA;AAPpC,YAAA,KAAK,EAAC,wBAAS;AACd,YAAA,UAAU,EAAE,KAAA,CAAA,MAAM,CAAC,IAAI,CAAC,GAAG,CAAA,IAAA,EAAA;AAC3B,YAAA,KAAK,EAAE,QAAU,CAAA,UAAA;AACjB,YAAA,YAAY,EAAE,KAAK;AACnB,YAAA,WAAW,EAAE,QAAC,CAAA,WAAA;AACd,YAAA,QAAQ,EAAE,KAAI,CAAA,IAAA;YACd,aAAW,EAAA,CAAA,oBAAA,EAAY,IAAA,CAAA,GAAA,CAAA,CAAA;AACvB,YAAA,qBAAiB,aAAE,QAAQ,CAAA,QAAA,CAAC,IAAI,IAAG,EAAA,MAAA,CAAA,CAAA;;AACzB,UAAA,CAAA,MAAA,CAAA,IAAG,IAAI,QAAa,CAAA,YAAA;4BAA/BD,kBAMK,CAAA,KAAA,EAAA;;AAN2B,gBAAA,KAAK,EAAC,uBAAuB;gBAAE,aAAW,EAAA,CAAA,oBAAA,EAAa,IAAA,CAAA,GAAA,CAAA,CAAA;;iBAEhF,QAAA,CAAA,WAAA;kCADPK,WAGqC,CAAA,0BAAA,EAAA;;AADnC,sBAAA,QAAQ,EAAE,QAAM,CAAA,YAAA;AAChB,sBAAA,UAAU,EAAE,QAAA,CAAA,SAAS,CAAC,IAAI,CAAC,GAAG,CAAA;;qBACU,QAAO,CAAA,aAAA;oCAAjDA,WAAiD,CAAAC,uBAAA,CAAjC,QAAa,CAAA,aAAA,CAAA,EAA7BC,UAAiD,CAAA;;;yBAAA,QAAA,CAAA,SAAA,CAAA,IAAA,CAAA,GAAA,CAAA,CAAA,EAAA,IAAA,EAAA,EAAA,kBAAA;;;;UAEjDE,WAMU,CAAA,mBAAA,EAAA;AALV,YAAA,OAAO,EAAC,WAAC;AACR,YAAA,QAAE,EAAA,KAAA,CAAA,IAAA,IAAA,CAAA,iBAAA,CAAA,IAAA,CAAA,GAAA,CAAA;YACJ,aAAA,EAAA,CAAA,sBAAA,EAAA,IAAA,CAAA,GAAA,CAAA,CAAA;YACE,OAAK,EAAA,MAAA,KAAE,QAAM,CAAA,MAAA,CAAC,IAAI,CAAA,CAAA;;6BACnB,MAAiB;8CAAd,QAAW,CAAA,WAAA,CAAA,EAAA,CAAA,YAAA;;;;;SAIZ,QAAA,CAAA,eAAA;0BADJT,kBAKI,CAAA,GAAA,EAAA;;AAHJ,cAAA,KAAM,EAAC,+BAAM;cACX,aAAW,EAAA,CAAA,4BAAA,EAAgB,IAAA,CAAA,GAAA,CAAA,CAAA;+BACzB,QAAO,CAAA,mBAAA,CAAA,EAAA,CAAA,oBAAA,UAAA,CAAA;;;;KAGT,KAAA,CAAA,KAAA;sBAAHA,kBAKI,CAAA,GAAA,EALJ,WAKI,EAAAE,eAAA,CADA,KAAK,CAAA,KAAA,CAAA,EAAA,CAAA,YAAA;;KAEE,MAAS,CAAA,SAAA;;AACjB,UAAAD,kBAAA,CAOI,OAPJ,WAOI,EAAA;AANF,YAAA,CAAA,MAAA,CAAA,SAAA,CAAA,KAAA;AAAH,iBAAAF,SAAA,EAAA,EAAAC,kBAAA,CAEK,IAFL,EAAA,WAEK,EADDE,eAAA,CAAA,MAAA,CAAA,SAAA,CAAA,KAAA,CAAA,EAAA,CAAA,YAAA;;AAEM,YAAA,CAAA,MAAA,CAAA,SAAQ,CAAC,IAAI;AAAvB,iBAAAH,SAAA,EAAA,EAAAC,kBAAA,CAEI,GAFJ,EAAA,WAEI,EADAE,eAAA,CAAA,MAAA,CAAA,SAAS,CAAC,IAAI,CAAA,EAAA,CAAA,YAAA;;;AAGjB,UAAAD,kBAAA,QAAA;AACc,YAAA,CAAA,MAAA,CAAA,SAAU,CAAC,MAAM;8BAA/BI,WAEU,CAAA,mBAAA,EAAA;;yBAFyB,EAAA,WAAA;AAAA,kBAAA,EAAA,EAAA,MAAA,CAAA,SAAA,CAAA,MAAA,CAAA,EAAA;;mCACnC,MAA4B;oDAAzB,MAAS,CAAA,SAAA,CAAC,MAAM,CAAC,KAAK,CAAA,EAAA,CAAA,YAAA;;;;;YAEzBI;AACC,cAAA,OAAM,WAAU;cAChB,QAAQ,EAAC,KAAA,CAAA,IAAA,IAAA,QAAA,CAAA,UAAA,KAAA,CAAA;AACV,cAAA,aAAW,EAAC,kCAAQ;AACnB,cAAA,OAAK,EAAE,QAAC,CAAA,WAAA;;+BAGR;gCAAA;;;;AAFE,cAAA,CAAA,MAAA,CAAA,SAAA,CAAA,IAAA;;0BAAA,MAAA;gCACF,MAAC;sBAADA,WAAC,CAAA,iBAAA,EAAA;wBAAA,IAAA,EAAA,MAAA,CAAA,SAAA,CAAA,IAAA;AAAA,wBAAA,IAAA,EAAA,EAAA;;;;;;;;;;;;;;;"}
|
|
@@ -3,7 +3,7 @@ export { rampColor } from './CnStackedBarWidget.vue2.js';
|
|
|
3
3
|
import { render } from './CnStackedBarWidget.vue3.js';
|
|
4
4
|
|
|
5
5
|
script.render = render;
|
|
6
|
-
script.__scopeId = "data-v-
|
|
6
|
+
script.__scopeId = "data-v-7bd1aead";
|
|
7
7
|
const __sfc_main = script;
|
|
8
8
|
|
|
9
9
|
export { __sfc_main as default };
|
|
@@ -124,7 +124,7 @@ var script = {
|
|
|
124
124
|
* segments to the card (`header` event, `count`), for a chip after the
|
|
125
125
|
* title or a `{count}` in it.
|
|
126
126
|
*
|
|
127
|
-
* @type {{inset?: boolean, headerCount?: boolean, source?: {register?: string, schema?: string, groupBy?: string, filter?: object}, segments?: Array<{key?: string, label: string, value: number}>, order?: Array<string>, labels?: {[key: string]: string}, emptyText?: string}}
|
|
127
|
+
* @type {{inset?: boolean, compact?: boolean, headerCount?: boolean, source?: {register?: string, schema?: string, groupBy?: string, filter?: object}, segments?: Array<{key?: string, label: string, value: number}>, order?: Array<string>, labels?: {[key: string]: string}, emptyText?: string}}
|
|
128
128
|
*/
|
|
129
129
|
content: {
|
|
130
130
|
type: Object,
|
|
@@ -182,6 +182,17 @@ var script = {
|
|
|
182
182
|
return Boolean(this.content && this.content.inset === true)
|
|
183
183
|
},
|
|
184
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Whether the widget is the compact summary box (`content.compact`):
|
|
187
|
+
* a 15px heading, one thin bar and the legend on one line.
|
|
188
|
+
*
|
|
189
|
+
* @spec openspec/changes/stacked-bar-compact/specs/stacked-bar-compact/spec.md#requirement-a-stacked-bar-can-be-drawn-as-the-compact-summary-box
|
|
190
|
+
* @return {boolean}
|
|
191
|
+
*/
|
|
192
|
+
compact() {
|
|
193
|
+
return Boolean(this.content && this.content.compact === true)
|
|
194
|
+
},
|
|
195
|
+
|
|
185
196
|
/**
|
|
186
197
|
* The translate function in use.
|
|
187
198
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnStackedBarWidget.vue2.js","sources":["../../../../src/components/CnStackedBarWidget/CnStackedBarWidget.vue"],"sourcesContent":["<!--\n SPDX-FileCopyrightText: 2026 Conduction B.V.\n SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-stacked-bar\" :class=\"{ 'cn-stacked-bar--inset': inset }\" data-testid=\"cn-stacked-bar\">\n\t\t<div v-if=\"isLoading\" class=\"cn-stacked-bar__state\" role=\"status\">\n\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t<span>{{ loadingLabel }}</span>\n\t\t</div>\n\t\t<p v-else-if=\"loadError\" class=\"cn-stacked-bar__state\" role=\"status\">\n\t\t\t{{ errorLabel }}\n\t\t</p>\n\t\t<!-- The designed empty state the list widgets use, compact: a bare\n\t\t line of text read as a broken widget in an empty card. -->\n\t\t<CnWidgetEmptyState\n\t\t\tv-else-if=\"total === 0\"\n\t\t\t:name=\"emptyLabel\"\n\t\t\tcompact\n\t\t\tclass=\"cn-stacked-bar__empty\"\n\t\t\tdata-testid=\"cn-stacked-bar-empty\" />\n\t\t<template v-else>\n\t\t\t<div v-if=\"heading\" class=\"cn-stacked-bar__heading\">\n\t\t\t\t<strong data-testid=\"cn-stacked-bar-heading\">{{ heading }}</strong>\n\t\t\t\t<span v-if=\"headingMeta\" class=\"cn-stacked-bar__heading-meta\" data-testid=\"cn-stacked-bar-heading-meta\">{{ headingMeta }}</span>\n\t\t\t</div>\n\t\t\t<!-- The bar is a picture of the legend below it. The legend holds\n\t\t\t every label and number, so the bar itself is hidden from\n\t\t\t assistive technology rather than described twice. -->\n\t\t\t<div class=\"cn-stacked-bar__bar\" aria-hidden=\"true\">\n\t\t\t\t<span\n\t\t\t\t\tv-for=\"segment in barSegments\"\n\t\t\t\t\t:key=\"segment.key\"\n\t\t\t\t\tclass=\"cn-stacked-bar__segment\"\n\t\t\t\t\t:style=\"{ flexGrow: segment.count, backgroundColor: segment.color }\" />\n\t\t\t</div>\n\t\t\t<ul class=\"cn-stacked-bar__legend\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"segment in segments\"\n\t\t\t\t\t:key=\"segment.key\"\n\t\t\t\t\tclass=\"cn-stacked-bar__legend-item\"\n\t\t\t\t\tdata-testid=\"cn-stacked-bar-legend-item\">\n\t\t\t\t\t<span class=\"cn-stacked-bar__label\">\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclass=\"cn-stacked-bar__dot\"\n\t\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\t\t:style=\"{ backgroundColor: segment.color }\" />\n\t\t\t\t\t\t{{ segment.label }}\n\t\t\t\t\t</span>\n\t\t\t\t\t<!-- The leading space keeps label and count two words for a\n\t\t\t\t\t screen reader; it is not drawn in this flex column. -->\n\t\t\t\t\t<strong class=\"cn-stacked-bar__count\">{{ ' ' + segment.count }}</strong>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { inject, isRef } from 'vue'\nimport CnWidgetEmptyState from '../CnWidgetEmptyState/CnWidgetEmptyState.vue'\nimport { useEndpointSource } from '../../composables/useEndpointSource.js'\nimport { fetchGroupedCounts } from '../../utils/fetchAggregate.js'\n\n/** The colour of a group's tone (`content.colors`); any other value is used as a CSS colour. */\nconst TONE_COLORS = {\n\tsuccess: 'var(--color-success)',\n\twarning: 'var(--color-warning)',\n\terror: 'var(--color-error)',\n\tinfo: 'var(--color-info, var(--color-primary-element))',\n\tneutral: 'var(--color-background-darker)',\n}\n\n/**\n * The value at a dot path of an object.\n *\n * @param {object} data The object.\n * @param {string} dotPath The path, '' for the object itself.\n * @return {unknown} The value, or undefined.\n */\nfunction pluck(data, dotPath) {\n\tif (!dotPath) {\n\t\treturn data\n\t}\n\treturn String(dotPath).split('.').reduce((node, key) => (node && typeof node === 'object' ? node[key] : undefined), data)\n}\n\n/** The lightest step keeps this share of the primary colour, so it still stands out from the page. */\nconst LIGHTEST_PRIMARY_SHARE = 70\n\n/**\n * The colour of step `index` out of `count`, on one ramp that runs from a\n * light tint of the primary colour, through the primary colour, to the text\n * colour. Steps differ in LIGHTNESS, so they stay apart for somebody who\n * cannot tell hues apart, and the ramp follows the theme because it is built\n * from Nextcloud's own variables.\n *\n * @param {number} index The step, 0-based.\n * @param {number} count The number of steps.\n * @return {string} A CSS colour.\n * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n */\nexport function rampColor(index, count) {\n\tif (count <= 1) {\n\t\treturn 'var(--color-primary-element)'\n\t}\n\tconst position = index / (count - 1)\n\tif (position < 0.5) {\n\t\tconst share = Math.round(LIGHTEST_PRIMARY_SHARE + (position / 0.5) * (100 - LIGHTEST_PRIMARY_SHARE))\n\t\treturn `color-mix(in srgb, var(--color-primary-element) ${share}%, var(--color-main-background))`\n\t}\n\tconst textShare = Math.round(((position - 0.5) / 0.5) * 100)\n\tif (textShare === 0) {\n\t\treturn 'var(--color-primary-element)'\n\t}\n\tif (textShare === 100) {\n\t\treturn 'var(--color-main-text)'\n\t}\n\treturn `color-mix(in srgb, var(--color-main-text) ${textShare}%, var(--color-primary-element))`\n}\n\n/**\n * CnStackedBarWidget shows how a total divides over a few groups: ONE bar cut\n * into segments, with a legend below it that names every segment and gives\n * its count. Use it for \"my cases per step\" or \"requests per channel\".\n *\n * Segments come from one grouped count request against an OpenRegister\n * source (`source.groupBy`), or from static `segments`. `order` fixes the\n * order of the segments, and `labels` gives a group key a readable name.\n * Colours come from one ramp built on the theme's primary colour; the steps\n * differ in lightness, never in hue alone. Resolved by its registry type key\n * `stacked-bar`.\n *\n * ```json\n * {\n * \"widgetKey\": \"stacked-bar\",\n * \"props\": {\n * \"content\": {\n * \"source\": { \"register\": \"dossiq\", \"schema\": \"case\", \"groupBy\": \"status\", \"filter\": { \"assignee\": \"@me\" } },\n * \"order\": [\"received\", \"in_progress\", \"decision\", \"publish\"],\n * \"labels\": { \"received\": \"Received\", \"in_progress\": \"In progress\", \"decision\": \"Decision\", \"publish\": \"Publish\" }\n * }\n * }\n * }\n * ```\n */\nexport default {\n\tname: 'CnStackedBarWidget',\n\n\tcomponents: { CnWidgetEmptyState, NcLoadingIcon },\n\n\tinject: {\n\t\t/**\n\t\t * Host translate function provided by CnAppRoot. Segment labels and\n\t\t * the empty text run through it. Identity by default.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget's configuration. `source` names the OpenRegister\n\t\t * register and schema, the `groupBy` field and an optional `filter`\n\t\t * (the shared @-token grammar). `segments` are static entries\n\t\t * `{ key?, label, value }` used when there is no source. `order` lists\n\t\t * group keys in the order they render; groups it does not list follow\n\t\t * in the order they arrive, and a listed key without records renders\n\t\t * with a count of 0. `labels` maps a group key to its label.\n\t\t * `emptyText` is shown when there is nothing to count. `inset: true`\n\t\t * draws the bar and legend inside the board's inset (16px above, 24px\n\t\t * at the sides, 22px below) instead of from card edge to card edge.\n\t\t * `endpointSource` (`{ url, responsePath? }`, `@objectId` filled from\n\t\t * the page) reads the groups from an app endpoint instead: the counts\n\t\t * at `groupsPath` (a `{ key: count }` map or `[{ key, count }]`) and the\n\t\t * whole at `totalPath`. `colors` maps a group key to a tone (`success`,\n\t\t * `warning`, `error`, `info`, `neutral`) or a CSS colour. `remainder`\n\t\t * (`{ key?, label }`) adds what the groups leave of the whole as a last,\n\t\t * neutral segment. `heading` and `headingMeta` are templates over\n\t\t * `{counted}`, `{total}`, `{remaining}` and the endpoint's own fields,\n\t\t * drawn above the bar. `headerCount: true` reports the sum of the\n\t\t * segments to the card (`header` event, `count`), for a chip after the\n\t\t * title or a `{count}` in it.\n\t\t *\n\t\t * @type {{inset?: boolean, headerCount?: boolean, source?: {register?: string, schema?: string, groupBy?: string, filter?: object}, segments?: Array<{key?: string, label: string, value: number}>, order?: Array<string>, labels?: {[key: string]: string}, emptyText?: string}}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to the injected `cnTranslate`. Pass it\n\t\t * when mounting the widget outside a CnAppRoot.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The record on the page, for the `@objectId` token of `endpointSource`. */\n\t\tobjectId: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['header'],\n\n\tsetup(props) {\n\t\tconst objectCtxRaw = inject('cnObjectContext', null)\n\t\tconst { data, loading, error } = useEndpointSource(() => props.content?.endpointSource || null, {\n\t\t\tctx: () => {\n\t\t\t\tconst ctx = (isRef(objectCtxRaw) ? objectCtxRaw.value : objectCtxRaw) || {}\n\t\t\t\treturn { ...ctx, objectId: props.objectId ?? ctx.objectId ?? null, object: ctx.object || null }\n\t\t\t},\n\t\t})\n\t\treturn { epData: data, epLoading: loading, epError: error }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tgroups: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the widget draws the board's inset (`content.inset`). Off\n\t\t * by default, which keeps it edge to edge as before.\n\t\t *\n\t\t * @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\n\t\t * @return {boolean}\n\t\t */\n\t\tinset() {\n\t\t\treturn Boolean(this.content && this.content.inset === true)\n\t\t},\n\n\t\t/**\n\t\t * The translate function in use.\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\teffectiveTranslate() {\n\t\t\treturn this.translate ?? this.cnTranslate\n\t\t},\n\n\t\t/**\n\t\t * Whether the segments come from OpenRegister.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\thasEndpoint() {\n\t\t\treturn Boolean(this.content.endpointSource && this.content.endpointSource.url)\n\t\t},\n\n\t\t/** @return {boolean} Whether the counts are still loading. */\n\t\tisLoading() {\n\t\t\treturn this.hasEndpoint ? Boolean(this.epLoading) && this.epData === null : this.loading\n\t\t},\n\n\t\t/** @return {string} The load error, or ''. */\n\t\tloadError() {\n\t\t\treturn this.hasEndpoint ? (this.epError || '') : this.error\n\t\t},\n\n\t\t/**\n\t\t * The endpoint's groups as `{ key, count }`, from a `{ key: count }`\n\t\t * map or a `[{ key, count }]` list at `groupsPath`.\n\t\t *\n\t\t * @return {Array<{key: string, count: number}>}\n\t\t */\n\t\tendpointGroups() {\n\t\t\tconst raw = pluck(this.epData, this.content.groupsPath || 'groups')\n\t\t\tconst entries = Array.isArray(raw)\n\t\t\t\t? raw.filter((entry) => entry && typeof entry === 'object').map((entry) => [entry.key, entry.count])\n\t\t\t\t: Object.entries(raw && typeof raw === 'object' ? raw : {})\n\t\t\treturn entries.map(([key, count]) => ({ key: String(key), count: Number(count) > 0 ? Number(count) : 0 }))\n\t\t},\n\n\t\t/**\n\t\t * The whole the groups are part of (`totalPath` of the endpoint), or\n\t\t * null when none is given.\n\t\t *\n\t\t * @return {number|null}\n\t\t */\n\t\twhole() {\n\t\t\tif (!this.hasEndpoint || !this.content.totalPath) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst value = Number(pluck(this.epData, this.content.totalPath))\n\t\t\treturn Number.isFinite(value) && value >= 0 ? value : null\n\t\t},\n\n\t\t/** @return {number} The sum of the groups, without the remainder. */\n\t\tcounted() {\n\t\t\treturn this.rawGroups.reduce((sum, group) => sum + group.count, 0)\n\t\t},\n\n\t\t/** @return {number} What the groups leave of the whole. */\n\t\tremaining() {\n\t\t\treturn this.whole === null ? 0 : Math.max(0, this.whole - this.counted)\n\t\t},\n\n\t\t/**\n\t\t * The values the heading templates read.\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\ttemplateValues() {\n\t\t\tconst own = this.epData && typeof this.epData === 'object' && !Array.isArray(this.epData) ? this.epData : {}\n\t\t\treturn { ...own, counted: this.counted, total: this.whole === null ? this.counted : this.whole, remaining: this.remaining }\n\t\t},\n\n\t\t/** @return {string} The line above the bar, or ''. */\n\t\theading() {\n\t\t\treturn this.fillTemplate(this.content.heading)\n\t\t},\n\n\t\t/** @return {string} The muted line beside the heading, or ''. */\n\t\theadingMeta() {\n\t\t\treturn this.fillTemplate(this.content.headingMeta)\n\t\t},\n\n\t\thasSource() {\n\t\t\tconst s = this.content.source || {}\n\t\t\treturn Boolean(s.register && s.schema && s.groupBy)\n\t\t},\n\n\t\t/**\n\t\t * The raw groups, from the source or the static list, as `{ key, label, count }`.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, count: number}>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\trawGroups() {\n\t\t\tif (this.hasEndpoint) {\n\t\t\t\treturn this.endpointGroups.map((group) => ({ key: group.key, label: group.key, count: group.count }))\n\t\t\t}\n\t\t\tif (this.hasSource) {\n\t\t\t\treturn this.groups.map((group) => ({ key: group.key, label: group.key, count: group.count }))\n\t\t\t}\n\t\t\tconst list = Array.isArray(this.content.segments) ? this.content.segments : []\n\t\t\treturn list\n\t\t\t\t.filter((segment) => segment && typeof segment === 'object')\n\t\t\t\t.map((segment, index) => {\n\t\t\t\t\tconst count = Number(segment.value)\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkey: String(segment.key ?? segment.label ?? index),\n\t\t\t\t\t\tlabel: String(segment.label ?? segment.key ?? ''),\n\t\t\t\t\t\tcount: Number.isFinite(count) && count > 0 ? count : 0,\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The segments in render order, each with its label and colour.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, count: number, color: string}>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\tsegments() {\n\t\t\tconst order = Array.isArray(this.content.order) ? this.content.order.map(String) : []\n\t\t\tconst labels = (this.content.labels && typeof this.content.labels === 'object') ? this.content.labels : {}\n\t\t\tconst byKey = {}\n\t\t\tfor (const group of this.rawGroups) {\n\t\t\t\tbyKey[group.key] = group\n\t\t\t}\n\t\t\tconst ordered = []\n\t\t\tfor (const key of order) {\n\t\t\t\tordered.push(byKey[key] || { key, label: key, count: 0 })\n\t\t\t}\n\t\t\tfor (const group of this.rawGroups) {\n\t\t\t\tif (!order.includes(group.key)) {\n\t\t\t\t\tordered.push(group)\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst colors = (this.content.colors && typeof this.content.colors === 'object') ? this.content.colors : {}\n\t\t\tconst segments = ordered.map((group, index) => ({\n\t\t\t\tkey: group.key,\n\t\t\t\tlabel: this.effectiveTranslate(typeof labels[group.key] === 'string' ? labels[group.key] : group.label),\n\t\t\t\tcount: group.count,\n\t\t\t\tcolor: this.colorOf(colors[group.key]) || rampColor(index, ordered.length),\n\t\t\t}))\n\t\t\tconst remainder = this.content.remainder\n\t\t\tif (remainder && typeof remainder === 'object' && this.remaining > 0) {\n\t\t\t\tsegments.push({\n\t\t\t\t\tkey: String(remainder.key || 'remainder'),\n\t\t\t\t\tlabel: this.effectiveTranslate(String(remainder.label || '')),\n\t\t\t\t\tcount: this.remaining,\n\t\t\t\t\tcolor: this.colorOf(remainder.color) || TONE_COLORS.neutral,\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn segments\n\t\t},\n\n\t\t/**\n\t\t * The segments that take up room in the bar.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\tbarSegments() {\n\t\t\treturn this.segments.filter((segment) => segment.count > 0)\n\t\t},\n\n\t\t/**\n\t\t * The total reported to the card under `headerCount: true`, or null\n\t\t * while loading, after an error or without `headerCount`.\n\t\t *\n\t\t * @return {number|null}\n\t\t * @spec openspec/changes/widget-title-count/specs/widget-title-count/spec.md#requirement-a-widget-title-can-carry-the-total-its-widget-reports\n\t\t */\n\t\theaderTotal() {\n\t\t\tif (this.content.headerCount !== true || this.isLoading || this.loadError) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn this.total\n\t\t},\n\n\t\t/**\n\t\t * The sum of all counts.\n\t\t *\n\t\t * @return {number}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\ttotal() {\n\t\t\treturn this.segments.reduce((sum, segment) => sum + segment.count, 0)\n\t\t},\n\n\t\t/**\n\t\t * The text shown when there is nothing to count.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\temptyLabel() {\n\t\t\tconst text = this.content.emptyText\n\t\t\treturn (typeof text === 'string' && text !== '')\n\t\t\t\t? this.effectiveTranslate(text)\n\t\t\t\t: t('nextcloud-vue', 'Nothing to show yet')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The loading text.\n\t\t *\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\tloadingLabel() {\n\t\t\treturn t('nextcloud-vue', 'Loading…')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The text shown when the counts could not be loaded.\n\t\t *\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\terrorLabel() {\n\t\t\treturn t('nextcloud-vue', 'The numbers could not be loaded.')\n\t\t},\n\n\t\t/**\n\t\t * Changes whenever a new fetch is needed.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\tsourceKey() {\n\t\t\treturn JSON.stringify(this.content.source || {})\n\t\t},\n\t},\n\n\twatch: {\n\t\t/** @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget */\n\t\tsourceKey() {\n\t\t\tthis.fetchGroups()\n\t\t},\n\n\t\t/** @spec openspec/changes/widget-title-count/specs/widget-title-count/spec.md#requirement-a-widget-title-can-carry-the-total-its-widget-reports */\n\t\theaderTotal: {\n\t\t\timmediate: true,\n\t\t\thandler(total) {\n\t\t\t\tif (total !== null) {\n\t\t\t\t\t/**\n\t\t\t\t\t * @event header With `headerCount: true`, once the counts have loaded: `{ link: null, note: '', count }`, the sum of the segments.\n\t\t\t\t\t */\n\t\t\t\t\tthis.$emit('header', { link: null, note: '', count: total })\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.fetchGroups()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * The CSS colour of a tone or colour value.\n\t\t *\n\t\t * @param {string} value A tone name or a CSS colour.\n\t\t * @return {string} The colour, or '' for none.\n\t\t */\n\t\tcolorOf(value) {\n\t\t\tif (typeof value !== 'string' || value === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn TONE_COLORS[value] || value\n\t\t},\n\n\t\t/**\n\t\t * Translate a heading template and fill its `{name}` placeholders.\n\t\t *\n\t\t * @param {string} template The template.\n\t\t * @return {string} The line, or '' without a template.\n\t\t */\n\t\tfillTemplate(template) {\n\t\t\tif (typeof template !== 'string' || template === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst values = this.templateValues\n\t\t\treturn this.effectiveTranslate(template).replace(/\\{(\\w+)\\}/g, (match, name) => (\n\t\t\t\tvalues[name] === undefined || values[name] === null ? '' : String(values[name])\n\t\t\t))\n\t\t},\n\n\t\t/**\n\t\t * Load the count per group in one request.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\tasync fetchGroups() {\n\t\t\tif (!this.hasSource) {\n\t\t\t\tthis.groups = []\n\t\t\t\tthis.error = ''\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst requestKey = this.sourceKey\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst groups = await fetchGroupedCounts(this.content.source)\n\t\t\t\tif (requestKey !== this.sourceKey) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.groups = groups\n\t\t\t} catch (e) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.warn('[CnStackedBarWidget] failed to load counts:', e)\n\t\t\t\tthis.error = (e && e.message) || 'error'\n\t\t\t\tthis.groups = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-stacked-bar {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n\twidth: 100%;\n}\n\n/* `content.inset: true`: the board's inset inside the card (zuiddrecht-pixel-\n gaps-3). A dashboard renders its widgets flush, so without it the strip ran\n from card edge to card edge. Theme hook: --cn-widget-board-inset. */\n.cn-stacked-bar--inset {\n\tbox-sizing: border-box;\n\tpadding: var(--cn-widget-board-inset, 16px 24px 22px);\n}\n\n.cn-stacked-bar__heading {\n\tdisplay: flex;\n\talign-items: baseline;\n\tjustify-content: space-between;\n\tgap: 12px;\n}\n\n.cn-stacked-bar__heading-meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-stacked-bar__state {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-stacked-bar__bar {\n\tdisplay: flex;\n\tgap: 3px;\n\theight: 12px;\n}\n\n.cn-stacked-bar__segment {\n\tflex-basis: 0;\n\tmin-width: 4px;\n\tborder-radius: 6px;\n}\n\n.cn-stacked-bar__legend {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fit, minmax(150px, 1fr));\n\tgap: 12px;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-stacked-bar__legend-item {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-stacked-bar__label {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tfont-size: 0.95em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow-wrap: anywhere;\n}\n\n.cn-stacked-bar__dot {\n\tflex: none;\n\twidth: 10px;\n\theight: 10px;\n\tborder-radius: 3px;\n}\n\n.cn-stacked-bar__count {\n\tfont-size: 1.5em;\n\tfont-weight: 700;\n\tline-height: 1.2;\n\tcolor: var(--color-main-text);\n}\n</style>\n"],"names":["CnWidgetEmptyState","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnStackedBarWidget.vue2.js","sources":["../../../../src/components/CnStackedBarWidget/CnStackedBarWidget.vue"],"sourcesContent":["<!--\n SPDX-FileCopyrightText: 2026 Conduction B.V.\n SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-stacked-bar\" :class=\"{ 'cn-stacked-bar--inset': inset, 'cn-stacked-bar--compact': compact }\" data-testid=\"cn-stacked-bar\">\n\t\t<div v-if=\"isLoading\" class=\"cn-stacked-bar__state\" role=\"status\">\n\t\t\t<NcLoadingIcon :size=\"20\" />\n\t\t\t<span>{{ loadingLabel }}</span>\n\t\t</div>\n\t\t<p v-else-if=\"loadError\" class=\"cn-stacked-bar__state\" role=\"status\">\n\t\t\t{{ errorLabel }}\n\t\t</p>\n\t\t<!-- The designed empty state the list widgets use, compact: a bare\n\t\t line of text read as a broken widget in an empty card. -->\n\t\t<CnWidgetEmptyState\n\t\t\tv-else-if=\"total === 0\"\n\t\t\t:name=\"emptyLabel\"\n\t\t\tcompact\n\t\t\tclass=\"cn-stacked-bar__empty\"\n\t\t\tdata-testid=\"cn-stacked-bar-empty\" />\n\t\t<template v-else>\n\t\t\t<div v-if=\"heading\" class=\"cn-stacked-bar__heading\">\n\t\t\t\t<strong data-testid=\"cn-stacked-bar-heading\">{{ heading }}</strong>\n\t\t\t\t<span v-if=\"headingMeta\" class=\"cn-stacked-bar__heading-meta\" data-testid=\"cn-stacked-bar-heading-meta\">{{ headingMeta }}</span>\n\t\t\t</div>\n\t\t\t<!-- The bar is a picture of the legend below it. The legend holds\n\t\t\t every label and number, so the bar itself is hidden from\n\t\t\t assistive technology rather than described twice. -->\n\t\t\t<div class=\"cn-stacked-bar__bar\" aria-hidden=\"true\">\n\t\t\t\t<span\n\t\t\t\t\tv-for=\"segment in barSegments\"\n\t\t\t\t\t:key=\"segment.key\"\n\t\t\t\t\tclass=\"cn-stacked-bar__segment\"\n\t\t\t\t\t:style=\"{ flexGrow: segment.count, backgroundColor: segment.color }\" />\n\t\t\t</div>\n\t\t\t<ul class=\"cn-stacked-bar__legend\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"segment in segments\"\n\t\t\t\t\t:key=\"segment.key\"\n\t\t\t\t\tclass=\"cn-stacked-bar__legend-item\"\n\t\t\t\t\tdata-testid=\"cn-stacked-bar-legend-item\">\n\t\t\t\t\t<span class=\"cn-stacked-bar__label\">\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclass=\"cn-stacked-bar__dot\"\n\t\t\t\t\t\t\taria-hidden=\"true\"\n\t\t\t\t\t\t\t:style=\"{ backgroundColor: segment.color }\" />\n\t\t\t\t\t\t{{ segment.label }}\n\t\t\t\t\t</span>\n\t\t\t\t\t<!-- The leading space keeps label and count two words for a\n\t\t\t\t\t screen reader; it is not drawn in this flex column. -->\n\t\t\t\t\t<strong class=\"cn-stacked-bar__count\">{{ ' ' + segment.count }}</strong>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { inject, isRef } from 'vue'\nimport CnWidgetEmptyState from '../CnWidgetEmptyState/CnWidgetEmptyState.vue'\nimport { useEndpointSource } from '../../composables/useEndpointSource.js'\nimport { fetchGroupedCounts } from '../../utils/fetchAggregate.js'\n\n/** The colour of a group's tone (`content.colors`); any other value is used as a CSS colour. */\nconst TONE_COLORS = {\n\tsuccess: 'var(--color-success)',\n\twarning: 'var(--color-warning)',\n\terror: 'var(--color-error)',\n\tinfo: 'var(--color-info, var(--color-primary-element))',\n\tneutral: 'var(--color-background-darker)',\n}\n\n/**\n * The value at a dot path of an object.\n *\n * @param {object} data The object.\n * @param {string} dotPath The path, '' for the object itself.\n * @return {unknown} The value, or undefined.\n */\nfunction pluck(data, dotPath) {\n\tif (!dotPath) {\n\t\treturn data\n\t}\n\treturn String(dotPath).split('.').reduce((node, key) => (node && typeof node === 'object' ? node[key] : undefined), data)\n}\n\n/** The lightest step keeps this share of the primary colour, so it still stands out from the page. */\nconst LIGHTEST_PRIMARY_SHARE = 70\n\n/**\n * The colour of step `index` out of `count`, on one ramp that runs from a\n * light tint of the primary colour, through the primary colour, to the text\n * colour. Steps differ in LIGHTNESS, so they stay apart for somebody who\n * cannot tell hues apart, and the ramp follows the theme because it is built\n * from Nextcloud's own variables.\n *\n * @param {number} index The step, 0-based.\n * @param {number} count The number of steps.\n * @return {string} A CSS colour.\n * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n */\nexport function rampColor(index, count) {\n\tif (count <= 1) {\n\t\treturn 'var(--color-primary-element)'\n\t}\n\tconst position = index / (count - 1)\n\tif (position < 0.5) {\n\t\tconst share = Math.round(LIGHTEST_PRIMARY_SHARE + (position / 0.5) * (100 - LIGHTEST_PRIMARY_SHARE))\n\t\treturn `color-mix(in srgb, var(--color-primary-element) ${share}%, var(--color-main-background))`\n\t}\n\tconst textShare = Math.round(((position - 0.5) / 0.5) * 100)\n\tif (textShare === 0) {\n\t\treturn 'var(--color-primary-element)'\n\t}\n\tif (textShare === 100) {\n\t\treturn 'var(--color-main-text)'\n\t}\n\treturn `color-mix(in srgb, var(--color-main-text) ${textShare}%, var(--color-primary-element))`\n}\n\n/**\n * CnStackedBarWidget shows how a total divides over a few groups: ONE bar cut\n * into segments, with a legend below it that names every segment and gives\n * its count. Use it for \"my cases per step\" or \"requests per channel\".\n *\n * Segments come from one grouped count request against an OpenRegister\n * source (`source.groupBy`), or from static `segments`. `order` fixes the\n * order of the segments, and `labels` gives a group key a readable name.\n * Colours come from one ramp built on the theme's primary colour; the steps\n * differ in lightness, never in hue alone. Resolved by its registry type key\n * `stacked-bar`.\n *\n * ```json\n * {\n * \"widgetKey\": \"stacked-bar\",\n * \"props\": {\n * \"content\": {\n * \"source\": { \"register\": \"dossiq\", \"schema\": \"case\", \"groupBy\": \"status\", \"filter\": { \"assignee\": \"@me\" } },\n * \"order\": [\"received\", \"in_progress\", \"decision\", \"publish\"],\n * \"labels\": { \"received\": \"Received\", \"in_progress\": \"In progress\", \"decision\": \"Decision\", \"publish\": \"Publish\" }\n * }\n * }\n * }\n * ```\n */\nexport default {\n\tname: 'CnStackedBarWidget',\n\n\tcomponents: { CnWidgetEmptyState, NcLoadingIcon },\n\n\tinject: {\n\t\t/**\n\t\t * Host translate function provided by CnAppRoot. Segment labels and\n\t\t * the empty text run through it. Identity by default.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget's configuration. `source` names the OpenRegister\n\t\t * register and schema, the `groupBy` field and an optional `filter`\n\t\t * (the shared @-token grammar). `segments` are static entries\n\t\t * `{ key?, label, value }` used when there is no source. `order` lists\n\t\t * group keys in the order they render; groups it does not list follow\n\t\t * in the order they arrive, and a listed key without records renders\n\t\t * with a count of 0. `labels` maps a group key to its label.\n\t\t * `emptyText` is shown when there is nothing to count. `inset: true`\n\t\t * draws the bar and legend inside the board's inset (16px above, 24px\n\t\t * at the sides, 22px below) instead of from card edge to card edge.\n\t\t * `endpointSource` (`{ url, responsePath? }`, `@objectId` filled from\n\t\t * the page) reads the groups from an app endpoint instead: the counts\n\t\t * at `groupsPath` (a `{ key: count }` map or `[{ key, count }]`) and the\n\t\t * whole at `totalPath`. `colors` maps a group key to a tone (`success`,\n\t\t * `warning`, `error`, `info`, `neutral`) or a CSS colour. `remainder`\n\t\t * (`{ key?, label }`) adds what the groups leave of the whole as a last,\n\t\t * neutral segment. `heading` and `headingMeta` are templates over\n\t\t * `{counted}`, `{total}`, `{remaining}` and the endpoint's own fields,\n\t\t * drawn above the bar. `headerCount: true` reports the sum of the\n\t\t * segments to the card (`header` event, `count`), for a chip after the\n\t\t * title or a `{count}` in it.\n\t\t *\n\t\t * @type {{inset?: boolean, compact?: boolean, headerCount?: boolean, source?: {register?: string, schema?: string, groupBy?: string, filter?: object}, segments?: Array<{key?: string, label: string, value: number}>, order?: Array<string>, labels?: {[key: string]: string}, emptyText?: string}}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to the injected `cnTranslate`. Pass it\n\t\t * when mounting the widget outside a CnAppRoot.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The record on the page, for the `@objectId` token of `endpointSource`. */\n\t\tobjectId: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\temits: ['header'],\n\n\tsetup(props) {\n\t\tconst objectCtxRaw = inject('cnObjectContext', null)\n\t\tconst { data, loading, error } = useEndpointSource(() => props.content?.endpointSource || null, {\n\t\t\tctx: () => {\n\t\t\t\tconst ctx = (isRef(objectCtxRaw) ? objectCtxRaw.value : objectCtxRaw) || {}\n\t\t\t\treturn { ...ctx, objectId: props.objectId ?? ctx.objectId ?? null, object: ctx.object || null }\n\t\t\t},\n\t\t})\n\t\treturn { epData: data, epLoading: loading, epError: error }\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tgroups: [],\n\t\t\tloading: false,\n\t\t\terror: '',\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Whether the widget draws the board's inset (`content.inset`). Off\n\t\t * by default, which keeps it edge to edge as before.\n\t\t *\n\t\t * @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\n\t\t * @return {boolean}\n\t\t */\n\t\tinset() {\n\t\t\treturn Boolean(this.content && this.content.inset === true)\n\t\t},\n\n\t\t/**\n\t\t * Whether the widget is the compact summary box (`content.compact`):\n\t\t * a 15px heading, one thin bar and the legend on one line.\n\t\t *\n\t\t * @spec openspec/changes/stacked-bar-compact/specs/stacked-bar-compact/spec.md#requirement-a-stacked-bar-can-be-drawn-as-the-compact-summary-box\n\t\t * @return {boolean}\n\t\t */\n\t\tcompact() {\n\t\t\treturn Boolean(this.content && this.content.compact === true)\n\t\t},\n\n\t\t/**\n\t\t * The translate function in use.\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\teffectiveTranslate() {\n\t\t\treturn this.translate ?? this.cnTranslate\n\t\t},\n\n\t\t/**\n\t\t * Whether the segments come from OpenRegister.\n\t\t *\n\t\t * @return {boolean}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\thasEndpoint() {\n\t\t\treturn Boolean(this.content.endpointSource && this.content.endpointSource.url)\n\t\t},\n\n\t\t/** @return {boolean} Whether the counts are still loading. */\n\t\tisLoading() {\n\t\t\treturn this.hasEndpoint ? Boolean(this.epLoading) && this.epData === null : this.loading\n\t\t},\n\n\t\t/** @return {string} The load error, or ''. */\n\t\tloadError() {\n\t\t\treturn this.hasEndpoint ? (this.epError || '') : this.error\n\t\t},\n\n\t\t/**\n\t\t * The endpoint's groups as `{ key, count }`, from a `{ key: count }`\n\t\t * map or a `[{ key, count }]` list at `groupsPath`.\n\t\t *\n\t\t * @return {Array<{key: string, count: number}>}\n\t\t */\n\t\tendpointGroups() {\n\t\t\tconst raw = pluck(this.epData, this.content.groupsPath || 'groups')\n\t\t\tconst entries = Array.isArray(raw)\n\t\t\t\t? raw.filter((entry) => entry && typeof entry === 'object').map((entry) => [entry.key, entry.count])\n\t\t\t\t: Object.entries(raw && typeof raw === 'object' ? raw : {})\n\t\t\treturn entries.map(([key, count]) => ({ key: String(key), count: Number(count) > 0 ? Number(count) : 0 }))\n\t\t},\n\n\t\t/**\n\t\t * The whole the groups are part of (`totalPath` of the endpoint), or\n\t\t * null when none is given.\n\t\t *\n\t\t * @return {number|null}\n\t\t */\n\t\twhole() {\n\t\t\tif (!this.hasEndpoint || !this.content.totalPath) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst value = Number(pluck(this.epData, this.content.totalPath))\n\t\t\treturn Number.isFinite(value) && value >= 0 ? value : null\n\t\t},\n\n\t\t/** @return {number} The sum of the groups, without the remainder. */\n\t\tcounted() {\n\t\t\treturn this.rawGroups.reduce((sum, group) => sum + group.count, 0)\n\t\t},\n\n\t\t/** @return {number} What the groups leave of the whole. */\n\t\tremaining() {\n\t\t\treturn this.whole === null ? 0 : Math.max(0, this.whole - this.counted)\n\t\t},\n\n\t\t/**\n\t\t * The values the heading templates read.\n\t\t *\n\t\t * @return {object}\n\t\t */\n\t\ttemplateValues() {\n\t\t\tconst own = this.epData && typeof this.epData === 'object' && !Array.isArray(this.epData) ? this.epData : {}\n\t\t\treturn { ...own, counted: this.counted, total: this.whole === null ? this.counted : this.whole, remaining: this.remaining }\n\t\t},\n\n\t\t/** @return {string} The line above the bar, or ''. */\n\t\theading() {\n\t\t\treturn this.fillTemplate(this.content.heading)\n\t\t},\n\n\t\t/** @return {string} The muted line beside the heading, or ''. */\n\t\theadingMeta() {\n\t\t\treturn this.fillTemplate(this.content.headingMeta)\n\t\t},\n\n\t\thasSource() {\n\t\t\tconst s = this.content.source || {}\n\t\t\treturn Boolean(s.register && s.schema && s.groupBy)\n\t\t},\n\n\t\t/**\n\t\t * The raw groups, from the source or the static list, as `{ key, label, count }`.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, count: number}>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\trawGroups() {\n\t\t\tif (this.hasEndpoint) {\n\t\t\t\treturn this.endpointGroups.map((group) => ({ key: group.key, label: group.key, count: group.count }))\n\t\t\t}\n\t\t\tif (this.hasSource) {\n\t\t\t\treturn this.groups.map((group) => ({ key: group.key, label: group.key, count: group.count }))\n\t\t\t}\n\t\t\tconst list = Array.isArray(this.content.segments) ? this.content.segments : []\n\t\t\treturn list\n\t\t\t\t.filter((segment) => segment && typeof segment === 'object')\n\t\t\t\t.map((segment, index) => {\n\t\t\t\t\tconst count = Number(segment.value)\n\t\t\t\t\treturn {\n\t\t\t\t\t\tkey: String(segment.key ?? segment.label ?? index),\n\t\t\t\t\t\tlabel: String(segment.label ?? segment.key ?? ''),\n\t\t\t\t\t\tcount: Number.isFinite(count) && count > 0 ? count : 0,\n\t\t\t\t\t}\n\t\t\t\t})\n\t\t},\n\n\t\t/**\n\t\t * The segments in render order, each with its label and colour.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, count: number, color: string}>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\tsegments() {\n\t\t\tconst order = Array.isArray(this.content.order) ? this.content.order.map(String) : []\n\t\t\tconst labels = (this.content.labels && typeof this.content.labels === 'object') ? this.content.labels : {}\n\t\t\tconst byKey = {}\n\t\t\tfor (const group of this.rawGroups) {\n\t\t\t\tbyKey[group.key] = group\n\t\t\t}\n\t\t\tconst ordered = []\n\t\t\tfor (const key of order) {\n\t\t\t\tordered.push(byKey[key] || { key, label: key, count: 0 })\n\t\t\t}\n\t\t\tfor (const group of this.rawGroups) {\n\t\t\t\tif (!order.includes(group.key)) {\n\t\t\t\t\tordered.push(group)\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst colors = (this.content.colors && typeof this.content.colors === 'object') ? this.content.colors : {}\n\t\t\tconst segments = ordered.map((group, index) => ({\n\t\t\t\tkey: group.key,\n\t\t\t\tlabel: this.effectiveTranslate(typeof labels[group.key] === 'string' ? labels[group.key] : group.label),\n\t\t\t\tcount: group.count,\n\t\t\t\tcolor: this.colorOf(colors[group.key]) || rampColor(index, ordered.length),\n\t\t\t}))\n\t\t\tconst remainder = this.content.remainder\n\t\t\tif (remainder && typeof remainder === 'object' && this.remaining > 0) {\n\t\t\t\tsegments.push({\n\t\t\t\t\tkey: String(remainder.key || 'remainder'),\n\t\t\t\t\tlabel: this.effectiveTranslate(String(remainder.label || '')),\n\t\t\t\t\tcount: this.remaining,\n\t\t\t\t\tcolor: this.colorOf(remainder.color) || TONE_COLORS.neutral,\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn segments\n\t\t},\n\n\t\t/**\n\t\t * The segments that take up room in the bar.\n\t\t *\n\t\t * @return {Array<object>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\tbarSegments() {\n\t\t\treturn this.segments.filter((segment) => segment.count > 0)\n\t\t},\n\n\t\t/**\n\t\t * The total reported to the card under `headerCount: true`, or null\n\t\t * while loading, after an error or without `headerCount`.\n\t\t *\n\t\t * @return {number|null}\n\t\t * @spec openspec/changes/widget-title-count/specs/widget-title-count/spec.md#requirement-a-widget-title-can-carry-the-total-its-widget-reports\n\t\t */\n\t\theaderTotal() {\n\t\t\tif (this.content.headerCount !== true || this.isLoading || this.loadError) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn this.total\n\t\t},\n\n\t\t/**\n\t\t * The sum of all counts.\n\t\t *\n\t\t * @return {number}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\ttotal() {\n\t\t\treturn this.segments.reduce((sum, segment) => sum + segment.count, 0)\n\t\t},\n\n\t\t/**\n\t\t * The text shown when there is nothing to count.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\temptyLabel() {\n\t\t\tconst text = this.content.emptyText\n\t\t\treturn (typeof text === 'string' && text !== '')\n\t\t\t\t? this.effectiveTranslate(text)\n\t\t\t\t: t('nextcloud-vue', 'Nothing to show yet')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The loading text.\n\t\t *\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\tloadingLabel() {\n\t\t\treturn t('nextcloud-vue', 'Loading…')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The text shown when the counts could not be loaded.\n\t\t *\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\terrorLabel() {\n\t\t\treturn t('nextcloud-vue', 'The numbers could not be loaded.')\n\t\t},\n\n\t\t/**\n\t\t * Changes whenever a new fetch is needed.\n\t\t *\n\t\t * @return {string}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\tsourceKey() {\n\t\t\treturn JSON.stringify(this.content.source || {})\n\t\t},\n\t},\n\n\twatch: {\n\t\t/** @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget */\n\t\tsourceKey() {\n\t\t\tthis.fetchGroups()\n\t\t},\n\n\t\t/** @spec openspec/changes/widget-title-count/specs/widget-title-count/spec.md#requirement-a-widget-title-can-carry-the-total-its-widget-reports */\n\t\theaderTotal: {\n\t\t\timmediate: true,\n\t\t\thandler(total) {\n\t\t\t\tif (total !== null) {\n\t\t\t\t\t/**\n\t\t\t\t\t * @event header With `headerCount: true`, once the counts have loaded: `{ link: null, note: '', count }`, the sum of the segments.\n\t\t\t\t\t */\n\t\t\t\t\tthis.$emit('header', { link: null, note: '', count: total })\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.fetchGroups()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * The CSS colour of a tone or colour value.\n\t\t *\n\t\t * @param {string} value A tone name or a CSS colour.\n\t\t * @return {string} The colour, or '' for none.\n\t\t */\n\t\tcolorOf(value) {\n\t\t\tif (typeof value !== 'string' || value === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\treturn TONE_COLORS[value] || value\n\t\t},\n\n\t\t/**\n\t\t * Translate a heading template and fill its `{name}` placeholders.\n\t\t *\n\t\t * @param {string} template The template.\n\t\t * @return {string} The line, or '' without a template.\n\t\t */\n\t\tfillTemplate(template) {\n\t\t\tif (typeof template !== 'string' || template === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst values = this.templateValues\n\t\t\treturn this.effectiveTranslate(template).replace(/\\{(\\w+)\\}/g, (match, name) => (\n\t\t\t\tvalues[name] === undefined || values[name] === null ? '' : String(values[name])\n\t\t\t))\n\t\t},\n\n\t\t/**\n\t\t * Load the count per group in one request.\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t * @spec openspec/changes/workplace-dashboard-primitives/specs/workplace-dashboard-primitives/spec.md#requirement-stacked-bar-widget\n\t\t */\n\t\tasync fetchGroups() {\n\t\t\tif (!this.hasSource) {\n\t\t\t\tthis.groups = []\n\t\t\t\tthis.error = ''\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst requestKey = this.sourceKey\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tconst groups = await fetchGroupedCounts(this.content.source)\n\t\t\t\tif (requestKey !== this.sourceKey) {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.groups = groups\n\t\t\t} catch (e) {\n\t\t\t\t// eslint-disable-next-line no-console\n\t\t\t\tconsole.warn('[CnStackedBarWidget] failed to load counts:', e)\n\t\t\t\tthis.error = (e && e.message) || 'error'\n\t\t\t\tthis.groups = []\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-stacked-bar {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n\twidth: 100%;\n}\n\n/* `content.inset: true`: the board's inset inside the card (zuiddrecht-pixel-\n gaps-3). A dashboard renders its widgets flush, so without it the strip ran\n from card edge to card edge. Theme hook: --cn-widget-board-inset. */\n.cn-stacked-bar--inset {\n\tbox-sizing: border-box;\n\tpadding: var(--cn-widget-board-inset, 16px 24px 22px);\n}\n\n.cn-stacked-bar__heading {\n\tdisplay: flex;\n\talign-items: baseline;\n\tjustify-content: space-between;\n\tgap: 12px;\n}\n\n.cn-stacked-bar__heading-meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-stacked-bar__state {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-stacked-bar__bar {\n\tdisplay: flex;\n\tgap: 3px;\n\theight: 12px;\n}\n\n.cn-stacked-bar__segment {\n\tflex-basis: 0;\n\tmin-width: 4px;\n\tborder-radius: 6px;\n}\n\n.cn-stacked-bar__legend {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fit, minmax(150px, 1fr));\n\tgap: 12px;\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-stacked-bar__legend-item {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-stacked-bar__label {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tfont-size: 0.95em;\n\tcolor: var(--color-text-maxcontrast);\n\toverflow-wrap: anywhere;\n}\n\n.cn-stacked-bar__dot {\n\tflex: none;\n\twidth: 10px;\n\theight: 10px;\n\tborder-radius: 3px;\n}\n\n.cn-stacked-bar__count {\n\tfont-size: 1.5em;\n\tfont-weight: 700;\n\tline-height: 1.2;\n\tcolor: var(--color-main-text);\n}\n\n/* `content.compact: true`: the summary box (DqZaakDocumenten \"4 van 7\n beoordeeld\"): the sunken ground, a 15px heading, one 8px bar of joined\n segments and the legend on one line, each count beside its label. */\n.cn-stacked-bar--compact {\n\tbox-sizing: border-box;\n\tgap: 10px;\n\tpadding: 14px 16px;\n\tborder-radius: 10px;\n\tbackground: var(--cn-board-sunken-ground, var(--color-background-hover));\n}\n\n.cn-stacked-bar--compact .cn-stacked-bar__heading {\n\tflex-wrap: wrap;\n\tgap: 8px;\n\tfont-size: 15px;\n}\n\n.cn-stacked-bar--compact .cn-stacked-bar__bar {\n\tgap: 0;\n\theight: 8px;\n\tborder-radius: 4px;\n\toverflow: hidden;\n}\n\n.cn-stacked-bar--compact .cn-stacked-bar__segment {\n\tborder-radius: 0;\n}\n\n.cn-stacked-bar--compact .cn-stacked-bar__legend {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\tgap: 8px 18px;\n\tfont-size: 14px;\n}\n\n.cn-stacked-bar--compact .cn-stacked-bar__legend-item {\n\tflex-direction: row;\n\talign-items: center;\n\tgap: 8px;\n}\n\n.cn-stacked-bar--compact .cn-stacked-bar__label {\n\tcolor: var(--color-main-text);\n\tfont-size: 1em;\n}\n\n.cn-stacked-bar--compact .cn-stacked-bar__dot {\n\tborder-radius: 50%;\n}\n\n.cn-stacked-bar--compact .cn-stacked-bar__count {\n\tfont-size: 1em;\n\tline-height: inherit;\n}\n</style>\n"],"names":["CnWidgetEmptyState","t"],"mappings":"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"}
|
|
@@ -33,7 +33,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
33
33
|
const _component_CnWidgetEmptyState = resolveComponent("CnWidgetEmptyState");
|
|
34
34
|
|
|
35
35
|
return (openBlock(), createElementBlock("div", {
|
|
36
|
-
class: normalizeClass(["cn-stacked-bar", { 'cn-stacked-bar--inset': $options.inset }]),
|
|
36
|
+
class: normalizeClass(["cn-stacked-bar", { 'cn-stacked-bar--inset': $options.inset, 'cn-stacked-bar--compact': $options.compact }]),
|
|
37
37
|
"data-testid": "cn-stacked-bar"
|
|
38
38
|
}, [
|
|
39
39
|
($options.isLoading)
|