@conduction/nextcloud-vue 2.143.0 → 2.144.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/esm/components/CnActionDialog/CnActionDialog.vue.js +1 -1
  2. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js +7 -2
  3. package/dist/esm/components/CnActionDialog/CnActionDialog.vue2.js.map +1 -1
  4. package/dist/esm/components/CnActionDialog/CnActionDialog.vue3.js.map +1 -1
  5. package/dist/esm/components/CnConversationThread/CnConversationThread.vue.js +1 -1
  6. package/dist/esm/components/CnConversationThread/CnConversationThread.vue2.js.map +1 -1
  7. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js +1 -1
  8. package/dist/esm/components/CnConversationThread/CnConversationThread.vue3.js.map +1 -1
  9. package/dist/esm/components/CnEventsCard/CnEventsCard.vue.js +1 -1
  10. package/dist/esm/components/CnEventsCard/CnEventsCard.vue2.js +9 -0
  11. package/dist/esm/components/CnEventsCard/CnEventsCard.vue2.js.map +1 -1
  12. package/dist/esm/components/CnEventsCard/CnEventsCard.vue3.js +24 -13
  13. package/dist/esm/components/CnEventsCard/CnEventsCard.vue3.js.map +1 -1
  14. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  15. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +2 -1
  16. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  17. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  18. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +3 -1
  19. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  20. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  21. package/dist/esm/components/CnNotesCard/CnNotesCard.vue.js +1 -1
  22. package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js +3 -0
  23. package/dist/esm/components/CnNotesCard/CnNotesCard.vue2.js.map +1 -1
  24. package/dist/esm/components/CnNotesCard/CnNotesCard.vue3.js +19 -14
  25. package/dist/esm/components/CnNotesCard/CnNotesCard.vue3.js.map +1 -1
  26. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue.js +1 -1
  27. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue2.js +30 -0
  28. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue2.js.map +1 -1
  29. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue3.js +158 -102
  30. package/dist/esm/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue3.js.map +1 -1
  31. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue.js +1 -1
  32. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue2.js.map +1 -1
  33. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js +12 -0
  34. package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js.map +1 -1
  35. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue.js +1 -1
  36. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +110 -2
  37. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
  38. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +146 -80
  39. package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
  40. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue.js +1 -1
  41. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js +45 -0
  42. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue2.js.map +1 -1
  43. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue3.js +67 -34
  44. package/dist/esm/integrations/builtin/calendar/CnCalendarCard.vue3.js.map +1 -1
  45. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue.js +1 -1
  46. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js +3 -22
  47. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue2.js.map +1 -1
  48. package/dist/esm/integrations/builtin/calendar/CnCalendarTab.vue3.js.map +1 -1
  49. package/dist/esm/integrations/builtin/calendar/eventStatus.js +67 -0
  50. package/dist/esm/integrations/builtin/calendar/eventStatus.js.map +1 -0
  51. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue.js +1 -1
  52. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +39 -0
  53. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
  54. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js +81 -32
  55. package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
  56. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue.js +1 -1
  57. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +5 -6
  58. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
  59. package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
  60. package/dist/esm/integrations/builtin/deck/deckCardParts.js +59 -0
  61. package/dist/esm/integrations/builtin/deck/deckCardParts.js.map +1 -0
  62. package/dist/esm/l10n/en.json.js +6 -0
  63. package/dist/esm/l10n/en.json.js.map +1 -1
  64. package/dist/esm/l10n/nl.json.js +6 -0
  65. package/dist/esm/l10n/nl.json.js.map +1 -1
  66. package/dist/esm/library-style.js +1 -1
  67. package/dist/esm/nextcloud-vue.css +815 -363
  68. package/dist/esm/utils/fetchFilterCounts.js +5 -2
  69. package/dist/esm/utils/fetchFilterCounts.js.map +1 -1
  70. package/dist/esm/utils/nextcloudDirectory.js +19 -6
  71. package/dist/esm/utils/nextcloudDirectory.js.map +1 -1
  72. package/dist/nextcloud-vue.cjs.js +1347 -758
  73. package/dist/nextcloud-vue.cjs.js.map +2 -2
  74. package/dist/nextcloud-vue.css +815 -363
  75. package/l10n/en.json +6 -0
  76. package/l10n/nl.json +6 -0
  77. package/package.json +1 -1
  78. package/src/components/CnActionDialog/CnActionDialog.vue +28 -3
  79. package/src/components/CnConversationThread/CnConversationThread.vue +5 -2
  80. package/src/components/CnEventsCard/CnEventsCard.vue +27 -5
  81. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +15 -1
  82. package/src/components/CnIndexPage/CnIndexPage.vue +3 -1
  83. package/src/components/CnNotesCard/CnNotesCard.vue +8 -0
  84. package/src/components/CnObjectPartiesWidget/CnObjectPartiesWidget.vue +114 -46
  85. package/src/components/CnRetentionWidget/CnRetentionWidget.vue +26 -1
  86. package/src/components/CnTasksWidget/CnTasksWidget.vue +170 -13
  87. package/src/css/dialog.css +7 -1
  88. package/src/css/kpi-card.css +3 -0
  89. package/src/css/look-board-detail.css +317 -0
  90. package/src/css/look-board-index.css +14 -0
  91. package/src/css/look-board.css +25 -0
  92. package/src/integrations/builtin/calendar/CnCalendarCard.vue +76 -11
  93. package/src/integrations/builtin/calendar/CnCalendarTab.vue +3 -22
  94. package/src/integrations/builtin/calendar/__tests__/CnCalendarCardBoardRows.spec.js +52 -0
  95. package/src/integrations/builtin/calendar/eventStatus.js +63 -0
  96. package/src/integrations/builtin/deck/CnDeckCard.vue +66 -0
  97. package/src/integrations/builtin/deck/CnDeckTab.vue +5 -6
  98. package/src/integrations/builtin/deck/__tests__/CnDeckCardBoardRows.spec.js +49 -0
  99. package/src/integrations/builtin/deck/deckCardParts.js +56 -0
  100. package/src/utils/fetchFilterCounts.js +5 -2
  101. package/src/utils/nextcloudDirectory.js +19 -6
