@conduction/nextcloud-vue 2.141.0 → 2.142.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/CnCellRenderer/CnCellRenderer.vue.js +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js +13 -0
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js +3 -3
- package/dist/esm/components/CnCellRenderer/CnCellRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +21 -0
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue.js +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js +34 -3
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFactsWidget/CnFactsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +43 -3
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +21 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +2 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js +14 -0
- package/dist/esm/components/CnIndexPage/useSelfFetchList.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +3 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +2 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue.js +1 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js +31 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue2.js.map +1 -1
- package/dist/esm/components/CnPartyPicker/CnPartyPicker.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue.js +1 -1
- package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue2.js +52 -1
- package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue3.js +119 -65
- package/dist/esm/integrations/builtin/contacts/CnContactsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue.js +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +86 -4
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js +67 -5
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +10 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +11 -2
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +527 -358
- package/dist/esm/utils/buildViewTree.js +23 -8
- package/dist/esm/utils/buildViewTree.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +1064 -616
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +527 -358
- package/l10n/en.json +10 -1
- package/l10n/nl.json +11 -2
- package/package.json +1 -1
- package/src/components/CnCellRenderer/CnCellRenderer.vue +15 -1
- package/src/components/CnDataTable/CnDataTable.md +1 -0
- package/src/components/CnDataTable/CnDataTable.vue +22 -1
- package/src/components/CnFactsWidget/CnFactsWidget.vue +34 -3
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +44 -4
- package/src/components/CnIndexPage/CnIndexPage.vue +22 -1
- package/src/components/CnIndexPage/useSelfFetchList.js +14 -0
- package/src/components/CnObjectSidebar/CnFilesTab.vue +4 -1
- package/src/components/CnPartyPicker/CnPartyPicker.vue +31 -1
- package/src/css/look-board-detail.css +95 -0
- package/src/css/look-board-index.css +53 -0
- package/src/css/look-board.css +17 -0
- package/src/integrations/builtin/contacts/CnContactsTab.vue +101 -26
- package/src/integrations/builtin/contacts/__tests__/CnContactsTabBoardRows.spec.js +67 -0
- package/src/integrations/builtin/deck/CnDeckCard.vue +121 -2
- package/src/integrations/builtin/deck/__tests__/CnDeckCardHeaderActions.spec.js +68 -0
- package/src/utils/buildViewTree.js +23 -8
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { translate } from '@nextcloud/l10n';
|
|
2
|
-
import { NcAvatar, NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue';
|
|
2
|
+
import { NcActionButton, NcActions, NcAvatar, NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue';
|
|
3
3
|
import AccountMultipleOutlineIcon from 'vue-material-design-icons/AccountMultipleOutline.vue';
|
|
4
4
|
import AccountPlus from 'vue-material-design-icons/AccountPlus.vue';
|
|
5
5
|
import AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue';
|
|
@@ -14,6 +14,7 @@ import __sfc_main$1 from '../../../components/CnContactPicker/CnContactPicker.vu
|
|
|
14
14
|
import __sfc_main from '../../../components/CnStatusBadge/CnStatusBadge.vue.js';
|
|
15
15
|
import __sfc_main$3 from '../../../components/CnWidgetWrapper/CnWidgetHeaderActionButtons.vue.js';
|
|
16
16
|
import { normalizeLook } from '../../../composables/useLook.js';
|
|
17
|
+
import { formatBoardDate } from '../../../utils/boardDate.js';
|
|
17
18
|
import { WIDGET_BODY_ACTIONS } from '../../../utils/widgetHeaderActions.js';
|
|
18
19
|
import { prefixUrl, buildHeaders } from '../../../utils/headers.js';
|
|
19
20
|
|
|
@@ -44,6 +45,8 @@ var script = {
|
|
|
44
45
|
name: 'CnContactsTab',
|
|
45
46
|
|
|
46
47
|
components: {
|
|
48
|
+
NcActionButton,
|
|
49
|
+
NcActions,
|
|
47
50
|
NcAvatar,
|
|
48
51
|
NcButton,
|
|
49
52
|
NcEmptyContent,
|
|
@@ -134,6 +137,11 @@ var script = {
|
|
|
134
137
|
},
|
|
135
138
|
|
|
136
139
|
computed: {
|
|
140
|
+
/** @return {boolean} The board look draws the rows as DqZaakBetrokkenen does. */
|
|
141
|
+
isBoard() {
|
|
142
|
+
return normalizeLook(this.cnLook) === 'board'
|
|
143
|
+
},
|
|
144
|
+
|
|
137
145
|
/**
|
|
138
146
|
* The two header buttons in the order and weight of the look: the
|
|
139
147
|
* board look draws Link contact then New contact, both outlined; the
|
|
@@ -426,6 +434,49 @@ var script = {
|
|
|
426
434
|
*
|
|
427
435
|
* @return {string} The window.
|
|
428
436
|
*/
|
|
437
|
+
/**
|
|
438
|
+
* A person's details on one line for the board look: e-mail, phone,
|
|
439
|
+
* organisation and note, separated by middots.
|
|
440
|
+
*
|
|
441
|
+
* @param {object} contact The contact link.
|
|
442
|
+
* @return {string}
|
|
443
|
+
*/
|
|
444
|
+
boardMetaOf(contact) {
|
|
445
|
+
return [contact?.email, contact?.phone, contact?.org, contact?.note].filter((part) => typeof part === 'string' && part !== '').join(' · ')
|
|
446
|
+
},
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* The validity window in the board's short dates: "Vanaf 3 okt",
|
|
450
|
+
* "Tot 31 okt" or "3 okt – 31 okt".
|
|
451
|
+
*
|
|
452
|
+
* @param {object} contact The contact link.
|
|
453
|
+
* @return {string}
|
|
454
|
+
*/
|
|
455
|
+
boardPeriodOf(contact) {
|
|
456
|
+
const from = formatBoardDate(contact?.validFrom || '');
|
|
457
|
+
const until = formatBoardDate(contact?.validUntil || '');
|
|
458
|
+
if (from === '' && until === '') {
|
|
459
|
+
return ''
|
|
460
|
+
}
|
|
461
|
+
if (until === '') {
|
|
462
|
+
return translate('nextcloud-vue', 'From {date}', { date: from })
|
|
463
|
+
}
|
|
464
|
+
if (from === '') {
|
|
465
|
+
return translate('nextcloud-vue', 'Until {date}', { date: until })
|
|
466
|
+
}
|
|
467
|
+
return `${from} – ${until}`
|
|
468
|
+
},
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* The label of a person's actions menu.
|
|
472
|
+
*
|
|
473
|
+
* @param {object} contact The contact link.
|
|
474
|
+
* @return {string}
|
|
475
|
+
*/
|
|
476
|
+
moreActionsLabel(contact) {
|
|
477
|
+
return translate('nextcloud-vue', 'More actions for {name}', { name: contact?.displayName || this.unknownLabel })
|
|
478
|
+
},
|
|
479
|
+
|
|
429
480
|
periodOf(contact) {
|
|
430
481
|
const from = contact?.validFrom || '';
|
|
431
482
|
const until = contact?.validUntil || '';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnContactsTab.vue2.js","sources":["../../../../../src/integrations/builtin/contacts/CnContactsTab.vue"],"sourcesContent":["<!--\n - SPDX-License-Identifier: EUPL-1.2\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n -\n - CnContactsTab — bespoke sidebar tab for the `contacts` integration leaf.\n -\n - Renders the people linked to an OR object grouped by role. Since\n - people-on-objects a person is a Nextcloud user or a vCard contact, and\n - the grouping is the server's when it sends one: the listing carries\n - `byRole` and the schema's `roles`, and the tab falls back to its own\n - buckets (Applicants / Handlers / Advisors / Other, AD-1 of\n - `openspec/changes/integration-contacts/design.md`) for a server that\n - does not. Each card shows avatar, display name, email and role, and a\n - link whose validity has passed reads as inactive.\n -\n - Clicking a contact opens the reverse-lookup flyout (AD-3) listing every\n - OR object linked to that vCard.\n -\n - Tier-2 (this revision): the tab now hosts two header actions — \"Link\n - existing contact\" (opens `CnContactPicker`) and \"Add new contact\"\n - (opens `CnContactCreate`). Both dialogs are loaded lazily via\n - dynamic import to keep the bundle below the integration-tab\n - threshold, and both delegate the actual API call back to this tab\n - so the loading + refresh handshake stays here.\n -\n - Backed by:\n - GET /api/objects/{register}/{schema}/{id}/contacts\n - POST /api/objects/{register}/{schema}/{id}/contacts — link existing\n - POST /api/objects/{register}/{schema}/{id}/contacts/new — create + link\n - DELETE /api/objects/{register}/{schema}/{id}/contacts/{uid} — unlink\n -\n - The payload shape mirrors `ContactLink::jsonSerialize()` —\n - `{id, contactUid, displayName, email, phone, org, avatarUrl, role,\n - addressbookId, contactUri, schemaId, metadata, linkedAt}`.\n -\n - @spec openspec/changes/integration-contacts-tier2/specs/integrations/contacts/spec.md\n -->\n<template>\n\t<div class=\"cn-contacts-tab\">\n\t\t<!-- Header actions: link existing / create new. The board look draws\n\t\t Link contact first and both outlined (DqZaakBetrokkenen); the\n\t\t Nextcloud look keeps Add new contact first and Link as the primary. -->\n\t\t<div class=\"cn-contacts-tab__header\">\n\t\t\t<NcButton\n\t\t\t\tv-for=\"button in headerButtons\"\n\t\t\t\t:key=\"button.key\"\n\t\t\t\t:variant=\"button.variant\"\n\t\t\t\t:aria-label=\"button.label\"\n\t\t\t\t@click=\"button.key === 'link' ? (showLinkDialog = true) : (showCreateDialog = true)\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant v-if=\"button.key === 'link'\" :size=\"20\" />\n\t\t\t\t\t<AccountPlus v-else :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ button.label }}\n\t\t\t</NcButton>\n\t\t\t<CnWidgetHeaderActionButtons v-if=\"bodyActions.length > 0\" :actions=\"bodyActions\" />\n\t\t</div>\n\n\t\t<!-- Loading -->\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- Error -->\n\t\t<NcEmptyContent\n\t\t\tv-else-if=\"error\"\n\t\t\t:name=\"errorLabel\"\n\t\t\t:description=\"error\">\n\t\t\t<template #icon>\n\t\t\t\t<AlertCircleOutline :size=\"48\" />\n\t\t\t</template>\n\t\t</NcEmptyContent>\n\n\t\t<!-- Empty -->\n\t\t<NcEmptyContent\n\t\t\tv-else-if=\"contactsArray.length === 0\"\n\t\t\t:name=\"emptyTitleLabel\"\n\t\t\t:description=\"emptyDescriptionLabel\">\n\t\t\t<template #icon>\n\t\t\t\t<AccountMultipleOutline :size=\"48\" />\n\t\t\t</template>\n\t\t</NcEmptyContent>\n\n\t\t<!-- Grouped list -->\n\t\t<div v-else class=\"cn-contacts-tab__groups\">\n\t\t\t<section\n\t\t\t\tv-for=\"group in groupedContacts\"\n\t\t\t\t:key=\"group.key\"\n\t\t\t\tclass=\"cn-contacts-tab__group\">\n\t\t\t\t<h3 class=\"cn-contacts-tab__group-title\">\n\t\t\t\t\t{{ group.label }}\n\t\t\t\t\t<span class=\"cn-contacts-tab__group-count\">{{ group.items.length }}</span>\n\t\t\t\t</h3>\n\t\t\t\t<ul class=\"cn-contacts-tab__list\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"contact in group.items\"\n\t\t\t\t\t\t:key=\"contact.id\"\n\t\t\t\t\t\tclass=\"cn-contacts-tab__item\"\n\t\t\t\t\t\t@click=\"openReverseLookup(contact)\">\n\t\t\t\t\t\t<div class=\"cn-contacts-tab__avatar\">\n\t\t\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\t\t\t:size=\"40\"\n\t\t\t\t\t\t\t\t:displayName=\"contact.displayName || unknownLabel\"\n\t\t\t\t\t\t\t\t:user=\"avatarSeed(contact)\"\n\t\t\t\t\t\t\t\t:url=\"contact.avatarUrl || undefined\"\n\t\t\t\t\t\t\t\t:isNoUser=\"true\"\n\t\t\t\t\t\t\t\t:disableMenu=\"true\"\n\t\t\t\t\t\t\t\t:disableTooltip=\"true\"\n\t\t\t\t\t\t\t\thideStatus />\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"cn-contacts-tab__details\">\n\t\t\t\t\t\t\t<div class=\"cn-contacts-tab__name-row\">\n\t\t\t\t\t\t\t\t<span class=\"cn-contacts-tab__name\">\n\t\t\t\t\t\t\t\t\t{{ contact.displayName || unknownLabel }}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\t\tv-if=\"contact.role\"\n\t\t\t\t\t\t\t\t\t:label=\"roleLabel(contact)\"\n\t\t\t\t\t\t\t\t\t:variant=\"roleVariant(contact)\"\n\t\t\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\t\tv-if=\"contact.active === false\"\n\t\t\t\t\t\t\t\t\t:label=\"inactiveLabel\"\n\t\t\t\t\t\t\t\t\tvariant=\"default\"\n\t\t\t\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-contacts-tab-inactive\" />\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div v-if=\"contact.email\" class=\"cn-contacts-tab__sub\">\n\t\t\t\t\t\t\t\t<Email :size=\"14\" class=\"cn-contacts-tab__sub-icon\" />\n\t\t\t\t\t\t\t\t<span class=\"cn-contacts-tab__sub-text\">{{ contact.email }}</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div v-if=\"contact.phone\" class=\"cn-contacts-tab__sub\">\n\t\t\t\t\t\t\t\t<Phone :size=\"14\" class=\"cn-contacts-tab__sub-icon\" />\n\t\t\t\t\t\t\t\t<span class=\"cn-contacts-tab__sub-text\">{{ contact.phone }}</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div v-if=\"contact.org\" class=\"cn-contacts-tab__sub\">\n\t\t\t\t\t\t\t\t<OfficeBuildingOutline :size=\"14\" class=\"cn-contacts-tab__sub-icon\" />\n\t\t\t\t\t\t\t\t<span class=\"cn-contacts-tab__sub-text\">{{ contact.org }}</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tv-if=\"periodOf(contact)\"\n\t\t\t\t\t\t\t\tclass=\"cn-contacts-tab__sub\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-contacts-tab-period\">\n\t\t\t\t\t\t\t\t<CalendarRange :size=\"14\" class=\"cn-contacts-tab__sub-icon\" />\n\t\t\t\t\t\t\t\t<span class=\"cn-contacts-tab__sub-text\">{{ periodOf(contact) }}</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<div v-if=\"contact.note\" class=\"cn-contacts-tab__sub\">\n\t\t\t\t\t\t\t\t<span class=\"cn-contacts-tab__sub-text\">{{ contact.note }}</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclass=\"cn-contacts-tab__unlink\"\n\t\t\t\t\t\t\t:aria-label=\"unlinkLabel\"\n\t\t\t\t\t\t\t:title=\"unlinkLabel\"\n\t\t\t\t\t\t\t@click.stop=\"unlink(contact)\">\n\t\t\t\t\t\t\t<Close :size=\"18\" />\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\t\t</div>\n\n\t\t<!-- Link existing contact dialog -->\n\t\t<CnContactPicker\n\t\t\tv-if=\"showLinkDialog\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\tv-bind=\"pickerRoleOptions\"\n\t\t\t@link=\"onPickerLink\"\n\t\t\t@close=\"showLinkDialog = false\" />\n\n\t\t<!-- Create new contact dialog -->\n\t\t<CnContactCreate\n\t\t\tv-if=\"showCreateDialog\"\n\t\t\t:loading=\"createLoading\"\n\t\t\t@create=\"onCreateSubmit\"\n\t\t\t@close=\"showCreateDialog = false\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcAvatar, NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport AccountMultipleOutline from 'vue-material-design-icons/AccountMultipleOutline.vue'\nimport AccountPlus from 'vue-material-design-icons/AccountPlus.vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport CalendarRange from 'vue-material-design-icons/CalendarRange.vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport Email from 'vue-material-design-icons/Email.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport OfficeBuildingOutline from 'vue-material-design-icons/OfficeBuildingOutline.vue'\nimport Phone from 'vue-material-design-icons/Phone.vue'\nimport CnContactCreate from '../../../components/CnContactCreate/CnContactCreate.vue'\nimport CnContactPicker from '../../../components/CnContactPicker/CnContactPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport CnWidgetHeaderActionButtons from '../../../components/CnWidgetWrapper/CnWidgetHeaderActionButtons.vue'\nimport { normalizeLook } from '../../../composables/useLook.js'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\nimport { WIDGET_BODY_ACTIONS } from '../../../utils/widgetHeaderActions.js'\n\n/**\n * Normalised role keys for grouping. Free-text vCard roles are\n * normalised by lowercasing + trimming; unknown roles fall into `other`.\n *\n * @type {Array<{key: string, match: (r: ?string) => boolean, labelKey: string}>}\n */\nconst ROLE_BUCKETS = [\n\t{ key: 'applicant', match: (r) => /^(applicant|aanvrager|requestor)$/i.test(r ?? ''), labelKey: 'Applicants' },\n\t{ key: 'handler', match: (r) => /^(handler|behandelaar|owner|assignee)$/i.test(r ?? ''), labelKey: 'Handlers' },\n\t{ key: 'advisor', match: (r) => /^(advisor|advisor|adviseur|reviewer)$/i.test(r ?? ''), labelKey: 'Advisors' },\n]\n\n/**\n * CnContactsTab — vCard contacts linked to an OR object, grouped by role.\n *\n * Basic usage\n * ```vue\n * <CnContactsTab\n * register=\"my-register\"\n * schema=\"case\"\n * object-id=\"abc-123\" />\n * ```\n */\nexport default {\n\tname: 'CnContactsTab',\n\n\tcomponents: {\n\t\tNcAvatar,\n\t\tNcButton,\n\t\tNcEmptyContent,\n\t\tNcLoadingIcon,\n\t\tAccountPlus,\n\t\tAccountMultipleOutline,\n\t\tAlertCircleOutline,\n\t\tCalendarRange,\n\t\tClose,\n\t\tEmail,\n\t\tLinkVariant,\n\t\tOfficeBuildingOutline,\n\t\tPhone,\n\t\tCnStatusBadge,\n\t\tCnContactPicker,\n\t\tCnContactCreate,\n\t\tCnWidgetHeaderActionButtons,\n\t},\n\n\tinject: {\n\t\t/** The look the app is drawn in (`board` or `nextcloud`), from CnAppRoot or the page. */\n\t\tcnLook: { default: 'nextcloud' },\n\t\t/** The widget's manifest `bodyActions`, drawn after this tab's own buttons. */\n\t\tcnWidgetBodyActions: { from: WIDGET_BODY_ACTIONS, default: null },\n\t},\n\n\tprops: {\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL for OpenRegister. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\n\t\t// --- Pre-translated labels (consumer-overridable for i18n flexibility) ---\n\t\t/** Label for the button that links an existing contact. */\n\t\tlinkExistingLabel: { type: String, default: () => t('nextcloud-vue', 'Link contact') },\n\t\t/** Label for the button that creates a contact; empty takes the look's own (New contact, or Add new contact). */\n\t\taddNewLabel: { type: String, default: '' },\n\t\t/** Title of the empty state shown when no contact is linked. */\n\t\temptyTitleLabel: { type: String, default: () => t('nextcloud-vue', 'No contacts linked') },\n\t\t/** Body of the empty state shown when no contact is linked. */\n\t\temptyDescriptionLabel: { type: String, default: () => t('nextcloud-vue', 'Link a contact from your address book.') },\n\t\t/** Message shown when the contacts could not be loaded. */\n\t\terrorLabel: { type: String, default: () => t('nextcloud-vue', 'Could not load contacts') },\n\t\t/** Stand-in name for a contact whose name is missing. */\n\t\tunknownLabel: { type: String, default: () => t('nextcloud-vue', 'Unknown contact') },\n\t\t/** Label for the action that unlinks a contact from the object. */\n\t\tunlinkLabel: { type: String, default: () => t('nextcloud-vue', 'Unlink contact') },\n\t\t/** Heading above the contacts grouped as applicants. */\n\t\tapplicantsLabel: { type: String, default: () => t('nextcloud-vue', 'Applicants') },\n\t\t/** Heading above the contacts grouped as handlers. */\n\t\thandlersLabel: { type: String, default: () => t('nextcloud-vue', 'Handlers') },\n\t\t/** Heading above the contacts grouped as advisors. */\n\t\tadvisorsLabel: { type: String, default: () => t('nextcloud-vue', 'Advisors') },\n\t\t/** Heading above the contacts with no recognised role. */\n\t\totherLabel: { type: String, default: () => t('nextcloud-vue', 'Other') },\n\t\t/** Badge on a link whose validity window has passed or not yet begun. */\n\t\tinactiveLabel: { type: String, default: () => t('nextcloud-vue', 'Not active') },\n\t\t/** Prefix for a validity window with an open end. */\n\t\tfromLabel: { type: String, default: () => t('nextcloud-vue', 'From') },\n\t\t/** Prefix for a validity window with an open start. */\n\t\tuntilLabel: { type: String, default: () => t('nextcloud-vue', 'Until') },\n\t\t/** Role badge shown on an applicant. */\n\t\tapplicantRoleLabel: { type: String, default: () => t('nextcloud-vue', 'Applicant') },\n\t\t/** Role badge shown on a handler. */\n\t\thandlerRoleLabel: { type: String, default: () => t('nextcloud-vue', 'Handler') },\n\t\t/** Role badge shown on an advisor. */\n\t\tadvisorRoleLabel: { type: String, default: () => t('nextcloud-vue', 'Advisor') },\n\t},\n\n\temits: ['open-reverse-lookup'],\n\n\tdata() {\n\t\treturn {\n\t\t\t// The server's grouping and role vocabulary, when it sends them.\n\t\t\tserverGroups: null,\n\t\t\troleVocabulary: [],\n\t\t\tcontacts: [],\n\t\t\tloading: false,\n\t\t\terror: null,\n\t\t\tshowLinkDialog: false,\n\t\t\tshowCreateDialog: false,\n\t\t\tcreateLoading: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * The two header buttons in the order and weight of the look: the\n\t\t * board look draws Link contact then New contact, both outlined; the\n\t\t * Nextcloud look keeps Add new contact first and Link as the primary.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, variant: string}>} The buttons.\n\t\t */\n\t\theaderButtons() {\n\t\t\tconst board = normalizeLook(this.cnLook) === 'board'\n\t\t\tconst newLabel = this.addNewLabel !== ''\n\t\t\t\t? this.addNewLabel\n\t\t\t\t: (board ? t('nextcloud-vue', 'New contact') : t('nextcloud-vue', 'Add new contact'))\n\t\t\tconst link = { key: 'link', label: this.linkExistingLabel, variant: board ? 'secondary' : 'primary' }\n\t\t\tconst create = { key: 'new', label: newLabel, variant: 'secondary' }\n\t\t\treturn board ? [link, create] : [create, link]\n\t\t},\n\n\t\t/**\n\t\t * The widget's body actions, unwrapped.\n\t\t *\n\t\t * @return {Array<object>} The resolved buttons, or none.\n\t\t */\n\t\tbodyActions() {\n\t\t\tconst provided = this.cnWidgetBodyActions\n\t\t\tconst list = provided && typeof provided === 'object' && 'value' in provided ? provided.value : provided\n\t\t\treturn Array.isArray(list) ? list : []\n\t\t},\n\n\t\t/**\n\t\t * Defensive view onto `this.contacts` — always an array.\n\t\t *\n\t\t * Guards against the well-known \"this.contacts is not iterable\"\n\t\t * TypeError observed in Phase A / D-1 when the provider returns\n\t\t * an object-shaped payload (or `null`) and the iteration site\n\t\t * blows up before the watch has even fired.\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\tcontactsArray() {\n\t\t\treturn Array.isArray(this.contacts) ? this.contacts : []\n\t\t},\n\n\t\t/**\n\t\t * Group contacts by normalised role bucket.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, items: Array}>}\n\t\t */\n\t\tgroupedContacts() {\n\t\t\tif (this.serverGroups !== null) {\n\t\t\t\treturn this.groupsFromServer()\n\t\t\t}\n\t\t\treturn this.groupsFromBuckets\n\t\t},\n\n\t\t/**\n\t\t * The role vocabulary as CnContactPicker options, or nothing when\n\t\t * the schema declares none (the picker keeps its own defaults).\n\t\t *\n\t\t * @return {object} Props to bind onto the picker.\n\t\t */\n\t\tpickerRoleOptions() {\n\t\t\tif (this.roleVocabulary.length === 0) {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\troleOptions: this.roleVocabulary.map((role) => ({ label: role.label || role.key, value: role.key })),\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Group by the tab's own role buckets, for a server that does not group.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, items: Array}>}\n\t\t */\n\t\tgroupsFromBuckets() {\n\t\t\tconst labelMap = {\n\t\t\t\tapplicant: this.applicantsLabel,\n\t\t\t\thandler: this.handlersLabel,\n\t\t\t\tadvisor: this.advisorsLabel,\n\t\t\t\tother: this.otherLabel,\n\t\t\t}\n\t\t\tconst buckets = { applicant: [], handler: [], advisor: [], other: [] }\n\t\t\tfor (const c of this.contactsArray) {\n\t\t\t\tconst matched = ROLE_BUCKETS.find((b) => b.match(c.role))\n\t\t\t\tif (matched) {\n\t\t\t\t\tbuckets[matched.key].push(c)\n\t\t\t\t} else {\n\t\t\t\t\tbuckets.other.push(c)\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst out = []\n\t\t\tfor (const key of ['applicant', 'handler', 'advisor', 'other']) {\n\t\t\t\tif (buckets[key].length > 0) {\n\t\t\t\t\tout.push({ key, label: labelMap[key], items: buckets[key] })\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/contacts`\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: {\n\t\t\timmediate: true,\n\t\t\thandler(id) {\n\t\t\t\tif (id) {\n\t\t\t\t\tthis.fetchContacts()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Initials from displayName (\"Jan de Vries\" → \"JV\"); falls back to `?`.\n\t\t *\n\t\t * @param {object} contact ContactLink JSON.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tinitialsFor(contact) {\n\t\t\tconst name = (contact?.displayName || '').trim()\n\t\t\tif (name === '') {\n\t\t\t\treturn '?'\n\t\t\t}\n\t\t\tconst parts = name.split(/\\s+/).filter(Boolean)\n\t\t\tif (parts.length === 1) {\n\t\t\t\treturn parts[0].charAt(0).toUpperCase()\n\t\t\t}\n\t\t\treturn (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase()\n\t\t},\n\n\t\t/**\n\t\t * Stable seed for NcAvatar's deterministic colour + initials.\n\t\t * Prefer the contact UID, then email, then display name so the\n\t\t * generated avatar colour stays consistent for a given contact.\n\t\t *\n\t\t * @param {object} contact ContactLink JSON.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tavatarSeed(contact) {\n\t\t\treturn contact.contactUid || contact.email || contact.displayName || '?'\n\t\t},\n\n\t\t/**\n\t\t * Resolve the normalised role bucket for a contact.\n\t\t *\n\t\t * @param {object} contact ContactLink JSON.\n\t\t *\n\t\t * @return {string} one of applicant|handler|advisor|other\n\t\t */\n\t\troleKey(contact) {\n\t\t\tconst matched = ROLE_BUCKETS.find((b) => b.match(contact.role))\n\t\t\treturn matched ? matched.key : 'other'\n\t\t},\n\n\t\t/**\n\t\t * Human-readable role chip label — the translated bucket name\n\t\t * when the role maps to a known bucket, else the raw vCard role.\n\t\t *\n\t\t * @param {object} contact ContactLink JSON.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\troleLabel(contact) {\n\t\t\tconst map = {\n\t\t\t\tapplicant: this.applicantRoleLabel,\n\t\t\t\thandler: this.handlerRoleLabel,\n\t\t\t\tadvisor: this.advisorRoleLabel,\n\t\t\t}\n\t\t\treturn map[this.roleKey(contact)] || contact.role\n\t\t},\n\n\t\t/**\n\t\t * CnStatusBadge variant per role bucket so each role reads as a\n\t\t * distinct accent (handlers = primary, advisors = info, etc.).\n\t\t *\n\t\t * @param {object} contact ContactLink JSON.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\troleVariant(contact) {\n\t\t\tconst map = {\n\t\t\t\tapplicant: 'info',\n\t\t\t\thandler: 'primary',\n\t\t\t\tadvisor: 'success',\n\t\t\t}\n\t\t\treturn map[this.roleKey(contact)] || 'default'\n\t\t},\n\n\t\t/**\n\t\t * Open the reverse-lookup flyout for a contact (AD-3). Emits an\n\t\t * event for the consuming app to handle (since the flyout's\n\t\t * implementation depends on the host router).\n\t\t *\n\t\t * @param {object} contact ContactLink JSON.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\topenReverseLookup(contact) {\n\t\t\tthis.$emit('open-reverse-lookup', {\n\t\t\t\tcontactUid: contact.contactUid,\n\t\t\t\tdisplayName: contact.displayName,\n\t\t\t})\n\t\t},\n\n\t\tasync fetchContacts() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = null\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl), { headers: buildHeaders() })\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthis.error = `${response.status} ${response.statusText}`\n\t\t\t\t\tthis.contacts = []\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst data = await response.json()\n\t\t\t\t// Canonical wrapper-key cascade (per ADR-022): provider\n\t\t\t\t// may return `{results: [...]}` (paginated), `{items:\n\t\t\t\t// [...]}` (some leaves), or a bare array. Anything else\n\t\t\t\t// falls back to `[]` rather than letting a non-iterable\n\t\t\t\t// object reach `groupedContacts` (Phase A / D-1 bug).\n\t\t\t\tthis.contacts = this.unwrapList(data)\n\t\t\t\t// people-on-objects: the server groups by role and names the\n\t\t\t\t// schema's vocabulary. Both optional, so a server that sends\n\t\t\t\t// neither keeps the tab's own buckets.\n\t\t\t\tthis.serverGroups = (data && typeof data === 'object' && data.byRole) ? data.byRole : null\n\t\t\t\tthis.roleVocabulary = (data && Array.isArray(data.roles)) ? data.roles : []\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('CnContactsTab: Failed to fetch contacts', err)\n\t\t\t\tthis.error = String(err?.message || err)\n\t\t\t\tthis.contacts = []\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 * Normalise a list-shaped response body to an array. Accepts\n\t\t * `{results:[...]}`, `{items:[...]}`, or a bare array; any\n\t\t * other shape (object, null, undefined) becomes `[]`.\n\t\t *\n\t\t * @param {object|Array<object>|null} data parsed JSON response body\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\tunwrapList(data) {\n\t\t\tif (Array.isArray(data)) {\n\t\t\t\treturn data\n\t\t\t}\n\t\t\tif (data && typeof data === 'object') {\n\t\t\t\tif (Array.isArray(data.results)) {\n\t\t\t\t\treturn data.results\n\t\t\t\t}\n\t\t\t\tif (Array.isArray(data.items)) {\n\t\t\t\t\treturn data.items\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn []\n\t\t},\n\n\t\t/**\n\t\t * The server's own grouping, labelled by the schema's vocabulary.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, items: Array}>}\n\t\t */\n\t\tgroupsFromServer() {\n\t\t\tconst labels = new Map(this.roleVocabulary.map((role) => [role.key, role.label || role.key]))\n\t\t\treturn Object.entries(this.serverGroups)\n\t\t\t\t.filter(([, items]) => Array.isArray(items) && items.length > 0)\n\t\t\t\t.map(([key, items]) => ({\n\t\t\t\t\tkey,\n\t\t\t\t\tlabel: labels.get(key) || (key === 'other' ? this.otherLabel : key),\n\t\t\t\t\titems,\n\t\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * The validity window of a link as a line, or '' when it is open.\n\t\t *\n\t\t * @param {object} contact The link.\n\t\t *\n\t\t * @return {string} The window.\n\t\t */\n\t\tperiodOf(contact) {\n\t\t\tconst from = contact?.validFrom || ''\n\t\t\tconst until = contact?.validUntil || ''\n\t\t\tif (from === '' && until === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (until === '') {\n\t\t\t\treturn `${this.fromLabel} ${from}`\n\t\t\t}\n\t\t\tif (from === '') {\n\t\t\t\treturn `${this.untilLabel} ${until}`\n\t\t\t}\n\t\t\treturn `${from} – ${until}`\n\t\t},\n\n\t\tasync unlink(contact) {\n\t\t\tif (!contact?.contactUid) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\t// people-on-objects: a person can hold several roles on one\n\t\t\t\t// object, so removing a row removes that role, not the person.\n\t\t\t\tconst role = contact.role ? `?role=${encodeURIComponent(contact.role)}` : ''\n\t\t\t\tconst url = prefixUrl(`${this.baseUrl}/${encodeURIComponent(contact.contactUid)}${role}`)\n\t\t\t\tawait fetch(url, { method: 'DELETE', headers: buildHeaders() })\n\t\t\t\tthis.contacts = this.contactsArray.filter((c) => c.id !== contact.id)\n\t\t\t\tthis.serverGroups = null\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('CnContactsTab: Failed to unlink contact', err)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Handle a `link` event from CnContactPicker — POST the picked\n\t\t * contact to the bare `/contacts` endpoint, then refresh.\n\t\t *\n\t\t * @param {object} payload picker payload\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onPickerLink(payload) {\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t...buildHeaders(),\n\t\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t},\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\t\tconsole.error('CnContactsTab: Failed to link picked contact', response.status)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.showLinkDialog = false\n\t\t\t\tawait this.fetchContacts()\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('CnContactsTab: Failed to link picked contact', err)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Handle a `create` event from CnContactCreate — POST the form\n\t\t * payload to the dedicated `/contacts/new` endpoint.\n\t\t *\n\t\t * @param {object} payload form payload\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onCreateSubmit(payload) {\n\t\t\tthis.createLoading = true\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl}/new`), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t...buildHeaders(),\n\t\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t},\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\t\tconsole.error('CnContactsTab: Failed to create+link contact', response.status)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.showCreateDialog = false\n\t\t\t\tawait this.fetchContacts()\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('CnContactsTab: Failed to create+link contact', err)\n\t\t\t} finally {\n\t\t\t\tthis.createLoading = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-contacts-tab {\n\tpadding: 12px;\n\toverflow-x: hidden;\n}\n\n.cn-contacts-tab__header {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\tgap: 8px;\n\tmargin-bottom: 12px;\n\tflex-wrap: wrap;\n}\n\n.cn-contacts-tab__group {\n\tmargin-bottom: 16px;\n}\n\n.cn-contacts-tab__group-title {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 13px;\n\tfont-weight: 600;\n\tcolor: var(--color-text-maxcontrast);\n\tmargin: 0 0 4px;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.5px;\n}\n\n.cn-contacts-tab__group-count {\n\tfont-weight: 600;\n\tfont-size: 11px;\n\tmin-width: 18px;\n\tpadding: 0 6px;\n\theight: 18px;\n\tline-height: 18px;\n\ttext-align: center;\n\tborder-radius: 9px;\n\tbackground-color: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-contacts-tab__list {\n\tlist-style: none;\n\tpadding: 0;\n\tmargin: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-contacts-tab__item {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 12px;\n\tpadding: 10px 8px;\n\tborder-radius: var(--border-radius-large, 8px);\n\tcursor: pointer;\n\ttransition: background-color 0.1s ease-in-out;\n}\n\n.cn-contacts-tab__item:hover,\n.cn-contacts-tab__item:focus-within {\n\tbackground-color: var(--color-background-hover);\n}\n\n.cn-contacts-tab__avatar {\n\tflex-shrink: 0;\n\tdisplay: flex;\n\talign-items: center;\n}\n\n.cn-contacts-tab__details {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tpadding-top: 2px;\n}\n\n.cn-contacts-tab__name-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmin-width: 0;\n}\n\n.cn-contacts-tab__name {\n\tfont-size: 14px;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-contacts-tab__sub {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 5px;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tmin-width: 0;\n}\n\n.cn-contacts-tab__sub-icon {\n\tflex-shrink: 0;\n\topacity: 0.7;\n}\n\n.cn-contacts-tab__sub-text {\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-contacts-tab__unlink {\n\tbackground: none;\n\tborder: none;\n\tpadding: 4px;\n\tcursor: pointer;\n\tcolor: var(--color-text-maxcontrast);\n\tborder-radius: 50%;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tflex-shrink: 0;\n\talign-self: center;\n}\n\n.cn-contacts-tab__unlink:hover,\n.cn-contacts-tab__unlink:focus {\n\tbackground-color: var(--color-background-dark);\n\tcolor: var(--color-error);\n}\n</style>\n"],"names":["AccountMultipleOutline","OfficeBuildingOutline","CnStatusBadge","CnContactPicker","CnContactCreate","CnWidgetHeaderActionButtons","t"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CnContactsTab.vue2.js","sources":["../../../../../src/integrations/builtin/contacts/CnContactsTab.vue"],"sourcesContent":["<!--\n - SPDX-License-Identifier: EUPL-1.2\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n -\n - CnContactsTab — bespoke sidebar tab for the `contacts` integration leaf.\n -\n - Renders the people linked to an OR object grouped by role. Since\n - people-on-objects a person is a Nextcloud user or a vCard contact, and\n - the grouping is the server's when it sends one: the listing carries\n - `byRole` and the schema's `roles`, and the tab falls back to its own\n - buckets (Applicants / Handlers / Advisors / Other, AD-1 of\n - `openspec/changes/integration-contacts/design.md`) for a server that\n - does not. Each card shows avatar, display name, email and role, and a\n - link whose validity has passed reads as inactive.\n -\n - Clicking a contact opens the reverse-lookup flyout (AD-3) listing every\n - OR object linked to that vCard.\n -\n - Tier-2 (this revision): the tab now hosts two header actions — \"Link\n - existing contact\" (opens `CnContactPicker`) and \"Add new contact\"\n - (opens `CnContactCreate`). Both dialogs are loaded lazily via\n - dynamic import to keep the bundle below the integration-tab\n - threshold, and both delegate the actual API call back to this tab\n - so the loading + refresh handshake stays here.\n -\n - Backed by:\n - GET /api/objects/{register}/{schema}/{id}/contacts\n - POST /api/objects/{register}/{schema}/{id}/contacts — link existing\n - POST /api/objects/{register}/{schema}/{id}/contacts/new — create + link\n - DELETE /api/objects/{register}/{schema}/{id}/contacts/{uid} — unlink\n -\n - The payload shape mirrors `ContactLink::jsonSerialize()` —\n - `{id, contactUid, displayName, email, phone, org, avatarUrl, role,\n - addressbookId, contactUri, schemaId, metadata, linkedAt}`.\n -\n - @spec openspec/changes/integration-contacts-tier2/specs/integrations/contacts/spec.md\n -->\n<template>\n\t<div class=\"cn-contacts-tab\">\n\t\t<!-- Header actions: link existing / create new. The board look draws\n\t\t Link contact first and both outlined (DqZaakBetrokkenen); the\n\t\t Nextcloud look keeps Add new contact first and Link as the primary. -->\n\t\t<div class=\"cn-contacts-tab__header\">\n\t\t\t<NcButton\n\t\t\t\tv-for=\"button in headerButtons\"\n\t\t\t\t:key=\"button.key\"\n\t\t\t\t:variant=\"button.variant\"\n\t\t\t\t:aria-label=\"button.label\"\n\t\t\t\t@click=\"button.key === 'link' ? (showLinkDialog = true) : (showCreateDialog = true)\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<LinkVariant v-if=\"button.key === 'link'\" :size=\"20\" />\n\t\t\t\t\t<AccountPlus v-else :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ button.label }}\n\t\t\t</NcButton>\n\t\t\t<CnWidgetHeaderActionButtons v-if=\"bodyActions.length > 0\" :actions=\"bodyActions\" />\n\t\t</div>\n\n\t\t<!-- Loading -->\n\t\t<NcLoadingIcon v-if=\"loading\" />\n\n\t\t<!-- Error -->\n\t\t<NcEmptyContent\n\t\t\tv-else-if=\"error\"\n\t\t\t:name=\"errorLabel\"\n\t\t\t:description=\"error\">\n\t\t\t<template #icon>\n\t\t\t\t<AlertCircleOutline :size=\"48\" />\n\t\t\t</template>\n\t\t</NcEmptyContent>\n\n\t\t<!-- Empty -->\n\t\t<NcEmptyContent\n\t\t\tv-else-if=\"contactsArray.length === 0\"\n\t\t\t:name=\"emptyTitleLabel\"\n\t\t\t:description=\"emptyDescriptionLabel\">\n\t\t\t<template #icon>\n\t\t\t\t<AccountMultipleOutline :size=\"48\" />\n\t\t\t</template>\n\t\t</NcEmptyContent>\n\n\t\t<!-- Grouped list -->\n\t\t<div v-else class=\"cn-contacts-tab__groups\">\n\t\t\t<section\n\t\t\t\tv-for=\"group in groupedContacts\"\n\t\t\t\t:key=\"group.key\"\n\t\t\t\tclass=\"cn-contacts-tab__group\">\n\t\t\t\t<h3 class=\"cn-contacts-tab__group-title\">\n\t\t\t\t\t{{ group.label }}\n\t\t\t\t\t<span v-if=\"!isBoard\" class=\"cn-contacts-tab__group-count\">{{ group.items.length }}</span>\n\t\t\t\t</h3>\n\t\t\t\t<ul class=\"cn-contacts-tab__list\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"contact in group.items\"\n\t\t\t\t\t\t:key=\"contact.id\"\n\t\t\t\t\t\tclass=\"cn-contacts-tab__item\"\n\t\t\t\t\t\t@click=\"openReverseLookup(contact)\">\n\t\t\t\t\t\t<div class=\"cn-contacts-tab__avatar\">\n\t\t\t\t\t\t\t<NcAvatar\n\t\t\t\t\t\t\t\t:size=\"isBoard ? 32 : 40\"\n\t\t\t\t\t\t\t\t:displayName=\"contact.displayName || unknownLabel\"\n\t\t\t\t\t\t\t\t:user=\"avatarSeed(contact)\"\n\t\t\t\t\t\t\t\t:url=\"contact.avatarUrl || undefined\"\n\t\t\t\t\t\t\t\t:isNoUser=\"true\"\n\t\t\t\t\t\t\t\t:disableMenu=\"true\"\n\t\t\t\t\t\t\t\t:disableTooltip=\"true\"\n\t\t\t\t\t\t\t\thideStatus />\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<div class=\"cn-contacts-tab__details\">\n\t\t\t\t\t\t\t<div class=\"cn-contacts-tab__name-row\">\n\t\t\t\t\t\t\t\t<span class=\"cn-contacts-tab__name\">\n\t\t\t\t\t\t\t\t\t{{ contact.displayName || unknownLabel }}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\t\tv-if=\"contact.role && !isBoard\"\n\t\t\t\t\t\t\t\t\t:label=\"roleLabel(contact)\"\n\t\t\t\t\t\t\t\t\t:variant=\"roleVariant(contact)\"\n\t\t\t\t\t\t\t\t\tsize=\"small\" />\n\t\t\t\t\t\t\t\t<CnStatusBadge\n\t\t\t\t\t\t\t\t\tv-if=\"contact.active === false\"\n\t\t\t\t\t\t\t\t\t:label=\"inactiveLabel\"\n\t\t\t\t\t\t\t\t\tvariant=\"default\"\n\t\t\t\t\t\t\t\t\tsize=\"small\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-contacts-tab-inactive\" />\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t<!-- The board look sets the details on one line (DqZaakBetrokkenen). -->\n\t\t\t\t\t\t\t<span v-if=\"isBoard && boardMetaOf(contact)\"\n\t\t\t\t\t\t\t\tclass=\"cn-contacts-tab__meta\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-contacts-tab-meta\">{{ boardMetaOf(contact) }}</span>\n\t\t\t\t\t\t\t<template v-if=\"!isBoard\">\n\t\t\t\t\t\t\t\t<div v-if=\"contact.email\" class=\"cn-contacts-tab__sub\">\n\t\t\t\t\t\t\t\t\t<Email :size=\"14\" class=\"cn-contacts-tab__sub-icon\" />\n\t\t\t\t\t\t\t\t\t<span class=\"cn-contacts-tab__sub-text\">{{ contact.email }}</span>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<div v-if=\"contact.phone\" class=\"cn-contacts-tab__sub\">\n\t\t\t\t\t\t\t\t\t<Phone :size=\"14\" class=\"cn-contacts-tab__sub-icon\" />\n\t\t\t\t\t\t\t\t\t<span class=\"cn-contacts-tab__sub-text\">{{ contact.phone }}</span>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<div v-if=\"contact.org\" class=\"cn-contacts-tab__sub\">\n\t\t\t\t\t\t\t\t\t<OfficeBuildingOutline :size=\"14\" class=\"cn-contacts-tab__sub-icon\" />\n\t\t\t\t\t\t\t\t\t<span class=\"cn-contacts-tab__sub-text\">{{ contact.org }}</span>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tv-if=\"periodOf(contact)\"\n\t\t\t\t\t\t\t\t\tclass=\"cn-contacts-tab__sub\"\n\t\t\t\t\t\t\t\t\tdata-testid=\"cn-contacts-tab-period\">\n\t\t\t\t\t\t\t\t\t<CalendarRange :size=\"14\" class=\"cn-contacts-tab__sub-icon\" />\n\t\t\t\t\t\t\t\t\t<span class=\"cn-contacts-tab__sub-text\">{{ periodOf(contact) }}</span>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t\t<div v-if=\"contact.note\" class=\"cn-contacts-tab__sub\">\n\t\t\t\t\t\t\t\t\t<span class=\"cn-contacts-tab__sub-text\">{{ contact.note }}</span>\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t</div>\n\t\t\t\t\t\t<template v-if=\"isBoard\">\n\t\t\t\t\t\t\t<span v-if=\"boardPeriodOf(contact)\"\n\t\t\t\t\t\t\t\tclass=\"cn-contacts-tab__period\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-contacts-tab-board-period\">{{ boardPeriodOf(contact) }}</span>\n\t\t\t\t\t\t\t<NcActions class=\"cn-contacts-tab__menu\"\n\t\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t\t:aria-label=\"moreActionsLabel(contact)\"\n\t\t\t\t\t\t\t\tdata-testid=\"cn-contacts-tab-menu\"\n\t\t\t\t\t\t\t\t@click.stop>\n\t\t\t\t\t\t\t\t<NcActionButton :closeAfterClick=\"true\" @click=\"unlink(contact)\">\n\t\t\t\t\t\t\t\t\t<template #icon>\n\t\t\t\t\t\t\t\t\t\t<Close :size=\"20\" />\n\t\t\t\t\t\t\t\t\t</template>\n\t\t\t\t\t\t\t\t\t{{ unlinkLabel }}\n\t\t\t\t\t\t\t\t</NcActionButton>\n\t\t\t\t\t\t\t</NcActions>\n\t\t\t\t\t\t</template>\n\t\t\t\t\t\t<button\n\t\t\t\t\t\t\tv-else\n\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\tclass=\"cn-contacts-tab__unlink\"\n\t\t\t\t\t\t\t:aria-label=\"unlinkLabel\"\n\t\t\t\t\t\t\t:title=\"unlinkLabel\"\n\t\t\t\t\t\t\t@click.stop=\"unlink(contact)\">\n\t\t\t\t\t\t\t<Close :size=\"18\" />\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t</section>\n\t\t</div>\n\n\t\t<!-- Link existing contact dialog -->\n\t\t<CnContactPicker\n\t\t\tv-if=\"showLinkDialog\"\n\t\t\t:apiBase=\"apiBase\"\n\t\t\tv-bind=\"pickerRoleOptions\"\n\t\t\t@link=\"onPickerLink\"\n\t\t\t@close=\"showLinkDialog = false\" />\n\n\t\t<!-- Create new contact dialog -->\n\t\t<CnContactCreate\n\t\t\tv-if=\"showCreateDialog\"\n\t\t\t:loading=\"createLoading\"\n\t\t\t@create=\"onCreateSubmit\"\n\t\t\t@close=\"showCreateDialog = false\" />\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcActionButton, NcActions, NcAvatar, NcButton, NcEmptyContent, NcLoadingIcon } from '@nextcloud/vue'\nimport AccountMultipleOutline from 'vue-material-design-icons/AccountMultipleOutline.vue'\nimport AccountPlus from 'vue-material-design-icons/AccountPlus.vue'\nimport AlertCircleOutline from 'vue-material-design-icons/AlertCircleOutline.vue'\nimport CalendarRange from 'vue-material-design-icons/CalendarRange.vue'\nimport Close from 'vue-material-design-icons/Close.vue'\nimport Email from 'vue-material-design-icons/Email.vue'\nimport LinkVariant from 'vue-material-design-icons/LinkVariant.vue'\nimport OfficeBuildingOutline from 'vue-material-design-icons/OfficeBuildingOutline.vue'\nimport Phone from 'vue-material-design-icons/Phone.vue'\nimport CnContactCreate from '../../../components/CnContactCreate/CnContactCreate.vue'\nimport CnContactPicker from '../../../components/CnContactPicker/CnContactPicker.vue'\nimport CnStatusBadge from '../../../components/CnStatusBadge/CnStatusBadge.vue'\nimport CnWidgetHeaderActionButtons from '../../../components/CnWidgetWrapper/CnWidgetHeaderActionButtons.vue'\nimport { normalizeLook } from '../../../composables/useLook.js'\nimport { formatBoardDate } from '../../../utils/boardDate.js'\nimport { buildHeaders, prefixUrl } from '../../../utils/index.js'\nimport { WIDGET_BODY_ACTIONS } from '../../../utils/widgetHeaderActions.js'\n\n/**\n * Normalised role keys for grouping. Free-text vCard roles are\n * normalised by lowercasing + trimming; unknown roles fall into `other`.\n *\n * @type {Array<{key: string, match: (r: ?string) => boolean, labelKey: string}>}\n */\nconst ROLE_BUCKETS = [\n\t{ key: 'applicant', match: (r) => /^(applicant|aanvrager|requestor)$/i.test(r ?? ''), labelKey: 'Applicants' },\n\t{ key: 'handler', match: (r) => /^(handler|behandelaar|owner|assignee)$/i.test(r ?? ''), labelKey: 'Handlers' },\n\t{ key: 'advisor', match: (r) => /^(advisor|advisor|adviseur|reviewer)$/i.test(r ?? ''), labelKey: 'Advisors' },\n]\n\n/**\n * CnContactsTab — vCard contacts linked to an OR object, grouped by role.\n *\n * Basic usage\n * ```vue\n * <CnContactsTab\n * register=\"my-register\"\n * schema=\"case\"\n * object-id=\"abc-123\" />\n * ```\n */\nexport default {\n\tname: 'CnContactsTab',\n\n\tcomponents: {\n\t\tNcActionButton,\n\t\tNcActions,\n\t\tNcAvatar,\n\t\tNcButton,\n\t\tNcEmptyContent,\n\t\tNcLoadingIcon,\n\t\tAccountPlus,\n\t\tAccountMultipleOutline,\n\t\tAlertCircleOutline,\n\t\tCalendarRange,\n\t\tClose,\n\t\tEmail,\n\t\tLinkVariant,\n\t\tOfficeBuildingOutline,\n\t\tPhone,\n\t\tCnStatusBadge,\n\t\tCnContactPicker,\n\t\tCnContactCreate,\n\t\tCnWidgetHeaderActionButtons,\n\t},\n\n\tinject: {\n\t\t/** The look the app is drawn in (`board` or `nextcloud`), from CnAppRoot or the page. */\n\t\tcnLook: { default: 'nextcloud' },\n\t\t/** The widget's manifest `bodyActions`, drawn after this tab's own buttons. */\n\t\tcnWidgetBodyActions: { from: WIDGET_BODY_ACTIONS, default: null },\n\t},\n\n\tprops: {\n\t\t/** Parent object id. */\n\t\tobjectId: { type: String, required: true },\n\t\t/** OpenRegister register id (slug or uuid). */\n\t\tregister: { type: String, default: '' },\n\t\t/** OpenRegister schema id (slug or uuid). */\n\t\tschema: { type: String, default: '' },\n\t\t/** Base API URL for OpenRegister. */\n\t\tapiBase: { type: String, default: '/apps/openregister/api' },\n\n\t\t// --- Pre-translated labels (consumer-overridable for i18n flexibility) ---\n\t\t/** Label for the button that links an existing contact. */\n\t\tlinkExistingLabel: { type: String, default: () => t('nextcloud-vue', 'Link contact') },\n\t\t/** Label for the button that creates a contact; empty takes the look's own (New contact, or Add new contact). */\n\t\taddNewLabel: { type: String, default: '' },\n\t\t/** Title of the empty state shown when no contact is linked. */\n\t\temptyTitleLabel: { type: String, default: () => t('nextcloud-vue', 'No contacts linked') },\n\t\t/** Body of the empty state shown when no contact is linked. */\n\t\temptyDescriptionLabel: { type: String, default: () => t('nextcloud-vue', 'Link a contact from your address book.') },\n\t\t/** Message shown when the contacts could not be loaded. */\n\t\terrorLabel: { type: String, default: () => t('nextcloud-vue', 'Could not load contacts') },\n\t\t/** Stand-in name for a contact whose name is missing. */\n\t\tunknownLabel: { type: String, default: () => t('nextcloud-vue', 'Unknown contact') },\n\t\t/** Label for the action that unlinks a contact from the object. */\n\t\tunlinkLabel: { type: String, default: () => t('nextcloud-vue', 'Unlink contact') },\n\t\t/** Heading above the contacts grouped as applicants. */\n\t\tapplicantsLabel: { type: String, default: () => t('nextcloud-vue', 'Applicants') },\n\t\t/** Heading above the contacts grouped as handlers. */\n\t\thandlersLabel: { type: String, default: () => t('nextcloud-vue', 'Handlers') },\n\t\t/** Heading above the contacts grouped as advisors. */\n\t\tadvisorsLabel: { type: String, default: () => t('nextcloud-vue', 'Advisors') },\n\t\t/** Heading above the contacts with no recognised role. */\n\t\totherLabel: { type: String, default: () => t('nextcloud-vue', 'Other') },\n\t\t/** Badge on a link whose validity window has passed or not yet begun. */\n\t\tinactiveLabel: { type: String, default: () => t('nextcloud-vue', 'Not active') },\n\t\t/** Prefix for a validity window with an open end. */\n\t\tfromLabel: { type: String, default: () => t('nextcloud-vue', 'From') },\n\t\t/** Prefix for a validity window with an open start. */\n\t\tuntilLabel: { type: String, default: () => t('nextcloud-vue', 'Until') },\n\t\t/** Role badge shown on an applicant. */\n\t\tapplicantRoleLabel: { type: String, default: () => t('nextcloud-vue', 'Applicant') },\n\t\t/** Role badge shown on a handler. */\n\t\thandlerRoleLabel: { type: String, default: () => t('nextcloud-vue', 'Handler') },\n\t\t/** Role badge shown on an advisor. */\n\t\tadvisorRoleLabel: { type: String, default: () => t('nextcloud-vue', 'Advisor') },\n\t},\n\n\temits: ['open-reverse-lookup'],\n\n\tdata() {\n\t\treturn {\n\t\t\t// The server's grouping and role vocabulary, when it sends them.\n\t\t\tserverGroups: null,\n\t\t\troleVocabulary: [],\n\t\t\tcontacts: [],\n\t\t\tloading: false,\n\t\t\terror: null,\n\t\t\tshowLinkDialog: false,\n\t\t\tshowCreateDialog: false,\n\t\t\tcreateLoading: false,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/** @return {boolean} The board look draws the rows as DqZaakBetrokkenen does. */\n\t\tisBoard() {\n\t\t\treturn normalizeLook(this.cnLook) === 'board'\n\t\t},\n\n\t\t/**\n\t\t * The two header buttons in the order and weight of the look: the\n\t\t * board look draws Link contact then New contact, both outlined; the\n\t\t * Nextcloud look keeps Add new contact first and Link as the primary.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, variant: string}>} The buttons.\n\t\t */\n\t\theaderButtons() {\n\t\t\tconst board = normalizeLook(this.cnLook) === 'board'\n\t\t\tconst newLabel = this.addNewLabel !== ''\n\t\t\t\t? this.addNewLabel\n\t\t\t\t: (board ? t('nextcloud-vue', 'New contact') : t('nextcloud-vue', 'Add new contact'))\n\t\t\tconst link = { key: 'link', label: this.linkExistingLabel, variant: board ? 'secondary' : 'primary' }\n\t\t\tconst create = { key: 'new', label: newLabel, variant: 'secondary' }\n\t\t\treturn board ? [link, create] : [create, link]\n\t\t},\n\n\t\t/**\n\t\t * The widget's body actions, unwrapped.\n\t\t *\n\t\t * @return {Array<object>} The resolved buttons, or none.\n\t\t */\n\t\tbodyActions() {\n\t\t\tconst provided = this.cnWidgetBodyActions\n\t\t\tconst list = provided && typeof provided === 'object' && 'value' in provided ? provided.value : provided\n\t\t\treturn Array.isArray(list) ? list : []\n\t\t},\n\n\t\t/**\n\t\t * Defensive view onto `this.contacts` — always an array.\n\t\t *\n\t\t * Guards against the well-known \"this.contacts is not iterable\"\n\t\t * TypeError observed in Phase A / D-1 when the provider returns\n\t\t * an object-shaped payload (or `null`) and the iteration site\n\t\t * blows up before the watch has even fired.\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\tcontactsArray() {\n\t\t\treturn Array.isArray(this.contacts) ? this.contacts : []\n\t\t},\n\n\t\t/**\n\t\t * Group contacts by normalised role bucket.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, items: Array}>}\n\t\t */\n\t\tgroupedContacts() {\n\t\t\tif (this.serverGroups !== null) {\n\t\t\t\treturn this.groupsFromServer()\n\t\t\t}\n\t\t\treturn this.groupsFromBuckets\n\t\t},\n\n\t\t/**\n\t\t * The role vocabulary as CnContactPicker options, or nothing when\n\t\t * the schema declares none (the picker keeps its own defaults).\n\t\t *\n\t\t * @return {object} Props to bind onto the picker.\n\t\t */\n\t\tpickerRoleOptions() {\n\t\t\tif (this.roleVocabulary.length === 0) {\n\t\t\t\treturn {}\n\t\t\t}\n\t\t\treturn {\n\t\t\t\troleOptions: this.roleVocabulary.map((role) => ({ label: role.label || role.key, value: role.key })),\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Group by the tab's own role buckets, for a server that does not group.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, items: Array}>}\n\t\t */\n\t\tgroupsFromBuckets() {\n\t\t\tconst labelMap = {\n\t\t\t\tapplicant: this.applicantsLabel,\n\t\t\t\thandler: this.handlersLabel,\n\t\t\t\tadvisor: this.advisorsLabel,\n\t\t\t\tother: this.otherLabel,\n\t\t\t}\n\t\t\tconst buckets = { applicant: [], handler: [], advisor: [], other: [] }\n\t\t\tfor (const c of this.contactsArray) {\n\t\t\t\tconst matched = ROLE_BUCKETS.find((b) => b.match(c.role))\n\t\t\t\tif (matched) {\n\t\t\t\t\tbuckets[matched.key].push(c)\n\t\t\t\t} else {\n\t\t\t\t\tbuckets.other.push(c)\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst out = []\n\t\t\tfor (const key of ['applicant', 'handler', 'advisor', 'other']) {\n\t\t\t\tif (buckets[key].length > 0) {\n\t\t\t\t\tout.push({ key, label: labelMap[key], items: buckets[key] })\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\tbaseUrl() {\n\t\t\treturn `${this.apiBase}/objects/${this.register}/${this.schema}/${this.objectId}/contacts`\n\t\t},\n\t},\n\n\twatch: {\n\t\tobjectId: {\n\t\t\timmediate: true,\n\t\t\thandler(id) {\n\t\t\t\tif (id) {\n\t\t\t\t\tthis.fetchContacts()\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Initials from displayName (\"Jan de Vries\" → \"JV\"); falls back to `?`.\n\t\t *\n\t\t * @param {object} contact ContactLink JSON.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tinitialsFor(contact) {\n\t\t\tconst name = (contact?.displayName || '').trim()\n\t\t\tif (name === '') {\n\t\t\t\treturn '?'\n\t\t\t}\n\t\t\tconst parts = name.split(/\\s+/).filter(Boolean)\n\t\t\tif (parts.length === 1) {\n\t\t\t\treturn parts[0].charAt(0).toUpperCase()\n\t\t\t}\n\t\t\treturn (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase()\n\t\t},\n\n\t\t/**\n\t\t * Stable seed for NcAvatar's deterministic colour + initials.\n\t\t * Prefer the contact UID, then email, then display name so the\n\t\t * generated avatar colour stays consistent for a given contact.\n\t\t *\n\t\t * @param {object} contact ContactLink JSON.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\tavatarSeed(contact) {\n\t\t\treturn contact.contactUid || contact.email || contact.displayName || '?'\n\t\t},\n\n\t\t/**\n\t\t * Resolve the normalised role bucket for a contact.\n\t\t *\n\t\t * @param {object} contact ContactLink JSON.\n\t\t *\n\t\t * @return {string} one of applicant|handler|advisor|other\n\t\t */\n\t\troleKey(contact) {\n\t\t\tconst matched = ROLE_BUCKETS.find((b) => b.match(contact.role))\n\t\t\treturn matched ? matched.key : 'other'\n\t\t},\n\n\t\t/**\n\t\t * Human-readable role chip label — the translated bucket name\n\t\t * when the role maps to a known bucket, else the raw vCard role.\n\t\t *\n\t\t * @param {object} contact ContactLink JSON.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\troleLabel(contact) {\n\t\t\tconst map = {\n\t\t\t\tapplicant: this.applicantRoleLabel,\n\t\t\t\thandler: this.handlerRoleLabel,\n\t\t\t\tadvisor: this.advisorRoleLabel,\n\t\t\t}\n\t\t\treturn map[this.roleKey(contact)] || contact.role\n\t\t},\n\n\t\t/**\n\t\t * CnStatusBadge variant per role bucket so each role reads as a\n\t\t * distinct accent (handlers = primary, advisors = info, etc.).\n\t\t *\n\t\t * @param {object} contact ContactLink JSON.\n\t\t *\n\t\t * @return {string}\n\t\t */\n\t\troleVariant(contact) {\n\t\t\tconst map = {\n\t\t\t\tapplicant: 'info',\n\t\t\t\thandler: 'primary',\n\t\t\t\tadvisor: 'success',\n\t\t\t}\n\t\t\treturn map[this.roleKey(contact)] || 'default'\n\t\t},\n\n\t\t/**\n\t\t * Open the reverse-lookup flyout for a contact (AD-3). Emits an\n\t\t * event for the consuming app to handle (since the flyout's\n\t\t * implementation depends on the host router).\n\t\t *\n\t\t * @param {object} contact ContactLink JSON.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\topenReverseLookup(contact) {\n\t\t\tthis.$emit('open-reverse-lookup', {\n\t\t\t\tcontactUid: contact.contactUid,\n\t\t\t\tdisplayName: contact.displayName,\n\t\t\t})\n\t\t},\n\n\t\tasync fetchContacts() {\n\t\t\tif (!this.register || !this.schema || !this.objectId) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.loading = true\n\t\t\tthis.error = null\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl), { headers: buildHeaders() })\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tthis.error = `${response.status} ${response.statusText}`\n\t\t\t\t\tthis.contacts = []\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tconst data = await response.json()\n\t\t\t\t// Canonical wrapper-key cascade (per ADR-022): provider\n\t\t\t\t// may return `{results: [...]}` (paginated), `{items:\n\t\t\t\t// [...]}` (some leaves), or a bare array. Anything else\n\t\t\t\t// falls back to `[]` rather than letting a non-iterable\n\t\t\t\t// object reach `groupedContacts` (Phase A / D-1 bug).\n\t\t\t\tthis.contacts = this.unwrapList(data)\n\t\t\t\t// people-on-objects: the server groups by role and names the\n\t\t\t\t// schema's vocabulary. Both optional, so a server that sends\n\t\t\t\t// neither keeps the tab's own buckets.\n\t\t\t\tthis.serverGroups = (data && typeof data === 'object' && data.byRole) ? data.byRole : null\n\t\t\t\tthis.roleVocabulary = (data && Array.isArray(data.roles)) ? data.roles : []\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('CnContactsTab: Failed to fetch contacts', err)\n\t\t\t\tthis.error = String(err?.message || err)\n\t\t\t\tthis.contacts = []\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 * Normalise a list-shaped response body to an array. Accepts\n\t\t * `{results:[...]}`, `{items:[...]}`, or a bare array; any\n\t\t * other shape (object, null, undefined) becomes `[]`.\n\t\t *\n\t\t * @param {object|Array<object>|null} data parsed JSON response body\n\t\t *\n\t\t * @return {Array}\n\t\t */\n\t\tunwrapList(data) {\n\t\t\tif (Array.isArray(data)) {\n\t\t\t\treturn data\n\t\t\t}\n\t\t\tif (data && typeof data === 'object') {\n\t\t\t\tif (Array.isArray(data.results)) {\n\t\t\t\t\treturn data.results\n\t\t\t\t}\n\t\t\t\tif (Array.isArray(data.items)) {\n\t\t\t\t\treturn data.items\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn []\n\t\t},\n\n\t\t/**\n\t\t * The server's own grouping, labelled by the schema's vocabulary.\n\t\t *\n\t\t * @return {Array<{key: string, label: string, items: Array}>}\n\t\t */\n\t\tgroupsFromServer() {\n\t\t\tconst labels = new Map(this.roleVocabulary.map((role) => [role.key, role.label || role.key]))\n\t\t\treturn Object.entries(this.serverGroups)\n\t\t\t\t.filter(([, items]) => Array.isArray(items) && items.length > 0)\n\t\t\t\t.map(([key, items]) => ({\n\t\t\t\t\tkey,\n\t\t\t\t\tlabel: labels.get(key) || (key === 'other' ? this.otherLabel : key),\n\t\t\t\t\titems,\n\t\t\t\t}))\n\t\t},\n\n\t\t/**\n\t\t * The validity window of a link as a line, or '' when it is open.\n\t\t *\n\t\t * @param {object} contact The link.\n\t\t *\n\t\t * @return {string} The window.\n\t\t */\n\t\t/**\n\t\t * A person's details on one line for the board look: e-mail, phone,\n\t\t * organisation and note, separated by middots.\n\t\t *\n\t\t * @param {object} contact The contact link.\n\t\t * @return {string}\n\t\t */\n\t\tboardMetaOf(contact) {\n\t\t\treturn [contact?.email, contact?.phone, contact?.org, contact?.note].filter((part) => typeof part === 'string' && part !== '').join(' · ')\n\t\t},\n\n\t\t/**\n\t\t * The validity window in the board's short dates: \"Vanaf 3 okt\",\n\t\t * \"Tot 31 okt\" or \"3 okt – 31 okt\".\n\t\t *\n\t\t * @param {object} contact The contact link.\n\t\t * @return {string}\n\t\t */\n\t\tboardPeriodOf(contact) {\n\t\t\tconst from = formatBoardDate(contact?.validFrom || '')\n\t\t\tconst until = formatBoardDate(contact?.validUntil || '')\n\t\t\tif (from === '' && until === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (until === '') {\n\t\t\t\treturn t('nextcloud-vue', 'From {date}', { date: from })\n\t\t\t}\n\t\t\tif (from === '') {\n\t\t\t\treturn t('nextcloud-vue', 'Until {date}', { date: until })\n\t\t\t}\n\t\t\treturn `${from} – ${until}`\n\t\t},\n\n\t\t/**\n\t\t * The label of a person's actions menu.\n\t\t *\n\t\t * @param {object} contact The contact link.\n\t\t * @return {string}\n\t\t */\n\t\tmoreActionsLabel(contact) {\n\t\t\treturn t('nextcloud-vue', 'More actions for {name}', { name: contact?.displayName || this.unknownLabel })\n\t\t},\n\n\t\tperiodOf(contact) {\n\t\t\tconst from = contact?.validFrom || ''\n\t\t\tconst until = contact?.validUntil || ''\n\t\t\tif (from === '' && until === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tif (until === '') {\n\t\t\t\treturn `${this.fromLabel} ${from}`\n\t\t\t}\n\t\t\tif (from === '') {\n\t\t\t\treturn `${this.untilLabel} ${until}`\n\t\t\t}\n\t\t\treturn `${from} – ${until}`\n\t\t},\n\n\t\tasync unlink(contact) {\n\t\t\tif (!contact?.contactUid) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\ttry {\n\t\t\t\t// people-on-objects: a person can hold several roles on one\n\t\t\t\t// object, so removing a row removes that role, not the person.\n\t\t\t\tconst role = contact.role ? `?role=${encodeURIComponent(contact.role)}` : ''\n\t\t\t\tconst url = prefixUrl(`${this.baseUrl}/${encodeURIComponent(contact.contactUid)}${role}`)\n\t\t\t\tawait fetch(url, { method: 'DELETE', headers: buildHeaders() })\n\t\t\t\tthis.contacts = this.contactsArray.filter((c) => c.id !== contact.id)\n\t\t\t\tthis.serverGroups = null\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('CnContactsTab: Failed to unlink contact', err)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Handle a `link` event from CnContactPicker — POST the picked\n\t\t * contact to the bare `/contacts` endpoint, then refresh.\n\t\t *\n\t\t * @param {object} payload picker payload\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onPickerLink(payload) {\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(this.baseUrl), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t...buildHeaders(),\n\t\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t},\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\t\tconsole.error('CnContactsTab: Failed to link picked contact', response.status)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.showLinkDialog = false\n\t\t\t\tawait this.fetchContacts()\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('CnContactsTab: Failed to link picked contact', err)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Handle a `create` event from CnContactCreate — POST the form\n\t\t * payload to the dedicated `/contacts/new` endpoint.\n\t\t *\n\t\t * @param {object} payload form payload\n\t\t *\n\t\t * @return {Promise<void>}\n\t\t */\n\t\tasync onCreateSubmit(payload) {\n\t\t\tthis.createLoading = true\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(prefixUrl(`${this.baseUrl}/new`), {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t...buildHeaders(),\n\t\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t},\n\t\t\t\t\tbody: JSON.stringify(payload),\n\t\t\t\t})\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\t\tconsole.error('CnContactsTab: Failed to create+link contact', response.status)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tthis.showCreateDialog = false\n\t\t\t\tawait this.fetchContacts()\n\t\t\t} catch (err) {\n\t\t\t\t// eslint-disable-next-line no-console -- the failure is already handled; the console is the only channel a host app can read the detail on\n\t\t\t\tconsole.error('CnContactsTab: Failed to create+link contact', err)\n\t\t\t} finally {\n\t\t\t\tthis.createLoading = false\n\t\t\t}\n\t\t},\n\t},\n}\n</script>\n\n<style scoped>\n.cn-contacts-tab {\n\tpadding: 12px;\n\toverflow-x: hidden;\n}\n\n.cn-contacts-tab__header {\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\tgap: 8px;\n\tmargin-bottom: 12px;\n\tflex-wrap: wrap;\n}\n\n.cn-contacts-tab__group {\n\tmargin-bottom: 16px;\n}\n\n.cn-contacts-tab__group-title {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n\tfont-size: 13px;\n\tfont-weight: 600;\n\tcolor: var(--color-text-maxcontrast);\n\tmargin: 0 0 4px;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.5px;\n}\n\n.cn-contacts-tab__group-count {\n\tfont-weight: 600;\n\tfont-size: 11px;\n\tmin-width: 18px;\n\tpadding: 0 6px;\n\theight: 18px;\n\tline-height: 18px;\n\ttext-align: center;\n\tborder-radius: 9px;\n\tbackground-color: var(--color-background-dark);\n\tcolor: var(--color-text-maxcontrast);\n}\n\n.cn-contacts-tab__list {\n\tlist-style: none;\n\tpadding: 0;\n\tmargin: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.cn-contacts-tab__item {\n\tdisplay: flex;\n\talign-items: flex-start;\n\tgap: 12px;\n\tpadding: 10px 8px;\n\tborder-radius: var(--border-radius-large, 8px);\n\tcursor: pointer;\n\ttransition: background-color 0.1s ease-in-out;\n}\n\n.cn-contacts-tab__item:hover,\n.cn-contacts-tab__item:focus-within {\n\tbackground-color: var(--color-background-hover);\n}\n\n.cn-contacts-tab__avatar {\n\tflex-shrink: 0;\n\tdisplay: flex;\n\talign-items: center;\n}\n\n.cn-contacts-tab__details {\n\tflex: 1;\n\tmin-width: 0;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tpadding-top: 2px;\n}\n\n.cn-contacts-tab__name-row {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 8px;\n\tmin-width: 0;\n}\n\n.cn-contacts-tab__name {\n\tfont-size: 14px;\n\tfont-weight: 600;\n\tcolor: var(--color-main-text);\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-contacts-tab__sub {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 5px;\n\tfont-size: 12px;\n\tcolor: var(--color-text-maxcontrast);\n\tmin-width: 0;\n}\n\n.cn-contacts-tab__sub-icon {\n\tflex-shrink: 0;\n\topacity: 0.7;\n}\n\n.cn-contacts-tab__sub-text {\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-contacts-tab__unlink {\n\tbackground: none;\n\tborder: none;\n\tpadding: 4px;\n\tcursor: pointer;\n\tcolor: var(--color-text-maxcontrast);\n\tborder-radius: 50%;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tflex-shrink: 0;\n\talign-self: center;\n}\n\n.cn-contacts-tab__unlink:hover,\n.cn-contacts-tab__unlink:focus {\n\tbackground-color: var(--color-background-dark);\n\tcolor: var(--color-error);\n}\n</style>\n"],"names":["AccountMultipleOutline","OfficeBuildingOutline","CnStatusBadge","CnContactPicker","CnContactCreate","CnWidgetHeaderActionButtons","t"],"mappings":"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"}
|
|
@@ -7,7 +7,10 @@ const _hoisted_3 = {
|
|
|
7
7
|
class: "cn-contacts-tab__groups"
|
|
8
8
|
};
|
|
9
9
|
const _hoisted_4 = { class: "cn-contacts-tab__group-title" };
|
|
10
|
-
const _hoisted_5 = {
|
|
10
|
+
const _hoisted_5 = {
|
|
11
|
+
key: 0,
|
|
12
|
+
class: "cn-contacts-tab__group-count"
|
|
13
|
+
};
|
|
11
14
|
const _hoisted_6 = { class: "cn-contacts-tab__list" };
|
|
12
15
|
const _hoisted_7 = ["onClick"];
|
|
13
16
|
const _hoisted_8 = { class: "cn-contacts-tab__avatar" };
|
|
@@ -15,32 +18,42 @@ const _hoisted_9 = { class: "cn-contacts-tab__details" };
|
|
|
15
18
|
const _hoisted_10 = { class: "cn-contacts-tab__name-row" };
|
|
16
19
|
const _hoisted_11 = { class: "cn-contacts-tab__name" };
|
|
17
20
|
const _hoisted_12 = {
|
|
21
|
+
key: 0,
|
|
22
|
+
class: "cn-contacts-tab__meta",
|
|
23
|
+
"data-testid": "cn-contacts-tab-meta"
|
|
24
|
+
};
|
|
25
|
+
const _hoisted_13 = {
|
|
18
26
|
key: 0,
|
|
19
27
|
class: "cn-contacts-tab__sub"
|
|
20
28
|
};
|
|
21
|
-
const
|
|
22
|
-
const
|
|
29
|
+
const _hoisted_14 = { class: "cn-contacts-tab__sub-text" };
|
|
30
|
+
const _hoisted_15 = {
|
|
23
31
|
key: 1,
|
|
24
32
|
class: "cn-contacts-tab__sub"
|
|
25
33
|
};
|
|
26
|
-
const
|
|
27
|
-
const
|
|
34
|
+
const _hoisted_16 = { class: "cn-contacts-tab__sub-text" };
|
|
35
|
+
const _hoisted_17 = {
|
|
28
36
|
key: 2,
|
|
29
37
|
class: "cn-contacts-tab__sub"
|
|
30
38
|
};
|
|
31
|
-
const
|
|
32
|
-
const
|
|
39
|
+
const _hoisted_18 = { class: "cn-contacts-tab__sub-text" };
|
|
40
|
+
const _hoisted_19 = {
|
|
33
41
|
key: 3,
|
|
34
42
|
class: "cn-contacts-tab__sub",
|
|
35
43
|
"data-testid": "cn-contacts-tab-period"
|
|
36
44
|
};
|
|
37
|
-
const
|
|
38
|
-
const
|
|
45
|
+
const _hoisted_20 = { class: "cn-contacts-tab__sub-text" };
|
|
46
|
+
const _hoisted_21 = {
|
|
39
47
|
key: 4,
|
|
40
48
|
class: "cn-contacts-tab__sub"
|
|
41
49
|
};
|
|
42
|
-
const
|
|
43
|
-
const
|
|
50
|
+
const _hoisted_22 = { class: "cn-contacts-tab__sub-text" };
|
|
51
|
+
const _hoisted_23 = {
|
|
52
|
+
key: 0,
|
|
53
|
+
class: "cn-contacts-tab__period",
|
|
54
|
+
"data-testid": "cn-contacts-tab-board-period"
|
|
55
|
+
};
|
|
56
|
+
const _hoisted_24 = ["aria-label", "title", "onClick"];
|
|
44
57
|
|
|
45
58
|
function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
46
59
|
const _component_LinkVariant = resolveComponent("LinkVariant");
|
|
@@ -58,6 +71,8 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
58
71
|
const _component_OfficeBuildingOutline = resolveComponent("OfficeBuildingOutline");
|
|
59
72
|
const _component_CalendarRange = resolveComponent("CalendarRange");
|
|
60
73
|
const _component_Close = resolveComponent("Close");
|
|
74
|
+
const _component_NcActionButton = resolveComponent("NcActionButton");
|
|
75
|
+
const _component_NcActions = resolveComponent("NcActions");
|
|
61
76
|
const _component_CnContactPicker = resolveComponent("CnContactPicker");
|
|
62
77
|
const _component_CnContactCreate = resolveComponent("CnContactCreate");
|
|
63
78
|
|
|
@@ -126,7 +141,9 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
126
141
|
}, [
|
|
127
142
|
createElementVNode("h3", _hoisted_4, [
|
|
128
143
|
createTextVNode(toDisplayString(group.label) + " ", 1 /* TEXT */),
|
|
129
|
-
|
|
144
|
+
(!$options.isBoard)
|
|
145
|
+
? (openBlock(), createElementBlock("span", _hoisted_5, toDisplayString(group.items.length), 1 /* TEXT */))
|
|
146
|
+
: createCommentVNode("v-if", true)
|
|
130
147
|
]),
|
|
131
148
|
createElementVNode("ul", _hoisted_6, [
|
|
132
149
|
(openBlock(true), createElementBlock(Fragment, null, renderList(group.items, (contact) => {
|
|
@@ -137,7 +154,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
137
154
|
}, [
|
|
138
155
|
createElementVNode("div", _hoisted_8, [
|
|
139
156
|
createVNode(_component_NcAvatar, {
|
|
140
|
-
size: 40,
|
|
157
|
+
size: $options.isBoard ? 32 : 40,
|
|
141
158
|
displayName: contact.displayName || $props.unknownLabel,
|
|
142
159
|
user: $options.avatarSeed(contact),
|
|
143
160
|
url: contact.avatarUrl || undefined,
|
|
@@ -145,12 +162,12 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
145
162
|
disableMenu: true,
|
|
146
163
|
disableTooltip: true,
|
|
147
164
|
hideStatus: ""
|
|
148
|
-
}, null, 8 /* PROPS */, ["displayName", "user", "url"])
|
|
165
|
+
}, null, 8 /* PROPS */, ["size", "displayName", "user", "url"])
|
|
149
166
|
]),
|
|
150
167
|
createElementVNode("div", _hoisted_9, [
|
|
151
168
|
createElementVNode("div", _hoisted_10, [
|
|
152
169
|
createElementVNode("span", _hoisted_11, toDisplayString(contact.displayName || $props.unknownLabel), 1 /* TEXT */),
|
|
153
|
-
(contact.role)
|
|
170
|
+
(contact.role && !$options.isBoard)
|
|
154
171
|
? (openBlock(), createBlock(_component_CnStatusBadge, {
|
|
155
172
|
key: 0,
|
|
156
173
|
label: $options.roleLabel(contact),
|
|
@@ -168,57 +185,94 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
168
185
|
}, null, 8 /* PROPS */, ["label"]))
|
|
169
186
|
: createCommentVNode("v-if", true)
|
|
170
187
|
]),
|
|
171
|
-
(contact
|
|
172
|
-
? (openBlock(), createElementBlock("
|
|
173
|
-
createVNode(_component_Email, {
|
|
174
|
-
size: 14,
|
|
175
|
-
class: "cn-contacts-tab__sub-icon"
|
|
176
|
-
}),
|
|
177
|
-
createElementVNode("span", _hoisted_13, toDisplayString(contact.email), 1 /* TEXT */)
|
|
178
|
-
]))
|
|
179
|
-
: createCommentVNode("v-if", true),
|
|
180
|
-
(contact.phone)
|
|
181
|
-
? (openBlock(), createElementBlock("div", _hoisted_14, [
|
|
182
|
-
createVNode(_component_Phone, {
|
|
183
|
-
size: 14,
|
|
184
|
-
class: "cn-contacts-tab__sub-icon"
|
|
185
|
-
}),
|
|
186
|
-
createElementVNode("span", _hoisted_15, toDisplayString(contact.phone), 1 /* TEXT */)
|
|
187
|
-
]))
|
|
188
|
-
: createCommentVNode("v-if", true),
|
|
189
|
-
(contact.org)
|
|
190
|
-
? (openBlock(), createElementBlock("div", _hoisted_16, [
|
|
191
|
-
createVNode(_component_OfficeBuildingOutline, {
|
|
192
|
-
size: 14,
|
|
193
|
-
class: "cn-contacts-tab__sub-icon"
|
|
194
|
-
}),
|
|
195
|
-
createElementVNode("span", _hoisted_17, toDisplayString(contact.org), 1 /* TEXT */)
|
|
196
|
-
]))
|
|
197
|
-
: createCommentVNode("v-if", true),
|
|
198
|
-
($options.periodOf(contact))
|
|
199
|
-
? (openBlock(), createElementBlock("div", _hoisted_18, [
|
|
200
|
-
createVNode(_component_CalendarRange, {
|
|
201
|
-
size: 14,
|
|
202
|
-
class: "cn-contacts-tab__sub-icon"
|
|
203
|
-
}),
|
|
204
|
-
createElementVNode("span", _hoisted_19, toDisplayString($options.periodOf(contact)), 1 /* TEXT */)
|
|
205
|
-
]))
|
|
188
|
+
($options.isBoard && $options.boardMetaOf(contact))
|
|
189
|
+
? (openBlock(), createElementBlock("span", _hoisted_12, toDisplayString($options.boardMetaOf(contact)), 1 /* TEXT */))
|
|
206
190
|
: createCommentVNode("v-if", true),
|
|
207
|
-
(
|
|
208
|
-
? (openBlock(), createElementBlock(
|
|
209
|
-
|
|
210
|
-
|
|
191
|
+
(!$options.isBoard)
|
|
192
|
+
? (openBlock(), createElementBlock(Fragment, { key: 1 }, [
|
|
193
|
+
(contact.email)
|
|
194
|
+
? (openBlock(), createElementBlock("div", _hoisted_13, [
|
|
195
|
+
createVNode(_component_Email, {
|
|
196
|
+
size: 14,
|
|
197
|
+
class: "cn-contacts-tab__sub-icon"
|
|
198
|
+
}),
|
|
199
|
+
createElementVNode("span", _hoisted_14, toDisplayString(contact.email), 1 /* TEXT */)
|
|
200
|
+
]))
|
|
201
|
+
: createCommentVNode("v-if", true),
|
|
202
|
+
(contact.phone)
|
|
203
|
+
? (openBlock(), createElementBlock("div", _hoisted_15, [
|
|
204
|
+
createVNode(_component_Phone, {
|
|
205
|
+
size: 14,
|
|
206
|
+
class: "cn-contacts-tab__sub-icon"
|
|
207
|
+
}),
|
|
208
|
+
createElementVNode("span", _hoisted_16, toDisplayString(contact.phone), 1 /* TEXT */)
|
|
209
|
+
]))
|
|
210
|
+
: createCommentVNode("v-if", true),
|
|
211
|
+
(contact.org)
|
|
212
|
+
? (openBlock(), createElementBlock("div", _hoisted_17, [
|
|
213
|
+
createVNode(_component_OfficeBuildingOutline, {
|
|
214
|
+
size: 14,
|
|
215
|
+
class: "cn-contacts-tab__sub-icon"
|
|
216
|
+
}),
|
|
217
|
+
createElementVNode("span", _hoisted_18, toDisplayString(contact.org), 1 /* TEXT */)
|
|
218
|
+
]))
|
|
219
|
+
: createCommentVNode("v-if", true),
|
|
220
|
+
($options.periodOf(contact))
|
|
221
|
+
? (openBlock(), createElementBlock("div", _hoisted_19, [
|
|
222
|
+
createVNode(_component_CalendarRange, {
|
|
223
|
+
size: 14,
|
|
224
|
+
class: "cn-contacts-tab__sub-icon"
|
|
225
|
+
}),
|
|
226
|
+
createElementVNode("span", _hoisted_20, toDisplayString($options.periodOf(contact)), 1 /* TEXT */)
|
|
227
|
+
]))
|
|
228
|
+
: createCommentVNode("v-if", true),
|
|
229
|
+
(contact.note)
|
|
230
|
+
? (openBlock(), createElementBlock("div", _hoisted_21, [
|
|
231
|
+
createElementVNode("span", _hoisted_22, toDisplayString(contact.note), 1 /* TEXT */)
|
|
232
|
+
]))
|
|
233
|
+
: createCommentVNode("v-if", true)
|
|
234
|
+
], 64 /* STABLE_FRAGMENT */))
|
|
211
235
|
: createCommentVNode("v-if", true)
|
|
212
236
|
]),
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
237
|
+
($options.isBoard)
|
|
238
|
+
? (openBlock(), createElementBlock(Fragment, { key: 0 }, [
|
|
239
|
+
($options.boardPeriodOf(contact))
|
|
240
|
+
? (openBlock(), createElementBlock("span", _hoisted_23, toDisplayString($options.boardPeriodOf(contact)), 1 /* TEXT */))
|
|
241
|
+
: createCommentVNode("v-if", true),
|
|
242
|
+
createVNode(_component_NcActions, {
|
|
243
|
+
class: "cn-contacts-tab__menu",
|
|
244
|
+
variant: "secondary",
|
|
245
|
+
"aria-label": $options.moreActionsLabel(contact),
|
|
246
|
+
"data-testid": "cn-contacts-tab-menu",
|
|
247
|
+
onClick: _cache[0] || (_cache[0] = withModifiers(() => {}, ["stop"]))
|
|
248
|
+
}, {
|
|
249
|
+
default: withCtx(() => [
|
|
250
|
+
createVNode(_component_NcActionButton, {
|
|
251
|
+
closeAfterClick: true,
|
|
252
|
+
onClick: $event => ($options.unlink(contact))
|
|
253
|
+
}, {
|
|
254
|
+
icon: withCtx(() => [
|
|
255
|
+
createVNode(_component_Close, { size: 20 })
|
|
256
|
+
]),
|
|
257
|
+
default: withCtx(() => [
|
|
258
|
+
createTextVNode(" " + toDisplayString($props.unlinkLabel), 1 /* TEXT */)
|
|
259
|
+
]),
|
|
260
|
+
_: 1 /* STABLE */
|
|
261
|
+
}, 8 /* PROPS */, ["onClick"])
|
|
262
|
+
]),
|
|
263
|
+
_: 2 /* DYNAMIC */
|
|
264
|
+
}, 1032 /* PROPS, DYNAMIC_SLOTS */, ["aria-label"])
|
|
265
|
+
], 64 /* STABLE_FRAGMENT */))
|
|
266
|
+
: (openBlock(), createElementBlock("button", {
|
|
267
|
+
key: 1,
|
|
268
|
+
type: "button",
|
|
269
|
+
class: "cn-contacts-tab__unlink",
|
|
270
|
+
"aria-label": $props.unlinkLabel,
|
|
271
|
+
title: $props.unlinkLabel,
|
|
272
|
+
onClick: withModifiers($event => ($options.unlink(contact)), ["stop"])
|
|
273
|
+
}, [
|
|
274
|
+
createVNode(_component_Close, { size: 18 })
|
|
275
|
+
], 8 /* PROPS */, _hoisted_24))
|
|
222
276
|
], 8 /* PROPS */, _hoisted_7))
|
|
223
277
|
}), 128 /* KEYED_FRAGMENT */))
|
|
224
278
|
])
|
|
@@ -231,7 +285,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
231
285
|
apiBase: $props.apiBase
|
|
232
286
|
}, $options.pickerRoleOptions, {
|
|
233
287
|
onLink: $options.onPickerLink,
|
|
234
|
-
onClose: _cache[
|
|
288
|
+
onClose: _cache[1] || (_cache[1] = $event => ($data.showLinkDialog = false))
|
|
235
289
|
}), null, 16 /* FULL_PROPS */, ["apiBase", "onLink"]))
|
|
236
290
|
: createCommentVNode("v-if", true),
|
|
237
291
|
($data.showCreateDialog)
|
|
@@ -239,7 +293,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
239
293
|
key: 5,
|
|
240
294
|
loading: $data.createLoading,
|
|
241
295
|
onCreate: $options.onCreateSubmit,
|
|
242
|
-
onClose: _cache[
|
|
296
|
+
onClose: _cache[2] || (_cache[2] = $event => ($data.showCreateDialog = false))
|
|
243
297
|
}, null, 8 /* PROPS */, ["loading", "onCreate"]))
|
|
244
298
|
: createCommentVNode("v-if", true)
|
|
245
299
|
]))
|