@conduction/nextcloud-vue 2.108.0 → 2.109.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/CnAddressSearch/CnAddressSearch.vue.js +10 -0
- package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue.js.map +1 -0
- package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue2.js +305 -0
- package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue2.js.map +1 -0
- package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue3.js +38 -0
- package/dist/esm/components/CnAddressSearch/CnAddressSearch.vue3.js.map +1 -0
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js +10 -0
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue.js.map +1 -0
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js +398 -0
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue2.js.map +1 -0
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js +155 -0
- package/dist/esm/components/CnAssignDialog/CnAssignDialog.vue3.js.map +1 -0
- package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue.js +9 -0
- package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue.js.map +1 -0
- package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue2.js +281 -0
- package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue3.js +137 -0
- package/dist/esm/components/CnDecisionListWidget/CnDecisionListWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue.js +9 -0
- package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue.js.map +1 -0
- package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue2.js +225 -0
- package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue2.js.map +1 -0
- package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue3.js +110 -0
- package/dist/esm/components/CnDuplicateWarning/CnDuplicateWarning.vue3.js.map +1 -0
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue.js +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js +19 -3
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue2.js.map +1 -1
- package/dist/esm/components/CnFormDialog/CnFormDialog.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +30 -0
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +513 -481
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue.js +9 -0
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue.js.map +1 -0
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js +181 -0
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue2.js.map +1 -0
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue3.js +50 -0
- package/dist/esm/components/CnNcDashboardList/CnNcDashboardList.vue3.js.map +1 -0
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue.js +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +102 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +6 -6
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue.js +9 -0
- package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue.js.map +1 -0
- package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue2.js +190 -0
- package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue3.js +91 -0
- package/dist/esm/components/CnPlanWidget/CnPlanWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue.js +9 -0
- package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue.js.map +1 -0
- package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue2.js +64 -0
- package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue2.js.map +1 -0
- package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue3.js +49 -0
- package/dist/esm/components/CnPlanWidget/CnPlanWidgetRow.vue3.js.map +1 -0
- package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue.js +9 -0
- package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue.js.map +1 -0
- package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue2.js +382 -0
- package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue2.js.map +1 -0
- package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue3.js +285 -0
- package/dist/esm/components/CnRedactionEditor/CnRedactionEditor.vue3.js.map +1 -0
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue.js +9 -0
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue.js.map +1 -0
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue2.js +277 -0
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue2.js.map +1 -0
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js +183 -0
- package/dist/esm/components/CnRetentionWidget/CnRetentionWidget.vue3.js.map +1 -0
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js +16 -14
- package/dist/esm/components/CnWidgetGrid/builtInWidgets.js.map +1 -1
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js +53 -22
- package/dist/esm/components/CnWidgetGrid/registerDashboardWidgets.js.map +1 -1
- package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue.js +9 -0
- package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue.js.map +1 -0
- package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue2.js +246 -0
- package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue2.js.map +1 -0
- package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue3.js +197 -0
- package/dist/esm/components/CnWorkQueue/CnWorkQueue.vue3.js.map +1 -0
- package/dist/esm/index.js +12 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/l10n/en.json.js +106 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +105 -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 +495 -61
- package/dist/esm/utils/actionsDispatcher.js +6 -1
- package/dist/esm/utils/actionsDispatcher.js.map +1 -1
- package/dist/esm/utils/builtInModals.js +3 -1
- package/dist/esm/utils/builtInModals.js.map +1 -1
- package/dist/esm/utils/duplicateWarning.js +62 -0
- package/dist/esm/utils/duplicateWarning.js.map +1 -0
- package/dist/esm/utils/errors.js +4 -0
- package/dist/esm/utils/errors.js.map +1 -1
- package/dist/esm/utils/libraryWidgetKeys.js +4 -0
- package/dist/esm/utils/libraryWidgetKeys.js.map +1 -1
- package/dist/esm/utils/openDuplicateWarning.js +39 -0
- package/dist/esm/utils/openDuplicateWarning.js.map +1 -0
- package/dist/esm/utils/optionsFrom.js +69 -2
- package/dist/esm/utils/optionsFrom.js.map +1 -1
- package/dist/esm/utils/planTree.js +57 -0
- package/dist/esm/utils/planTree.js.map +1 -0
- package/dist/esm/utils/redactionSegments.js +53 -0
- package/dist/esm/utils/redactionSegments.js.map +1 -0
- package/dist/esm/utils/registerNcDashboardWidgets.js +61 -0
- package/dist/esm/utils/registerNcDashboardWidgets.js.map +1 -0
- package/dist/esm/utils/searchRefusal.js +62 -0
- package/dist/esm/utils/searchRefusal.js.map +1 -0
- package/dist/esm/utils/validateManifestV2.compiled.js +3 -3
- package/dist/esm/utils/validateManifestV2.compiled.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +17409 -12832
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +495 -61
- package/l10n/en.json +106 -1
- package/l10n/nl.json +105 -1
- package/package.json +1 -1
- package/src/components/CnAddressSearch/CnAddressSearch.vue +343 -0
- package/src/components/CnAddressSearch/index.js +12 -0
- package/src/components/CnAssignDialog/CnAssignDialog.vue +525 -0
- package/src/components/CnAssignDialog/index.js +12 -0
- package/src/components/CnDecisionListWidget/CnDecisionListWidget.vue +388 -0
- package/src/components/CnDecisionListWidget/index.js +12 -0
- package/src/components/CnDuplicateWarning/CnDuplicateWarning.vue +318 -0
- package/src/components/CnDuplicateWarning/index.js +12 -0
- package/src/components/CnFormDialog/CnFormDialog.vue +19 -3
- package/src/components/CnIndexPage/CnIndexPage.vue +52 -0
- package/src/components/CnNcDashboardList/CnNcDashboardList.vue +218 -0
- package/src/components/CnNcDashboardList/index.js +12 -0
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +107 -6
- package/src/components/CnPlanWidget/CnPlanWidget.vue +246 -0
- package/src/components/CnPlanWidget/CnPlanWidgetRow.vue +117 -0
- package/src/components/CnPlanWidget/index.js +12 -0
- package/src/components/CnRedactionEditor/CnRedactionEditor.vue +655 -0
- package/src/components/CnRedactionEditor/index.js +13 -0
- package/src/components/CnRetentionWidget/CnRetentionWidget.vue +413 -0
- package/src/components/CnRetentionWidget/index.js +12 -0
- package/src/components/CnWidgetGrid/builtInWidgets.js +2 -0
- package/src/components/CnWidgetGrid/registerDashboardWidgets.js +31 -0
- package/src/components/CnWorkQueue/CnWorkQueue.vue +456 -0
- package/src/components/CnWorkQueue/index.js +12 -0
- package/src/components/index.js +9 -0
- package/src/css/index-page.css +24 -0
- package/src/index.js +12 -0
- package/src/schemas/app-manifest-v2.schema.json +119 -3
- package/src/utils/actionsDispatcher.js +6 -1
- package/src/utils/builtInModals.js +3 -1
- package/src/utils/duplicateWarning.js +59 -0
- package/src/utils/errors.js +4 -0
- package/src/utils/libraryWidgetKeys.js +4 -0
- package/src/utils/openDuplicateWarning.js +35 -0
- package/src/utils/optionsFrom.js +68 -1
- package/src/utils/planTree.js +54 -0
- package/src/utils/redactionSegments.js +50 -0
- package/src/utils/registerNcDashboardWidgets.js +57 -0
- package/src/utils/searchRefusal.js +59 -0
- package/src/utils/validateManifestV2.compiled.js +3 -3
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
- SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
- SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
-->
|
|
5
|
+
<template>
|
|
6
|
+
<div class="cn-retention" data-testid="cn-retention">
|
|
7
|
+
<header class="cn-retention__head">
|
|
8
|
+
<h3 class="cn-retention__title">
|
|
9
|
+
{{ text(content.title) || t('nextcloud-vue', 'Archiving') }}
|
|
10
|
+
</h3>
|
|
11
|
+
<p class="cn-retention__muted">
|
|
12
|
+
{{ text(content.subtitle) || t('nextcloud-vue', 'What happens to this record once it is closed') }}
|
|
13
|
+
</p>
|
|
14
|
+
</header>
|
|
15
|
+
|
|
16
|
+
<NcLoadingIcon v-if="loading" :size="24" />
|
|
17
|
+
<NcEmptyContent v-else-if="error"
|
|
18
|
+
:name="t('nextcloud-vue', 'The archival facts are unavailable')"
|
|
19
|
+
:description="error"
|
|
20
|
+
data-testid="cn-retention-error">
|
|
21
|
+
<template #action>
|
|
22
|
+
<NcButton data-testid="cn-retention-retry" @click="load">
|
|
23
|
+
{{ t('nextcloud-vue', 'Try again') }}
|
|
24
|
+
</NcButton>
|
|
25
|
+
</template>
|
|
26
|
+
</NcEmptyContent>
|
|
27
|
+
|
|
28
|
+
<template v-else>
|
|
29
|
+
<p v-if="archived" class="cn-retention__archived" data-testid="cn-retention-archived">
|
|
30
|
+
{{ t('nextcloud-vue', 'Archived on {at} by {by}. {reason}', { at: String(archived.at ?? ''), by: String(archived.by ?? ''), reason: String(archived.reason ?? '') }) }}
|
|
31
|
+
</p>
|
|
32
|
+
<NcNoteCard v-if="disagrees" type="warning" data-testid="cn-retention-disagreement">
|
|
33
|
+
{{ t('nextcloud-vue', 'This record and OpenRegister do not agree on whether it is archived.') }}
|
|
34
|
+
</NcNoteCard>
|
|
35
|
+
|
|
36
|
+
<NcNoteCard v-if="nomination && nomination.status === 'unnominatable'" type="warning" data-testid="cn-retention-unnominatable">
|
|
37
|
+
{{ t('nextcloud-vue', 'Nothing could decide what happens to this record.') }}
|
|
38
|
+
{{ String(nomination.unnominatableReason ?? '') }}
|
|
39
|
+
</NcNoteCard>
|
|
40
|
+
<div v-else-if="!nomination" data-testid="cn-retention-pending">
|
|
41
|
+
<dl class="cn-retention__facts">
|
|
42
|
+
<div v-for="fact in pendingFacts" :key="fact.key" class="cn-retention__fact">
|
|
43
|
+
<dt>{{ fact.label }}</dt>
|
|
44
|
+
<dd>{{ fact.value }}</dd>
|
|
45
|
+
</div>
|
|
46
|
+
</dl>
|
|
47
|
+
<NcNoteCard type="info">
|
|
48
|
+
{{ t('nextcloud-vue', 'This record has not been closed yet. Its archival future is recorded when it closes.') }}
|
|
49
|
+
</NcNoteCard>
|
|
50
|
+
</div>
|
|
51
|
+
<dl v-else class="cn-retention__facts" data-testid="cn-retention-facts">
|
|
52
|
+
<div v-for="fact in facts" :key="fact.key" class="cn-retention__fact">
|
|
53
|
+
<dt>{{ fact.label }}</dt>
|
|
54
|
+
<dd :data-testid="`cn-retention-${fact.key}`">
|
|
55
|
+
{{ fact.value }}
|
|
56
|
+
</dd>
|
|
57
|
+
</div>
|
|
58
|
+
</dl>
|
|
59
|
+
|
|
60
|
+
<div v-if="outcome" class="cn-retention__outcome" data-testid="cn-retention-outcome">
|
|
61
|
+
<h4>{{ t('nextcloud-vue', 'Outcome') }}</h4>
|
|
62
|
+
<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>
|
|
63
|
+
<p v-if="outcome.transferListUuid">
|
|
64
|
+
{{ t('nextcloud-vue', 'Transfer list') }}: {{ outcome.transferListUuid }}
|
|
65
|
+
</p>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div v-if="content.recompute !== false" class="cn-retention__recompute">
|
|
69
|
+
<NcButton v-if="!mayRecompute" disabled data-testid="cn-retention-recompute-denied">
|
|
70
|
+
{{ t('nextcloud-vue', 'Recomputing needs the archivist role') }}
|
|
71
|
+
</NcButton>
|
|
72
|
+
<template v-else>
|
|
73
|
+
<NcTextField :modelValue="reason"
|
|
74
|
+
:label="t('nextcloud-vue', 'Why are you recomputing this?')"
|
|
75
|
+
data-testid="cn-retention-reason"
|
|
76
|
+
@update:modelValue="(v) => (reason = String(v ?? ''))" />
|
|
77
|
+
<p class="cn-retention__muted">
|
|
78
|
+
{{ t('nextcloud-vue', 'This is recorded against {user}.', { user: currentUid }) }}
|
|
79
|
+
</p>
|
|
80
|
+
<NcButton :disabled="busy || reason.trim() === ''" data-testid="cn-retention-recompute" @click="recompute">
|
|
81
|
+
{{ t('nextcloud-vue', 'Recompute the nomination') }}
|
|
82
|
+
</NcButton>
|
|
83
|
+
</template>
|
|
84
|
+
</div>
|
|
85
|
+
</template>
|
|
86
|
+
</div>
|
|
87
|
+
</template>
|
|
88
|
+
|
|
89
|
+
<script>
|
|
90
|
+
import { getCurrentUser } from '@nextcloud/auth'
|
|
91
|
+
import axios from '@nextcloud/axios'
|
|
92
|
+
import { translate as t } from '@nextcloud/l10n'
|
|
93
|
+
import { generateUrl } from '@nextcloud/router'
|
|
94
|
+
import { NcButton, NcEmptyContent, NcLoadingIcon, NcNoteCard, NcTextField } from '@nextcloud/vue'
|
|
95
|
+
import { readPath } from '../../utils/readPath.js'
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* CnRetentionWidget — the `retention` detail widget: what happens to a
|
|
99
|
+
* record when its business use ends, as OpenRegister decided it. Every value
|
|
100
|
+
* is read off the record's `@self._retention` (appraisal, disposal date,
|
|
101
|
+
* retention period, selection list row, the rule and when it was written)
|
|
102
|
+
* and `@self.archived`; the widget derives nothing, so it can never disagree
|
|
103
|
+
* with the stored decision. A failed read is an error with a retry, never an
|
|
104
|
+
* empty block. A record nothing could nominate is drawn apart from one that
|
|
105
|
+
* is not closed yet. An archivist (or an admin) may ask OpenRegister to
|
|
106
|
+
* recompute the nomination, with a reason. Any app on OpenRegister uses it;
|
|
107
|
+
* dossiq's case Archiving tab is the first.
|
|
108
|
+
*
|
|
109
|
+
* `content`:
|
|
110
|
+
* - `title`, `subtitle`: texts, through the host translate.
|
|
111
|
+
* - `statusField`, `archivedValues`: a record field and the values that mean
|
|
112
|
+
* archived (`archiveStatus`, `["archived", ...]`); when set, a mismatch with
|
|
113
|
+
* `@self.archived` is said out loud.
|
|
114
|
+
* - `archivistGroups`: groups that may recompute (default `["archivaris"]`;
|
|
115
|
+
* admins always may); `recompute: false` hides the control.
|
|
116
|
+
*
|
|
117
|
+
* The record is read fresh from OpenRegister by `register`, `schema` and
|
|
118
|
+
* `objectId` (props, else the bound record's `@self`).
|
|
119
|
+
*/
|
|
120
|
+
export default {
|
|
121
|
+
name: 'CnRetentionWidget',
|
|
122
|
+
|
|
123
|
+
components: { NcButton, NcEmptyContent, NcLoadingIcon, NcNoteCard, NcTextField },
|
|
124
|
+
|
|
125
|
+
inject: {
|
|
126
|
+
/** The host app's translate (CnAppRoot `cnTranslate`). */
|
|
127
|
+
cnTranslate: { default: () => (key) => key },
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
props: {
|
|
131
|
+
/**
|
|
132
|
+
* The widget's configuration (see the component docs).
|
|
133
|
+
*
|
|
134
|
+
* @type {object}
|
|
135
|
+
*/
|
|
136
|
+
content: {
|
|
137
|
+
type: Object,
|
|
138
|
+
default: () => ({}),
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The bound record, on a detail page.
|
|
143
|
+
*
|
|
144
|
+
* @type {object|null}
|
|
145
|
+
*/
|
|
146
|
+
objectData: {
|
|
147
|
+
type: Object,
|
|
148
|
+
default: null,
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
/** The record's register (else the bound record's `@self.register`). */
|
|
152
|
+
register: {
|
|
153
|
+
type: [String, Number],
|
|
154
|
+
default: '',
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The record's schema (else the bound record's `@self.schema`); a schema object gives its slug.
|
|
159
|
+
*
|
|
160
|
+
* @type {string|number|object}
|
|
161
|
+
*/
|
|
162
|
+
schema: {
|
|
163
|
+
type: [String, Number, Object],
|
|
164
|
+
default: '',
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
/** The record's id (else the bound record's). */
|
|
168
|
+
objectId: {
|
|
169
|
+
type: [String, Number],
|
|
170
|
+
default: '',
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
|
|
174
|
+
data() {
|
|
175
|
+
return {
|
|
176
|
+
loading: true,
|
|
177
|
+
error: '',
|
|
178
|
+
record: null,
|
|
179
|
+
reason: '',
|
|
180
|
+
busy: false,
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
computed: {
|
|
185
|
+
/** @return {object} The bound record's `@self`. */
|
|
186
|
+
boundSelf() {
|
|
187
|
+
return (this.objectData && this.objectData['@self']) || {}
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
/** @return {string} The record id. */
|
|
191
|
+
id() {
|
|
192
|
+
return String(this.objectId || (this.objectData && this.objectData.id) || this.boundSelf.id || '')
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
/** @return {string} The register. */
|
|
196
|
+
registerRef() {
|
|
197
|
+
return String(this.register || this.boundSelf.register || '')
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
/** @return {string} The schema. */
|
|
201
|
+
schemaRef() {
|
|
202
|
+
const s = this.schema || this.boundSelf.schema || ''
|
|
203
|
+
return String(typeof s === 'object' ? (s.slug || s.id || '') : s)
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
/** @return {object} The record's `@self`. */
|
|
207
|
+
self() {
|
|
208
|
+
return (this.record && this.record['@self']) || {}
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
/** @return {object|null} OpenRegister's stored retention decision. */
|
|
212
|
+
retention() {
|
|
213
|
+
return this.self._retention || null
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
/** @return {object|null} The nomination. */
|
|
217
|
+
nomination() {
|
|
218
|
+
return (this.retention && this.retention.nomination) || null
|
|
219
|
+
},
|
|
220
|
+
|
|
221
|
+
/** @return {object|null} The hand-over outcome. */
|
|
222
|
+
outcome() {
|
|
223
|
+
return (this.retention && this.retention.outcome) || null
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
/** @return {object|null} OpenRegister's archived marker. */
|
|
227
|
+
archived() {
|
|
228
|
+
return this.self.archived || null
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Whether the record's own field and the platform marker disagree.
|
|
233
|
+
*
|
|
234
|
+
* @spec openspec/changes/retention-widget/specs/retention-widget/spec.md#requirement-a-record-shows-its-stored-retention
|
|
235
|
+
* @return {boolean}
|
|
236
|
+
*/
|
|
237
|
+
disagrees() {
|
|
238
|
+
const field = this.content.statusField
|
|
239
|
+
if (!field || !this.record) {
|
|
240
|
+
return false
|
|
241
|
+
}
|
|
242
|
+
const values = Array.isArray(this.content.archivedValues) ? this.content.archivedValues.map(String) : ['archived']
|
|
243
|
+
const saysArchived = values.includes(String(readPath(this.record, field) ?? ''))
|
|
244
|
+
return saysArchived !== Boolean(this.archived)
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* The stored facts that have a value, in reading order.
|
|
249
|
+
*
|
|
250
|
+
* @spec openspec/changes/retention-widget/specs/retention-widget/spec.md#requirement-a-record-shows-its-stored-retention
|
|
251
|
+
* @return {Array<{key: string, label: string, value: string}>}
|
|
252
|
+
*/
|
|
253
|
+
facts() {
|
|
254
|
+
const r = this.retention || {}
|
|
255
|
+
const n = this.nomination || {}
|
|
256
|
+
return [
|
|
257
|
+
{ key: 'appraisal', label: t('nextcloud-vue', 'Appraisal'), value: r.appraisal },
|
|
258
|
+
{ key: 'disposal-date', label: t('nextcloud-vue', 'Disposal date'), value: r.disposalDate },
|
|
259
|
+
{ key: 'retention-period', label: t('nextcloud-vue', 'Retention period'), value: r.retentionPeriod },
|
|
260
|
+
{ key: 'selection-list-row', label: t('nextcloud-vue', 'Selection list row'), value: r.selectionListRow },
|
|
261
|
+
{ key: 'rule', label: t('nextcloud-vue', 'Decided by'), value: n.rule },
|
|
262
|
+
{ key: 'decided-at', label: t('nextcloud-vue', 'Written on'), value: n.at },
|
|
263
|
+
].filter((fact) => fact.value !== undefined && fact.value !== null && fact.value !== '').map((fact) => ({ ...fact, value: String(fact.value) }))
|
|
264
|
+
},
|
|
265
|
+
|
|
266
|
+
/** @return {Array<{key: string, label: string, value: string}>} The facts of a record not closed yet. */
|
|
267
|
+
pendingFacts() {
|
|
268
|
+
const atClosure = t('nextcloud-vue', 'Decided when it closes')
|
|
269
|
+
return [
|
|
270
|
+
{ key: 'appraisal', label: t('nextcloud-vue', 'Appraisal'), value: atClosure },
|
|
271
|
+
{ key: 'retention-period', label: t('nextcloud-vue', 'Retention period'), value: atClosure },
|
|
272
|
+
{ key: 'disposal-date', label: t('nextcloud-vue', 'Disposal date'), value: atClosure },
|
|
273
|
+
{ key: 'decided-at', label: t('nextcloud-vue', 'Written on'), value: t('nextcloud-vue', 'Not yet') },
|
|
274
|
+
]
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
/** @return {string} The signed-in user's id. */
|
|
278
|
+
currentUid() {
|
|
279
|
+
const user = getCurrentUser()
|
|
280
|
+
return String((user && user.uid) || '')
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Whether this user may recompute: an admin or an archivist group member.
|
|
285
|
+
* Shown disabled otherwise, so the user learns which role it needs.
|
|
286
|
+
*
|
|
287
|
+
* @return {boolean}
|
|
288
|
+
*/
|
|
289
|
+
mayRecompute() {
|
|
290
|
+
const user = getCurrentUser()
|
|
291
|
+
if (user && user.isAdmin === true) {
|
|
292
|
+
return true
|
|
293
|
+
}
|
|
294
|
+
const groups = Array.isArray(user && user.groups) ? user.groups : []
|
|
295
|
+
const allowed = Array.isArray(this.content.archivistGroups) ? this.content.archivistGroups : ['archivaris']
|
|
296
|
+
return allowed.some((g) => groups.includes(g))
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
|
|
300
|
+
mounted() {
|
|
301
|
+
this.load()
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
methods: {
|
|
305
|
+
t,
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* A config text through the host translate.
|
|
309
|
+
*
|
|
310
|
+
* @param {unknown} value The text.
|
|
311
|
+
* @return {string}
|
|
312
|
+
*/
|
|
313
|
+
text(value) {
|
|
314
|
+
return typeof value === 'string' && value !== '' ? this.cnTranslate(value) : ''
|
|
315
|
+
},
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Read the record fresh from OpenRegister; a failure is an error, never an empty block.
|
|
319
|
+
*
|
|
320
|
+
* @spec openspec/changes/retention-widget/specs/retention-widget/spec.md#requirement-a-record-shows-its-stored-retention
|
|
321
|
+
* @return {Promise<void>}
|
|
322
|
+
*/
|
|
323
|
+
async load() {
|
|
324
|
+
this.loading = true
|
|
325
|
+
this.error = ''
|
|
326
|
+
try {
|
|
327
|
+
if (!this.registerRef || !this.schemaRef || !this.id) {
|
|
328
|
+
throw new Error(t('nextcloud-vue', 'This widget has no record to read.'))
|
|
329
|
+
}
|
|
330
|
+
const { data } = await axios.get(generateUrl(`/apps/openregister/api/objects/${encodeURIComponent(this.registerRef)}/${encodeURIComponent(this.schemaRef)}/${encodeURIComponent(this.id)}`))
|
|
331
|
+
this.record = data && typeof data === 'object' ? data : null
|
|
332
|
+
} catch (failure) {
|
|
333
|
+
const body = failure && failure.response && failure.response.data
|
|
334
|
+
this.error = String((body && (body.message || body.error)) || (failure && failure.message) || failure)
|
|
335
|
+
} finally {
|
|
336
|
+
this.loading = false
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Ask OpenRegister to derive the nomination again, then read it back.
|
|
342
|
+
*
|
|
343
|
+
* @spec openspec/changes/retention-widget/specs/retention-widget/spec.md#requirement-a-record-shows-its-stored-retention
|
|
344
|
+
* @return {Promise<void>}
|
|
345
|
+
*/
|
|
346
|
+
async recompute() {
|
|
347
|
+
if (this.busy || this.reason.trim() === '') {
|
|
348
|
+
return
|
|
349
|
+
}
|
|
350
|
+
this.busy = true
|
|
351
|
+
try {
|
|
352
|
+
await axios.post(generateUrl(`/apps/openregister/api/archival/objects/${encodeURIComponent(this.id)}/nomination/recompute`), { reason: this.reason.trim() })
|
|
353
|
+
this.reason = ''
|
|
354
|
+
await this.load()
|
|
355
|
+
} catch (failure) {
|
|
356
|
+
const body = failure && failure.response && failure.response.data
|
|
357
|
+
this.error = String((body && (body.message || body.error)) || (failure && failure.message) || failure)
|
|
358
|
+
} finally {
|
|
359
|
+
this.busy = false
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
}
|
|
364
|
+
</script>
|
|
365
|
+
|
|
366
|
+
<style scoped>
|
|
367
|
+
.cn-retention {
|
|
368
|
+
display: flex;
|
|
369
|
+
flex-direction: column;
|
|
370
|
+
gap: 16px;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.cn-retention__head {
|
|
374
|
+
display: flex;
|
|
375
|
+
flex-direction: column;
|
|
376
|
+
gap: 2px;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.cn-retention__title {
|
|
380
|
+
margin: 0;
|
|
381
|
+
font-size: 18px;
|
|
382
|
+
font-weight: 700;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.cn-retention__muted {
|
|
386
|
+
margin: 0;
|
|
387
|
+
color: var(--color-text-maxcontrast);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.cn-retention__facts {
|
|
391
|
+
display: grid;
|
|
392
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
393
|
+
gap: 12px 24px;
|
|
394
|
+
margin: 0;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.cn-retention__fact dt {
|
|
398
|
+
color: var(--color-text-maxcontrast);
|
|
399
|
+
font-size: 13px;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.cn-retention__fact dd {
|
|
403
|
+
margin: 0;
|
|
404
|
+
font-weight: 600;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.cn-retention__recompute {
|
|
408
|
+
display: flex;
|
|
409
|
+
flex-direction: column;
|
|
410
|
+
gap: 8px;
|
|
411
|
+
max-width: 480px;
|
|
412
|
+
}
|
|
413
|
+
</style>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SPDX-FileCopyrightText: 2026 Conduction B.V.
|
|
3
|
+
* SPDX-License-Identifier: EUPL-1.2
|
|
4
|
+
*
|
|
5
|
+
* CnRetentionWidget — the `retention` detail widget: a record's stored
|
|
6
|
+
* OpenRegister retention decision, with a recompute for archivists.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import CnRetentionWidget from './CnRetentionWidget.vue'
|
|
10
|
+
|
|
11
|
+
export { CnRetentionWidget }
|
|
12
|
+
export default CnRetentionWidget
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
import CnAuditTrailWidget from '../CnAuditTrailWidget/CnAuditTrailWidget.vue'
|
|
18
18
|
import CnBannerWidget from '../CnBannerWidget/CnBannerWidget.vue'
|
|
19
|
+
import CnDecisionListWidget from '../CnDecisionListWidget/CnDecisionListWidget.vue'
|
|
19
20
|
import CnDividerWidget from '../CnDividerWidget/CnDividerWidget.vue'
|
|
20
21
|
import CnExportsWidget from '../CnExportsWidget/CnExportsWidget.vue'
|
|
21
22
|
import CnHeaderWidget from '../CnHeaderWidget/CnHeaderWidget.vue'
|
|
@@ -88,6 +89,7 @@ export const BUILT_IN_WIDGETS = {
|
|
|
88
89
|
integration: CnIntegrationWidget,
|
|
89
90
|
banner: CnBannerWidget,
|
|
90
91
|
notice: CnNoticeWidget,
|
|
92
|
+
'decision-list': CnDecisionListWidget,
|
|
91
93
|
'audit-trail': CnAuditTrailWidget,
|
|
92
94
|
timeline: CnTimelineWidget,
|
|
93
95
|
header: CnHeaderWidget,
|
|
@@ -28,8 +28,10 @@ import CnMarkdownWidgetForm2 from '../CnMarkdownWidgetForm/CnMarkdownWidgetForm.
|
|
|
28
28
|
import CnNextStepWidget from '../CnNextStepCard/CnNextStepWidget.vue'
|
|
29
29
|
import CnObjectListWidget2 from '../CnObjectListWidget/CnObjectListWidget.vue'
|
|
30
30
|
import CnObjectListWidgetForm2 from '../CnObjectListWidgetForm/CnObjectListWidgetForm.vue'
|
|
31
|
+
import CnPlanWidget from '../CnPlanWidget/CnPlanWidget.vue'
|
|
31
32
|
import CnRelatedObjectsWidget from '../CnRelatedObjectsWidget/CnRelatedObjectsWidget.vue'
|
|
32
33
|
import CnRelatedObjectsWidgetForm from '../CnRelatedObjectsWidgetForm/CnRelatedObjectsWidgetForm.vue'
|
|
34
|
+
import CnRetentionWidget from '../CnRetentionWidget/CnRetentionWidget.vue'
|
|
33
35
|
import CnSavedViewWidget2 from '../CnSavedViewWidget/CnSavedViewWidget.vue'
|
|
34
36
|
import CnSavedViewWidgetForm2 from '../CnSavedViewWidgetForm/CnSavedViewWidgetForm.vue'
|
|
35
37
|
import CnStackedBarWidget from '../CnStackedBarWidget/CnStackedBarWidget.vue'
|
|
@@ -43,6 +45,7 @@ import CnTabsWidgetForm from '../CnTabsWidgetForm/CnTabsWidgetForm.vue'
|
|
|
43
45
|
import CnTeamWorkloadWidget from '../CnTeamWorkloadWidget/CnTeamWorkloadWidget.vue'
|
|
44
46
|
import CnWeekStripWidget from '../CnWeekStripWidget/CnWeekStripWidget.vue'
|
|
45
47
|
import CnWeekStripWidgetForm from '../CnWeekStripWidgetForm/CnWeekStripWidgetForm.vue'
|
|
48
|
+
import CnWorkQueue from '../CnWorkQueue/CnWorkQueue.vue'
|
|
46
49
|
// Typed widgets registered with an explicit renderer + config FORM. chart /
|
|
47
50
|
// stats-block render through CnDashboardPage's own isChart()/isStatsBlock()
|
|
48
51
|
// branches and `related` through CnDetailPage's isRelatedWidget() branch — the
|
|
@@ -290,6 +293,34 @@ registerDashboardWidget('next-step', {
|
|
|
290
293
|
ownsTitle: true,
|
|
291
294
|
})
|
|
292
295
|
|
|
296
|
+
registerDashboardWidget('retention', {
|
|
297
|
+
renderer: CnRetentionWidget,
|
|
298
|
+
form: null,
|
|
299
|
+
defaultContent: { statusField: '', archivedValues: [], archivistGroups: ['archivaris'] },
|
|
300
|
+
displayName: 'Retention',
|
|
301
|
+
icon: 'ArchiveOutline',
|
|
302
|
+
surfaces: ['detail-page'],
|
|
303
|
+
ownsTitle: true,
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
registerDashboardWidget('plan', {
|
|
307
|
+
renderer: CnPlanWidget,
|
|
308
|
+
form: null,
|
|
309
|
+
defaultContent: { emptyText: '' },
|
|
310
|
+
displayName: 'Plan',
|
|
311
|
+
icon: 'FormatListChecks',
|
|
312
|
+
surfaces: ['detail-page'],
|
|
313
|
+
ownsTitle: true,
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
registerDashboardWidget('work-queue', {
|
|
317
|
+
renderer: CnWorkQueue,
|
|
318
|
+
form: null,
|
|
319
|
+
defaultContent: { source: null },
|
|
320
|
+
displayName: 'Work queue',
|
|
321
|
+
icon: 'TrayFull',
|
|
322
|
+
})
|
|
323
|
+
|
|
293
324
|
registerDashboardWidget('document-review', {
|
|
294
325
|
renderer: CnDocumentReviewWidget,
|
|
295
326
|
form: null,
|