@@ -1 +1 @@
1
- {"version":3,"file":"CnRetentionWidget.vue3.js","sources":["../../../../src/components/CnRetentionWidget/CnRetentionWidget.vue?vue&type=template&id=5415dbb4&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-retention\" data-testid=\"cn-retention\">\n\t\t<header class=\"cn-retention__head\">\n\t\t\t<div class=\"cn-retention__head-text\">\n\t\t\t\t<h3 class=\"cn-retention__title\">\n\t\t\t\t\t{{ text(content.title) || t('nextcloud-vue', 'Archiving') }}\n\t\t\t\t</h3>\n\t\t\t\t<p class=\"cn-retention__muted\">\n\t\t\t\t\t{{ text(content.subtitle) || t('nextcloud-vue', 'What happens to this record once it is closed') }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t\t<!-- Recompute starts in the header (board DqZaakArchivering); the\n\t\t\t dialog asks for the reason OpenRegister records with it. -->\n\t\t\t<NcButton\n\t\t\t\tv-if=\"content.recompute !== false && !loading && !error\"\n\t\t\t\t:disabled=\"!mayRecompute || busy\"\n\t\t\t\t:title=\"mayRecompute ? undefined : t('nextcloud-vue', 'Recomputing needs the archivist role')\"\n\t\t\t\t:aria-description=\"mayRecompute ? undefined : t('nextcloud-vue', 'Recomputing needs the archivist role')\"\n\t\t\t\t:data-testid=\"mayRecompute ? 'cn-retention-recompute-open' : 'cn-retention-recompute-denied'\"\n\t\t\t\t@click=\"recomputeOpen = true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Refresh :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Recompute nomination') }}\n\t\t\t</NcButton>\n\t\t</header>\n\t\t<CnRetentionRecomputeDialog\n\t\t\tv-if=\"recomputeOpen\"\n\t\t\t:user=\"currentUid\"\n\t\t\t:busy=\"busy\"\n\t\t\t@close=\"recomputeOpen = false\"\n\t\t\t@confirm=\"recompute\" />\n\n\t\t<NcLoadingIcon v-if=\"loading\" :size=\"24\" />\n\t\t<NcEmptyContent v-else-if=\"error\"\n\t\t\t:name=\"t('nextcloud-vue', 'The archival facts are unavailable')\"\n\t\t\t:description=\"error\"\n\t\t\tdata-testid=\"cn-retention-error\">\n\t\t\t<template #action>\n\t\t\t\t<NcButton data-testid=\"cn-retention-retry\" @click=\"load\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Try again') }}\n\t\t\t\t</NcButton>\n\t\t\t</template>\n\t\t</NcEmptyContent>\n\n\t\t<template v-else>\n\t\t\t<p v-if=\"archived\" class=\"cn-retention__archived\" data-testid=\"cn-retention-archived\">\n\t\t\t\t{{ t('nextcloud-vue', 'Archived on {at} by {by}. {reason}', { at: String(archived.at ?? ''), by: String(archived.by ?? ''), reason: String(archived.reason ?? '') }) }}\n\t\t\t</p>\n\t\t\t<NcNoteCard v-if=\"disagrees\" type=\"warning\" data-testid=\"cn-retention-disagreement\">\n\t\t\t\t{{ t('nextcloud-vue', 'This record and OpenRegister do not agree on whether it is archived.') }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<NcNoteCard v-if=\"nomination && nomination.status === 'unnominatable'\" type=\"warning\" data-testid=\"cn-retention-unnominatable\">\n\t\t\t\t{{ t('nextcloud-vue', 'Nothing could decide what happens to this record.') }}\n\t\t\t\t{{ String(nomination.unnominatableReason ?? '') }}\n\t\t\t</NcNoteCard>\n\t\t\t<div v-else-if=\"!nomination\" data-testid=\"cn-retention-pending\">\n\t\t\t\t<dl class=\"cn-retention__facts\">\n\t\t\t\t\t<div v-for=\"fact in pendingFacts\" :key=\"fact.key\" class=\"cn-retention__fact\">\n\t\t\t\t\t\t<dt>{{ fact.label }}</dt>\n\t\t\t\t\t\t<dd>{{ fact.value }}</dd>\n\t\t\t\t\t</div>\n\t\t\t\t</dl>\n\t\t\t\t<NcNoteCard type=\"info\">\n\t\t\t\t\t{{ text(content.pendingNote) || t('nextcloud-vue', 'This record has not been closed yet. Its archival future is recorded when it closes.') }}\n\t\t\t\t</NcNoteCard>\n\t\t\t</div>\n\t\t\t<dl v-else class=\"cn-retention__facts\" data-testid=\"cn-retention-facts\">\n\t\t\t\t<div v-for=\"fact in facts\" :key=\"fact.key\" class=\"cn-retention__fact\">\n\t\t\t\t\t<dt>{{ fact.label }}</dt>\n\t\t\t\t\t<dd :data-testid=\"`cn-retention-${fact.key}`\">\n\t\t\t\t\t\t{{ fact.value }}\n\t\t\t\t\t</dd>\n\t\t\t\t</div>\n\t\t\t</dl>\n\n\t\t\t<section v-if=\"content.legalHold\" class=\"cn-retention__section\" data-testid=\"cn-retention-hold\">\n\t\t\t\t<component :is=\"innerHeadingTag\" class=\"cn-inner-heading\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Kept for a procedure') }}\n\t\t\t\t</component>\n\t\t\t\t<ul class=\"cn-retention__rows\">\n\t\t\t\t\t<li v-for=\"hold in holds\" :key=\"hold.id\" class=\"cn-retention__row\">\n\t\t\t\t\t\t<span class=\"cn-retention__row-text\">\n\t\t\t\t\t\t\t<span class=\"cn-retention__row-title\">{{ hold.reason || t('nextcloud-vue', 'Legal hold') }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-retention__muted\">{{ t('nextcloud-vue', 'Placed on {date} by {by}', { date: hold.placedDate, by: hold.placedBy }) }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<CnStatusBadge :label=\"t('nextcloud-vue', 'Active')\" variant=\"warning\" />\n\t\t\t\t\t</li>\n\t\t\t\t\t<li v-if=\"holds.length === 0\" class=\"cn-retention__row\">\n\t\t\t\t\t\t<span class=\"cn-retention__row-text\">\n\t\t\t\t\t\t\t<span class=\"cn-retention__row-title\">{{ t('nextcloud-vue', 'No legal hold') }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-retention__muted\">{{ t('nextcloud-vue', 'If an objection or appeal runs, destruction is suspended') }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<CnStatusBadge :label=\"t('nextcloud-vue', 'None')\" />\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\n\t\t\t<section v-if=\"content.transfer\" class=\"cn-retention__section\" data-testid=\"cn-retention-transfer\">\n\t\t\t\t<component :is=\"innerHeadingTag\" class=\"cn-inner-heading\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Transfer to the e-Depot') }}\n\t\t\t\t</component>\n\t\t\t\t<ul class=\"cn-retention__rows\">\n\t\t\t\t\t<li class=\"cn-retention__row\">\n\t\t\t\t\t\t<span class=\"cn-retention__row-text\">\n\t\t\t\t\t\t\t<span class=\"cn-retention__row-title\">{{ transfers ? t('nextcloud-vue', 'Goes to the e-Depot') : t('nextcloud-vue', 'Not applicable') }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-retention__muted\">{{ transfers ? t('nextcloud-vue', 'After the retention period the record is transferred') : t('nextcloud-vue', 'Only records appraised to keep go to the e-Depot') }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<CnStatusBadge :label=\"transfers ? t('nextcloud-vue', 'Transfer') : t('nextcloud-vue', 'No transfer')\" :variant=\"transfers ? 'info' : 'default'\" />\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\n\t\t\t<div v-if=\"outcome\" class=\"cn-retention__outcome\" data-testid=\"cn-retention-outcome\">\n\t\t\t\t<component :is=\"innerHeadingTag\" class=\"cn-inner-heading\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Outcome') }}\n\t\t\t\t</component>\n\t\t\t\t<p>{{ t('nextcloud-vue', 'Handed over as {kind} on {at} by {by}.', { kind: String(outcome.kind ?? ''), at: String(outcome.at ?? ''), by: String(outcome.by ?? '') }) }}</p>\n\t\t\t\t<p v-if=\"outcome.transferListUuid\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Transfer list') }}: {{ outcome.transferListUuid }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { getCurrentUser } from '@nextcloud/auth'\nimport axios from '@nextcloud/axios'\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton, NcEmptyContent, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport Refresh from 'vue-material-design-icons/Refresh.vue'\nimport CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'\nimport CnRetentionRecomputeDialog from './CnRetentionRecomputeDialog.vue'\nimport innerHeading from '../../mixins/innerHeading.js'\nimport { parseDuration } from '../../utils/isoDuration.js'\nimport { readPath } from '../../utils/readPath.js'\nimport { hasUnresolvedTokens, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'\n\n/**\n * CnRetentionWidget — the `retention` detail widget: what happens to a\n * record when its business use ends, as OpenRegister decided it. Every value\n * is read off the record's `@self._retention` (appraisal, disposal date,\n * retention period, selection list row, the rule and when it was written)\n * and `@self.archived`; the widget derives nothing, so it can never disagree\n * with the stored decision. A failed read is an error with a retry, never an\n * empty block. A record nothing could nominate is drawn apart from one that\n * is not closed yet. An archivist (or an admin) may ask OpenRegister to\n * recompute the nomination, with a reason. Any app on OpenRegister uses it;\n * dossiq's case Archiving tab is the first.\n *\n * `content`:\n * - `title`, `subtitle`: texts, through the host translate.\n * - `statusField`, `archivedValues`: a record field and the values that mean\n * archived (`archiveStatus`, `[\"archived\", ...]`); when set, a mismatch with\n * `@self.archived` is said out loud.\n * - `archivistGroups`: groups that may recompute (default `[\"archivaris\"]`;\n * admins always may); `recompute: false` hides the control.\n * - `expected`: before the record closes, the outcome its type expects.\n * `source: { register, schema, filter }` lists the candidate rules (a case\n * type's result types, `filter` tokens such as `@object.caseType`\n * resolved against the record); `fields: { appraisal, period,\n * selectionListRow }` names their fields; `appraisalLabels` names the\n * appraisal values; `periodText` writes the period (`{period} after the\n * decision`); `decidedBy` says who decides. When the candidates agree the\n * facts show that outcome, otherwise \"Depends on the result\". A failed\n * read keeps the plain facts.\n * - `pendingNote`: the note under the facts of a record not closed yet.\n * - `legalHold: true`: a section with the record's active legal holds\n * (`@self._retention.legalHold.holds`), or that none applies.\n * - `transfer: { keepValues }`: a section saying whether the record goes to\n * the e-Depot, from the stored or expected appraisal.\n *\n * The record is read fresh from OpenRegister by `register`, `schema` and\n * `objectId` (props, else the bound record's `@self`).\n */\nexport default {\n\tname: 'CnRetentionWidget',\n\n\tcomponents: { CnRetentionRecomputeDialog, CnStatusBadge, NcButton, NcEmptyContent, NcLoadingIcon, NcNoteCard, Refresh },\n\n\tmixins: [innerHeading],\n\n\tinject: {\n\t\t/** The host app's translate (CnAppRoot `cnTranslate`). */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget's configuration (see the component docs).\n\t\t *\n\t\t * @type {object}\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 * The bound record, on a detail page.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The record's register (else the bound record's `@self.register`). */\n\t\tregister: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * The record's schema (else the bound record's `@self.schema`); a schema object gives its slug.\n\t\t *\n\t\t * @type {string|number|object}\n\t\t */\n\t\tschema: {\n\t\t\ttype: [String, Number, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The record's id (else the bound record's). */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tloading: true,\n\t\t\terror: '',\n\t\t\trecord: null,\n\t\t\t/** Whether the recompute dialog is open. */\n\t\t\trecomputeOpen: false,\n\t\t\tbusy: false,\n\t\t\t/** The candidate rules `content.expected` reads, null until read or when unreadable. */\n\t\t\texpectedRows: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {object} The bound record's `@self`. */\n\t\tboundSelf() {\n\t\t\treturn (this.objectData && this.objectData['@self']) || {}\n\t\t},\n\n\t\t/** @return {string} The record id. */\n\t\tid() {\n\t\t\treturn String(this.objectId || (this.objectData && this.objectData.id) || this.boundSelf.id || '')\n\t\t},\n\n\t\t/** @return {string} The register. */\n\t\tregisterRef() {\n\t\t\treturn String(this.register || this.boundSelf.register || '')\n\t\t},\n\n\t\t/** @return {string} The schema. */\n\t\tschemaRef() {\n\t\t\tconst s = this.schema || this.boundSelf.schema || ''\n\t\t\treturn String(typeof s === 'object' ? (s.slug || s.id || '') : s)\n\t\t},\n\n\t\t/** @return {object} The record's `@self`. */\n\t\tself() {\n\t\t\treturn (this.record && this.record['@self']) || {}\n\t\t},\n\n\t\t/** @return {object|null} OpenRegister's stored retention decision. */\n\t\tretention() {\n\t\t\treturn this.self._retention || null\n\t\t},\n\n\t\t/** @return {object|null} The nomination. */\n\t\tnomination() {\n\t\t\treturn (this.retention && this.retention.nomination) || null\n\t\t},\n\n\t\t/** @return {object|null} The hand-over outcome. */\n\t\toutcome() {\n\t\t\treturn (this.retention && this.retention.outcome) || null\n\t\t},\n\n\t\t/** @return {object|null} OpenRegister's archived marker. */\n\t\tarchived() {\n\t\t\treturn this.self.archived || null\n\t\t},\n\n\t\t/**\n\t\t * Whether the record's own field and the platform marker disagree.\n\t\t *\n\t\t * @spec openspec/changes/retention-widget/specs/retention-widget/spec.md#requirement-a-record-shows-its-stored-retention\n\t\t * @return {boolean}\n\t\t */\n\t\tdisagrees() {\n\t\t\tconst field = this.content.statusField\n\t\t\tif (!field || !this.record) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst values = Array.isArray(this.content.archivedValues) ? this.content.archivedValues.map(String) : ['archived']\n\t\t\tconst saysArchived = values.includes(String(readPath(this.record, field) ?? ''))\n\t\t\treturn saysArchived !== Boolean(this.archived)\n\t\t},\n\n\t\t/**\n\t\t * The stored facts that have a value, in reading order.\n\t\t *\n\t\t * @spec openspec/changes/retention-widget/specs/retention-widget/spec.md#requirement-a-record-shows-its-stored-retention\n\t\t * @return {Array<{key: string, label: string, value: string}>}\n\t\t */\n\t\tfacts() {\n\t\t\tconst r = this.retention || {}\n\t\t\tconst n = this.nomination || {}\n\t\t\treturn [\n\t\t\t\t{ key: 'appraisal', label: t('nextcloud-vue', 'Appraisal'), value: r.appraisal },\n\t\t\t\t{ key: 'disposal-date', label: t('nextcloud-vue', 'Disposal date'), value: r.disposalDate },\n\t\t\t\t{ key: 'retention-period', label: t('nextcloud-vue', 'Retention period'), value: r.retentionPeriod },\n\t\t\t\t{ key: 'selection-list-row', label: t('nextcloud-vue', 'Selection list row'), value: r.selectionListRow },\n\t\t\t\t{ key: 'rule', label: t('nextcloud-vue', 'Decided by'), value: n.rule },\n\t\t\t\t{ key: 'decided-at', label: t('nextcloud-vue', 'Written on'), value: n.at },\n\t\t\t].filter((fact) => fact.value !== undefined && fact.value !== null && fact.value !== '').map((fact) => ({ ...fact, value: String(fact.value) }))\n\t\t},\n\n\t\t/**\n\t\t * The outcome the record's type expects before it closes: the agreed\n\t\t * appraisal, period and selection list row of the candidate rules, or\n\t\t * `mixed` when they disagree; null when nothing was read.\n\t\t *\n\t\t * @spec openspec/changes/retention-expected-outcome/specs/retention-widget/spec.md#requirement-an-open-record-shows-the-outcome-its-type-expects\n\t\t * @return {{appraisal: string, period: string, selectionListRow: string, mixed: boolean}|null}\n\t\t */\n\t\texpectedOutcome() {\n\t\t\tconst rows = this.expectedRows\n\t\t\tif (!Array.isArray(rows) || rows.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst fields = { appraisal: 'appraisal', period: 'period', selectionListRow: 'selectionListRow', ...(this.content.expected?.fields || {}) }\n\t\t\tconst pick = (row, key) => String(readPath(row, fields[key]) ?? '')\n\t\t\tconst outcomes = rows.map((row) => ({ appraisal: pick(row, 'appraisal'), period: pick(row, 'period'), selectionListRow: pick(row, 'selectionListRow') }))\n\t\t\tconst first = outcomes[0]\n\t\t\tconst agree = outcomes.every((o) => o.appraisal === first.appraisal && o.period === first.period)\n\t\t\tif (first.appraisal === '' && first.period === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn agree\n\t\t\t\t? { ...first, selectionListRow: outcomes.every((o) => o.selectionListRow === first.selectionListRow) ? first.selectionListRow : '', mixed: false }\n\t\t\t\t: { appraisal: '', period: '', selectionListRow: '', mixed: true }\n\t\t},\n\n\t\t/**\n\t\t * The facts of a record not closed yet: the expected outcome where the\n\t\t * type gives one, \"decided when it closes\" otherwise.\n\t\t *\n\t\t * @spec openspec/changes/retention-expected-outcome/specs/retention-widget/spec.md#requirement-an-open-record-shows-the-outcome-its-type-expects\n\t\t * @return {Array<{key: string, label: string, value: string}>}\n\t\t */\n\t\tpendingFacts() {\n\t\t\tconst atClosure = t('nextcloud-vue', 'Decided when it closes')\n\t\t\tconst expected = this.expectedOutcome\n\t\t\tif (!expected) {\n\t\t\t\treturn [\n\t\t\t\t\t{ key: 'appraisal', label: t('nextcloud-vue', 'Appraisal'), value: atClosure },\n\t\t\t\t\t{ key: 'retention-period', label: t('nextcloud-vue', 'Retention period'), value: atClosure },\n\t\t\t\t\t{ key: 'disposal-date', label: t('nextcloud-vue', 'Disposal date'), value: atClosure },\n\t\t\t\t\t{ key: 'decided-at', label: t('nextcloud-vue', 'Written on'), value: t('nextcloud-vue', 'Not yet') },\n\t\t\t\t]\n\t\t\t}\n\t\t\tconst depends = t('nextcloud-vue', 'Depends on the result')\n\t\t\tconst config = this.content.expected || {}\n\t\t\tconst facts = [\n\t\t\t\t{ key: 'appraisal', label: t('nextcloud-vue', 'Appraisal'), value: expected.mixed ? depends : this.appraisalLabel(expected.appraisal) },\n\t\t\t\t{ key: 'retention-period', label: t('nextcloud-vue', 'Retention period'), value: expected.mixed ? depends : this.periodLabel(expected.period) },\n\t\t\t]\n\t\t\tif (expected.selectionListRow) {\n\t\t\t\tfacts.push({ key: 'selection-list-row', label: t('nextcloud-vue', 'Selection list row'), value: expected.selectionListRow })\n\t\t\t}\n\t\t\tfacts.push({ key: 'disposal-date', label: t('nextcloud-vue', 'Disposal date'), value: atClosure })\n\t\t\tif (config.decidedBy) {\n\t\t\t\tfacts.push({ key: 'rule', label: t('nextcloud-vue', 'Decided by'), value: this.text(config.decidedBy) })\n\t\t\t}\n\t\t\tfacts.push({ key: 'decided-at', label: t('nextcloud-vue', 'Written on'), value: t('nextcloud-vue', 'Not yet') })\n\t\t\treturn facts.filter((fact) => fact.value !== '')\n\t\t},\n\n\t\t/** @return {Array<{id: string, reason: string, placedBy: string, placedDate: string}>} The active legal holds. */\n\t\tholds() {\n\t\t\tconst hold = (this.retention && this.retention.legalHold) || {}\n\t\t\tconst list = Array.isArray(hold.holds) ? hold.holds : []\n\t\t\treturn list.map((h, index) => ({ id: String(h.id ?? index), reason: String(h.reason ?? ''), placedBy: String(h.placedBy ?? ''), placedDate: String(h.placedDate ?? '') }))\n\t\t},\n\n\t\t/**\n\t\t * Whether the record goes to the e-Depot: its stored appraisal, else\n\t\t * the expected one, is one of `transfer.keepValues`.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\ttransfers() {\n\t\t\tconst keep = Array.isArray(this.content.transfer?.keepValues) ? this.content.transfer.keepValues.map(String) : ['bewaren', 'blijvend_bewaren', 'retain', 'keep']\n\t\t\tconst stored = this.retention && this.retention.appraisal\n\t\t\tconst appraisal = stored || (this.expectedOutcome && !this.expectedOutcome.mixed ? this.expectedOutcome.appraisal : '')\n\t\t\treturn keep.includes(String(appraisal || ''))\n\t\t},\n\n\t\t/** @return {string} The signed-in user's id. */\n\t\tcurrentUid() {\n\t\t\tconst user = getCurrentUser()\n\t\t\treturn String((user && user.uid) || '')\n\t\t},\n\n\t\t/**\n\t\t * Whether this user may recompute: an admin or an archivist group member.\n\t\t * Shown disabled otherwise, so the user learns which role it needs.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tmayRecompute() {\n\t\t\tconst user = getCurrentUser()\n\t\t\tif (user && user.isAdmin === true) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst groups = Array.isArray(user && user.groups) ? user.groups : []\n\t\t\tconst allowed = Array.isArray(this.content.archivistGroups) ? this.content.archivistGroups : ['archivaris']\n\t\t\treturn allowed.some((g) => groups.includes(g))\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.load()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * A config text through the host translate.\n\t\t *\n\t\t * @param {unknown} value The text.\n\t\t * @return {string}\n\t\t */\n\t\ttext(value) {\n\t\t\treturn typeof value === 'string' && value !== '' ? this.cnTranslate(value) : ''\n\t\t},\n\n\t\t/**\n\t\t * An appraisal value in words, through `expected.appraisalLabels`.\n\t\t *\n\t\t * @param {string} value The stored value.\n\t\t * @return {string}\n\t\t */\n\t\tappraisalLabel(value) {\n\t\t\tconst labels = this.content.expected?.appraisalLabels || {}\n\t\t\treturn labels[value] ? this.text(labels[value]) : value\n\t\t},\n\n\t\t/**\n\t\t * An ISO period in words (\"5 years\"), placed in `expected.periodText`.\n\t\t *\n\t\t * @param {string} value The ISO 8601 duration.\n\t\t * @return {string}\n\t\t */\n\t\tperiodLabel(value) {\n\t\t\tconst parsed = parseDuration(value)\n\t\t\tconst words = parsed\n\t\t\t\t? {\n\t\t\t\t\t\tyears: () => n('nextcloud-vue', '%n year', '%n years', parsed.amount),\n\t\t\t\t\t\tmonths: () => n('nextcloud-vue', '%n month', '%n months', parsed.amount),\n\t\t\t\t\t\tweeks: () => n('nextcloud-vue', '%n week', '%n weeks', parsed.amount),\n\t\t\t\t\t\tdays: () => n('nextcloud-vue', '%n day', '%n days', parsed.amount),\n\t\t\t\t\t}[parsed.unit]\n\t\t\t\t: null\n\t\t\tconst period = words ? words() : value\n\t\t\tconst template = this.content.expected?.periodText\n\t\t\treturn template ? this.text(template).replace('{period}', period) : period\n\t\t},\n\n\t\t/**\n\t\t * Read the record fresh from OpenRegister; a failure is an error, never an empty block.\n\t\t *\n\t\t * @spec openspec/changes/retention-widget/specs/retention-widget/spec.md#requirement-a-record-shows-its-stored-retention\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tif (!this.registerRef || !this.schemaRef || !this.id) {\n\t\t\t\t\tthrow new Error(t('nextcloud-vue', 'This widget has no record to read.'))\n\t\t\t\t}\n\t\t\t\tconst { data } = await axios.get(generateUrl(`/apps/openregister/api/objects/${encodeURIComponent(this.registerRef)}/${encodeURIComponent(this.schemaRef)}/${encodeURIComponent(this.id)}`))\n\t\t\t\tthis.record = data && typeof data === 'object' ? data : null\n\t\t\t\tawait this.loadExpected()\n\t\t\t} catch (failure) {\n\t\t\t\tconst body = failure && failure.response && failure.response.data\n\t\t\t\tthis.error = String((body && (body.message || body.error)) || (failure && failure.message) || failure)\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Read the candidate rules `content.expected` names, for a record that\n\t\t * is not nominated yet. A failure leaves the plain pending facts.\n\t\t *\n\t\t * @spec openspec/changes/retention-expected-outcome/specs/retention-widget/spec.md#requirement-an-open-record-shows-the-outcome-its-type-expects\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadExpected() {\n\t\t\tconst source = this.content.expected && this.content.expected.source\n\t\t\tthis.expectedRows = null\n\t\t\tif (!source || !source.register || !source.schema || this.nomination) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst filter = resolveFilterTokens(source.filter || {}, { objectId: this.id, object: this.record })\n\t\t\tif (hasUnresolvedTokens(filter)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst { data } = await axios.get(generateUrl(`/apps/openregister/api/objects/${encodeURIComponent(source.register)}/${encodeURIComponent(source.schema)}`), { params: { ...filter, _limit: 100 } })\n\t\t\t\tthis.expectedRows = Array.isArray(data?.results) ? data.results : []\n\t\t\t} catch {\n\t\t\t\tthis.expectedRows = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Ask OpenRegister to derive the nomination again with the reason the\n\t\t * dialog asked for, then read it back.\n\t\t *\n\t\t * @spec openspec/changes/retention-recompute-header/specs/retention-recompute-header/spec.md#requirement-recomputing-the-nomination-starts-in-the-card-header-and-asks-for-a-reason\n\t\t * @param {string} reason Why it is recomputed.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync recompute(reason) {\n\t\t\tconst why = String(reason ?? '').trim()\n\t\t\tif (this.busy || why === '') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.busy = true\n\t\t\ttry {\n\t\t\t\tawait axios.post(generateUrl(`/apps/openregister/api/archival/objects/${encodeURIComponent(this.id)}/nomination/recompute`), { reason: why })\n\t\t\t\tthis.recomputeOpen = false\n\t\t\t\tawait this.load()\n\t\t\t} catch (failure) {\n\t\t\t\tconst body = failure && failure.response && failure.response.data\n\t\t\t\tthis.error = String((body && (body.message || body.error)) || (failure && failure.message) || failure)\n\t\t\t} finally {\n\t\t\t\tthis.busy = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-retention {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n}\n\n.cn-retention__head {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: flex-start;\n\tjustify-content: space-between;\n\tgap: 10px 16px;\n}\n\n.cn-retention__head-text {\n\tdisplay: flex;\n\tflex: 1 1 260px;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-retention__title {\n\tmargin: 0;\n\tfont-size: 18px;\n\tfont-weight: 700;\n}\n\n.cn-retention__muted {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-retention__facts {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n\tgap: 12px 24px;\n\tmargin: 0;\n}\n\n/* The server draws every dt as a 130px right-aligned inline block and pads\n dt and dd 12px; a fact is a label over its value. */\n.cn-retention__fact dt,\n.cn-retention__fact dd {\n\tdisplay: block;\n\twidth: auto;\n\twhite-space: normal;\n\ttext-align: start;\n}\n\n.cn-retention__fact dt {\n\tmargin: 0;\n\tpadding: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-retention__fact dd {\n\tmargin: 0;\n\tpadding: 0;\n\tfont-weight: 600;\n}\n\n.cn-retention__section {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-retention__rows {\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-retention__row {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tpadding: 10px 0;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-retention__row-text {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-retention__row-title {\n\tfont-weight: 600;\n}\n\n</style>\n"],"names":["_createElementVNode","_toDisplayString","_createVNode","_createTextVNode","_createBlock","_createElementBlock","_Fragment","_renderList","_resolveDynamicComponent","_openBlock"],"mappings":"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"}
1
+ {"version":3,"file":"CnRetentionWidget.vue3.js","sources":["../../../../src/components/CnRetentionWidget/CnRetentionWidget.vue?vue&type=template&id=912351be&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-retention\" data-testid=\"cn-retention\">\n\t\t<header class=\"cn-retention__head\">\n\t\t\t<div class=\"cn-retention__head-text\">\n\t\t\t\t<h3 class=\"cn-retention__title\">\n\t\t\t\t\t{{ text(content.title) || t('nextcloud-vue', 'Archiving') }}\n\t\t\t\t</h3>\n\t\t\t\t<p class=\"cn-retention__muted\">\n\t\t\t\t\t{{ text(content.subtitle) || t('nextcloud-vue', 'What happens to this record once it is closed') }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t\t<!-- Recompute starts in the header (board DqZaakArchivering); the\n\t\t\t dialog asks for the reason OpenRegister records with it. -->\n\t\t\t<NcButton\n\t\t\t\tv-if=\"content.recompute !== false && !loading && !error\"\n\t\t\t\t:disabled=\"!mayRecompute || busy\"\n\t\t\t\t:title=\"mayRecompute ? undefined : t('nextcloud-vue', 'Recomputing needs the archivist role')\"\n\t\t\t\t:aria-description=\"mayRecompute ? undefined : t('nextcloud-vue', 'Recomputing needs the archivist role')\"\n\t\t\t\t:data-testid=\"mayRecompute ? 'cn-retention-recompute-open' : 'cn-retention-recompute-denied'\"\n\t\t\t\t@click=\"recomputeOpen = true\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Refresh :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t('nextcloud-vue', 'Recompute nomination') }}\n\t\t\t</NcButton>\n\t\t</header>\n\t\t<CnRetentionRecomputeDialog\n\t\t\tv-if=\"recomputeOpen\"\n\t\t\t:user=\"currentUid\"\n\t\t\t:busy=\"busy\"\n\t\t\t@close=\"recomputeOpen = false\"\n\t\t\t@confirm=\"recompute\" />\n\n\t\t<NcLoadingIcon v-if=\"loading\" :size=\"24\" />\n\t\t<NcEmptyContent v-else-if=\"error\"\n\t\t\t:name=\"t('nextcloud-vue', 'The archival facts are unavailable')\"\n\t\t\t:description=\"error\"\n\t\t\tdata-testid=\"cn-retention-error\">\n\t\t\t<template #action>\n\t\t\t\t<NcButton data-testid=\"cn-retention-retry\" @click=\"load\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Try again') }}\n\t\t\t\t</NcButton>\n\t\t\t</template>\n\t\t</NcEmptyContent>\n\n\t\t<template v-else>\n\t\t\t<p v-if=\"archived\" class=\"cn-retention__archived\" data-testid=\"cn-retention-archived\">\n\t\t\t\t{{ t('nextcloud-vue', 'Archived on {at} by {by}. {reason}', { at: String(archived.at ?? ''), by: String(archived.by ?? ''), reason: String(archived.reason ?? '') }) }}\n\t\t\t</p>\n\t\t\t<NcNoteCard v-if=\"disagrees\" type=\"warning\" data-testid=\"cn-retention-disagreement\">\n\t\t\t\t{{ t('nextcloud-vue', 'This record and OpenRegister do not agree on whether it is archived.') }}\n\t\t\t</NcNoteCard>\n\n\t\t\t<NcNoteCard v-if=\"nomination && nomination.status === 'unnominatable'\" type=\"warning\" data-testid=\"cn-retention-unnominatable\">\n\t\t\t\t{{ t('nextcloud-vue', 'Nothing could decide what happens to this record.') }}\n\t\t\t\t{{ String(nomination.unnominatableReason ?? '') }}\n\t\t\t</NcNoteCard>\n\t\t\t<div v-else-if=\"!nomination\" data-testid=\"cn-retention-pending\">\n\t\t\t\t<dl class=\"cn-retention__facts\">\n\t\t\t\t\t<div v-for=\"fact in pendingFacts\" :key=\"fact.key\" class=\"cn-retention__fact\">\n\t\t\t\t\t\t<dt>{{ fact.label }}</dt>\n\t\t\t\t\t\t<dd>{{ fact.value }}</dd>\n\t\t\t\t\t</div>\n\t\t\t\t</dl>\n\t\t\t\t<NcNoteCard type=\"info\">\n\t\t\t\t\t{{ text(content.pendingNote) || t('nextcloud-vue', 'This record has not been closed yet. Its archival future is recorded when it closes.') }}\n\t\t\t\t</NcNoteCard>\n\t\t\t</div>\n\t\t\t<dl v-else class=\"cn-retention__facts\" data-testid=\"cn-retention-facts\">\n\t\t\t\t<div v-for=\"fact in facts\" :key=\"fact.key\" class=\"cn-retention__fact\">\n\t\t\t\t\t<dt>{{ fact.label }}</dt>\n\t\t\t\t\t<dd :data-testid=\"`cn-retention-${fact.key}`\">\n\t\t\t\t\t\t{{ fact.value }}\n\t\t\t\t\t</dd>\n\t\t\t\t</div>\n\t\t\t</dl>\n\n\t\t\t<section v-if=\"content.legalHold\" class=\"cn-retention__section\" data-testid=\"cn-retention-hold\">\n\t\t\t\t<component :is=\"innerHeadingTag\" class=\"cn-inner-heading\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Kept for a procedure') }}\n\t\t\t\t</component>\n\t\t\t\t<ul class=\"cn-retention__rows\">\n\t\t\t\t\t<li v-for=\"hold in holds\" :key=\"hold.id\" class=\"cn-retention__row\">\n\t\t\t\t\t\t<span class=\"cn-retention__mark cn-retention__mark--hold\" aria-hidden=\"true\">LH</span>\n\t\t\t\t\t\t<span class=\"cn-retention__row-text\">\n\t\t\t\t\t\t\t<span class=\"cn-retention__row-title\">{{ hold.reason || t('nextcloud-vue', 'Legal hold') }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-retention__muted\">{{ t('nextcloud-vue', 'Placed on {date} by {by}', { date: hold.placedDate, by: hold.placedBy }) }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<CnStatusBadge :label=\"t('nextcloud-vue', 'Active')\" variant=\"warning\" />\n\t\t\t\t\t</li>\n\t\t\t\t\t<li v-if=\"holds.length === 0\" class=\"cn-retention__row\">\n\t\t\t\t\t\t<span class=\"cn-retention__mark cn-retention__mark--hold\" aria-hidden=\"true\">LH</span>\n\t\t\t\t\t\t<span class=\"cn-retention__row-text\">\n\t\t\t\t\t\t\t<span class=\"cn-retention__row-title\">{{ t('nextcloud-vue', 'No legal hold') }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-retention__muted\">{{ t('nextcloud-vue', 'If an objection or appeal runs, destruction is suspended') }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<CnStatusBadge :label=\"t('nextcloud-vue', 'None')\" />\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\n\t\t\t<section v-if=\"content.transfer\" class=\"cn-retention__section\" data-testid=\"cn-retention-transfer\">\n\t\t\t\t<component :is=\"innerHeadingTag\" class=\"cn-inner-heading\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Transfer to the e-Depot') }}\n\t\t\t\t</component>\n\t\t\t\t<ul class=\"cn-retention__rows\">\n\t\t\t\t\t<li class=\"cn-retention__row\">\n\t\t\t\t\t\t<span class=\"cn-retention__mark\" aria-hidden=\"true\">ED</span>\n\t\t\t\t\t\t<span class=\"cn-retention__row-text\">\n\t\t\t\t\t\t\t<span class=\"cn-retention__row-title\">{{ transfers ? t('nextcloud-vue', 'Goes to the e-Depot') : t('nextcloud-vue', 'Not applicable') }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-retention__muted\">{{ transfers ? t('nextcloud-vue', 'After the retention period the record is transferred') : t('nextcloud-vue', 'Only records appraised to keep go to the e-Depot') }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<CnStatusBadge :label=\"transfers ? t('nextcloud-vue', 'Transfer') : t('nextcloud-vue', 'No transfer')\" :variant=\"transfers ? 'info' : 'default'\" />\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\n\t\t\t<div v-if=\"outcome\" class=\"cn-retention__outcome\" data-testid=\"cn-retention-outcome\">\n\t\t\t\t<component :is=\"innerHeadingTag\" class=\"cn-inner-heading\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Outcome') }}\n\t\t\t\t</component>\n\t\t\t\t<p>{{ t('nextcloud-vue', 'Handed over as {kind} on {at} by {by}.', { kind: String(outcome.kind ?? ''), at: String(outcome.at ?? ''), by: String(outcome.by ?? '') }) }}</p>\n\t\t\t\t<p v-if=\"outcome.transferListUuid\">\n\t\t\t\t\t{{ t('nextcloud-vue', 'Transfer list') }}: {{ outcome.transferListUuid }}\n\t\t\t\t</p>\n\t\t\t</div>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { getCurrentUser } from '@nextcloud/auth'\nimport axios from '@nextcloud/axios'\nimport { translatePlural as n, translate as t } from '@nextcloud/l10n'\nimport { generateUrl } from '@nextcloud/router'\nimport { NcButton, NcEmptyContent, NcLoadingIcon, NcNoteCard } from '@nextcloud/vue'\nimport Refresh from 'vue-material-design-icons/Refresh.vue'\nimport CnStatusBadge from '../CnStatusBadge/CnStatusBadge.vue'\nimport CnRetentionRecomputeDialog from './CnRetentionRecomputeDialog.vue'\nimport innerHeading from '../../mixins/innerHeading.js'\nimport { parseDuration } from '../../utils/isoDuration.js'\nimport { readPath } from '../../utils/readPath.js'\nimport { hasUnresolvedTokens, resolveFilterTokens } from '../../utils/resolveFilterTokens.js'\n\n/**\n * CnRetentionWidget — the `retention` detail widget: what happens to a\n * record when its business use ends, as OpenRegister decided it. Every value\n * is read off the record's `@self._retention` (appraisal, disposal date,\n * retention period, selection list row, the rule and when it was written)\n * and `@self.archived`; the widget derives nothing, so it can never disagree\n * with the stored decision. A failed read is an error with a retry, never an\n * empty block. A record nothing could nominate is drawn apart from one that\n * is not closed yet. An archivist (or an admin) may ask OpenRegister to\n * recompute the nomination, with a reason. Any app on OpenRegister uses it;\n * dossiq's case Archiving tab is the first.\n *\n * `content`:\n * - `title`, `subtitle`: texts, through the host translate.\n * - `statusField`, `archivedValues`: a record field and the values that mean\n * archived (`archiveStatus`, `[\"archived\", ...]`); when set, a mismatch with\n * `@self.archived` is said out loud.\n * - `archivistGroups`: groups that may recompute (default `[\"archivaris\"]`;\n * admins always may); `recompute: false` hides the control.\n * - `expected`: before the record closes, the outcome its type expects.\n * `source: { register, schema, filter }` lists the candidate rules (a case\n * type's result types, `filter` tokens such as `@object.caseType`\n * resolved against the record); `fields: { appraisal, period,\n * selectionListRow }` names their fields; `appraisalLabels` names the\n * appraisal values; `periodText` writes the period (`{period} after the\n * decision`); `decidedBy` says who decides. When the candidates agree the\n * facts show that outcome, otherwise \"Depends on the result\". A failed\n * read keeps the plain facts.\n * - `pendingNote`: the note under the facts of a record not closed yet.\n * - `legalHold: true`: a section with the record's active legal holds\n * (`@self._retention.legalHold.holds`), or that none applies.\n * - `transfer: { keepValues }`: a section saying whether the record goes to\n * the e-Depot, from the stored or expected appraisal.\n *\n * The record is read fresh from OpenRegister by `register`, `schema` and\n * `objectId` (props, else the bound record's `@self`).\n */\nexport default {\n\tname: 'CnRetentionWidget',\n\n\tcomponents: { CnRetentionRecomputeDialog, CnStatusBadge, NcButton, NcEmptyContent, NcLoadingIcon, NcNoteCard, Refresh },\n\n\tmixins: [innerHeading],\n\n\tinject: {\n\t\t/** The host app's translate (CnAppRoot `cnTranslate`). */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget's configuration (see the component docs).\n\t\t *\n\t\t * @type {object}\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 * The bound record, on a detail page.\n\t\t *\n\t\t * @type {object|null}\n\t\t */\n\t\tobjectData: {\n\t\t\ttype: Object,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/** The record's register (else the bound record's `@self.register`). */\n\t\tregister: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * The record's schema (else the bound record's `@self.schema`); a schema object gives its slug.\n\t\t *\n\t\t * @type {string|number|object}\n\t\t */\n\t\tschema: {\n\t\t\ttype: [String, Number, Object],\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/** The record's id (else the bound record's). */\n\t\tobjectId: {\n\t\t\ttype: [String, Number],\n\t\t\tdefault: '',\n\t\t},\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tloading: true,\n\t\t\terror: '',\n\t\t\trecord: null,\n\t\t\t/** Whether the recompute dialog is open. */\n\t\t\trecomputeOpen: false,\n\t\t\tbusy: false,\n\t\t\t/** The candidate rules `content.expected` reads, null until read or when unreadable. */\n\t\t\texpectedRows: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {object} The bound record's `@self`. */\n\t\tboundSelf() {\n\t\t\treturn (this.objectData && this.objectData['@self']) || {}\n\t\t},\n\n\t\t/** @return {string} The record id. */\n\t\tid() {\n\t\t\treturn String(this.objectId || (this.objectData && this.objectData.id) || this.boundSelf.id || '')\n\t\t},\n\n\t\t/** @return {string} The register. */\n\t\tregisterRef() {\n\t\t\treturn String(this.register || this.boundSelf.register || '')\n\t\t},\n\n\t\t/** @return {string} The schema. */\n\t\tschemaRef() {\n\t\t\tconst s = this.schema || this.boundSelf.schema || ''\n\t\t\treturn String(typeof s === 'object' ? (s.slug || s.id || '') : s)\n\t\t},\n\n\t\t/** @return {object} The record's `@self`. */\n\t\tself() {\n\t\t\treturn (this.record && this.record['@self']) || {}\n\t\t},\n\n\t\t/** @return {object|null} OpenRegister's stored retention decision. */\n\t\tretention() {\n\t\t\treturn this.self._retention || null\n\t\t},\n\n\t\t/** @return {object|null} The nomination. */\n\t\tnomination() {\n\t\t\treturn (this.retention && this.retention.nomination) || null\n\t\t},\n\n\t\t/** @return {object|null} The hand-over outcome. */\n\t\toutcome() {\n\t\t\treturn (this.retention && this.retention.outcome) || null\n\t\t},\n\n\t\t/** @return {object|null} OpenRegister's archived marker. */\n\t\tarchived() {\n\t\t\treturn this.self.archived || null\n\t\t},\n\n\t\t/**\n\t\t * Whether the record's own field and the platform marker disagree.\n\t\t *\n\t\t * @spec openspec/changes/retention-widget/specs/retention-widget/spec.md#requirement-a-record-shows-its-stored-retention\n\t\t * @return {boolean}\n\t\t */\n\t\tdisagrees() {\n\t\t\tconst field = this.content.statusField\n\t\t\tif (!field || !this.record) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\tconst values = Array.isArray(this.content.archivedValues) ? this.content.archivedValues.map(String) : ['archived']\n\t\t\tconst saysArchived = values.includes(String(readPath(this.record, field) ?? ''))\n\t\t\treturn saysArchived !== Boolean(this.archived)\n\t\t},\n\n\t\t/**\n\t\t * The stored facts that have a value, in reading order.\n\t\t *\n\t\t * @spec openspec/changes/retention-widget/specs/retention-widget/spec.md#requirement-a-record-shows-its-stored-retention\n\t\t * @return {Array<{key: string, label: string, value: string}>}\n\t\t */\n\t\tfacts() {\n\t\t\tconst r = this.retention || {}\n\t\t\tconst n = this.nomination || {}\n\t\t\treturn [\n\t\t\t\t{ key: 'appraisal', label: t('nextcloud-vue', 'Appraisal'), value: r.appraisal },\n\t\t\t\t{ key: 'disposal-date', label: t('nextcloud-vue', 'Disposal date'), value: r.disposalDate },\n\t\t\t\t{ key: 'retention-period', label: t('nextcloud-vue', 'Retention period'), value: r.retentionPeriod },\n\t\t\t\t{ key: 'selection-list-row', label: t('nextcloud-vue', 'Selection list row'), value: r.selectionListRow },\n\t\t\t\t{ key: 'rule', label: t('nextcloud-vue', 'Decided by'), value: n.rule },\n\t\t\t\t{ key: 'decided-at', label: t('nextcloud-vue', 'Written on'), value: n.at },\n\t\t\t].filter((fact) => fact.value !== undefined && fact.value !== null && fact.value !== '').map((fact) => ({ ...fact, value: String(fact.value) }))\n\t\t},\n\n\t\t/**\n\t\t * The outcome the record's type expects before it closes: the agreed\n\t\t * appraisal, period and selection list row of the candidate rules, or\n\t\t * `mixed` when they disagree; null when nothing was read.\n\t\t *\n\t\t * @spec openspec/changes/retention-expected-outcome/specs/retention-widget/spec.md#requirement-an-open-record-shows-the-outcome-its-type-expects\n\t\t * @return {{appraisal: string, period: string, selectionListRow: string, mixed: boolean}|null}\n\t\t */\n\t\texpectedOutcome() {\n\t\t\tconst rows = this.expectedRows\n\t\t\tif (!Array.isArray(rows) || rows.length === 0) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst fields = { appraisal: 'appraisal', period: 'period', selectionListRow: 'selectionListRow', ...(this.content.expected?.fields || {}) }\n\t\t\tconst pick = (row, key) => String(readPath(row, fields[key]) ?? '')\n\t\t\tconst outcomes = rows.map((row) => ({ appraisal: pick(row, 'appraisal'), period: pick(row, 'period'), selectionListRow: pick(row, 'selectionListRow') }))\n\t\t\tconst first = outcomes[0]\n\t\t\tconst agree = outcomes.every((o) => o.appraisal === first.appraisal && o.period === first.period)\n\t\t\tif (first.appraisal === '' && first.period === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn agree\n\t\t\t\t? { ...first, selectionListRow: outcomes.every((o) => o.selectionListRow === first.selectionListRow) ? first.selectionListRow : '', mixed: false }\n\t\t\t\t: { appraisal: '', period: '', selectionListRow: '', mixed: true }\n\t\t},\n\n\t\t/**\n\t\t * The facts of a record not closed yet: the expected outcome where the\n\t\t * type gives one, \"decided when it closes\" otherwise.\n\t\t *\n\t\t * @spec openspec/changes/retention-expected-outcome/specs/retention-widget/spec.md#requirement-an-open-record-shows-the-outcome-its-type-expects\n\t\t * @return {Array<{key: string, label: string, value: string}>}\n\t\t */\n\t\tpendingFacts() {\n\t\t\tconst atClosure = t('nextcloud-vue', 'Decided when it closes')\n\t\t\tconst expected = this.expectedOutcome\n\t\t\tif (!expected) {\n\t\t\t\treturn [\n\t\t\t\t\t{ key: 'appraisal', label: t('nextcloud-vue', 'Appraisal'), value: atClosure },\n\t\t\t\t\t{ key: 'retention-period', label: t('nextcloud-vue', 'Retention period'), value: atClosure },\n\t\t\t\t\t{ key: 'disposal-date', label: t('nextcloud-vue', 'Disposal date'), value: atClosure },\n\t\t\t\t\t{ key: 'decided-at', label: t('nextcloud-vue', 'Written on'), value: t('nextcloud-vue', 'Not yet') },\n\t\t\t\t]\n\t\t\t}\n\t\t\tconst depends = t('nextcloud-vue', 'Depends on the result')\n\t\t\tconst config = this.content.expected || {}\n\t\t\tconst facts = [\n\t\t\t\t{ key: 'appraisal', label: t('nextcloud-vue', 'Appraisal'), value: expected.mixed ? depends : this.appraisalLabel(expected.appraisal) },\n\t\t\t\t{ key: 'retention-period', label: t('nextcloud-vue', 'Retention period'), value: expected.mixed ? depends : this.periodLabel(expected.period) },\n\t\t\t]\n\t\t\tif (expected.selectionListRow) {\n\t\t\t\tfacts.push({ key: 'selection-list-row', label: t('nextcloud-vue', 'Selection list row'), value: expected.selectionListRow })\n\t\t\t}\n\t\t\tfacts.push({ key: 'disposal-date', label: t('nextcloud-vue', 'Disposal date'), value: atClosure })\n\t\t\tif (config.decidedBy) {\n\t\t\t\tfacts.push({ key: 'rule', label: t('nextcloud-vue', 'Decided by'), value: this.text(config.decidedBy) })\n\t\t\t}\n\t\t\tfacts.push({ key: 'decided-at', label: t('nextcloud-vue', 'Written on'), value: t('nextcloud-vue', 'Not yet') })\n\t\t\treturn facts.filter((fact) => fact.value !== '')\n\t\t},\n\n\t\t/** @return {Array<{id: string, reason: string, placedBy: string, placedDate: string}>} The active legal holds. */\n\t\tholds() {\n\t\t\tconst hold = (this.retention && this.retention.legalHold) || {}\n\t\t\tconst list = Array.isArray(hold.holds) ? hold.holds : []\n\t\t\treturn list.map((h, index) => ({ id: String(h.id ?? index), reason: String(h.reason ?? ''), placedBy: String(h.placedBy ?? ''), placedDate: String(h.placedDate ?? '') }))\n\t\t},\n\n\t\t/**\n\t\t * Whether the record goes to the e-Depot: its stored appraisal, else\n\t\t * the expected one, is one of `transfer.keepValues`.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\ttransfers() {\n\t\t\tconst keep = Array.isArray(this.content.transfer?.keepValues) ? this.content.transfer.keepValues.map(String) : ['bewaren', 'blijvend_bewaren', 'retain', 'keep']\n\t\t\tconst stored = this.retention && this.retention.appraisal\n\t\t\tconst appraisal = stored || (this.expectedOutcome && !this.expectedOutcome.mixed ? this.expectedOutcome.appraisal : '')\n\t\t\treturn keep.includes(String(appraisal || ''))\n\t\t},\n\n\t\t/** @return {string} The signed-in user's id. */\n\t\tcurrentUid() {\n\t\t\tconst user = getCurrentUser()\n\t\t\treturn String((user && user.uid) || '')\n\t\t},\n\n\t\t/**\n\t\t * Whether this user may recompute: an admin or an archivist group member.\n\t\t * Shown disabled otherwise, so the user learns which role it needs.\n\t\t *\n\t\t * @return {boolean}\n\t\t */\n\t\tmayRecompute() {\n\t\t\tconst user = getCurrentUser()\n\t\t\tif (user && user.isAdmin === true) {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tconst groups = Array.isArray(user && user.groups) ? user.groups : []\n\t\t\tconst allowed = Array.isArray(this.content.archivistGroups) ? this.content.archivistGroups : ['archivaris']\n\t\t\treturn allowed.some((g) => groups.includes(g))\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.load()\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * A config text through the host translate.\n\t\t *\n\t\t * @param {unknown} value The text.\n\t\t * @return {string}\n\t\t */\n\t\ttext(value) {\n\t\t\treturn typeof value === 'string' && value !== '' ? this.cnTranslate(value) : ''\n\t\t},\n\n\t\t/**\n\t\t * An appraisal value in words, through `expected.appraisalLabels`.\n\t\t *\n\t\t * @param {string} value The stored value.\n\t\t * @return {string}\n\t\t */\n\t\tappraisalLabel(value) {\n\t\t\tconst labels = this.content.expected?.appraisalLabels || {}\n\t\t\treturn labels[value] ? this.text(labels[value]) : value\n\t\t},\n\n\t\t/**\n\t\t * An ISO period in words (\"5 years\"), placed in `expected.periodText`.\n\t\t *\n\t\t * @param {string} value The ISO 8601 duration.\n\t\t * @return {string}\n\t\t */\n\t\tperiodLabel(value) {\n\t\t\tconst parsed = parseDuration(value)\n\t\t\tconst words = parsed\n\t\t\t\t? {\n\t\t\t\t\t\tyears: () => n('nextcloud-vue', '%n year', '%n years', parsed.amount),\n\t\t\t\t\t\tmonths: () => n('nextcloud-vue', '%n month', '%n months', parsed.amount),\n\t\t\t\t\t\tweeks: () => n('nextcloud-vue', '%n week', '%n weeks', parsed.amount),\n\t\t\t\t\t\tdays: () => n('nextcloud-vue', '%n day', '%n days', parsed.amount),\n\t\t\t\t\t}[parsed.unit]\n\t\t\t\t: null\n\t\t\tconst period = words ? words() : value\n\t\t\tconst template = this.content.expected?.periodText\n\t\t\treturn template ? this.text(template).replace('{period}', period) : period\n\t\t},\n\n\t\t/**\n\t\t * Read the record fresh from OpenRegister; a failure is an error, never an empty block.\n\t\t *\n\t\t * @spec openspec/changes/retention-widget/specs/retention-widget/spec.md#requirement-a-record-shows-its-stored-retention\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync load() {\n\t\t\tthis.loading = true\n\t\t\tthis.error = ''\n\t\t\ttry {\n\t\t\t\tif (!this.registerRef || !this.schemaRef || !this.id) {\n\t\t\t\t\tthrow new Error(t('nextcloud-vue', 'This widget has no record to read.'))\n\t\t\t\t}\n\t\t\t\tconst { data } = await axios.get(generateUrl(`/apps/openregister/api/objects/${encodeURIComponent(this.registerRef)}/${encodeURIComponent(this.schemaRef)}/${encodeURIComponent(this.id)}`))\n\t\t\t\tthis.record = data && typeof data === 'object' ? data : null\n\t\t\t\tawait this.loadExpected()\n\t\t\t} catch (failure) {\n\t\t\t\tconst body = failure && failure.response && failure.response.data\n\t\t\t\tthis.error = String((body && (body.message || body.error)) || (failure && failure.message) || failure)\n\t\t\t} finally {\n\t\t\t\tthis.loading = false\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Read the candidate rules `content.expected` names, for a record that\n\t\t * is not nominated yet. A failure leaves the plain pending facts.\n\t\t *\n\t\t * @spec openspec/changes/retention-expected-outcome/specs/retention-widget/spec.md#requirement-an-open-record-shows-the-outcome-its-type-expects\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync loadExpected() {\n\t\t\tconst source = this.content.expected && this.content.expected.source\n\t\t\tthis.expectedRows = null\n\t\t\tif (!source || !source.register || !source.schema || this.nomination) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tconst filter = resolveFilterTokens(source.filter || {}, { objectId: this.id, object: this.record })\n\t\t\tif (hasUnresolvedTokens(filter)) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst { data } = await axios.get(generateUrl(`/apps/openregister/api/objects/${encodeURIComponent(source.register)}/${encodeURIComponent(source.schema)}`), { params: { ...filter, _limit: 100 } })\n\t\t\t\tthis.expectedRows = Array.isArray(data?.results) ? data.results : []\n\t\t\t} catch {\n\t\t\t\tthis.expectedRows = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Ask OpenRegister to derive the nomination again with the reason the\n\t\t * dialog asked for, then read it back.\n\t\t *\n\t\t * @spec openspec/changes/retention-recompute-header/specs/retention-recompute-header/spec.md#requirement-recomputing-the-nomination-starts-in-the-card-header-and-asks-for-a-reason\n\t\t * @param {string} reason Why it is recomputed.\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync recompute(reason) {\n\t\t\tconst why = String(reason ?? '').trim()\n\t\t\tif (this.busy || why === '') {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.busy = true\n\t\t\ttry {\n\t\t\t\tawait axios.post(generateUrl(`/apps/openregister/api/archival/objects/${encodeURIComponent(this.id)}/nomination/recompute`), { reason: why })\n\t\t\t\tthis.recomputeOpen = false\n\t\t\t\tawait this.load()\n\t\t\t} catch (failure) {\n\t\t\t\tconst body = failure && failure.response && failure.response.data\n\t\t\t\tthis.error = String((body && (body.message || body.error)) || (failure && failure.message) || failure)\n\t\t\t} finally {\n\t\t\t\tthis.busy = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-retention {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 16px;\n}\n\n.cn-retention__head {\n\tdisplay: flex;\n\tflex-wrap: wrap;\n\talign-items: flex-start;\n\tjustify-content: space-between;\n\tgap: 10px 16px;\n}\n\n.cn-retention__head-text {\n\tdisplay: flex;\n\tflex: 1 1 260px;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-retention__title {\n\tmargin: 0;\n\tfont-size: 18px;\n\tfont-weight: 700;\n}\n\n.cn-retention__muted {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-retention__facts {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n\tgap: 12px 24px;\n\tmargin: 0;\n}\n\n/* The server draws every dt as a 130px right-aligned inline block and pads\n dt and dd 12px; a fact is a label over its value. */\n.cn-retention__fact dt,\n.cn-retention__fact dd {\n\tdisplay: block;\n\twidth: auto;\n\twhite-space: normal;\n\ttext-align: start;\n}\n\n.cn-retention__fact dt {\n\tmargin: 0;\n\tpadding: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-retention__fact dd {\n\tmargin: 0;\n\tpadding: 0;\n\tfont-weight: 600;\n}\n\n.cn-retention__section {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 4px;\n}\n\n.cn-retention__rows {\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.cn-retention__row {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tgap: 12px;\n\tpadding: 12px 0;\n\tborder-top: 1px solid var(--color-border);\n}\n\n.cn-retention__row-text {\n\tdisplay: flex;\n\tflex: 1 1 auto;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n/* A round mark before a hold or transfer row: LH (legal hold) on the error\n tint, ED (e-Depot) on the chip ground (DqZaakArchivering). */\n.cn-retention__mark {\n\tdisplay: inline-flex;\n\tflex: none;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 32px;\n\theight: 32px;\n\tborder-radius: 16px;\n\tbackground: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-weight: 700;\n}\n\n.cn-retention__mark--hold {\n\tbackground: var(--color-error-light, #fcedec);\n\tcolor: var(--color-error-text, #a30000);\n}\n\n.cn-retention__row-title {\n\tfont-weight: 600;\n}\n\n</style>\n"],"names":["_createElementVNode","_toDisplayString","_createVNode","_createTextVNode","_createBlock","_createElementBlock","_Fragment","_renderList","_resolveDynamicComponent","_openBlock"],"mappings":"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"}
@@ -2,7 +2,7 @@ import script from './CnTasksWidget.vue2.js';
2
2
  import { render } from './CnTasksWidget.vue3.js';
3
3
 
4
4
  script.render = render;
5
- script.__scopeId = "data-v-9804876c";
5
+ script.__scopeId = "data-v-7dc074ce";
6
6
  const __sfc_main = script;
7
7
 
8
8
  export { __sfc_main as default };
@@ -3,10 +3,13 @@ import axios__default from '@nextcloud/axios';
3
3
  import { s as showError, x as showSuccess } from '../../node_modules/@nextcloud/dialogs/dist/chunks/index.js';
4
4
  import { translate } from '@nextcloud/l10n';
5
5
  import { generateUrl } from '@nextcloud/router';
6
- import { NcActions, NcActionButton, NcAvatar, NcButton, NcLoadingIcon, NcTextField } from '@nextcloud/vue';
6
+ import { NcActions, NcActionButton, NcActionCheckbox, NcAvatar, NcButton, NcLoadingIcon, NcTextField } from '@nextcloud/vue';
7
7
  import { ref, inject } from 'vue';
8
8
  import CheckCircle from 'vue-material-design-icons/CheckCircle.vue';
9
+ import ChevronDown from 'vue-material-design-icons/ChevronDown.vue';
10
+ import ChevronUp from 'vue-material-design-icons/ChevronUp.vue';
9
11
  import CircleOutline from 'vue-material-design-icons/CircleOutline.vue';
12
+ import FilterOutline from 'vue-material-design-icons/FilterOutline.vue';
10
13
  import __sfc_main from '../CnStatusBadge/CnStatusBadge.vue.js';
11
14
  import __sfc_main$1 from '../CnTaskFormDialog/CnTaskFormDialog.vue.js';
12
15
  import __sfc_main$2 from './CnTaskWorkCard.vue.js';
@@ -86,12 +89,16 @@ var script = {
86
89
 
87
90
  components: {
88
91
  CheckCircle,
92
+ ChevronDown,
93
+ ChevronUp,
89
94
  CircleOutline,
90
95
  CnStatusBadge: __sfc_main,
91
96
  CnTaskFormDialog: __sfc_main$1,
92
97
  CnTaskWorkCard: __sfc_main$2,
98
+ FilterOutline,
93
99
  NcActions,
94
100
  NcActionButton,
101
+ NcActionCheckbox,
95
102
  NcAvatar,
96
103
  NcButton,
97
104
  NcLoadingIcon,
@@ -246,6 +253,10 @@ var script = {
246
253
  data() {
247
254
  return {
248
255
  pollTimer: null,
256
+ /** The table's head sort, `{ key, dir }`; no key keeps open tasks first. */
257
+ headSort: { key: '', dir: 'asc' },
258
+ /** The table's head filters, `{ column: values[] }`. */
259
+ headFilters: {},
249
260
  /** Uuids of the tasks a checkbox is completing, until the next read. */
250
261
  completing: [],
251
262
  /** Whether the table's create form is open. */
@@ -405,7 +416,26 @@ var script = {
405
416
  * @spec openspec/changes/record-task-table/specs/record-task-table/spec.md#requirement-a-records-tasks-show-as-a-table-with-their-summary-and-new-task-in-the-header
406
417
  */
407
418
  tableRows() {
408
- return [...this.rows.filter((task) => task.isTerminal !== true), ...this.rows.filter((task) => task.isTerminal === true)]
419
+ const kept = this.rows.filter((task) => Object.entries(this.headFilters).every(([key, values]) => values.length === 0 || values.includes(this.headValue(task, key))));
420
+ if (this.headSort.key) {
421
+ const dir = this.headSort.dir === 'desc' ? -1 : 1;
422
+ return [...kept].sort((a, b) => {
423
+ const x = this.headSortValue(a, this.headSort.key);
424
+ const y = this.headSortValue(b, this.headSort.key);
425
+ return (x < y ? -1 : (x > y ? 1 : 0)) * dir
426
+ })
427
+ }
428
+ return [...kept.filter((task) => task.isTerminal !== true), ...kept.filter((task) => task.isTerminal === true)]
429
+ },
430
+
431
+ /** @return {Array<{key: string, label: string}>} The table's data columns. */
432
+ headColumns() {
433
+ return [
434
+ { key: 'title', label: this.tr('Task') },
435
+ { key: 'handler', label: this.tr('Handler') },
436
+ { key: 'due', label: this.tr('Task deadline') },
437
+ { key: 'state', label: this.tr('Status') },
438
+ ]
409
439
  },
410
440
 
411
441
  /**
@@ -786,6 +816,84 @@ var script = {
786
816
  * @param {object} task The task row.
787
817
  * @return {string} The name.
788
818
  */
819
+ /**
820
+ * A row's value in a column, as a head filter keeps it.
821
+ *
822
+ * @param {object} task The task row.
823
+ * @param {string} key The column.
824
+ * @return {string}
825
+ */
826
+ headValue(task, key) {
827
+ if (key === 'title') {
828
+ return this.titleOf(task)
829
+ }
830
+ if (key === 'handler') {
831
+ return String(task.assignee || '')
832
+ }
833
+ if (key === 'due') {
834
+ return this.content.dueFormat === 'date' ? this.dueDate(task) : this.dueLabel(task)
835
+ }
836
+ return String(task.state || '')
837
+ },
838
+
839
+ /**
840
+ * A row's value in a column, as its head sorts it: the deadline by date.
841
+ *
842
+ * @param {object} task The task row.
843
+ * @param {string} key The column.
844
+ * @return {string|number}
845
+ */
846
+ headSortValue(task, key) {
847
+ if (key === 'due') {
848
+ const time = Date.parse(task.dueAt || task.dueDate || '');
849
+ return Number.isNaN(time) ? Number.POSITIVE_INFINITY : time
850
+ }
851
+ if (key === 'handler') {
852
+ return this.handlerName(task).toLowerCase()
853
+ }
854
+ if (key === 'state') {
855
+ return this.stateLabel(task).toLowerCase()
856
+ }
857
+ return this.titleOf(task).toLowerCase()
858
+ },
859
+
860
+ /**
861
+ * The values a column's head filter offers, one per value the rows hold.
862
+ *
863
+ * @param {string} key The column.
864
+ * @return {Array<{value: string, label: string}>}
865
+ */
866
+ headOptions(key) {
867
+ const seen = new Map();
868
+ for (const task of this.rows) {
869
+ const value = this.headValue(task, key);
870
+ if (value !== '' && !seen.has(value)) {
871
+ seen.set(value, key === 'handler' ? this.handlerName(task) : (key === 'state' ? this.stateLabel(task) : value));
872
+ }
873
+ }
874
+ return [...seen].map(([value, label]) => ({ value, label }))
875
+ },
876
+
877
+ /**
878
+ * Sort by a column: ascending first, then descending.
879
+ *
880
+ * @param {string} key The column.
881
+ */
882
+ sortBy(key) {
883
+ this.headSort = this.headSort.key === key && this.headSort.dir === 'asc' ? { key, dir: 'desc' } : { key, dir: 'asc' };
884
+ },
885
+
886
+ /**
887
+ * Keep or drop one value of a column's head filter.
888
+ *
889
+ * @param {string} key The column.
890
+ * @param {string} value The value.
891
+ */
892
+ toggleHeadFilter(key, value) {
893
+ const current = this.headFilters[key] || [];
894
+ this.headFilters = { ...this.headFilters, [key]: current.includes(value) ? current.filter((v) => v !== value) : [...current, value] };
895
+ },
896
+
789
897
  handlerName(task) {
790
898
  return userDisplayName(task.assignee) || String(task.assignee || '')
791
899
  },