@masterteam/delegations 0.0.35 → 0.0.36

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.
@@ -1,85 +1,35 @@
1
- import * as i2 from '@angular/common';
1
+ import * as i1 from '@angular/common';
2
2
  import { CommonModule, Location } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
- import { input, computed, Component, inject, Injectable, ChangeDetectionStrategy, viewChild, signal, effect, model, untracked, booleanAttribute, linkedSignal } from '@angular/core';
5
- import { RouterLink, ActivatedRoute, Router, NavigationEnd, RouterOutlet } from '@angular/router';
6
- import { TranslocoDirective, TranslocoService } from '@jsverse/transloco';
7
- import { Button } from '@masterteam/components/button';
8
- import { EntityPreview } from '@masterteam/components/entities';
4
+ import { inject, Injectable, computed, input, ChangeDetectionStrategy, Component, output, viewChild, signal, effect, model, untracked, booleanAttribute, linkedSignal } from '@angular/core';
5
+ import { TranslocoService, TranslocoDirective } from '@jsverse/transloco';
6
+ import { Avatar } from '@masterteam/components/avatar';
9
7
  import { ModalService } from '@masterteam/components/modal';
10
8
  import { Icon } from '@masterteam/icons';
11
9
  import { Popover } from 'primeng/popover';
12
- import { Chip } from '@masterteam/components/chip';
13
10
  import { Actions, Store, ofActionSuccessful, ofActionDispatched, Action, Selector, State, select } from '@ngxs/store';
14
11
  import { HttpClient, HttpHeaders, HttpParams, HttpContext, HttpErrorResponse } from '@angular/common/http';
15
12
  import { handleApiRequest, REQUEST_CONTEXT, UserSearchFieldConfig, TextareaFieldConfig, DateFieldConfig, RadioButtonFieldConfig, MultiSelectFieldConfig, ToggleFieldConfig, ValidatorConfig } from '@masterteam/components';
16
13
  import { EMPTY, switchMap, filter, finalize, catchError, throwError } from 'rxjs';
14
+ import { Button } from '@masterteam/components/button';
17
15
  import { ModalRef } from '@masterteam/components/dialog';
16
+ import { EntityPreview } from '@masterteam/components/entities';
18
17
  import { ToastService } from '@masterteam/components/toast';
18
+ import { RouterLink, ActivatedRoute, Router, NavigationEnd, RouterOutlet } from '@angular/router';
19
+ import { Chip } from '@masterteam/components/chip';
19
20
  import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
20
21
  import { Page } from '@masterteam/components/page';
21
22
  import { Breadcrumb } from '@masterteam/components/breadcrumb';
22
23
  import { Card } from '@masterteam/components/card';
23
24
  import { Tabs } from '@masterteam/components/tabs';
24
25
  import { Table } from '@masterteam/components/table';
25
- import * as i1$1 from '@angular/forms';
26
- import { FormControl, ReactiveFormsModule } from '@angular/forms';
26
+ import * as i2 from '@angular/forms';
27
+ import { FormsModule, FormControl, ReactiveFormsModule } from '@angular/forms';
27
28
  import { DynamicForm } from '@masterteam/forms/dynamic-form';
28
- import { Tree } from '@masterteam/components/tree';
29
- import * as i1 from 'primeng/skeleton';
29
+ import { TextField } from '@masterteam/components/text-field';
30
+ import * as i3 from 'primeng/skeleton';
30
31
  import { SkeletonModule } from 'primeng/skeleton';
31
32
 
32
- const STATUS_VISUAL = {
33
- Active: {
34
- i18nKey: 'delegations.status.active',
35
- styleClass: 'mt-status-chip mt-status-chip--active',
36
- },
37
- Scheduled: {
38
- i18nKey: 'delegations.status.scheduled',
39
- styleClass: 'mt-status-chip mt-status-chip--scheduled',
40
- },
41
- PendingApproval: {
42
- i18nKey: 'delegations.status.pendingApproval',
43
- styleClass: 'mt-status-chip mt-status-chip--pending',
44
- },
45
- InactiveToday: {
46
- i18nKey: 'delegations.status.inactiveToday',
47
- styleClass: 'mt-status-chip mt-status-chip--scheduled',
48
- },
49
- Expired: {
50
- i18nKey: 'delegations.status.expired',
51
- styleClass: 'mt-status-chip mt-status-chip--expired',
52
- },
53
- Rejected: {
54
- i18nKey: 'delegations.status.rejected',
55
- styleClass: 'mt-status-chip mt-status-chip--rejected',
56
- },
57
- Cancelled: {
58
- i18nKey: 'delegations.status.cancelled',
59
- styleClass: 'mt-status-chip mt-status-chip--cancelled',
60
- },
61
- };
62
- class DelegationStatusChip {
63
- status = input.required(...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
64
- visual = computed(() => STATUS_VISUAL[this.status()] ?? STATUS_VISUAL.Scheduled, ...(ngDevMode ? [{ debugName: "visual" }] : /* istanbul ignore next */ []));
65
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationStatusChip, deps: [], target: i0.ɵɵFactoryTarget.Component });
66
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.8", type: DelegationStatusChip, isStandalone: true, selector: "mt-delegation-status-chip", inputs: { status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
67
- <ng-container *transloco="let t">
68
- @let v = visual();
69
- <mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
70
- </ng-container>
71
- `, isInline: true, styles: [":host{display:inline-flex}:host ::ng-deep .mt-status-chip{font-weight:500;font-size:.75rem;border-radius:9999px;padding-inline:.625rem;padding-block:.125rem}:host ::ng-deep .mt-status-chip--active{background-color:#dcfce7;color:#166534}:host ::ng-deep .mt-status-chip--scheduled{background-color:#dbeafe;color:#1e40af}:host ::ng-deep .mt-status-chip--pending{background-color:#fef9c3;color:#854d0e}:host ::ng-deep .mt-status-chip--expired{background-color:#f3f4f6;color:#4b5563}:host ::ng-deep .mt-status-chip--rejected{background-color:#fee2e2;color:#991b1b}:host ::ng-deep .mt-status-chip--cancelled{background-color:#e5e7eb;color:#374151}:host-context(.dark) ::ng-deep .mt-status-chip--active{background-color:#22c55e33;color:#86efac}:host-context(.dark) ::ng-deep .mt-status-chip--scheduled{background-color:#3b82f633;color:#93c5fd}:host-context(.dark) ::ng-deep .mt-status-chip--pending{background-color:#eab30833;color:#fde047}:host-context(.dark) ::ng-deep .mt-status-chip--expired{background-color:#94a3b833;color:#cbd5e1}:host-context(.dark) ::ng-deep .mt-status-chip--rejected{background-color:#ef444433;color:#fca5a5}:host-context(.dark) ::ng-deep .mt-status-chip--cancelled{background-color:#94a3b826;color:#cbd5e1}\n"], dependencies: [{ kind: "component", type: Chip, selector: "mt-chip", inputs: ["label", "icon", "image", "removable", "removeIcon", "styleClass"], outputs: ["onRemove", "onImageError"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] });
72
- }
73
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationStatusChip, decorators: [{
74
- type: Component,
75
- args: [{ selector: 'mt-delegation-status-chip', standalone: true, imports: [Chip, TranslocoDirective], template: `
76
- <ng-container *transloco="let t">
77
- @let v = visual();
78
- <mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
79
- </ng-container>
80
- `, styles: [":host{display:inline-flex}:host ::ng-deep .mt-status-chip{font-weight:500;font-size:.75rem;border-radius:9999px;padding-inline:.625rem;padding-block:.125rem}:host ::ng-deep .mt-status-chip--active{background-color:#dcfce7;color:#166534}:host ::ng-deep .mt-status-chip--scheduled{background-color:#dbeafe;color:#1e40af}:host ::ng-deep .mt-status-chip--pending{background-color:#fef9c3;color:#854d0e}:host ::ng-deep .mt-status-chip--expired{background-color:#f3f4f6;color:#4b5563}:host ::ng-deep .mt-status-chip--rejected{background-color:#fee2e2;color:#991b1b}:host ::ng-deep .mt-status-chip--cancelled{background-color:#e5e7eb;color:#374151}:host-context(.dark) ::ng-deep .mt-status-chip--active{background-color:#22c55e33;color:#86efac}:host-context(.dark) ::ng-deep .mt-status-chip--scheduled{background-color:#3b82f633;color:#93c5fd}:host-context(.dark) ::ng-deep .mt-status-chip--pending{background-color:#eab30833;color:#fde047}:host-context(.dark) ::ng-deep .mt-status-chip--expired{background-color:#94a3b833;color:#cbd5e1}:host-context(.dark) ::ng-deep .mt-status-chip--rejected{background-color:#ef444433;color:#fca5a5}:host-context(.dark) ::ng-deep .mt-status-chip--cancelled{background-color:#94a3b826;color:#cbd5e1}\n"] }]
81
- }], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: true }] }] } });
82
-
83
33
  /** Fetch the active delegations the current user may start (user/activedelegations). */
84
34
  class LoadDelegationCandidates {
85
35
  static type = '[DelegationSession] Load Candidates';
@@ -419,10 +369,11 @@ function formatDelegationDisplayValue(value, preferredLanguage = 'en') {
419
369
  return resolveDisplayValue(value, preferredLanguage) ?? '';
420
370
  }
421
371
  function formatDelegationTargetLabel(target, preferredLanguage = 'en') {
422
- return resolveDisplayOrHumanizedKey(target, ['displayName', 'displayLabel', 'label'], target.targetKey, preferredLanguage);
372
+ return resolveDisplayOrHumanizedKey(target, ['name', 'displayName', 'displayLabel', 'label'], target.targetKey, preferredLanguage);
423
373
  }
424
374
  function formatDelegationActionLabel(action, preferredLanguage = 'en') {
425
375
  return resolveDisplayOrHumanizedKey(action, [
376
+ 'name',
426
377
  'displayName',
427
378
  'displayLabel',
428
379
  'operationDisplayName',
@@ -502,11 +453,11 @@ class StartSessionDialog {
502
453
  this.ref.close(false);
503
454
  }
504
455
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: StartSessionDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
505
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: StartSessionDialog, isStandalone: true, selector: "mt-start-session-dialog", inputs: { delegation: { classPropertyName: "delegation", publicName: "delegation", isSignal: true, isRequired: true, transformFunction: null }, intent: { classPropertyName: "intent", publicName: "intent", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col gap-4 p-2\">\r\n <div class=\"flex items-start gap-3\">\r\n <div class=\"flex flex-col min-w-0\">\r\n <mt-entity-preview\r\n [data]=\"userEntity(delegation().delegator)\"\r\n ></mt-entity-preview>\r\n @if (delegation().scopeSummary) {\r\n <div class=\"text-xs text-gray-500 mt-1\">\r\n {{ t(\"delegations.column.scopeSummary\") }}:\r\n {{ formatScopeSummary(delegation().scopeSummary) }}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <p class=\"text-sm text-gray-600 leading-relaxed\">\r\n {{ t(bodyKey()) }}\r\n </p>\r\n\r\n <div class=\"flex justify-end gap-2 pt-2 border-t border-gray-200\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.cancel')\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"cancel()\"\r\n ></mt-button>\r\n <mt-button\r\n color=\"primary\"\r\n icon=\"user.users-check\"\r\n [label]=\"t(confirmLabelKey())\"\r\n [loading]=\"isBusy()\"\r\n (click)=\"confirm()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
456
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: StartSessionDialog, isStandalone: true, selector: "mt-start-session-dialog", inputs: { delegation: { classPropertyName: "delegation", publicName: "delegation", isSignal: true, isRequired: true, transformFunction: null }, intent: { classPropertyName: "intent", publicName: "intent", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col gap-4 p-2\">\r\n <div class=\"flex items-start gap-3\">\r\n <div class=\"flex flex-col min-w-0\">\r\n <mt-entity-preview\r\n [data]=\"userEntity(delegation().delegator)\"\r\n ></mt-entity-preview>\r\n @if (delegation().scopeSummary) {\r\n <div class=\"text-xs text-surface-500 mt-1\">\r\n {{ t(\"delegations.column.scopeSummary\") }}:\r\n {{ formatScopeSummary(delegation().scopeSummary) }}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <p class=\"text-sm text-surface-600 leading-relaxed\">\r\n {{ t(bodyKey()) }}\r\n </p>\r\n\r\n <div class=\"flex justify-end gap-2 pt-2 border-t border-surface-200\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.cancel')\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"cancel()\"\r\n ></mt-button>\r\n <mt-button\r\n color=\"primary\"\r\n icon=\"user.users-check\"\r\n [label]=\"t(confirmLabelKey())\"\r\n [loading]=\"isBusy()\"\r\n (click)=\"confirm()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
506
457
  }
507
458
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: StartSessionDialog, decorators: [{
508
459
  type: Component,
509
- args: [{ selector: 'mt-start-session-dialog', imports: [CommonModule, Button, EntityPreview, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col gap-4 p-2\">\r\n <div class=\"flex items-start gap-3\">\r\n <div class=\"flex flex-col min-w-0\">\r\n <mt-entity-preview\r\n [data]=\"userEntity(delegation().delegator)\"\r\n ></mt-entity-preview>\r\n @if (delegation().scopeSummary) {\r\n <div class=\"text-xs text-gray-500 mt-1\">\r\n {{ t(\"delegations.column.scopeSummary\") }}:\r\n {{ formatScopeSummary(delegation().scopeSummary) }}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <p class=\"text-sm text-gray-600 leading-relaxed\">\r\n {{ t(bodyKey()) }}\r\n </p>\r\n\r\n <div class=\"flex justify-end gap-2 pt-2 border-t border-gray-200\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.cancel')\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"cancel()\"\r\n ></mt-button>\r\n <mt-button\r\n color=\"primary\"\r\n icon=\"user.users-check\"\r\n [label]=\"t(confirmLabelKey())\"\r\n [loading]=\"isBusy()\"\r\n (click)=\"confirm()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n" }]
460
+ args: [{ selector: 'mt-start-session-dialog', imports: [CommonModule, Button, EntityPreview, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col gap-4 p-2\">\r\n <div class=\"flex items-start gap-3\">\r\n <div class=\"flex flex-col min-w-0\">\r\n <mt-entity-preview\r\n [data]=\"userEntity(delegation().delegator)\"\r\n ></mt-entity-preview>\r\n @if (delegation().scopeSummary) {\r\n <div class=\"text-xs text-surface-500 mt-1\">\r\n {{ t(\"delegations.column.scopeSummary\") }}:\r\n {{ formatScopeSummary(delegation().scopeSummary) }}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <p class=\"text-sm text-surface-600 leading-relaxed\">\r\n {{ t(bodyKey()) }}\r\n </p>\r\n\r\n <div class=\"flex justify-end gap-2 pt-2 border-t border-surface-200\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.cancel')\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"cancel()\"\r\n ></mt-button>\r\n <mt-button\r\n color=\"primary\"\r\n icon=\"user.users-check\"\r\n [label]=\"t(confirmLabelKey())\"\r\n [loading]=\"isBusy()\"\r\n (click)=\"confirm()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n" }]
510
461
  }], propDecorators: { delegation: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegation", required: true }] }], intent: [{ type: i0.Input, args: [{ isSignal: true, alias: "intent", required: false }] }] } });
511
462
 
512
463
  /**
@@ -516,10 +467,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
516
467
  class DelegationCandidatesPromptDialog {
517
468
  candidates = input.required(...(ngDevMode ? [{ debugName: "candidates" }] : /* istanbul ignore next */ []));
518
469
  ref = inject(ModalRef);
519
- transloco = inject(TranslocoService);
520
- formatScopeSummary(summary) {
521
- return formatDelegationScopeSummary(summary, this.transloco.getActiveLang());
522
- }
523
470
  userEntity(party) {
524
471
  return toDelegationUserEntity(party, { showEmail: false });
525
472
  }
@@ -530,20 +477,162 @@ class DelegationCandidatesPromptDialog {
530
477
  this.ref.close(null);
531
478
  }
532
479
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationCandidatesPromptDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
533
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationCandidatesPromptDialog, isStandalone: true, selector: "mt-delegation-candidates-prompt-dialog", inputs: { candidates: { classPropertyName: "candidates", publicName: "candidates", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col gap-4 p-2\">\r\n <p class=\"text-sm leading-relaxed text-gray-600\">\r\n {{ t(\"delegations.session.promptBody\") }}\r\n </p>\r\n\r\n <div class=\"flex max-h-80 flex-col gap-2 overflow-y-auto pr-1\">\r\n @for (cand of candidates(); track $index) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-center justify-between gap-3 rounded-2xl border border-solid border-surface-200 bg-surface-0 px-3 py-3 text-start transition-colors hover:border-primary-300 hover:bg-primary-50/50 focus-visible:border-primary-400 focus-visible:outline-none\"\r\n [attr.aria-label]=\"\r\n t('delegations.session.promptStartAria', {\r\n delegatorName: cand.delegator.displayName,\r\n })\r\n \"\r\n (click)=\"select(cand)\"\r\n >\r\n <span class=\"flex min-w-0 items-center gap-3\">\r\n <span class=\"flex min-w-0 flex-col\">\r\n <mt-entity-preview\r\n [data]=\"userEntity(cand.delegator)\"\r\n ></mt-entity-preview>\r\n @if (cand.scopeSummary) {\r\n @let summary = formatScopeSummary(cand.scopeSummary);\r\n <span class=\"truncate text-xs text-gray-500\" [title]=\"summary\">\r\n {{ summary }}\r\n </span>\r\n }\r\n </span>\r\n </span>\r\n <span class=\"inline-flex items-center gap-2 text-sm text-primary\">\r\n {{ t(\"delegations.action.startSession\") }}\r\n <mt-icon icon=\"arrow.arrow-right\" class=\"text-base\"></mt-icon>\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n\r\n <div class=\"flex justify-end border-t border-gray-200 pt-3\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.close')\"\r\n (click)=\"close()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
480
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationCandidatesPromptDialog, isStandalone: true, selector: "mt-delegation-candidates-prompt-dialog", inputs: { candidates: { classPropertyName: "candidates", publicName: "candidates", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col gap-4 p-2\">\r\n <p class=\"text-sm leading-relaxed text-surface-600\">\r\n {{ t(\"delegations.session.promptBody\") }}\r\n </p>\r\n\r\n <div class=\"flex max-h-80 flex-col gap-2 overflow-y-auto pr-1\">\r\n @for (cand of candidates(); track $index) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-center justify-between gap-3 rounded-2xl border border-solid border-surface-200 bg-surface-0 px-3 py-3 text-start transition-colors hover:border-primary-300 hover:bg-primary-50/50 focus-visible:border-primary-400 focus-visible:outline-none\"\r\n [attr.aria-label]=\"\r\n t('delegations.session.promptStartAria', {\r\n delegatorName: cand.delegator.displayName,\r\n })\r\n \"\r\n (click)=\"select(cand)\"\r\n >\r\n <span class=\"flex min-w-0 items-center gap-3\">\r\n <mt-entity-preview\r\n [data]=\"userEntity(cand.delegator)\"\r\n ></mt-entity-preview>\r\n </span>\r\n <span\r\n class=\"inline-flex shrink-0 items-center gap-1.5 text-sm font-medium text-primary\"\r\n >\r\n <span class=\"hidden sm:inline\">\r\n {{ t(\"delegations.action.startSession\") }}\r\n </span>\r\n <mt-icon icon=\"arrow.arrow-right\" class=\"text-base\"></mt-icon>\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n\r\n <div class=\"flex justify-end border-t border-surface-200 pt-3\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.close')\"\r\n (click)=\"close()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
534
481
  }
535
482
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationCandidatesPromptDialog, decorators: [{
536
483
  type: Component,
537
- args: [{ selector: 'mt-delegation-candidates-prompt-dialog', imports: [CommonModule, Button, EntityPreview, Icon, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col gap-4 p-2\">\r\n <p class=\"text-sm leading-relaxed text-gray-600\">\r\n {{ t(\"delegations.session.promptBody\") }}\r\n </p>\r\n\r\n <div class=\"flex max-h-80 flex-col gap-2 overflow-y-auto pr-1\">\r\n @for (cand of candidates(); track $index) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-center justify-between gap-3 rounded-2xl border border-solid border-surface-200 bg-surface-0 px-3 py-3 text-start transition-colors hover:border-primary-300 hover:bg-primary-50/50 focus-visible:border-primary-400 focus-visible:outline-none\"\r\n [attr.aria-label]=\"\r\n t('delegations.session.promptStartAria', {\r\n delegatorName: cand.delegator.displayName,\r\n })\r\n \"\r\n (click)=\"select(cand)\"\r\n >\r\n <span class=\"flex min-w-0 items-center gap-3\">\r\n <span class=\"flex min-w-0 flex-col\">\r\n <mt-entity-preview\r\n [data]=\"userEntity(cand.delegator)\"\r\n ></mt-entity-preview>\r\n @if (cand.scopeSummary) {\r\n @let summary = formatScopeSummary(cand.scopeSummary);\r\n <span class=\"truncate text-xs text-gray-500\" [title]=\"summary\">\r\n {{ summary }}\r\n </span>\r\n }\r\n </span>\r\n </span>\r\n <span class=\"inline-flex items-center gap-2 text-sm text-primary\">\r\n {{ t(\"delegations.action.startSession\") }}\r\n <mt-icon icon=\"arrow.arrow-right\" class=\"text-base\"></mt-icon>\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n\r\n <div class=\"flex justify-end border-t border-gray-200 pt-3\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.close')\"\r\n (click)=\"close()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n" }]
484
+ args: [{ selector: 'mt-delegation-candidates-prompt-dialog', imports: [CommonModule, Button, EntityPreview, Icon, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col gap-4 p-2\">\r\n <p class=\"text-sm leading-relaxed text-surface-600\">\r\n {{ t(\"delegations.session.promptBody\") }}\r\n </p>\r\n\r\n <div class=\"flex max-h-80 flex-col gap-2 overflow-y-auto pr-1\">\r\n @for (cand of candidates(); track $index) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-center justify-between gap-3 rounded-2xl border border-solid border-surface-200 bg-surface-0 px-3 py-3 text-start transition-colors hover:border-primary-300 hover:bg-primary-50/50 focus-visible:border-primary-400 focus-visible:outline-none\"\r\n [attr.aria-label]=\"\r\n t('delegations.session.promptStartAria', {\r\n delegatorName: cand.delegator.displayName,\r\n })\r\n \"\r\n (click)=\"select(cand)\"\r\n >\r\n <span class=\"flex min-w-0 items-center gap-3\">\r\n <mt-entity-preview\r\n [data]=\"userEntity(cand.delegator)\"\r\n ></mt-entity-preview>\r\n </span>\r\n <span\r\n class=\"inline-flex shrink-0 items-center gap-1.5 text-sm font-medium text-primary\"\r\n >\r\n <span class=\"hidden sm:inline\">\r\n {{ t(\"delegations.action.startSession\") }}\r\n </span>\r\n <mt-icon icon=\"arrow.arrow-right\" class=\"text-base\"></mt-icon>\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n\r\n <div class=\"flex justify-end border-t border-surface-200 pt-3\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.close')\"\r\n (click)=\"close()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n" }]
538
485
  }], propDecorators: { candidates: [{ type: i0.Input, args: [{ isSignal: true, alias: "candidates", required: true }] }] } });
539
486
 
487
+ const STATUS_VISUAL = {
488
+ Active: {
489
+ i18nKey: 'delegations.status.active',
490
+ styleClass: 'mt-status-chip mt-status-chip--active',
491
+ },
492
+ Scheduled: {
493
+ i18nKey: 'delegations.status.scheduled',
494
+ styleClass: 'mt-status-chip mt-status-chip--scheduled',
495
+ },
496
+ PendingApproval: {
497
+ i18nKey: 'delegations.status.pendingApproval',
498
+ styleClass: 'mt-status-chip mt-status-chip--pending',
499
+ },
500
+ InactiveToday: {
501
+ i18nKey: 'delegations.status.inactiveToday',
502
+ styleClass: 'mt-status-chip mt-status-chip--scheduled',
503
+ },
504
+ Expired: {
505
+ i18nKey: 'delegations.status.expired',
506
+ styleClass: 'mt-status-chip mt-status-chip--expired',
507
+ },
508
+ Rejected: {
509
+ i18nKey: 'delegations.status.rejected',
510
+ styleClass: 'mt-status-chip mt-status-chip--rejected',
511
+ },
512
+ Cancelled: {
513
+ i18nKey: 'delegations.status.cancelled',
514
+ styleClass: 'mt-status-chip mt-status-chip--cancelled',
515
+ },
516
+ };
517
+ class DelegationStatusChip {
518
+ status = input.required(...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
519
+ visual = computed(() => STATUS_VISUAL[this.status()] ?? STATUS_VISUAL.Scheduled, ...(ngDevMode ? [{ debugName: "visual" }] : /* istanbul ignore next */ []));
520
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationStatusChip, deps: [], target: i0.ɵɵFactoryTarget.Component });
521
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.8", type: DelegationStatusChip, isStandalone: true, selector: "mt-delegation-status-chip", inputs: { status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
522
+ <ng-container *transloco="let t">
523
+ @let v = visual();
524
+ <mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
525
+ </ng-container>
526
+ `, isInline: true, styles: [":host{display:inline-flex}:host ::ng-deep .mt-status-chip{font-weight:500;font-size:.75rem;border-radius:9999px;padding-inline:.625rem;padding-block:.125rem}:host ::ng-deep .mt-status-chip--active{background-color:#dcfce7;color:#166534}:host ::ng-deep .mt-status-chip--scheduled{background-color:#dbeafe;color:#1e40af}:host ::ng-deep .mt-status-chip--pending{background-color:#fef9c3;color:#854d0e}:host ::ng-deep .mt-status-chip--expired{background-color:#f3f4f6;color:#4b5563}:host ::ng-deep .mt-status-chip--rejected{background-color:#fee2e2;color:#991b1b}:host ::ng-deep .mt-status-chip--cancelled{background-color:#e5e7eb;color:#374151}:host-context(.dark) ::ng-deep .mt-status-chip--active{background-color:#22c55e33;color:#86efac}:host-context(.dark) ::ng-deep .mt-status-chip--scheduled{background-color:#3b82f633;color:#93c5fd}:host-context(.dark) ::ng-deep .mt-status-chip--pending{background-color:#eab30833;color:#fde047}:host-context(.dark) ::ng-deep .mt-status-chip--expired{background-color:#94a3b833;color:#cbd5e1}:host-context(.dark) ::ng-deep .mt-status-chip--rejected{background-color:#ef444433;color:#fca5a5}:host-context(.dark) ::ng-deep .mt-status-chip--cancelled{background-color:#94a3b826;color:#cbd5e1}\n"], dependencies: [{ kind: "component", type: Chip, selector: "mt-chip", inputs: ["label", "icon", "image", "removable", "removeIcon", "styleClass"], outputs: ["onRemove", "onImageError"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] });
527
+ }
528
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationStatusChip, decorators: [{
529
+ type: Component,
530
+ args: [{ selector: 'mt-delegation-status-chip', standalone: true, imports: [Chip, TranslocoDirective], template: `
531
+ <ng-container *transloco="let t">
532
+ @let v = visual();
533
+ <mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
534
+ </ng-container>
535
+ `, styles: [":host{display:inline-flex}:host ::ng-deep .mt-status-chip{font-weight:500;font-size:.75rem;border-radius:9999px;padding-inline:.625rem;padding-block:.125rem}:host ::ng-deep .mt-status-chip--active{background-color:#dcfce7;color:#166534}:host ::ng-deep .mt-status-chip--scheduled{background-color:#dbeafe;color:#1e40af}:host ::ng-deep .mt-status-chip--pending{background-color:#fef9c3;color:#854d0e}:host ::ng-deep .mt-status-chip--expired{background-color:#f3f4f6;color:#4b5563}:host ::ng-deep .mt-status-chip--rejected{background-color:#fee2e2;color:#991b1b}:host ::ng-deep .mt-status-chip--cancelled{background-color:#e5e7eb;color:#374151}:host-context(.dark) ::ng-deep .mt-status-chip--active{background-color:#22c55e33;color:#86efac}:host-context(.dark) ::ng-deep .mt-status-chip--scheduled{background-color:#3b82f633;color:#93c5fd}:host-context(.dark) ::ng-deep .mt-status-chip--pending{background-color:#eab30833;color:#fde047}:host-context(.dark) ::ng-deep .mt-status-chip--expired{background-color:#94a3b833;color:#cbd5e1}:host-context(.dark) ::ng-deep .mt-status-chip--rejected{background-color:#ef444433;color:#fca5a5}:host-context(.dark) ::ng-deep .mt-status-chip--cancelled{background-color:#94a3b826;color:#cbd5e1}\n"] }]
536
+ }], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: true }] }] } });
537
+
540
538
  /**
541
- * Topbar surface for the delegation runtime (doc 05, 09).
539
+ * Embeddable delegation menu content (doc 05, 09): renders the candidate /
540
+ * active-session rows and the start / switch / end actions, with no trigger of
541
+ * its own. Designed to be dropped inside a host menu (e.g. the user-avatar
542
+ * dropdown) or the topbar popover. Reads the shared `DelegationSessionFacade`
543
+ * signals; candidate loading + the post-login prompt stay in the always-mounted
544
+ * `TopbarDelegationMenu` controller.
542
545
  *
543
- * State A no candidates, no active session: renders nothing.
544
- * State B — candidates exist, no active session: lets the user start a session.
545
- * State C — active delegated session: shows "Acting on behalf of {delegator}" +
546
- * "Executed by {actual user}", plus Back / Switch.
546
+ * Emits `closeRequested` after any action so the host overlay can dismiss.
547
+ */
548
+ class DelegationMenuPanel {
549
+ /** Path to the management page, e.g. `/control-panel/delegations` or `/delegations`. */
550
+ managePath = input('/delegations', ...(ngDevMode ? [{ debugName: "managePath" }] : /* istanbul ignore next */ []));
551
+ showManageLink = input(true, ...(ngDevMode ? [{ debugName: "showManageLink" }] : /* istanbul ignore next */ []));
552
+ closeRequested = output();
553
+ facade = inject(DelegationSessionFacade);
554
+ modal = inject(ModalService);
555
+ transloco = inject(TranslocoService);
556
+ active = this.facade.active;
557
+ candidates = this.facade.candidates;
558
+ hasCandidates = this.facade.hasCandidates;
559
+ onBehalfOf = this.facade.onBehalfOf;
560
+ executedBy = this.facade.executedBy;
561
+ mode = computed(() => {
562
+ if (this.active())
563
+ return 'active';
564
+ if (this.hasCandidates())
565
+ return 'candidates';
566
+ return 'hidden';
567
+ }, ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
568
+ /** Two-letter initials for a delegator avatar (matches the user-menu pattern). */
569
+ initials(party) {
570
+ const name = party?.displayName?.trim() || party?.email?.trim() || '';
571
+ const parts = name.split(/\s+/).filter(Boolean);
572
+ if (!parts.length)
573
+ return '?';
574
+ if (parts.length === 1) {
575
+ return parts[0].slice(0, 2).toUpperCase();
576
+ }
577
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
578
+ }
579
+ start(row, event) {
580
+ event.stopPropagation();
581
+ this.closeRequested.emit();
582
+ this.openConfirm(row, 'start');
583
+ }
584
+ switchTo(row, event) {
585
+ event.stopPropagation();
586
+ this.closeRequested.emit();
587
+ this.openConfirm(row, 'switch');
588
+ }
589
+ endSession(event) {
590
+ event.stopPropagation();
591
+ this.closeRequested.emit();
592
+ this.facade.endSession('Manual').subscribe();
593
+ }
594
+ onManage() {
595
+ this.closeRequested.emit();
596
+ }
597
+ openConfirm(row, intent) {
598
+ this.modal.openModal(StartSessionDialog, 'dialog', {
599
+ header: this.transloco.translate(intent === 'switch'
600
+ ? 'delegations.action.switchSession'
601
+ : 'delegations.action.startSession'),
602
+ styleClass: '!w-[min(96vw,28rem)] !max-w-[96vw]',
603
+ dismissableMask: true,
604
+ dismissible: true,
605
+ inputValues: { delegation: row, intent },
606
+ });
607
+ }
608
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationMenuPanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
609
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationMenuPanel, isStandalone: true, selector: "mt-delegation-menu-panel", inputs: { managePath: { classPropertyName: "managePath", publicName: "managePath", isSignal: true, isRequired: false, transformFunction: null }, showManageLink: { classPropertyName: "showManageLink", publicName: "showManageLink", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closeRequested: "closeRequested" }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n @if (mode() !== \"hidden\") {\n <div class=\"flex w-full flex-col\">\n @if (mode() === \"active\" && active(); as session) {\n <!-- Active session -->\n <div class=\"flex items-center gap-3 border-b border-surface px-4 py-3\">\n <mt-avatar\n [label]=\"initials(onBehalfOf())\"\n shape=\"circle\"\n styleClass=\"!size-9 !text-sm !bg-primary-100 !text-primary-700\"\n ></mt-avatar>\n <div class=\"flex min-w-0 flex-1 flex-col\">\n <span class=\"text-[0.625rem] font-medium uppercase text-surface-500\">\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\n </span>\n <span class=\"truncate text-sm font-semibold text-surface-900\">\n {{ onBehalfOf()?.displayName }}\n </span>\n <span class=\"truncate text-xs text-surface-500\">\n {{\n t(\"delegations.session.executedBy\", {\n actualUserName: executedBy()?.displayName,\n })\n }}\n </span>\n </div>\n <mt-delegation-status-chip status=\"Active\"></mt-delegation-status-chip>\n </div>\n\n <div class=\"flex flex-col p-1.5\">\n <button\n type=\"button\"\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-surface-700 hover:bg-surface-100\"\n (click)=\"endSession($event)\"\n >\n <mt-icon icon=\"arrow.arrow-left\" styleClass=\"text-base\"></mt-icon>\n <span>{{ t(\"delegations.action.endSession\") }}</span>\n </button>\n </div>\n\n @if (candidates().length > 1) {\n <div class=\"border-t border-surface p-1.5\">\n <div\n class=\"px-2 pb-1 text-[0.625rem] font-medium uppercase text-surface-400\"\n >\n {{ t(\"delegations.session.switchToAnother\") }}\n </div>\n @for (cand of candidates(); track $index) {\n @if (cand.delegationId !== session.delegation.delegationId) {\n <button\n type=\"button\"\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-lg px-2 py-2 text-start hover:bg-surface-100\"\n [attr.aria-label]=\"\n t('delegations.action.switchSession') +\n ': ' +\n cand.delegator.displayName\n \"\n (click)=\"switchTo(cand, $event)\"\n >\n <mt-avatar\n [label]=\"initials(cand.delegator)\"\n shape=\"circle\"\n styleClass=\"!size-8 !text-xs !bg-surface-100 !text-surface-600\"\n ></mt-avatar>\n <span\n class=\"min-w-0 flex-1 truncate text-sm font-medium text-surface-800\"\n >\n {{ cand.delegator.displayName }}\n </span>\n <mt-icon\n icon=\"arrow.switch-horizontal-01\"\n styleClass=\"text-base text-surface-400\"\n ></mt-icon>\n </button>\n }\n }\n </div>\n }\n } @else if (mode() === \"candidates\") {\n <!-- Candidates available -->\n <div class=\"border-b border-surface px-4 py-3\">\n <div class=\"text-sm font-semibold text-surface-900\">\n {{ t(\"delegations.session.youCanActAs\") }}\n </div>\n <div class=\"mt-0.5 text-xs text-surface-500\">\n {{ t(\"delegations.session.chooseDelegatorHint\") }}\n </div>\n </div>\n <div class=\"flex max-h-72 flex-col overflow-y-auto p-1.5\">\n @for (cand of candidates(); track $index) {\n <button\n type=\"button\"\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-lg px-2 py-2 text-start hover:bg-surface-100\"\n [attr.aria-label]=\"\n t('delegations.action.startSession') +\n ': ' +\n cand.delegator.displayName\n \"\n (click)=\"start(cand, $event)\"\n >\n <mt-avatar\n [label]=\"initials(cand.delegator)\"\n shape=\"circle\"\n styleClass=\"!size-8 !text-xs !bg-primary-100 !text-primary-700\"\n ></mt-avatar>\n <span class=\"flex min-w-0 flex-1 flex-col\">\n <span class=\"truncate text-sm font-medium text-surface-800\">\n {{ cand.delegator.displayName }}\n </span>\n @if (cand.delegator.email) {\n <span class=\"truncate text-xs text-surface-500\">\n {{ cand.delegator.email }}\n </span>\n }\n </span>\n <mt-icon\n icon=\"arrow.arrow-right\"\n styleClass=\"text-base text-surface-400\"\n ></mt-icon>\n </button>\n }\n </div>\n }\n\n @if (showManageLink()) {\n <div class=\"border-t border-surface p-1.5\">\n <a\n [routerLink]=\"managePath()\"\n (click)=\"onManage()\"\n class=\"flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-sm text-surface-700 hover:bg-surface-100\"\n >\n <mt-icon icon=\"general.settings-02\" styleClass=\"text-base\"></mt-icon>\n <span>{{ t(\"delegations.session.manage\") }}</span>\n </a>\n </div>\n }\n </div>\n }\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationStatusChip, selector: "mt-delegation-status-chip", inputs: ["status"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
610
+ }
611
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationMenuPanel, decorators: [{
612
+ type: Component,
613
+ args: [{ selector: 'mt-delegation-menu-panel', standalone: true, imports: [
614
+ CommonModule,
615
+ Avatar,
616
+ Icon,
617
+ RouterLink,
618
+ TranslocoDirective,
619
+ DelegationStatusChip,
620
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n @if (mode() !== \"hidden\") {\n <div class=\"flex w-full flex-col\">\n @if (mode() === \"active\" && active(); as session) {\n <!-- Active session -->\n <div class=\"flex items-center gap-3 border-b border-surface px-4 py-3\">\n <mt-avatar\n [label]=\"initials(onBehalfOf())\"\n shape=\"circle\"\n styleClass=\"!size-9 !text-sm !bg-primary-100 !text-primary-700\"\n ></mt-avatar>\n <div class=\"flex min-w-0 flex-1 flex-col\">\n <span class=\"text-[0.625rem] font-medium uppercase text-surface-500\">\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\n </span>\n <span class=\"truncate text-sm font-semibold text-surface-900\">\n {{ onBehalfOf()?.displayName }}\n </span>\n <span class=\"truncate text-xs text-surface-500\">\n {{\n t(\"delegations.session.executedBy\", {\n actualUserName: executedBy()?.displayName,\n })\n }}\n </span>\n </div>\n <mt-delegation-status-chip status=\"Active\"></mt-delegation-status-chip>\n </div>\n\n <div class=\"flex flex-col p-1.5\">\n <button\n type=\"button\"\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-surface-700 hover:bg-surface-100\"\n (click)=\"endSession($event)\"\n >\n <mt-icon icon=\"arrow.arrow-left\" styleClass=\"text-base\"></mt-icon>\n <span>{{ t(\"delegations.action.endSession\") }}</span>\n </button>\n </div>\n\n @if (candidates().length > 1) {\n <div class=\"border-t border-surface p-1.5\">\n <div\n class=\"px-2 pb-1 text-[0.625rem] font-medium uppercase text-surface-400\"\n >\n {{ t(\"delegations.session.switchToAnother\") }}\n </div>\n @for (cand of candidates(); track $index) {\n @if (cand.delegationId !== session.delegation.delegationId) {\n <button\n type=\"button\"\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-lg px-2 py-2 text-start hover:bg-surface-100\"\n [attr.aria-label]=\"\n t('delegations.action.switchSession') +\n ': ' +\n cand.delegator.displayName\n \"\n (click)=\"switchTo(cand, $event)\"\n >\n <mt-avatar\n [label]=\"initials(cand.delegator)\"\n shape=\"circle\"\n styleClass=\"!size-8 !text-xs !bg-surface-100 !text-surface-600\"\n ></mt-avatar>\n <span\n class=\"min-w-0 flex-1 truncate text-sm font-medium text-surface-800\"\n >\n {{ cand.delegator.displayName }}\n </span>\n <mt-icon\n icon=\"arrow.switch-horizontal-01\"\n styleClass=\"text-base text-surface-400\"\n ></mt-icon>\n </button>\n }\n }\n </div>\n }\n } @else if (mode() === \"candidates\") {\n <!-- Candidates available -->\n <div class=\"border-b border-surface px-4 py-3\">\n <div class=\"text-sm font-semibold text-surface-900\">\n {{ t(\"delegations.session.youCanActAs\") }}\n </div>\n <div class=\"mt-0.5 text-xs text-surface-500\">\n {{ t(\"delegations.session.chooseDelegatorHint\") }}\n </div>\n </div>\n <div class=\"flex max-h-72 flex-col overflow-y-auto p-1.5\">\n @for (cand of candidates(); track $index) {\n <button\n type=\"button\"\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-lg px-2 py-2 text-start hover:bg-surface-100\"\n [attr.aria-label]=\"\n t('delegations.action.startSession') +\n ': ' +\n cand.delegator.displayName\n \"\n (click)=\"start(cand, $event)\"\n >\n <mt-avatar\n [label]=\"initials(cand.delegator)\"\n shape=\"circle\"\n styleClass=\"!size-8 !text-xs !bg-primary-100 !text-primary-700\"\n ></mt-avatar>\n <span class=\"flex min-w-0 flex-1 flex-col\">\n <span class=\"truncate text-sm font-medium text-surface-800\">\n {{ cand.delegator.displayName }}\n </span>\n @if (cand.delegator.email) {\n <span class=\"truncate text-xs text-surface-500\">\n {{ cand.delegator.email }}\n </span>\n }\n </span>\n <mt-icon\n icon=\"arrow.arrow-right\"\n styleClass=\"text-base text-surface-400\"\n ></mt-icon>\n </button>\n }\n </div>\n }\n\n @if (showManageLink()) {\n <div class=\"border-t border-surface p-1.5\">\n <a\n [routerLink]=\"managePath()\"\n (click)=\"onManage()\"\n class=\"flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-sm text-surface-700 hover:bg-surface-100\"\n >\n <mt-icon icon=\"general.settings-02\" styleClass=\"text-base\"></mt-icon>\n <span>{{ t(\"delegations.session.manage\") }}</span>\n </a>\n </div>\n }\n </div>\n }\n</ng-container>\n" }]
621
+ }], propDecorators: { managePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "managePath", required: false }] }], showManageLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "showManageLink", required: false }] }], closeRequested: [{ type: i0.Output, args: ["closeRequested"] }] } });
622
+
623
+ /**
624
+ * Topbar surface + controller for the delegation runtime (doc 05, 09).
625
+ *
626
+ * Always-mounted controller: loads the delegation candidates and shows the
627
+ * post-login prompt. Visible trigger states:
628
+ * - hidden — no candidates, no active session;
629
+ * - candidates — icon button (matches sibling topbar icons) + count badge;
630
+ * - active — delegator avatar + "Acting on behalf of {delegator}".
631
+ *
632
+ * The popover content is the shared {@link DelegationMenuPanel}, which can also
633
+ * be embedded directly in a host menu (e.g. the user-avatar dropdown). Set
634
+ * `headless` to keep this controller mounted (loading + prompt) while rendering
635
+ * the panel elsewhere.
547
636
  */
548
637
  class TopbarDelegationMenu {
549
638
  /** Path to the management page, e.g. `/control-panel/delegations` or `/delegations`. */
@@ -551,10 +640,16 @@ class TopbarDelegationMenu {
551
640
  compact = input(false, ...(ngDevMode ? [{ debugName: "compact" }] : /* istanbul ignore next */ []));
552
641
  showManageLink = input(true, ...(ngDevMode ? [{ debugName: "showManageLink" }] : /* istanbul ignore next */ []));
553
642
  promptOnCandidates = input(true, ...(ngDevMode ? [{ debugName: "promptOnCandidates" }] : /* istanbul ignore next */ []));
643
+ /**
644
+ * Render no visible trigger/popover but keep loading candidates + the prompt.
645
+ * Use when the delegation menu is embedded elsewhere (e.g. the user dropdown
646
+ * via {@link DelegationMenuPanel}).
647
+ */
648
+ headless = input(false, ...(ngDevMode ? [{ debugName: "headless" }] : /* istanbul ignore next */ []));
554
649
  facade = inject(DelegationSessionFacade);
555
650
  modal = inject(ModalService);
556
651
  transloco = inject(TranslocoService);
557
- popover = viewChild.required('popover');
652
+ popover = viewChild('popover', ...(ngDevMode ? [{ debugName: "popover" }] : /* istanbul ignore next */ []));
558
653
  active = this.facade.active;
559
654
  candidates = this.facade.candidates;
560
655
  hasCandidates = this.facade.hasCandidates;
@@ -585,29 +680,22 @@ class TopbarDelegationMenu {
585
680
  ngOnInit() {
586
681
  this.facade.loadCandidates();
587
682
  }
588
- formatScopeSummary(summary) {
589
- return formatDelegationScopeSummary(summary, this.transloco.getActiveLang());
590
- }
591
- userEntity(party, showEmail = false) {
592
- return toDelegationUserEntity(party, { showEmail });
683
+ /** Two-letter initials for a delegator avatar (matches the user-menu pattern). */
684
+ initials(party) {
685
+ const name = party?.displayName?.trim() || party?.email?.trim() || '';
686
+ const parts = name.split(/\s+/).filter(Boolean);
687
+ if (!parts.length)
688
+ return '?';
689
+ if (parts.length === 1) {
690
+ return parts[0].slice(0, 2).toUpperCase();
691
+ }
692
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
593
693
  }
594
694
  togglePopover(event) {
595
- this.popover().toggle(event);
596
- }
597
- start(row, event) {
598
- event.stopPropagation();
599
- this.popover().hide();
600
- this.openConfirm(row, 'start');
695
+ this.popover()?.toggle(event);
601
696
  }
602
- switchTo(row, event) {
603
- event.stopPropagation();
604
- this.popover().hide();
605
- this.openConfirm(row, 'switch');
606
- }
607
- endSession(event) {
608
- event.stopPropagation();
609
- this.popover().hide();
610
- this.facade.endSession('Manual').subscribe();
697
+ closePopover() {
698
+ this.popover()?.hide();
611
699
  }
612
700
  openConfirm(row, intent) {
613
701
  this.modal.openModal(StartSessionDialog, 'dialog', {
@@ -635,21 +723,19 @@ class TopbarDelegationMenu {
635
723
  });
636
724
  }
637
725
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: TopbarDelegationMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
638
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: TopbarDelegationMenu, isStandalone: true, selector: "mt-topbar-delegation-menu", inputs: { managePath: { classPropertyName: "managePath", publicName: "managePath", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, showManageLink: { classPropertyName: "showManageLink", publicName: "showManageLink", isSignal: true, isRequired: false, transformFunction: null }, promptOnCandidates: { classPropertyName: "promptOnCandidates", publicName: "promptOnCandidates", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "popover", first: true, predicate: ["popover"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n @if (mode() !== \"hidden\") {\r\n @if (mode() === \"active\") {\r\n <span class=\"sr-only\" role=\"status\" aria-live=\"polite\">\r\n {{\r\n t(\"delegations.session.banner\", {\r\n delegatorName: onBehalfOf()?.displayName,\r\n })\r\n }}.\r\n {{\r\n t(\"delegations.session.executedBy\", {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n }}\r\n </span>\r\n }\r\n <div class=\"mt-delegation-trigger flex items-center gap-2\">\r\n <button\r\n type=\"button\"\r\n class=\"mt-delegation-trigger__button flex min-w-0 max-w-full items-center gap-2 rounded-full px-2 py-1 hover:bg-surface-100 cursor-pointer\"\r\n [class.mt-delegation-trigger__button--active]=\"mode() === 'active'\"\r\n [attr.aria-label]=\"\r\n mode() === 'active'\r\n ? t('delegations.session.banner', {\r\n delegatorName: onBehalfOf()?.displayName,\r\n }) +\r\n '. ' +\r\n t('delegations.session.executedBy', {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n : t('delegations.session.availableTitle')\r\n \"\r\n [attr.title]=\"\r\n mode() === 'active'\r\n ? t('delegations.session.banner', {\r\n delegatorName: onBehalfOf()?.displayName,\r\n })\r\n : null\r\n \"\r\n (click)=\"togglePopover($event)\"\r\n >\r\n @if (mode() === \"active\") {\r\n <mt-icon\r\n icon=\"user.users-check\"\r\n styleClass=\"text-lg text-primary\"\r\n ></mt-icon>\r\n @if (!compact()) {\r\n <span\r\n class=\"mt-delegation-trigger__label flex min-w-0 flex-col text-start leading-tight\"\r\n >\r\n <span class=\"truncate text-sm font-medium\">\r\n {{\r\n t(\"delegations.session.banner\", {\r\n delegatorName: onBehalfOf()?.displayName,\r\n })\r\n }}\r\n </span>\r\n <span class=\"truncate text-[0.6875rem] font-normal text-gray-500\">\r\n {{\r\n t(\"delegations.session.executedBy\", {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n }}\r\n </span>\r\n </span>\r\n }\r\n <mt-delegation-status-chip\r\n status=\"Active\"\r\n ></mt-delegation-status-chip>\r\n } @else {\r\n <mt-icon\r\n icon=\"user.users-plus\"\r\n styleClass=\"text-lg text-primary\"\r\n ></mt-icon>\r\n @if (!compact()) {\r\n <span class=\"mt-delegation-trigger__label text-sm font-medium\">\r\n {{ t(\"delegations.session.availableTitle\") }}\r\n </span>\r\n }\r\n }\r\n </button>\r\n </div>\r\n\r\n <p-popover #popover [pt]=\"{ content: { class: 'p-0!' } }\">\r\n <div class=\"flex flex-col min-w-80 max-w-96 p-0\">\r\n @if (mode() === \"active\" && active(); as session) {\r\n <!-- STATE C \u2014 active session -->\r\n <div\r\n class=\"flex items-start gap-3 border-b border-gray-200 px-4 py-4\"\r\n >\r\n <div class=\"flex min-w-0 flex-1 flex-col\">\r\n <div class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\r\n </div>\r\n <mt-entity-preview\r\n [data]=\"userEntity(onBehalfOf())\"\r\n ></mt-entity-preview>\r\n <div class=\"text-xs text-gray-500 mt-1\">\r\n {{\r\n t(\"delegations.session.executedBy\", {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n }}\r\n </div>\r\n @if (session.delegation.scopeSummary) {\r\n @let summary =\r\n formatScopeSummary(session.delegation.scopeSummary);\r\n <div\r\n class=\"text-xs text-gray-500 mt-1 truncate\"\r\n [title]=\"summary\"\r\n >\r\n {{ t(\"delegations.column.scopeSummary\") }}:\r\n {{ summary }}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"flex flex-col gap-1 px-2 py-2\">\r\n <mt-button\r\n variant=\"text\"\r\n icon=\"arrow.arrow-left\"\r\n [label]=\"t('delegations.action.endSession')\"\r\n styleClass=\"w-full justify-start\"\r\n (click)=\"endSession($event)\"\r\n ></mt-button>\r\n </div>\r\n\r\n @if (candidates().length > 1) {\r\n <div class=\"border-t border-gray-200 pt-2 px-2 pb-2\">\r\n <div class=\"text-xs text-gray-500 px-2 pb-1\">\r\n {{ t(\"delegations.session.switchToAnother\") }}\r\n </div>\r\n @for (cand of candidates(); track $index) {\r\n @if (cand.delegationId !== session.delegation.delegationId) {\r\n <button\r\n type=\"button\"\r\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer\"\r\n [attr.aria-label]=\"\r\n t('delegations.action.switchSession') +\r\n ': ' +\r\n cand.delegator.displayName\r\n \"\r\n (click)=\"switchTo(cand, $event)\"\r\n >\r\n <div class=\"min-w-0 flex-1\">\r\n <mt-entity-preview\r\n [data]=\"userEntity(cand.delegator)\"\r\n ></mt-entity-preview>\r\n </div>\r\n <mt-icon\r\n icon=\"arrow.switch-horizontal-01\"\r\n styleClass=\"text-base text-gray-400\"\r\n ></mt-icon>\r\n </button>\r\n }\r\n }\r\n </div>\r\n }\r\n } @else if (mode() === \"candidates\") {\r\n <!-- STATE B \u2014 candidates available -->\r\n <div class=\"border-b border-gray-200 px-4 py-3\">\r\n <div class=\"text-sm font-medium text-gray-900\">\r\n {{ t(\"delegations.session.youCanActAs\") }}\r\n </div>\r\n <div class=\"text-xs text-gray-500 mt-1\">\r\n {{ t(\"delegations.session.chooseDelegatorHint\") }}\r\n </div>\r\n </div>\r\n <div class=\"flex flex-col gap-1 px-2 py-2 max-h-72 overflow-y-auto\">\r\n @for (cand of candidates(); track $index) {\r\n <button\r\n type=\"button\"\r\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer text-start\"\r\n [attr.aria-label]=\"\r\n t('delegations.action.startSession') +\r\n ': ' +\r\n cand.delegator.displayName\r\n \"\r\n (click)=\"start(cand, $event)\"\r\n >\r\n <div class=\"flex min-w-0 flex-1 flex-col gap-1\">\r\n <mt-entity-preview\r\n [data]=\"userEntity(cand.delegator)\"\r\n ></mt-entity-preview>\r\n @if (cand.scopeSummary) {\r\n @let summary = formatScopeSummary(cand.scopeSummary);\r\n <span\r\n class=\"text-xs text-gray-500 truncate\"\r\n [title]=\"summary\"\r\n >\r\n {{ summary }}\r\n </span>\r\n }\r\n </div>\r\n <mt-icon\r\n icon=\"arrow.arrow-right\"\r\n styleClass=\"text-base text-gray-400\"\r\n ></mt-icon>\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Footer -->\r\n @if (showManageLink()) {\r\n <div class=\"border-t border-gray-200 px-2 py-2\">\r\n <a\r\n [routerLink]=\"managePath()\"\r\n (click)=\"popover().hide()\"\r\n class=\"flex items-center gap-2 px-3 py-2 rounded-md text-gray-700 hover:bg-surface-100 text-sm cursor-pointer\"\r\n >\r\n <mt-icon\r\n icon=\"general.settings-02\"\r\n styleClass=\"text-base\"\r\n ></mt-icon>\r\n <span>{{ t(\"delegations.session.manage\") }}</span>\r\n </a>\r\n </div>\r\n }\r\n </div>\r\n </p-popover>\r\n }\r\n</ng-container>\r\n", styles: [":host{display:inline-flex;align-items:center;min-width:0;max-width:100%}.mt-delegation-trigger{min-width:0;max-width:100%}.mt-delegation-trigger__button{transition:background-color .2s ease-out;min-width:0;max-width:100%;border:1px solid transparent}.mt-delegation-trigger__button--active{border-color:color-mix(in srgb,var(--p-primary-color, #2aaec0) 34%,transparent);background-color:color-mix(in srgb,var(--p-primary-color, #2aaec0) 10%,transparent);box-shadow:0 8px 18px #0f172a14}.mt-delegation-trigger__label{max-width:min(16rem,28vw);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media(max-width:1200px){.mt-delegation-trigger__label{max-width:10rem}}@media(max-width:1024px){.mt-delegation-trigger__button{gap:.375rem;padding-inline:.375rem}.mt-delegation-trigger__label{display:none}}:host-context(.dark) .mt-delegation-trigger__button:hover{background-color:#94a3b826}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationStatusChip, selector: "mt-delegation-status-chip", inputs: ["status"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
726
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: TopbarDelegationMenu, isStandalone: true, selector: "mt-topbar-delegation-menu", inputs: { managePath: { classPropertyName: "managePath", publicName: "managePath", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null }, showManageLink: { classPropertyName: "showManageLink", publicName: "showManageLink", isSignal: true, isRequired: false, transformFunction: null }, promptOnCandidates: { classPropertyName: "promptOnCandidates", publicName: "promptOnCandidates", isSignal: true, isRequired: false, transformFunction: null }, headless: { classPropertyName: "headless", publicName: "headless", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "popover", first: true, predicate: ["popover"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t\">\n @if (!headless() && mode() !== \"hidden\") {\n @if (mode() === \"active\") {\n <span class=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {{\n t(\"delegations.session.banner\", {\n delegatorName: onBehalfOf()?.displayName,\n })\n }}.\n {{\n t(\"delegations.session.executedBy\", {\n actualUserName: executedBy()?.displayName,\n })\n }}\n </span>\n }\n\n <div class=\"mt-delegation-trigger flex items-center\">\n @if (mode() === \"active\") {\n <!-- Active: show the delegator like a user identity, with a switcher caret -->\n <button\n type=\"button\"\n class=\"mt-delegation-trigger__button mt-delegation-trigger__button--active flex min-w-0 max-w-full cursor-pointer items-center gap-2 rounded-full p-1 pe-2 text-current\"\n [attr.aria-label]=\"\n t('delegations.session.banner', {\n delegatorName: onBehalfOf()?.displayName,\n }) +\n '. ' +\n t('delegations.session.executedBy', {\n actualUserName: executedBy()?.displayName,\n })\n \"\n (click)=\"togglePopover($event)\"\n >\n <span class=\"relative shrink-0\">\n <mt-avatar\n [label]=\"initials(onBehalfOf())\"\n shape=\"circle\"\n styleClass=\"!size-7 !text-[0.7rem] !bg-primary-100 !text-primary-700\"\n ></mt-avatar>\n <span\n class=\"absolute -bottom-0.5 -end-0.5 flex size-3.5 items-center justify-center rounded-full bg-emerald-500 ring-2 ring-white\"\n aria-hidden=\"true\"\n >\n <mt-icon\n icon=\"user.users-check\"\n styleClass=\"text-[0.55rem] text-white\"\n ></mt-icon>\n </span>\n </span>\n @if (!compact()) {\n <span\n class=\"mt-delegation-trigger__label hidden min-w-0 flex-col text-start leading-tight md:flex\"\n >\n <span class=\"text-[0.625rem] font-medium uppercase opacity-60\">\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\n </span>\n <span class=\"truncate text-sm font-semibold\">\n {{ onBehalfOf()?.displayName }}\n </span>\n </span>\n }\n <mt-icon\n icon=\"arrow.chevron-down\"\n styleClass=\"text-sm opacity-70\"\n ></mt-icon>\n </button>\n } @else {\n <!-- Candidates: plain icon button matching the other topbar icons -->\n <button\n type=\"button\"\n class=\"mt-delegation-trigger__icon relative flex size-9 cursor-pointer items-center justify-center rounded-full text-current\"\n [attr.aria-label]=\"t('delegations.session.availableTitle')\"\n (click)=\"togglePopover($event)\"\n >\n <mt-icon icon=\"user.users-plus\" styleClass=\"text-xl\"></mt-icon>\n @if (candidates().length > 0) {\n <span\n class=\"absolute -top-0.5 -end-0.5 inline-flex min-w-[1.05rem] items-center justify-center rounded-full bg-primary px-1 text-[0.625rem] font-bold leading-4 text-white ring-2 ring-white\"\n >\n {{ candidates().length }}\n </span>\n }\n </button>\n }\n </div>\n\n <p-popover #popover [pt]=\"{ content: { class: 'p-0!' } }\">\n <div class=\"w-72 max-w-[88vw]\">\n <mt-delegation-menu-panel\n [managePath]=\"managePath()\"\n [showManageLink]=\"showManageLink()\"\n (closeRequested)=\"closePopover()\"\n ></mt-delegation-menu-panel>\n </div>\n </p-popover>\n }\n</ng-container>\n", styles: [":host{display:inline-flex;align-items:center;min-width:0;max-width:100%}.mt-delegation-trigger{min-width:0;max-width:100%}.mt-delegation-trigger__button,.mt-delegation-trigger__icon{transition:background-color .15s ease-out;border:1px solid transparent}.mt-delegation-trigger__button:hover,.mt-delegation-trigger__icon:hover{background-color:var(--p-surface-100, rgba(0, 0, 0, .05))}.mt-delegation-trigger__button--active{background-color:var(--p-surface-100, rgba(0, 0, 0, .05));border-color:var(--p-surface-200, rgba(0, 0, 0, .08))}.mt-delegation-trigger__button--active:hover{background-color:var(--p-surface-200, rgba(0, 0, 0, .08))}.mt-delegation-trigger__label{max-width:min(12rem,20vw);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media(max-width:1280px){.mt-delegation-trigger__label{max-width:8rem}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationMenuPanel, selector: "mt-delegation-menu-panel", inputs: ["managePath", "showManageLink"], outputs: ["closeRequested"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
639
727
  }
640
728
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: TopbarDelegationMenu, decorators: [{
641
729
  type: Component,
642
730
  args: [{ selector: 'mt-topbar-delegation-menu', standalone: true, imports: [
643
731
  CommonModule,
644
- Button,
645
- EntityPreview,
732
+ Avatar,
646
733
  Icon,
647
734
  Popover,
648
- RouterLink,
649
735
  TranslocoDirective,
650
- DelegationStatusChip,
651
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n @if (mode() !== \"hidden\") {\r\n @if (mode() === \"active\") {\r\n <span class=\"sr-only\" role=\"status\" aria-live=\"polite\">\r\n {{\r\n t(\"delegations.session.banner\", {\r\n delegatorName: onBehalfOf()?.displayName,\r\n })\r\n }}.\r\n {{\r\n t(\"delegations.session.executedBy\", {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n }}\r\n </span>\r\n }\r\n <div class=\"mt-delegation-trigger flex items-center gap-2\">\r\n <button\r\n type=\"button\"\r\n class=\"mt-delegation-trigger__button flex min-w-0 max-w-full items-center gap-2 rounded-full px-2 py-1 hover:bg-surface-100 cursor-pointer\"\r\n [class.mt-delegation-trigger__button--active]=\"mode() === 'active'\"\r\n [attr.aria-label]=\"\r\n mode() === 'active'\r\n ? t('delegations.session.banner', {\r\n delegatorName: onBehalfOf()?.displayName,\r\n }) +\r\n '. ' +\r\n t('delegations.session.executedBy', {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n : t('delegations.session.availableTitle')\r\n \"\r\n [attr.title]=\"\r\n mode() === 'active'\r\n ? t('delegations.session.banner', {\r\n delegatorName: onBehalfOf()?.displayName,\r\n })\r\n : null\r\n \"\r\n (click)=\"togglePopover($event)\"\r\n >\r\n @if (mode() === \"active\") {\r\n <mt-icon\r\n icon=\"user.users-check\"\r\n styleClass=\"text-lg text-primary\"\r\n ></mt-icon>\r\n @if (!compact()) {\r\n <span\r\n class=\"mt-delegation-trigger__label flex min-w-0 flex-col text-start leading-tight\"\r\n >\r\n <span class=\"truncate text-sm font-medium\">\r\n {{\r\n t(\"delegations.session.banner\", {\r\n delegatorName: onBehalfOf()?.displayName,\r\n })\r\n }}\r\n </span>\r\n <span class=\"truncate text-[0.6875rem] font-normal text-gray-500\">\r\n {{\r\n t(\"delegations.session.executedBy\", {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n }}\r\n </span>\r\n </span>\r\n }\r\n <mt-delegation-status-chip\r\n status=\"Active\"\r\n ></mt-delegation-status-chip>\r\n } @else {\r\n <mt-icon\r\n icon=\"user.users-plus\"\r\n styleClass=\"text-lg text-primary\"\r\n ></mt-icon>\r\n @if (!compact()) {\r\n <span class=\"mt-delegation-trigger__label text-sm font-medium\">\r\n {{ t(\"delegations.session.availableTitle\") }}\r\n </span>\r\n }\r\n }\r\n </button>\r\n </div>\r\n\r\n <p-popover #popover [pt]=\"{ content: { class: 'p-0!' } }\">\r\n <div class=\"flex flex-col min-w-80 max-w-96 p-0\">\r\n @if (mode() === \"active\" && active(); as session) {\r\n <!-- STATE C \u2014 active session -->\r\n <div\r\n class=\"flex items-start gap-3 border-b border-gray-200 px-4 py-4\"\r\n >\r\n <div class=\"flex min-w-0 flex-1 flex-col\">\r\n <div class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\r\n </div>\r\n <mt-entity-preview\r\n [data]=\"userEntity(onBehalfOf())\"\r\n ></mt-entity-preview>\r\n <div class=\"text-xs text-gray-500 mt-1\">\r\n {{\r\n t(\"delegations.session.executedBy\", {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n }}\r\n </div>\r\n @if (session.delegation.scopeSummary) {\r\n @let summary =\r\n formatScopeSummary(session.delegation.scopeSummary);\r\n <div\r\n class=\"text-xs text-gray-500 mt-1 truncate\"\r\n [title]=\"summary\"\r\n >\r\n {{ t(\"delegations.column.scopeSummary\") }}:\r\n {{ summary }}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"flex flex-col gap-1 px-2 py-2\">\r\n <mt-button\r\n variant=\"text\"\r\n icon=\"arrow.arrow-left\"\r\n [label]=\"t('delegations.action.endSession')\"\r\n styleClass=\"w-full justify-start\"\r\n (click)=\"endSession($event)\"\r\n ></mt-button>\r\n </div>\r\n\r\n @if (candidates().length > 1) {\r\n <div class=\"border-t border-gray-200 pt-2 px-2 pb-2\">\r\n <div class=\"text-xs text-gray-500 px-2 pb-1\">\r\n {{ t(\"delegations.session.switchToAnother\") }}\r\n </div>\r\n @for (cand of candidates(); track $index) {\r\n @if (cand.delegationId !== session.delegation.delegationId) {\r\n <button\r\n type=\"button\"\r\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer\"\r\n [attr.aria-label]=\"\r\n t('delegations.action.switchSession') +\r\n ': ' +\r\n cand.delegator.displayName\r\n \"\r\n (click)=\"switchTo(cand, $event)\"\r\n >\r\n <div class=\"min-w-0 flex-1\">\r\n <mt-entity-preview\r\n [data]=\"userEntity(cand.delegator)\"\r\n ></mt-entity-preview>\r\n </div>\r\n <mt-icon\r\n icon=\"arrow.switch-horizontal-01\"\r\n styleClass=\"text-base text-gray-400\"\r\n ></mt-icon>\r\n </button>\r\n }\r\n }\r\n </div>\r\n }\r\n } @else if (mode() === \"candidates\") {\r\n <!-- STATE B \u2014 candidates available -->\r\n <div class=\"border-b border-gray-200 px-4 py-3\">\r\n <div class=\"text-sm font-medium text-gray-900\">\r\n {{ t(\"delegations.session.youCanActAs\") }}\r\n </div>\r\n <div class=\"text-xs text-gray-500 mt-1\">\r\n {{ t(\"delegations.session.chooseDelegatorHint\") }}\r\n </div>\r\n </div>\r\n <div class=\"flex flex-col gap-1 px-2 py-2 max-h-72 overflow-y-auto\">\r\n @for (cand of candidates(); track $index) {\r\n <button\r\n type=\"button\"\r\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer text-start\"\r\n [attr.aria-label]=\"\r\n t('delegations.action.startSession') +\r\n ': ' +\r\n cand.delegator.displayName\r\n \"\r\n (click)=\"start(cand, $event)\"\r\n >\r\n <div class=\"flex min-w-0 flex-1 flex-col gap-1\">\r\n <mt-entity-preview\r\n [data]=\"userEntity(cand.delegator)\"\r\n ></mt-entity-preview>\r\n @if (cand.scopeSummary) {\r\n @let summary = formatScopeSummary(cand.scopeSummary);\r\n <span\r\n class=\"text-xs text-gray-500 truncate\"\r\n [title]=\"summary\"\r\n >\r\n {{ summary }}\r\n </span>\r\n }\r\n </div>\r\n <mt-icon\r\n icon=\"arrow.arrow-right\"\r\n styleClass=\"text-base text-gray-400\"\r\n ></mt-icon>\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Footer -->\r\n @if (showManageLink()) {\r\n <div class=\"border-t border-gray-200 px-2 py-2\">\r\n <a\r\n [routerLink]=\"managePath()\"\r\n (click)=\"popover().hide()\"\r\n class=\"flex items-center gap-2 px-3 py-2 rounded-md text-gray-700 hover:bg-surface-100 text-sm cursor-pointer\"\r\n >\r\n <mt-icon\r\n icon=\"general.settings-02\"\r\n styleClass=\"text-base\"\r\n ></mt-icon>\r\n <span>{{ t(\"delegations.session.manage\") }}</span>\r\n </a>\r\n </div>\r\n }\r\n </div>\r\n </p-popover>\r\n }\r\n</ng-container>\r\n", styles: [":host{display:inline-flex;align-items:center;min-width:0;max-width:100%}.mt-delegation-trigger{min-width:0;max-width:100%}.mt-delegation-trigger__button{transition:background-color .2s ease-out;min-width:0;max-width:100%;border:1px solid transparent}.mt-delegation-trigger__button--active{border-color:color-mix(in srgb,var(--p-primary-color, #2aaec0) 34%,transparent);background-color:color-mix(in srgb,var(--p-primary-color, #2aaec0) 10%,transparent);box-shadow:0 8px 18px #0f172a14}.mt-delegation-trigger__label{max-width:min(16rem,28vw);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media(max-width:1200px){.mt-delegation-trigger__label{max-width:10rem}}@media(max-width:1024px){.mt-delegation-trigger__button{gap:.375rem;padding-inline:.375rem}.mt-delegation-trigger__label{display:none}}:host-context(.dark) .mt-delegation-trigger__button:hover{background-color:#94a3b826}\n"] }]
652
- }], ctorParameters: () => [], propDecorators: { managePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "managePath", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], showManageLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "showManageLink", required: false }] }], promptOnCandidates: [{ type: i0.Input, args: [{ isSignal: true, alias: "promptOnCandidates", required: false }] }], popover: [{ type: i0.ViewChild, args: ['popover', { isSignal: true }] }] } });
736
+ DelegationMenuPanel,
737
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n @if (!headless() && mode() !== \"hidden\") {\n @if (mode() === \"active\") {\n <span class=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {{\n t(\"delegations.session.banner\", {\n delegatorName: onBehalfOf()?.displayName,\n })\n }}.\n {{\n t(\"delegations.session.executedBy\", {\n actualUserName: executedBy()?.displayName,\n })\n }}\n </span>\n }\n\n <div class=\"mt-delegation-trigger flex items-center\">\n @if (mode() === \"active\") {\n <!-- Active: show the delegator like a user identity, with a switcher caret -->\n <button\n type=\"button\"\n class=\"mt-delegation-trigger__button mt-delegation-trigger__button--active flex min-w-0 max-w-full cursor-pointer items-center gap-2 rounded-full p-1 pe-2 text-current\"\n [attr.aria-label]=\"\n t('delegations.session.banner', {\n delegatorName: onBehalfOf()?.displayName,\n }) +\n '. ' +\n t('delegations.session.executedBy', {\n actualUserName: executedBy()?.displayName,\n })\n \"\n (click)=\"togglePopover($event)\"\n >\n <span class=\"relative shrink-0\">\n <mt-avatar\n [label]=\"initials(onBehalfOf())\"\n shape=\"circle\"\n styleClass=\"!size-7 !text-[0.7rem] !bg-primary-100 !text-primary-700\"\n ></mt-avatar>\n <span\n class=\"absolute -bottom-0.5 -end-0.5 flex size-3.5 items-center justify-center rounded-full bg-emerald-500 ring-2 ring-white\"\n aria-hidden=\"true\"\n >\n <mt-icon\n icon=\"user.users-check\"\n styleClass=\"text-[0.55rem] text-white\"\n ></mt-icon>\n </span>\n </span>\n @if (!compact()) {\n <span\n class=\"mt-delegation-trigger__label hidden min-w-0 flex-col text-start leading-tight md:flex\"\n >\n <span class=\"text-[0.625rem] font-medium uppercase opacity-60\">\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\n </span>\n <span class=\"truncate text-sm font-semibold\">\n {{ onBehalfOf()?.displayName }}\n </span>\n </span>\n }\n <mt-icon\n icon=\"arrow.chevron-down\"\n styleClass=\"text-sm opacity-70\"\n ></mt-icon>\n </button>\n } @else {\n <!-- Candidates: plain icon button matching the other topbar icons -->\n <button\n type=\"button\"\n class=\"mt-delegation-trigger__icon relative flex size-9 cursor-pointer items-center justify-center rounded-full text-current\"\n [attr.aria-label]=\"t('delegations.session.availableTitle')\"\n (click)=\"togglePopover($event)\"\n >\n <mt-icon icon=\"user.users-plus\" styleClass=\"text-xl\"></mt-icon>\n @if (candidates().length > 0) {\n <span\n class=\"absolute -top-0.5 -end-0.5 inline-flex min-w-[1.05rem] items-center justify-center rounded-full bg-primary px-1 text-[0.625rem] font-bold leading-4 text-white ring-2 ring-white\"\n >\n {{ candidates().length }}\n </span>\n }\n </button>\n }\n </div>\n\n <p-popover #popover [pt]=\"{ content: { class: 'p-0!' } }\">\n <div class=\"w-72 max-w-[88vw]\">\n <mt-delegation-menu-panel\n [managePath]=\"managePath()\"\n [showManageLink]=\"showManageLink()\"\n (closeRequested)=\"closePopover()\"\n ></mt-delegation-menu-panel>\n </div>\n </p-popover>\n }\n</ng-container>\n", styles: [":host{display:inline-flex;align-items:center;min-width:0;max-width:100%}.mt-delegation-trigger{min-width:0;max-width:100%}.mt-delegation-trigger__button,.mt-delegation-trigger__icon{transition:background-color .15s ease-out;border:1px solid transparent}.mt-delegation-trigger__button:hover,.mt-delegation-trigger__icon:hover{background-color:var(--p-surface-100, rgba(0, 0, 0, .05))}.mt-delegation-trigger__button--active{background-color:var(--p-surface-100, rgba(0, 0, 0, .05));border-color:var(--p-surface-200, rgba(0, 0, 0, .08))}.mt-delegation-trigger__button--active:hover{background-color:var(--p-surface-200, rgba(0, 0, 0, .08))}.mt-delegation-trigger__label{max-width:min(12rem,20vw);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media(max-width:1280px){.mt-delegation-trigger__label{max-width:8rem}}\n"] }]
738
+ }], ctorParameters: () => [], propDecorators: { managePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "managePath", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], showManageLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "showManageLink", required: false }] }], promptOnCandidates: [{ type: i0.Input, args: [{ isSignal: true, alias: "promptOnCandidates", required: false }] }], headless: [{ type: i0.Input, args: [{ isSignal: true, alias: "headless", required: false }] }], popover: [{ type: i0.ViewChild, args: ['popover', { isSignal: true }] }] } });
653
739
 
654
740
  // ---------------------------------------------------------------------------
655
741
  // Lists / detail
@@ -1340,11 +1426,11 @@ class RejectDelegationDialog {
1340
1426
  this.ref.close(false);
1341
1427
  }
1342
1428
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: RejectDelegationDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
1343
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: RejectDelegationDialog, isStandalone: true, selector: "mt-reject-delegation-dialog", inputs: { delegation: { classPropertyName: "delegation", publicName: "delegation", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col gap-4 p-2\">\r\n <div class=\"flex flex-col gap-1\">\r\n <div class=\"text-sm text-gray-700\">\r\n {{ t(\"delegations.confirm.reject\") }}\r\n </div>\r\n <mt-entity-preview\r\n [data]=\"userEntity(delegation().delegator)\"\r\n ></mt-entity-preview>\r\n </div>\r\n\r\n <div class=\"flex flex-col gap-1\">\r\n <label class=\"text-sm font-medium text-gray-800\" for=\"mt-reject-reason\">\r\n {{ t(\"delegations.form.rejectionReason\") }}\r\n <span class=\"text-red-600\">*</span>\r\n </label>\r\n <textarea\r\n id=\"mt-reject-reason\"\r\n rows=\"3\"\r\n class=\"w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:outline-none\"\r\n [class.border-red-400]=\"submitted() && !isValid()\"\r\n [value]=\"reason()\"\r\n (input)=\"onReasonInput($event)\"\r\n [attr.aria-invalid]=\"submitted() && !isValid()\"\r\n [placeholder]=\"t('delegations.form.rejectionReason')\"\r\n ></textarea>\r\n @if (submitted() && !isValid()) {\r\n <span class=\"text-xs text-red-600\">\r\n {{ t(\"delegations.form.rejectReasonRequired\") }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <div class=\"flex justify-end gap-2 pt-2 border-t border-gray-200\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.cancel')\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"cancel()\"\r\n ></mt-button>\r\n <mt-button\r\n color=\"danger\"\r\n icon=\"general.x-close\"\r\n [label]=\"t('delegations.action.reject')\"\r\n [loading]=\"isBusy()\"\r\n [disabled]=\"!isValid()\"\r\n (click)=\"confirm()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1429
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: RejectDelegationDialog, isStandalone: true, selector: "mt-reject-delegation-dialog", inputs: { delegation: { classPropertyName: "delegation", publicName: "delegation", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col gap-4 p-2\">\r\n <div class=\"flex flex-col gap-1\">\r\n <div class=\"text-sm text-surface-700\">\r\n {{ t(\"delegations.confirm.reject\") }}\r\n </div>\r\n <mt-entity-preview\r\n [data]=\"userEntity(delegation().delegator)\"\r\n ></mt-entity-preview>\r\n </div>\r\n\r\n <div class=\"flex flex-col gap-1\">\r\n <label class=\"text-sm font-medium text-surface-800\" for=\"mt-reject-reason\">\r\n {{ t(\"delegations.form.rejectionReason\") }}\r\n <span class=\"text-red-600\">*</span>\r\n </label>\r\n <textarea\r\n id=\"mt-reject-reason\"\r\n rows=\"3\"\r\n class=\"w-full rounded-md border border-surface-300 px-3 py-2 text-sm focus:border-primary focus:outline-none\"\r\n [class.border-red-400]=\"submitted() && !isValid()\"\r\n [value]=\"reason()\"\r\n (input)=\"onReasonInput($event)\"\r\n [attr.aria-invalid]=\"submitted() && !isValid()\"\r\n [placeholder]=\"t('delegations.form.rejectionReason')\"\r\n ></textarea>\r\n @if (submitted() && !isValid()) {\r\n <span class=\"text-xs text-red-600\">\r\n {{ t(\"delegations.form.rejectReasonRequired\") }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <div class=\"flex justify-end gap-2 pt-2 border-t border-surface-200\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.cancel')\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"cancel()\"\r\n ></mt-button>\r\n <mt-button\r\n color=\"danger\"\r\n icon=\"general.x-close\"\r\n [label]=\"t('delegations.action.reject')\"\r\n [loading]=\"isBusy()\"\r\n [disabled]=\"!isValid()\"\r\n (click)=\"confirm()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1344
1430
  }
1345
1431
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: RejectDelegationDialog, decorators: [{
1346
1432
  type: Component,
1347
- args: [{ selector: 'mt-reject-delegation-dialog', imports: [CommonModule, Button, EntityPreview, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col gap-4 p-2\">\r\n <div class=\"flex flex-col gap-1\">\r\n <div class=\"text-sm text-gray-700\">\r\n {{ t(\"delegations.confirm.reject\") }}\r\n </div>\r\n <mt-entity-preview\r\n [data]=\"userEntity(delegation().delegator)\"\r\n ></mt-entity-preview>\r\n </div>\r\n\r\n <div class=\"flex flex-col gap-1\">\r\n <label class=\"text-sm font-medium text-gray-800\" for=\"mt-reject-reason\">\r\n {{ t(\"delegations.form.rejectionReason\") }}\r\n <span class=\"text-red-600\">*</span>\r\n </label>\r\n <textarea\r\n id=\"mt-reject-reason\"\r\n rows=\"3\"\r\n class=\"w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-primary focus:outline-none\"\r\n [class.border-red-400]=\"submitted() && !isValid()\"\r\n [value]=\"reason()\"\r\n (input)=\"onReasonInput($event)\"\r\n [attr.aria-invalid]=\"submitted() && !isValid()\"\r\n [placeholder]=\"t('delegations.form.rejectionReason')\"\r\n ></textarea>\r\n @if (submitted() && !isValid()) {\r\n <span class=\"text-xs text-red-600\">\r\n {{ t(\"delegations.form.rejectReasonRequired\") }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <div class=\"flex justify-end gap-2 pt-2 border-t border-gray-200\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.cancel')\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"cancel()\"\r\n ></mt-button>\r\n <mt-button\r\n color=\"danger\"\r\n icon=\"general.x-close\"\r\n [label]=\"t('delegations.action.reject')\"\r\n [loading]=\"isBusy()\"\r\n [disabled]=\"!isValid()\"\r\n (click)=\"confirm()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n" }]
1433
+ args: [{ selector: 'mt-reject-delegation-dialog', imports: [CommonModule, Button, EntityPreview, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col gap-4 p-2\">\r\n <div class=\"flex flex-col gap-1\">\r\n <div class=\"text-sm text-surface-700\">\r\n {{ t(\"delegations.confirm.reject\") }}\r\n </div>\r\n <mt-entity-preview\r\n [data]=\"userEntity(delegation().delegator)\"\r\n ></mt-entity-preview>\r\n </div>\r\n\r\n <div class=\"flex flex-col gap-1\">\r\n <label class=\"text-sm font-medium text-surface-800\" for=\"mt-reject-reason\">\r\n {{ t(\"delegations.form.rejectionReason\") }}\r\n <span class=\"text-red-600\">*</span>\r\n </label>\r\n <textarea\r\n id=\"mt-reject-reason\"\r\n rows=\"3\"\r\n class=\"w-full rounded-md border border-surface-300 px-3 py-2 text-sm focus:border-primary focus:outline-none\"\r\n [class.border-red-400]=\"submitted() && !isValid()\"\r\n [value]=\"reason()\"\r\n (input)=\"onReasonInput($event)\"\r\n [attr.aria-invalid]=\"submitted() && !isValid()\"\r\n [placeholder]=\"t('delegations.form.rejectionReason')\"\r\n ></textarea>\r\n @if (submitted() && !isValid()) {\r\n <span class=\"text-xs text-red-600\">\r\n {{ t(\"delegations.form.rejectReasonRequired\") }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <div class=\"flex justify-end gap-2 pt-2 border-t border-surface-200\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.cancel')\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"cancel()\"\r\n ></mt-button>\r\n <mt-button\r\n color=\"danger\"\r\n icon=\"general.x-close\"\r\n [label]=\"t('delegations.action.reject')\"\r\n [loading]=\"isBusy()\"\r\n [disabled]=\"!isValid()\"\r\n (click)=\"confirm()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n" }]
1348
1434
  }], propDecorators: { delegation: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegation", required: true }] }] } });
1349
1435
 
1350
1436
  class Delegations {
@@ -1404,7 +1490,7 @@ const DEFAULT_SORT_ORDER = Number.MAX_SAFE_INTEGER;
1404
1490
  function toArray(value) {
1405
1491
  return Array.isArray(value) ? value : [];
1406
1492
  }
1407
- function normalizeGrant$1(grant) {
1493
+ function normalizeGrant(grant) {
1408
1494
  return {
1409
1495
  ...grant,
1410
1496
  action: normalizeAction(grant.action),
@@ -1449,7 +1535,7 @@ function groupIdentity(groupType, groupKey, fallbackTargetKey) {
1449
1535
  return `${groupType}::${groupKey || fallbackTargetKey || 'group'}`;
1450
1536
  }
1451
1537
  function buildHierarchyGrant(target, action) {
1452
- return normalizeGrant$1({
1538
+ return normalizeGrant({
1453
1539
  applicationKey: normalizeKey(target.applicationKey) ??
1454
1540
  normalizeKey(action.applicationKey) ??
1455
1541
  '',
@@ -1587,7 +1673,7 @@ function getLegacyGrantableGrants(options) {
1587
1673
  if (!options) {
1588
1674
  return [];
1589
1675
  }
1590
- const legacyGrants = toArray(options.grants).map(normalizeGrant$1);
1676
+ const legacyGrants = toArray(options.grants).map(normalizeGrant);
1591
1677
  if (legacyGrants.length > 0) {
1592
1678
  return legacyGrants;
1593
1679
  }
@@ -1616,7 +1702,7 @@ function getLegacyGrantableGrants(options) {
1616
1702
  if (!action) {
1617
1703
  continue;
1618
1704
  }
1619
- const grant = normalizeGrant$1({
1705
+ const grant = normalizeGrant({
1620
1706
  applicationKey: applicationKey ??
1621
1707
  normalizeKey(action.applicationKey) ??
1622
1708
  normalizeKey(target.applicationKey) ??
@@ -1691,72 +1777,182 @@ function getDelegationGrantableGrants(options) {
1691
1777
  return getDelegationGrantableGroups(options).flatMap((group) => group.targets.flatMap((targetGroup) => targetGroup.grants));
1692
1778
  }
1693
1779
  function hasDelegationGrantableScope(options) {
1694
- return getDelegationGrantableGroups(options).length > 0;
1780
+ return (getDelegationPermissionTree(options).length > 0 ||
1781
+ getDelegationGrantableGroups(options).length > 0);
1695
1782
  }
1696
-
1697
- const EMPTY_SCOPE$1 = { grants: [], metadata: {} };
1698
- /**
1699
- * Full grant identity. Two grants with the same target/action but different
1700
- * accessibilities, data filters, or constraints are DIFFERENT grants and must
1701
- * not collapse (backend scope contract). Prefer a backend-provided stable
1702
- * `key`; otherwise hash the complete grant shape.
1703
- */
1704
- function grantKey(g) {
1705
- if (g.key)
1706
- return g.key;
1707
- return JSON.stringify({
1708
- app: g.applicationKey,
1709
- target: [
1710
- g.target.applicationKey,
1711
- g.target.targetType,
1712
- g.target.targetKey,
1713
- g.target.permissionModuleType ?? null,
1714
- g.target.permissionTargetId ?? null,
1715
- g.target.legacyModuleId ?? null,
1716
- g.target.levelId ?? null,
1717
- g.target.levelModuleId ?? null,
1718
- g.target.domainModuleId ?? null,
1719
- g.target.moduleKey ?? null,
1720
- ],
1721
- action: [
1722
- g.action.applicationKey,
1723
- g.action.operationKey,
1724
- g.action.operationKind,
1725
- g.action.permissionCommand ?? null,
1726
- g.action.businessActionCode ?? null,
1727
- ],
1728
- accessibilities: g.accessibilities ?? [],
1729
- dataFilters: g.dataFilters ?? [],
1730
- constraints: g.constraints ?? [],
1731
- });
1783
+ /** Stable selection key for an operation leaf — independent of accessibilities. */
1784
+ function delegationOperationNodeKey(grant) {
1785
+ return `op:${targetIdentity(grant.target)}::${grant.action.operationKey}`;
1732
1786
  }
1733
- function normalizeGrant(g) {
1787
+ function buildScopeTarget(node) {
1788
+ const moduleNode = node;
1734
1789
  return {
1735
- ...g,
1736
- accessibilities: g.accessibilities ?? [],
1737
- dataFilters: g.dataFilters ?? [],
1738
- constraints: g.constraints ?? [],
1790
+ applicationKey: node.applicationKey,
1791
+ targetType: node.targetType,
1792
+ targetKey: node.targetKey,
1793
+ name: node.name ?? null,
1794
+ displayName: node.name ?? null,
1795
+ legacyModuleId: node.legacyModuleId ?? null,
1796
+ targetId: node.targetId ?? null,
1797
+ levelId: node.levelId ?? null,
1798
+ levelModuleId: moduleNode.levelModuleId ?? null,
1799
+ domainModuleId: moduleNode.domainModuleId ?? null,
1800
+ moduleKey: moduleNode.moduleKey ?? null,
1801
+ permissionModuleType: node.permissionModuleType ?? null,
1802
+ permissionTargetId: node.permissionTargetId ?? null,
1739
1803
  };
1740
1804
  }
1741
- function sameSelectionKeys(left, right) {
1742
- const leftKeys = (left ?? [])
1743
- .map((node) => node.key)
1744
- .filter((key) => !!key);
1745
- if (leftKeys.length !== right.length) {
1746
- return false;
1805
+ function buildOperationGrant(target, operation) {
1806
+ const action = {
1807
+ applicationKey: operation.applicationKey ?? target.applicationKey,
1808
+ operationKey: operation.operationKey,
1809
+ operationKind: operation.operationKind,
1810
+ name: operation.name ?? null,
1811
+ displayName: operation.name ?? operation.displayName ?? null,
1812
+ permissionCommand: operation.permissionCommand ?? null,
1813
+ businessActionCode: operation.businessActionCode ?? null,
1814
+ isHighRisk: operation.isHighRisk ?? false,
1815
+ isDelegableSnapshot: operation.isDelegableSnapshot ?? true,
1816
+ sortOrder: operation.sortOrder ?? null,
1817
+ };
1818
+ return {
1819
+ applicationKey: target.applicationKey,
1820
+ target,
1821
+ action,
1822
+ accessibilities: [],
1823
+ dataFilters: [],
1824
+ constraints: [],
1825
+ };
1826
+ }
1827
+ function buildOperationNodes(target, operations) {
1828
+ return toArray(operations)
1829
+ .map((operation) => {
1830
+ const grant = buildOperationGrant(target, operation);
1831
+ return {
1832
+ key: delegationOperationNodeKey(grant),
1833
+ kind: 'operation',
1834
+ displayName: operation.name ?? operation.displayName ?? null,
1835
+ fallbackKey: operation.operationKey,
1836
+ isHighRisk: operation.isHighRisk ?? false,
1837
+ grant,
1838
+ sortOrder: normalizeSortOrder(operation.sortOrder),
1839
+ };
1840
+ })
1841
+ .sort((left, right) => compareBySortThenKey(left.sortOrder, right.sortOrder, left.key, right.key))
1842
+ .map(({ sortOrder: _drop, ...node }) => node);
1843
+ }
1844
+ function buildModuleNode(module) {
1845
+ const target = buildScopeTarget(module);
1846
+ return {
1847
+ key: `mod:${targetIdentity(target)}`,
1848
+ kind: 'module',
1849
+ displayName: module.name ?? null,
1850
+ fallbackKey: module.targetKey,
1851
+ children: buildOperationNodes(target, module.operations),
1852
+ sortOrder: normalizeSortOrder(module.sortOrder ?? module.levelModuleId),
1853
+ };
1854
+ }
1855
+ function buildLevelNode(level) {
1856
+ const target = buildScopeTarget(level);
1857
+ const operationNodes = buildOperationNodes(target, level.operations);
1858
+ const moduleNodes = toArray(level.modules)
1859
+ .map(buildModuleNode)
1860
+ .filter((node) => (node.children?.length ?? 0) > 0)
1861
+ .sort((left, right) => compareBySortThenKey(left.sortOrder, right.sortOrder, left.key, right.key))
1862
+ .map(({ sortOrder: _drop, ...node }) => node);
1863
+ return {
1864
+ key: `lvl:${targetIdentity(target)}`,
1865
+ kind: 'level',
1866
+ displayName: level.name ?? null,
1867
+ fallbackKey: level.targetKey,
1868
+ children: [...operationNodes, ...moduleNodes],
1869
+ sortOrder: normalizeSortOrder(level.sortOrder ?? level.levelId),
1870
+ };
1871
+ }
1872
+ function buildTemplateNode(group) {
1873
+ const levelNodes = toArray(group.levels)
1874
+ .map(buildLevelNode)
1875
+ .filter((node) => (node.children?.length ?? 0) > 0)
1876
+ .sort((left, right) => compareBySortThenKey(left.sortOrder, right.sortOrder, left.key, right.key))
1877
+ .map(({ sortOrder: _drop, ...node }) => node);
1878
+ return {
1879
+ key: `tpl:${group.groupKey}`,
1880
+ kind: 'template',
1881
+ displayName: group.name ?? null,
1882
+ fallbackKey: group.groupKey,
1883
+ children: levelNodes,
1884
+ sortOrder: normalizeSortOrder(group.sortOrder ?? group.templateId),
1885
+ };
1886
+ }
1887
+ function getDelegationPermissionTree(options) {
1888
+ return toArray(options?.permissions)
1889
+ .map(buildTemplateNode)
1890
+ .filter((node) => (node.children?.length ?? 0) > 0)
1891
+ .sort((left, right) => compareBySortThenKey(left.sortOrder, right.sortOrder, left.key, right.key))
1892
+ .map(({ sortOrder: _drop, ...node }) => node);
1893
+ }
1894
+ /** Back-compat: project the legacy group/target/grant shape onto the tree model. */
1895
+ function legacyGroupsToTree(groups) {
1896
+ return groups.map((group) => ({
1897
+ key: `grp:${group.key}`,
1898
+ kind: 'template',
1899
+ displayName: group.displayName ?? null,
1900
+ fallbackKey: group.groupType,
1901
+ children: group.targets.map((targetGroup) => ({
1902
+ key: `tgt:${targetGroup.key}`,
1903
+ kind: 'module',
1904
+ displayName: targetGroup.target.name ?? targetGroup.target.displayName ?? null,
1905
+ fallbackKey: targetGroup.target.targetKey,
1906
+ children: targetGroup.grants.map((grant) => ({
1907
+ key: delegationOperationNodeKey(grant),
1908
+ kind: 'operation',
1909
+ displayName: grant.action.name ?? grant.action.displayName ?? null,
1910
+ fallbackKey: grant.action.operationKey,
1911
+ isHighRisk: grant.action.isHighRisk ?? false,
1912
+ grant,
1913
+ })),
1914
+ })),
1915
+ }));
1916
+ }
1917
+ /**
1918
+ * Unified scope tree for the picker. Prefers the new `permissions` contract;
1919
+ * falls back to the legacy hierarchy/grants projection for older environments.
1920
+ */
1921
+ function getDelegationScopeTree(options) {
1922
+ const fromPermissions = getDelegationPermissionTree(options);
1923
+ if (fromPermissions.length > 0) {
1924
+ return fromPermissions;
1747
1925
  }
1748
- const rightSet = new Set(right);
1749
- return leftKeys.every((key) => rightSet.has(key));
1926
+ return legacyGroupsToTree(getDelegationGrantableGroups(options));
1927
+ }
1928
+ /** All operation-leaf grants in the tree, keyed by their stable node key. */
1929
+ function indexDelegationTreeGrants(nodes) {
1930
+ const index = new Map();
1931
+ const walk = (list) => {
1932
+ for (const node of list) {
1933
+ if (node.kind === 'operation' && node.grant) {
1934
+ index.set(node.key, node.grant);
1935
+ }
1936
+ if (node.children?.length) {
1937
+ walk(node.children);
1938
+ }
1939
+ }
1940
+ };
1941
+ walk(nodes);
1942
+ return index;
1750
1943
  }
1944
+
1945
+ const EMPTY_SCOPE$1 = { grants: [], metadata: {} };
1751
1946
  /**
1752
1947
  * Hierarchy-based scope picker (doc 03). Driven entirely by `scope/options`:
1753
- * - renders backend-owned Level -> Level Module -> operations hierarchy,
1754
- * - lets the user toggle each explicit grant,
1948
+ * - renders the backend-owned Template -> Level -> Module -> operation tree as a
1949
+ * clean, accessible checkbox tree (parents propagate to descendant operations),
1950
+ * - offers optional app-accessibility grants,
1755
1951
  * - calls `scope/preview` (debounced) and surfaces summary / warnings / denied.
1756
1952
  *
1757
- * `[(scope)]` two-way binds a `DelegationScopeSelection`. The component never
1758
- * invents permission metadata it only echoes targets/actions returned by
1759
- * options.
1953
+ * `[(scope)]` two-way binds a `DelegationScopeSelection`. Scope grants are the
1954
+ * single source of truth for selection; the component never invents permission
1955
+ * metadata — it only echoes targets/actions/accessibilities returned by options.
1760
1956
  */
1761
1957
  class ScopePicker {
1762
1958
  scope = model(EMPTY_SCOPE$1, ...(ngDevMode ? [{ debugName: "scope" }] : /* istanbul ignore next */ []));
@@ -1775,25 +1971,62 @@ class ScopePicker {
1775
1971
  isLoadingOptions = this.facade.isLoadingScopeOptions;
1776
1972
  isPreviewing = this.facade.isPreviewingScope;
1777
1973
  errorOptions = this.facade.errorScopeOptions;
1778
- groups = computed(() => getDelegationGrantableGroups(this.options()), ...(ngDevMode ? [{ debugName: "groups" }] : /* istanbul ignore next */ []));
1779
1974
  activeLang = toSignal(this.transloco.langChanges$, {
1780
1975
  initialValue: this.transloco.getActiveLang(),
1781
1976
  });
1782
- /** Selected grant keys for O(1) checkbox state. */
1783
- selectedKeys = computed(() => new Set(this.scope().grants.map(grantKey)), ...(ngDevMode ? [{ debugName: "selectedKeys" }] : /* istanbul ignore next */ []));
1784
- treeSelection = signal([], ...(ngDevMode ? [{ debugName: "treeSelection" }] : /* istanbul ignore next */ []));
1785
- treeNodes = computed(() => this.buildTreeNodes(this.groups()), ...(ngDevMode ? [{ debugName: "treeNodes" }] : /* istanbul ignore next */ []));
1786
- grantIndex = computed(() => {
1787
- const index = new Map();
1788
- for (const group of this.groups()) {
1789
- for (const targetGroup of group.targets) {
1790
- for (const grant of targetGroup.grants) {
1791
- index.set(grantKey(grant), normalizeGrant(grant));
1792
- }
1977
+ // --- Permission tree ------------------------------------------------------
1978
+ treeModel = computed(() => getDelegationScopeTree(this.options()), ...(ngDevMode ? [{ debugName: "treeModel" }] : /* istanbul ignore next */ []));
1979
+ hasOptions = computed(() => this.treeModel().length > 0, ...(ngDevMode ? [{ debugName: "hasOptions" }] : /* istanbul ignore next */ []));
1980
+ grantIndex = computed(() => indexDelegationTreeGrants(this.treeModel()), ...(ngDevMode ? [{ debugName: "grantIndex" }] : /* istanbul ignore next */ []));
1981
+ /** nodeKey -> all descendant operation-leaf keys (self for operation nodes). */
1982
+ opKeyMap = computed(() => {
1983
+ const map = new Map();
1984
+ const collect = (node) => {
1985
+ if (node.kind === 'operation') {
1986
+ map.set(node.key, [node.key]);
1987
+ return [node.key];
1793
1988
  }
1989
+ const keys = (node.children ?? []).flatMap(collect);
1990
+ map.set(node.key, keys);
1991
+ return keys;
1992
+ };
1993
+ this.treeModel().forEach(collect);
1994
+ return map;
1995
+ }, ...(ngDevMode ? [{ debugName: "opKeyMap" }] : /* istanbul ignore next */ []));
1996
+ /** Stable operation-node keys for the current scope grants. */
1997
+ selectedKeys = computed(() => new Set(this.scope().grants.map(delegationOperationNodeKey)), ...(ngDevMode ? [{ debugName: "selectedKeys" }] : /* istanbul ignore next */ []));
1998
+ selectedCount = computed(() => this.selectedKeys().size, ...(ngDevMode ? [{ debugName: "selectedCount" }] : /* istanbul ignore next */ []));
1999
+ searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : /* istanbul ignore next */ []));
2000
+ expandedKeys = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedKeys" }] : /* istanbul ignore next */ []));
2001
+ filteredTree = computed(() => {
2002
+ const term = this.searchTerm().trim().toLowerCase();
2003
+ if (!term) {
2004
+ return this.treeModel();
1794
2005
  }
1795
- return index;
1796
- }, ...(ngDevMode ? [{ debugName: "grantIndex" }] : /* istanbul ignore next */ []));
2006
+ const prune = (node) => {
2007
+ const selfMatch = this.nodeLabel(node).toLowerCase().includes(term);
2008
+ const children = (node.children ?? [])
2009
+ .map(prune)
2010
+ .filter((child) => !!child);
2011
+ if (children.length) {
2012
+ return { ...node, children };
2013
+ }
2014
+ if (selfMatch) {
2015
+ return node;
2016
+ }
2017
+ return null;
2018
+ };
2019
+ return this.treeModel()
2020
+ .map(prune)
2021
+ .filter((node) => !!node);
2022
+ }, ...(ngDevMode ? [{ debugName: "filteredTree" }] : /* istanbul ignore next */ []));
2023
+ // --- App accessibility ----------------------------------------------------
2024
+ appAccessibilities = computed(() => this.options()?.appAccessibility ?? [], ...(ngDevMode ? [{ debugName: "appAccessibilities" }] : /* istanbul ignore next */ []));
2025
+ selectedAccessibilityKeys = signal(new Set(), ...(ngDevMode ? [{ debugName: "selectedAccessibilityKeys" }] : /* istanbul ignore next */ []));
2026
+ selectedAccessibilityCount = computed(() => this.selectedAccessibilityKeys().size, ...(ngDevMode ? [{ debugName: "selectedAccessibilityCount" }] : /* istanbul ignore next */ []));
2027
+ hasAccessibility = computed(() => this.appAccessibilities().length > 0, ...(ngDevMode ? [{ debugName: "hasAccessibility" }] : /* istanbul ignore next */ []));
2028
+ /** Inner view switch: permission tree vs app-accessibility grants. */
2029
+ activeScopeTab = signal('permissions', ...(ngDevMode ? [{ debugName: "activeScopeTab" }] : /* istanbul ignore next */ []));
1797
2030
  constructor() {
1798
2031
  // Load (and reload) grantable options whenever the delegator changes.
1799
2032
  let initialized = false;
@@ -1810,10 +2043,26 @@ class ScopePicker {
1810
2043
  if (lastDelegatorUserId !== delegator) {
1811
2044
  lastDelegatorUserId = delegator;
1812
2045
  this.scope.set(EMPTY_SCOPE$1);
1813
- this.treeSelection.set([]);
2046
+ this.selectedAccessibilityKeys.set(new Set());
2047
+ this.searchTerm.set('');
2048
+ this.activeScopeTab.set('permissions');
1814
2049
  this.facade.clearScopePreview();
1815
2050
  }
1816
2051
  });
2052
+ // Default-expand Template + Level nodes whenever options (re)load.
2053
+ effect(() => {
2054
+ const model = this.treeModel();
2055
+ const keys = new Set();
2056
+ const walk = (node) => {
2057
+ if (node.kind === 'template' || node.kind === 'level') {
2058
+ keys.add(node.key);
2059
+ }
2060
+ (node.children ?? []).forEach(walk);
2061
+ };
2062
+ model.forEach(walk);
2063
+ untracked(() => this.expandedKeys.set(keys));
2064
+ });
2065
+ // Debounced scope preview.
1817
2066
  let timer = null;
1818
2067
  effect(() => {
1819
2068
  const s = this.scope();
@@ -1832,30 +2081,156 @@ class ScopePicker {
1832
2081
  }), 300);
1833
2082
  });
1834
2083
  });
2084
+ // Initialize selected accessibilities once from the bound scope (edit).
2085
+ let accessibilityInitialized = false;
1835
2086
  effect(() => {
1836
- const selectedKeys = Array.from(this.selectedKeys());
1837
- const selection = this.treeSelection();
1838
- if (sameSelectionKeys(selection, selectedKeys)) {
2087
+ const grants = this.scope().grants;
2088
+ if (accessibilityInitialized || !grants.length) {
1839
2089
  return;
1840
2090
  }
1841
- this.treeSelection.set(selectedKeys.map((key) => ({
1842
- key,
1843
- })));
2091
+ accessibilityInitialized = true;
2092
+ const keys = new Set();
2093
+ for (const grant of grants) {
2094
+ for (const fact of grant.accessibilities ?? []) {
2095
+ const key = typeof fact['accessibilityKey'] === 'string'
2096
+ ? fact['accessibilityKey']
2097
+ : typeof fact.key === 'string'
2098
+ ? fact.key
2099
+ : null;
2100
+ if (key)
2101
+ keys.add(key);
2102
+ }
2103
+ }
2104
+ if (keys.size) {
2105
+ untracked(() => this.selectedAccessibilityKeys.set(keys));
2106
+ }
2107
+ });
2108
+ }
2109
+ // --- Expansion ------------------------------------------------------------
2110
+ isExpanded(node) {
2111
+ return this.searchTerm().trim()
2112
+ ? true
2113
+ : this.expandedKeys().has(node.key);
2114
+ }
2115
+ toggleExpand(node) {
2116
+ if (!node.children?.length) {
2117
+ return;
2118
+ }
2119
+ this.expandedKeys.update((set) => {
2120
+ const next = new Set(set);
2121
+ if (next.has(node.key)) {
2122
+ next.delete(node.key);
2123
+ }
2124
+ else {
2125
+ next.add(node.key);
2126
+ }
2127
+ return next;
1844
2128
  });
1845
2129
  }
1846
- onTreeAction(event) {
1847
- if (event.action !== 'selectionChange' || this.readonly()) {
2130
+ hasChildren(node) {
2131
+ return !!node.children?.length;
2132
+ }
2133
+ // --- Selection ------------------------------------------------------------
2134
+ nodeState(node) {
2135
+ const opKeys = this.opKeyMap().get(node.key) ?? [];
2136
+ if (!opKeys.length) {
2137
+ return 'unchecked';
2138
+ }
2139
+ const selected = this.selectedKeys();
2140
+ let count = 0;
2141
+ for (const key of opKeys) {
2142
+ if (selected.has(key))
2143
+ count++;
2144
+ }
2145
+ if (count === 0)
2146
+ return 'unchecked';
2147
+ return count === opKeys.length ? 'checked' : 'indeterminate';
2148
+ }
2149
+ onRowClick(node) {
2150
+ if (this.hasChildren(node)) {
2151
+ this.toggleExpand(node);
2152
+ }
2153
+ else {
2154
+ this.toggle(node);
2155
+ }
2156
+ }
2157
+ toggle(node) {
2158
+ if (this.readonly()) {
2159
+ return;
2160
+ }
2161
+ const opKeys = this.opKeyMap().get(node.key) ?? [];
2162
+ if (!opKeys.length) {
1848
2163
  return;
1849
2164
  }
2165
+ const next = new Set(this.selectedKeys());
2166
+ const allSelected = opKeys.every((key) => next.has(key));
2167
+ for (const key of opKeys) {
2168
+ if (allSelected) {
2169
+ next.delete(key);
2170
+ }
2171
+ else {
2172
+ next.add(key);
2173
+ }
2174
+ }
2175
+ this.applySelection(next);
2176
+ }
2177
+ applySelection(selected) {
1850
2178
  const index = this.grantIndex();
1851
- const grants = (this.treeSelection() ?? [])
1852
- .map((node) => (node.key ? index.get(node.key) : undefined))
1853
- .filter((grant) => !!grant);
1854
- this.scope.update((scope) => ({
1855
- ...scope,
1856
- grants,
2179
+ const facts = this.accessibilityFacts();
2180
+ const grants = Array.from(selected)
2181
+ .map((key) => index.get(key))
2182
+ .filter((grant) => !!grant)
2183
+ .map((grant) => facts.length ? { ...grant, accessibilities: facts } : grant);
2184
+ this.scope.update((scope) => ({ ...scope, grants }));
2185
+ }
2186
+ clearSelection() {
2187
+ if (this.readonly()) {
2188
+ return;
2189
+ }
2190
+ this.scope.update((scope) => ({ ...scope, grants: [] }));
2191
+ }
2192
+ // --- App accessibility ----------------------------------------------------
2193
+ toggleAccessibility(key) {
2194
+ if (this.readonly()) {
2195
+ return;
2196
+ }
2197
+ this.selectedAccessibilityKeys.update((set) => {
2198
+ const next = new Set(set);
2199
+ if (next.has(key)) {
2200
+ next.delete(key);
2201
+ }
2202
+ else {
2203
+ next.add(key);
2204
+ }
2205
+ return next;
2206
+ });
2207
+ // Re-apply current selection so grants carry the new accessibility facts.
2208
+ this.applySelection(new Set(this.selectedKeys()));
2209
+ }
2210
+ isAccessibilitySelected(key) {
2211
+ return this.selectedAccessibilityKeys().has(key);
2212
+ }
2213
+ accessibilityFacts() {
2214
+ const selected = this.selectedAccessibilityKeys();
2215
+ if (!selected.size) {
2216
+ return [];
2217
+ }
2218
+ return this.appAccessibilities()
2219
+ .filter((item) => selected.has(item.accessibilityKey))
2220
+ .map((item) => ({
2221
+ key: item.accessibilityKey,
2222
+ accessibilityKey: item.accessibilityKey,
2223
+ name: item.name ?? null,
2224
+ displayName: item.name ?? null,
1857
2225
  }));
1858
2226
  }
2227
+ // --- Labels / preview -----------------------------------------------------
2228
+ nodeLabel(node) {
2229
+ return (formatDelegationDisplayValue(node.displayName ?? undefined, this.currentLanguage()) || humanizeDelegationKey(node.fallbackKey));
2230
+ }
2231
+ accessibilityLabel(item) {
2232
+ return (formatDelegationDisplayValue(item.name ?? undefined, this.currentLanguage()) || humanizeDelegationKey(item.accessibilityKey));
2233
+ }
1859
2234
  getPreviewSummary(summary) {
1860
2235
  return formatDelegationScopeSummary(summary, this.currentLanguage());
1861
2236
  }
@@ -1865,59 +2240,29 @@ class ScopePicker {
1865
2240
  formatDeniedTarget(value) {
1866
2241
  return humanizeDelegationKey(value);
1867
2242
  }
1868
- groupTargetCount(group) {
1869
- return group.targets.length;
1870
- }
1871
- targetGrantCount(targetGroup) {
1872
- return targetGroup.grants.length;
1873
- }
1874
- buildTreeNodes(groups) {
1875
- return groups.map((group) => ({
1876
- key: `group:${group.key}`,
1877
- label: `${this.getGroupLabel(group)} (${this.groupTargetCount(group)})`,
1878
- selectable: !this.readonly(),
1879
- expanded: true,
1880
- children: group.targets.map((targetGroup) => this.buildTargetNode(targetGroup)),
1881
- }));
1882
- }
1883
- buildTargetNode(targetGroup) {
1884
- return {
1885
- key: `target:${targetGroup.key}`,
1886
- label: `${this.getTargetLabel(targetGroup)} (${this.targetGrantCount(targetGroup)})`,
1887
- selectable: !this.readonly(),
1888
- expanded: true,
1889
- children: targetGroup.grants.map((grant) => ({
1890
- key: grantKey(grant),
1891
- label: this.getActionLabel(grant),
1892
- selectable: !this.readonly(),
1893
- })),
1894
- };
1895
- }
1896
- getGroupLabel(group) {
1897
- return group.displayName
1898
- ? humanizeDelegationDisplay(group.displayName, this.currentLanguage())
1899
- : humanizeDelegationKey(group.groupType);
1900
- }
1901
- getTargetLabel(targetGroup) {
1902
- return formatDelegationTargetLabel(targetGroup.target, this.currentLanguage());
1903
- }
1904
- getActionLabel(grant) {
1905
- return formatDelegationActionLabel(grant.action, this.currentLanguage());
2243
+ trackNode(_index, node) {
2244
+ return node.key;
1906
2245
  }
1907
2246
  currentLanguage() {
1908
2247
  return this.activeLang() ?? this.transloco.getActiveLang();
1909
2248
  }
1910
2249
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ScopePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
1911
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ScopePicker, isStandalone: true, selector: "mt-scope-picker", inputs: { scope: { classPropertyName: "scope", publicName: "scope", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null }, delegatorUserId: { classPropertyName: "delegatorUserId", publicName: "delegatorUserId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { scope: "scopeChange" }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col gap-4\">\r\n @if (isLoadingOptions()) {\r\n <div class=\"grid gap-4 2xl:grid-cols-[minmax(0,1fr)_20rem]\">\r\n <section\r\n class=\"flex flex-col gap-4 rounded-3xl border border-surface-200 bg-surface-0 p-4 shadow-xs\"\r\n >\r\n <div class=\"flex flex-col gap-2\">\r\n <p-skeleton width=\"9rem\" height=\"1rem\"></p-skeleton>\r\n <p-skeleton width=\"20rem\" height=\"0.9rem\"></p-skeleton>\r\n </div>\r\n\r\n <p-skeleton height=\"3rem\"></p-skeleton>\r\n\r\n <div class=\"grid gap-3\">\r\n @for (item of [0, 1, 2, 3]; track $index) {\r\n <div class=\"rounded-2xl border border-surface-200 p-4\">\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <p-skeleton width=\"12rem\" height=\"1rem\"></p-skeleton>\r\n <p-skeleton width=\"1.5rem\" height=\"1.5rem\"></p-skeleton>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n\r\n <section\r\n class=\"flex flex-col gap-4 rounded-3xl border border-dashed border-surface-300 bg-surface-50 p-4\"\r\n >\r\n <div class=\"flex flex-col gap-2\">\r\n <p-skeleton width=\"8rem\" height=\"1rem\"></p-skeleton>\r\n <p-skeleton width=\"100%\" height=\"0.85rem\"></p-skeleton>\r\n </div>\r\n\r\n <div class=\"flex flex-col gap-3\">\r\n @for (item of [0, 1, 2]; track $index) {\r\n <p-skeleton height=\"1rem\"></p-skeleton>\r\n }\r\n </div>\r\n </section>\r\n </div>\r\n } @else {\r\n <section class=\"flex flex-col gap-3\">\r\n @if (errorOptions(); as errorMessage) {\r\n <div\r\n class=\"rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700\"\r\n >\r\n {{ errorMessage }}\r\n </div>\r\n }\r\n\r\n @if (groups().length === 0 && !errorOptions()) {\r\n <div\r\n class=\"flex min-h-72 flex-col items-center justify-center gap-4 rounded-3xl border border-dashed border-surface-300 bg-surface-50 px-6 py-8 text-center\"\r\n >\r\n <div\r\n class=\"flex size-18 items-center justify-center rounded-3xl bg-surface-0 text-primary shadow-sm\"\r\n >\r\n <svg\r\n viewBox=\"0 0 64 64\"\r\n class=\"size-10\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n aria-hidden=\"true\"\r\n >\r\n <rect\r\n x=\"10\"\r\n y=\"12\"\r\n width=\"44\"\r\n height=\"38\"\r\n rx=\"12\"\r\n class=\"fill-primary/10 stroke-primary/35\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M21 27H43\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M21 35H37\"\r\n class=\"stroke-primary/70\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <circle\r\n cx=\"46\"\r\n cy=\"19\"\r\n r=\"6\"\r\n class=\"fill-surface-0 stroke-primary\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M43 16L49 22\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"2.5\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M49 16L43 22\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"2.5\"\r\n stroke-linecap=\"round\"\r\n />\r\n </svg>\r\n </div>\r\n\r\n <div class=\"space-y-1\">\r\n <p class=\"text-base font-medium text-surface-900\">\r\n {{ t(\"delegations.scope.permissionsTitle\") }}\r\n </p>\r\n <p class=\"text-sm text-surface-500\">\r\n {{ t(\"delegations.scope.noGrantableOptions\") }}\r\n </p>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (groups().length > 0) {\r\n <div\r\n class=\"rounded-3xl border border-surface-200 bg-surface-0 p-3 shadow-xs\"\r\n >\r\n <mt-tree\r\n [value]=\"treeNodes()\"\r\n [(selection)]=\"treeSelection\"\r\n selectionMode=\"checkbox\"\r\n [loading]=\"isLoadingOptions()\"\r\n [propagateSelectionDown]=\"true\"\r\n [propagateSelectionUp]=\"true\"\r\n (action)=\"onTreeAction($event)\"\r\n [filter]=\"true\"\r\n filterMode=\"lenient\"\r\n scrollHeight=\"28rem\"\r\n [virtualScroll]=\"true\"\r\n [virtualScrollItemSize]=\"72\"\r\n />\r\n </div>\r\n }\r\n </section>\r\n\r\n <section\r\n class=\"flex flex-col gap-3 rounded-3xl border border-dashed border-surface-300 bg-surface-50 px-4 py-4\"\r\n >\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <div class=\"text-base font-medium text-surface-800\">\r\n {{ t(\"delegations.scope.previewTitle\") }}\r\n </div>\r\n @if (isPreviewing()) {\r\n <p-skeleton width=\"6rem\" height=\"0.75rem\"></p-skeleton>\r\n }\r\n </div>\r\n @if (preview(); as p) {\r\n @if (p.isValid) {\r\n <div class=\"text-sm font-medium text-surface-900\">\r\n {{\r\n getPreviewSummary(p.summary) ||\r\n t(\"delegations.column.scopeSummary\")\r\n }}\r\n </div>\r\n } @else {\r\n <div class=\"text-sm font-medium text-red-700\">\r\n {{ t(\"delegations.scope.previewInvalid\") }}\r\n </div>\r\n }\r\n @if (p.warnings.length > 0) {\r\n <ul class=\"list-inside list-disc space-y-1 text-xs text-amber-700\">\r\n @for (w of p.warnings; track $index) {\r\n <li>{{ w.message }}</li>\r\n }\r\n </ul>\r\n }\r\n @if (p.deniedItems.length > 0) {\r\n <ul class=\"list-inside list-disc space-y-1 text-xs text-red-700\">\r\n @for (d of p.deniedItems; track $index) {\r\n <li>\r\n {{ formatDeniedTarget(d.targetKey) }} /\r\n {{ formatDeniedOperation(d.operationKey) }} -\r\n {{ d.reasonCode }}\r\n </li>\r\n }\r\n </ul>\r\n }\r\n } @else {\r\n <div class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.scope.noSelection\") }}\r\n </div>\r\n }\r\n </section>\r\n }\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Tree, selector: "mt-tree", inputs: ["value", "selection", "selectionMode", "nodeIcon", "propagateSelectionUp", "propagateSelectionDown", "checkAllChildren", "loading", "emptyMessage", "checkAllLabel", "filterPlaceholder", "dataKey", "filter", "filterMode", "virtualScroll", "virtualScrollItemSize", "scrollHeight", "styleClass", "style", "pInputs", "nodeActions", "nodeContextmenuActions", "contextMenuSelection"], outputs: ["selectionChange", "contextMenuSelectionChange", "action"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2250
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ScopePicker, isStandalone: true, selector: "mt-scope-picker", inputs: { scope: { classPropertyName: "scope", publicName: "scope", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null }, delegatorUserId: { classPropertyName: "delegatorUserId", publicName: "delegatorUserId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { scope: "scopeChange" }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <!-- Recursive node template -->\n <ng-template #nodeTpl let-node let-depth=\"depth\">\n @let state = nodeState(node);\n @let branch = hasChildren(node);\n <div\n class=\"group flex items-center gap-2 rounded-lg py-1.5 pe-2 transition-colors hover:bg-surface-50\"\n [style.padding-inline-start.rem]=\"0.25 + depth * 1.25\"\n >\n @if (branch) {\n <button\n type=\"button\"\n class=\"flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-surface-400 hover:bg-surface-100 hover:text-surface-600\"\n [attr.aria-expanded]=\"isExpanded(node)\"\n [attr.aria-label]=\"nodeLabel(node)\"\n (click)=\"toggleExpand(node); $event.stopPropagation()\"\n >\n <mt-icon\n [icon]=\"isExpanded(node) ? 'arrow.chevron-down' : 'arrow.chevron-right'\"\n styleClass=\"text-base\"\n ></mt-icon>\n </button>\n } @else {\n <span class=\"size-6 shrink-0\"></span>\n }\n\n <button\n type=\"button\"\n role=\"checkbox\"\n [attr.aria-checked]=\"\n state === 'indeterminate' ? 'mixed' : state === 'checked'\n \"\n [attr.aria-label]=\"nodeLabel(node)\"\n class=\"flex size-[18px] shrink-0 items-center justify-center rounded-[5px] border transition-colors\"\n [class.cursor-pointer]=\"!readonly()\"\n [class.border-primary-500]=\"state !== 'unchecked'\"\n [class.bg-primary-500]=\"state !== 'unchecked'\"\n [class.text-white]=\"state !== 'unchecked'\"\n [class.border-surface-300]=\"state === 'unchecked'\"\n [class.bg-surface-0]=\"state === 'unchecked'\"\n [disabled]=\"readonly()\"\n (click)=\"toggle(node); $event.stopPropagation()\"\n >\n @if (state === \"checked\") {\n <svg\n viewBox=\"0 0 16 16\"\n class=\"size-3\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M3.5 8.5L6.5 11.5L12.5 4.5\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n } @else if (state === \"indeterminate\") {\n <span class=\"h-[2px] w-2.5 rounded-full bg-white\"></span>\n }\n </button>\n\n <button\n type=\"button\"\n class=\"flex min-w-0 flex-1 cursor-pointer items-center gap-2 text-start\"\n (click)=\"onRowClick(node)\"\n >\n <span\n class=\"truncate text-sm\"\n [class.font-semibold]=\"node.kind === 'template'\"\n [class.text-surface-900]=\"node.kind === 'template'\"\n [class.font-medium]=\"node.kind === 'level' || node.kind === 'module'\"\n [class.text-surface-800]=\"node.kind === 'level' || node.kind === 'module'\"\n [class.text-surface-600]=\"node.kind === 'operation'\"\n >\n {{ nodeLabel(node) }}\n </span>\n @if (node.kind === \"operation\" && node.isHighRisk) {\n <span\n class=\"shrink-0 rounded-full bg-amber-100 px-2 py-0.5 text-[0.625rem] font-medium text-amber-700\"\n >\n {{ t(\"delegations.scope.highRisk\") }}\n </span>\n }\n </button>\n </div>\n\n @if (branch && isExpanded(node)) {\n @for (child of node.children; track trackNode($index, child)) {\n <ng-container\n *ngTemplateOutlet=\"nodeTpl; context: { $implicit: child, depth: depth + 1 }\"\n ></ng-container>\n }\n }\n </ng-template>\n\n <div class=\"flex flex-col gap-3\">\n @if (isLoadingOptions()) {\n <mt-card [paddingless]=\"true\">\n <div class=\"flex flex-col gap-3 p-4\">\n <p-skeleton height=\"2.5rem\"></p-skeleton>\n @for (item of [0, 1, 2, 3, 4, 5]; track $index) {\n <p-skeleton height=\"2rem\"></p-skeleton>\n }\n </div>\n </mt-card>\n } @else {\n @if (errorOptions(); as errorMessage) {\n <div\n class=\"rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700\"\n >\n {{ errorMessage }}\n </div>\n }\n\n @if (!hasOptions() && !errorOptions()) {\n <mt-card [paddingless]=\"true\">\n <div\n class=\"flex min-h-72 flex-col items-center justify-center gap-4 px-6 py-8 text-center\"\n >\n <div\n class=\"flex size-16 items-center justify-center rounded-3xl bg-surface-50 text-primary\"\n >\n <svg\n viewBox=\"0 0 64 64\"\n class=\"size-9\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <rect\n x=\"10\"\n y=\"12\"\n width=\"44\"\n height=\"38\"\n rx=\"12\"\n class=\"fill-primary/10 stroke-primary/35\"\n stroke-width=\"2\"\n />\n <path\n d=\"M21 27H43\"\n class=\"stroke-primary\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n />\n <path\n d=\"M21 35H37\"\n class=\"stroke-primary/70\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n />\n </svg>\n </div>\n <div class=\"space-y-1\">\n <p class=\"text-base font-medium text-surface-900\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </p>\n <p class=\"text-sm text-surface-500\">\n {{ t(\"delegations.scope.noGrantableOptions\") }}\n </p>\n </div>\n </div>\n </mt-card>\n }\n\n @if (hasOptions()) {\n <mt-card [paddingless]=\"true\" class=\"overflow-hidden\">\n <!-- Inner view switch: Permissions / Pages & accessibility -->\n @if (hasAccessibility()) {\n <div class=\"border-b border-surface px-3 pt-2.5 pb-0\">\n <mt-tabs\n mode=\"underline\"\n [(active)]=\"activeScopeTab\"\n [options]=\"[\n {\n value: 'permissions',\n label: t('delegations.scope.permissionsTab'),\n badge: selectedCount() || null,\n },\n {\n value: 'accessibility',\n label: t('delegations.scope.accessibilityTitle'),\n badge: selectedAccessibilityCount() || null,\n },\n ]\"\n fluid\n ></mt-tabs>\n </div>\n }\n\n @if (activeScopeTab() === \"permissions\" || !hasAccessibility()) {\n <!-- Toolbar: table-style search + clear -->\n <div class=\"flex items-center gap-2 border-b border-surface px-3 py-2\">\n <div class=\"min-w-0 flex-1\">\n <mt-text-field\n [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"searchTerm.set($event)\"\n icon=\"general.search-lg\"\n [placeholder]=\"t('delegations.scope.searchPlaceholder')\"\n ></mt-text-field>\n </div>\n @if (selectedCount() > 0) {\n <span class=\"shrink-0 text-xs font-medium text-surface-500\">\n {{ selectedCount() }} {{ t(\"delegations.scope.selected\") }}\n </span>\n <mt-button\n variant=\"text\"\n size=\"small\"\n [label]=\"t('delegations.scope.clear')\"\n [disabled]=\"readonly()\"\n (click)=\"clearSelection()\"\n ></mt-button>\n }\n </div>\n\n <!-- Permission tree -->\n <div class=\"max-h-[22rem] overflow-y-auto p-1.5\">\n @for (node of filteredTree(); track trackNode($index, node)) {\n <ng-container\n *ngTemplateOutlet=\"nodeTpl; context: { $implicit: node, depth: 0 }\"\n ></ng-container>\n } @empty {\n <p class=\"px-3 py-8 text-center text-sm text-surface-500\">\n {{ t(\"components.table.no-data-found\") }}\n </p>\n }\n </div>\n } @else {\n <!-- App accessibility -->\n <div class=\"flex flex-col gap-3 p-4\">\n <p class=\"text-xs text-surface-500\">\n {{ t(\"delegations.scope.accessibilityHint\") }}\n </p>\n <div class=\"flex flex-wrap gap-2\">\n @for (item of appAccessibilities(); track item.accessibilityKey) {\n @let selected = isAccessibilitySelected(item.accessibilityKey);\n <button\n type=\"button\"\n class=\"inline-flex cursor-pointer items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs font-medium transition-colors\"\n [class.border-primary-400]=\"selected\"\n [class.bg-primary-50]=\"selected\"\n [class.text-primary-700]=\"selected\"\n [class.border-surface-200]=\"!selected\"\n [class.text-surface-600]=\"!selected\"\n [class.hover:border-primary-300]=\"!readonly()\"\n [disabled]=\"readonly()\"\n [attr.aria-pressed]=\"selected\"\n (click)=\"toggleAccessibility(item.accessibilityKey)\"\n >\n <span\n class=\"inline-flex size-1.5 rounded-full\"\n [class.bg-primary-500]=\"selected\"\n [class.bg-surface-300]=\"!selected\"\n ></span>\n {{ accessibilityLabel(item) }}\n </button>\n }\n </div>\n </div>\n }\n </mt-card>\n }\n\n <!-- Compact live scope preview -->\n <div\n class=\"flex flex-col gap-1.5 rounded-lg border border-surface bg-surface-50 px-3 py-2\"\n >\n <div class=\"flex items-center gap-2 text-xs\">\n <span class=\"shrink-0 font-semibold text-surface-700\">\n {{ t(\"delegations.scope.previewTitle\") }}:\n </span>\n @if (isPreviewing()) {\n <p-skeleton width=\"8rem\" height=\"0.7rem\"></p-skeleton>\n } @else if (preview(); as p) {\n @if (p.isValid) {\n <span class=\"min-w-0 flex-1 truncate text-surface-900\">\n {{\n getPreviewSummary(p.summary) ||\n t(\"delegations.column.scopeSummary\")\n }}\n </span>\n } @else {\n <span class=\"font-medium text-red-700\">\n {{ t(\"delegations.scope.previewInvalid\") }}\n </span>\n }\n } @else {\n <span class=\"text-surface-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </span>\n }\n </div>\n @if (preview(); as p) {\n @if (p.warnings.length > 0) {\n <ul class=\"list-inside list-disc space-y-0.5 text-xs text-amber-700\">\n @for (w of p.warnings; track $index) {\n <li>{{ w.message }}</li>\n }\n </ul>\n }\n @if (p.deniedItems.length > 0) {\n <ul class=\"list-inside list-disc space-y-0.5 text-xs text-red-700\">\n @for (d of p.deniedItems; track $index) {\n <li>\n {{ formatDeniedTarget(d.targetKey) }} /\n {{ formatDeniedOperation(d.operationKey) }} -\n {{ d.reasonCode }}\n </li>\n }\n </ul>\n }\n }\n </div>\n }\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "maxLength", "icon", "iconPosition"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i3.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1912
2251
  }
1913
2252
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ScopePicker, decorators: [{
1914
2253
  type: Component,
1915
- args: [{ selector: 'mt-scope-picker', imports: [CommonModule, Tree, SkeletonModule, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col gap-4\">\r\n @if (isLoadingOptions()) {\r\n <div class=\"grid gap-4 2xl:grid-cols-[minmax(0,1fr)_20rem]\">\r\n <section\r\n class=\"flex flex-col gap-4 rounded-3xl border border-surface-200 bg-surface-0 p-4 shadow-xs\"\r\n >\r\n <div class=\"flex flex-col gap-2\">\r\n <p-skeleton width=\"9rem\" height=\"1rem\"></p-skeleton>\r\n <p-skeleton width=\"20rem\" height=\"0.9rem\"></p-skeleton>\r\n </div>\r\n\r\n <p-skeleton height=\"3rem\"></p-skeleton>\r\n\r\n <div class=\"grid gap-3\">\r\n @for (item of [0, 1, 2, 3]; track $index) {\r\n <div class=\"rounded-2xl border border-surface-200 p-4\">\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <p-skeleton width=\"12rem\" height=\"1rem\"></p-skeleton>\r\n <p-skeleton width=\"1.5rem\" height=\"1.5rem\"></p-skeleton>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </section>\r\n\r\n <section\r\n class=\"flex flex-col gap-4 rounded-3xl border border-dashed border-surface-300 bg-surface-50 p-4\"\r\n >\r\n <div class=\"flex flex-col gap-2\">\r\n <p-skeleton width=\"8rem\" height=\"1rem\"></p-skeleton>\r\n <p-skeleton width=\"100%\" height=\"0.85rem\"></p-skeleton>\r\n </div>\r\n\r\n <div class=\"flex flex-col gap-3\">\r\n @for (item of [0, 1, 2]; track $index) {\r\n <p-skeleton height=\"1rem\"></p-skeleton>\r\n }\r\n </div>\r\n </section>\r\n </div>\r\n } @else {\r\n <section class=\"flex flex-col gap-3\">\r\n @if (errorOptions(); as errorMessage) {\r\n <div\r\n class=\"rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700\"\r\n >\r\n {{ errorMessage }}\r\n </div>\r\n }\r\n\r\n @if (groups().length === 0 && !errorOptions()) {\r\n <div\r\n class=\"flex min-h-72 flex-col items-center justify-center gap-4 rounded-3xl border border-dashed border-surface-300 bg-surface-50 px-6 py-8 text-center\"\r\n >\r\n <div\r\n class=\"flex size-18 items-center justify-center rounded-3xl bg-surface-0 text-primary shadow-sm\"\r\n >\r\n <svg\r\n viewBox=\"0 0 64 64\"\r\n class=\"size-10\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n aria-hidden=\"true\"\r\n >\r\n <rect\r\n x=\"10\"\r\n y=\"12\"\r\n width=\"44\"\r\n height=\"38\"\r\n rx=\"12\"\r\n class=\"fill-primary/10 stroke-primary/35\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M21 27H43\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M21 35H37\"\r\n class=\"stroke-primary/70\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <circle\r\n cx=\"46\"\r\n cy=\"19\"\r\n r=\"6\"\r\n class=\"fill-surface-0 stroke-primary\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M43 16L49 22\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"2.5\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M49 16L43 22\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"2.5\"\r\n stroke-linecap=\"round\"\r\n />\r\n </svg>\r\n </div>\r\n\r\n <div class=\"space-y-1\">\r\n <p class=\"text-base font-medium text-surface-900\">\r\n {{ t(\"delegations.scope.permissionsTitle\") }}\r\n </p>\r\n <p class=\"text-sm text-surface-500\">\r\n {{ t(\"delegations.scope.noGrantableOptions\") }}\r\n </p>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (groups().length > 0) {\r\n <div\r\n class=\"rounded-3xl border border-surface-200 bg-surface-0 p-3 shadow-xs\"\r\n >\r\n <mt-tree\r\n [value]=\"treeNodes()\"\r\n [(selection)]=\"treeSelection\"\r\n selectionMode=\"checkbox\"\r\n [loading]=\"isLoadingOptions()\"\r\n [propagateSelectionDown]=\"true\"\r\n [propagateSelectionUp]=\"true\"\r\n (action)=\"onTreeAction($event)\"\r\n [filter]=\"true\"\r\n filterMode=\"lenient\"\r\n scrollHeight=\"28rem\"\r\n [virtualScroll]=\"true\"\r\n [virtualScrollItemSize]=\"72\"\r\n />\r\n </div>\r\n }\r\n </section>\r\n\r\n <section\r\n class=\"flex flex-col gap-3 rounded-3xl border border-dashed border-surface-300 bg-surface-50 px-4 py-4\"\r\n >\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <div class=\"text-base font-medium text-surface-800\">\r\n {{ t(\"delegations.scope.previewTitle\") }}\r\n </div>\r\n @if (isPreviewing()) {\r\n <p-skeleton width=\"6rem\" height=\"0.75rem\"></p-skeleton>\r\n }\r\n </div>\r\n @if (preview(); as p) {\r\n @if (p.isValid) {\r\n <div class=\"text-sm font-medium text-surface-900\">\r\n {{\r\n getPreviewSummary(p.summary) ||\r\n t(\"delegations.column.scopeSummary\")\r\n }}\r\n </div>\r\n } @else {\r\n <div class=\"text-sm font-medium text-red-700\">\r\n {{ t(\"delegations.scope.previewInvalid\") }}\r\n </div>\r\n }\r\n @if (p.warnings.length > 0) {\r\n <ul class=\"list-inside list-disc space-y-1 text-xs text-amber-700\">\r\n @for (w of p.warnings; track $index) {\r\n <li>{{ w.message }}</li>\r\n }\r\n </ul>\r\n }\r\n @if (p.deniedItems.length > 0) {\r\n <ul class=\"list-inside list-disc space-y-1 text-xs text-red-700\">\r\n @for (d of p.deniedItems; track $index) {\r\n <li>\r\n {{ formatDeniedTarget(d.targetKey) }} /\r\n {{ formatDeniedOperation(d.operationKey) }} -\r\n {{ d.reasonCode }}\r\n </li>\r\n }\r\n </ul>\r\n }\r\n } @else {\r\n <div class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.scope.noSelection\") }}\r\n </div>\r\n }\r\n </section>\r\n }\r\n </div>\r\n</ng-container>\r\n" }]
2254
+ args: [{ selector: 'mt-scope-picker', imports: [
2255
+ CommonModule,
2256
+ FormsModule,
2257
+ Button,
2258
+ Card,
2259
+ Icon,
2260
+ Tabs,
2261
+ TextField,
2262
+ SkeletonModule,
2263
+ TranslocoDirective,
2264
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <!-- Recursive node template -->\n <ng-template #nodeTpl let-node let-depth=\"depth\">\n @let state = nodeState(node);\n @let branch = hasChildren(node);\n <div\n class=\"group flex items-center gap-2 rounded-lg py-1.5 pe-2 transition-colors hover:bg-surface-50\"\n [style.padding-inline-start.rem]=\"0.25 + depth * 1.25\"\n >\n @if (branch) {\n <button\n type=\"button\"\n class=\"flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-surface-400 hover:bg-surface-100 hover:text-surface-600\"\n [attr.aria-expanded]=\"isExpanded(node)\"\n [attr.aria-label]=\"nodeLabel(node)\"\n (click)=\"toggleExpand(node); $event.stopPropagation()\"\n >\n <mt-icon\n [icon]=\"isExpanded(node) ? 'arrow.chevron-down' : 'arrow.chevron-right'\"\n styleClass=\"text-base\"\n ></mt-icon>\n </button>\n } @else {\n <span class=\"size-6 shrink-0\"></span>\n }\n\n <button\n type=\"button\"\n role=\"checkbox\"\n [attr.aria-checked]=\"\n state === 'indeterminate' ? 'mixed' : state === 'checked'\n \"\n [attr.aria-label]=\"nodeLabel(node)\"\n class=\"flex size-[18px] shrink-0 items-center justify-center rounded-[5px] border transition-colors\"\n [class.cursor-pointer]=\"!readonly()\"\n [class.border-primary-500]=\"state !== 'unchecked'\"\n [class.bg-primary-500]=\"state !== 'unchecked'\"\n [class.text-white]=\"state !== 'unchecked'\"\n [class.border-surface-300]=\"state === 'unchecked'\"\n [class.bg-surface-0]=\"state === 'unchecked'\"\n [disabled]=\"readonly()\"\n (click)=\"toggle(node); $event.stopPropagation()\"\n >\n @if (state === \"checked\") {\n <svg\n viewBox=\"0 0 16 16\"\n class=\"size-3\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M3.5 8.5L6.5 11.5L12.5 4.5\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n } @else if (state === \"indeterminate\") {\n <span class=\"h-[2px] w-2.5 rounded-full bg-white\"></span>\n }\n </button>\n\n <button\n type=\"button\"\n class=\"flex min-w-0 flex-1 cursor-pointer items-center gap-2 text-start\"\n (click)=\"onRowClick(node)\"\n >\n <span\n class=\"truncate text-sm\"\n [class.font-semibold]=\"node.kind === 'template'\"\n [class.text-surface-900]=\"node.kind === 'template'\"\n [class.font-medium]=\"node.kind === 'level' || node.kind === 'module'\"\n [class.text-surface-800]=\"node.kind === 'level' || node.kind === 'module'\"\n [class.text-surface-600]=\"node.kind === 'operation'\"\n >\n {{ nodeLabel(node) }}\n </span>\n @if (node.kind === \"operation\" && node.isHighRisk) {\n <span\n class=\"shrink-0 rounded-full bg-amber-100 px-2 py-0.5 text-[0.625rem] font-medium text-amber-700\"\n >\n {{ t(\"delegations.scope.highRisk\") }}\n </span>\n }\n </button>\n </div>\n\n @if (branch && isExpanded(node)) {\n @for (child of node.children; track trackNode($index, child)) {\n <ng-container\n *ngTemplateOutlet=\"nodeTpl; context: { $implicit: child, depth: depth + 1 }\"\n ></ng-container>\n }\n }\n </ng-template>\n\n <div class=\"flex flex-col gap-3\">\n @if (isLoadingOptions()) {\n <mt-card [paddingless]=\"true\">\n <div class=\"flex flex-col gap-3 p-4\">\n <p-skeleton height=\"2.5rem\"></p-skeleton>\n @for (item of [0, 1, 2, 3, 4, 5]; track $index) {\n <p-skeleton height=\"2rem\"></p-skeleton>\n }\n </div>\n </mt-card>\n } @else {\n @if (errorOptions(); as errorMessage) {\n <div\n class=\"rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700\"\n >\n {{ errorMessage }}\n </div>\n }\n\n @if (!hasOptions() && !errorOptions()) {\n <mt-card [paddingless]=\"true\">\n <div\n class=\"flex min-h-72 flex-col items-center justify-center gap-4 px-6 py-8 text-center\"\n >\n <div\n class=\"flex size-16 items-center justify-center rounded-3xl bg-surface-50 text-primary\"\n >\n <svg\n viewBox=\"0 0 64 64\"\n class=\"size-9\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <rect\n x=\"10\"\n y=\"12\"\n width=\"44\"\n height=\"38\"\n rx=\"12\"\n class=\"fill-primary/10 stroke-primary/35\"\n stroke-width=\"2\"\n />\n <path\n d=\"M21 27H43\"\n class=\"stroke-primary\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n />\n <path\n d=\"M21 35H37\"\n class=\"stroke-primary/70\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n />\n </svg>\n </div>\n <div class=\"space-y-1\">\n <p class=\"text-base font-medium text-surface-900\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </p>\n <p class=\"text-sm text-surface-500\">\n {{ t(\"delegations.scope.noGrantableOptions\") }}\n </p>\n </div>\n </div>\n </mt-card>\n }\n\n @if (hasOptions()) {\n <mt-card [paddingless]=\"true\" class=\"overflow-hidden\">\n <!-- Inner view switch: Permissions / Pages & accessibility -->\n @if (hasAccessibility()) {\n <div class=\"border-b border-surface px-3 pt-2.5 pb-0\">\n <mt-tabs\n mode=\"underline\"\n [(active)]=\"activeScopeTab\"\n [options]=\"[\n {\n value: 'permissions',\n label: t('delegations.scope.permissionsTab'),\n badge: selectedCount() || null,\n },\n {\n value: 'accessibility',\n label: t('delegations.scope.accessibilityTitle'),\n badge: selectedAccessibilityCount() || null,\n },\n ]\"\n fluid\n ></mt-tabs>\n </div>\n }\n\n @if (activeScopeTab() === \"permissions\" || !hasAccessibility()) {\n <!-- Toolbar: table-style search + clear -->\n <div class=\"flex items-center gap-2 border-b border-surface px-3 py-2\">\n <div class=\"min-w-0 flex-1\">\n <mt-text-field\n [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"searchTerm.set($event)\"\n icon=\"general.search-lg\"\n [placeholder]=\"t('delegations.scope.searchPlaceholder')\"\n ></mt-text-field>\n </div>\n @if (selectedCount() > 0) {\n <span class=\"shrink-0 text-xs font-medium text-surface-500\">\n {{ selectedCount() }} {{ t(\"delegations.scope.selected\") }}\n </span>\n <mt-button\n variant=\"text\"\n size=\"small\"\n [label]=\"t('delegations.scope.clear')\"\n [disabled]=\"readonly()\"\n (click)=\"clearSelection()\"\n ></mt-button>\n }\n </div>\n\n <!-- Permission tree -->\n <div class=\"max-h-[22rem] overflow-y-auto p-1.5\">\n @for (node of filteredTree(); track trackNode($index, node)) {\n <ng-container\n *ngTemplateOutlet=\"nodeTpl; context: { $implicit: node, depth: 0 }\"\n ></ng-container>\n } @empty {\n <p class=\"px-3 py-8 text-center text-sm text-surface-500\">\n {{ t(\"components.table.no-data-found\") }}\n </p>\n }\n </div>\n } @else {\n <!-- App accessibility -->\n <div class=\"flex flex-col gap-3 p-4\">\n <p class=\"text-xs text-surface-500\">\n {{ t(\"delegations.scope.accessibilityHint\") }}\n </p>\n <div class=\"flex flex-wrap gap-2\">\n @for (item of appAccessibilities(); track item.accessibilityKey) {\n @let selected = isAccessibilitySelected(item.accessibilityKey);\n <button\n type=\"button\"\n class=\"inline-flex cursor-pointer items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs font-medium transition-colors\"\n [class.border-primary-400]=\"selected\"\n [class.bg-primary-50]=\"selected\"\n [class.text-primary-700]=\"selected\"\n [class.border-surface-200]=\"!selected\"\n [class.text-surface-600]=\"!selected\"\n [class.hover:border-primary-300]=\"!readonly()\"\n [disabled]=\"readonly()\"\n [attr.aria-pressed]=\"selected\"\n (click)=\"toggleAccessibility(item.accessibilityKey)\"\n >\n <span\n class=\"inline-flex size-1.5 rounded-full\"\n [class.bg-primary-500]=\"selected\"\n [class.bg-surface-300]=\"!selected\"\n ></span>\n {{ accessibilityLabel(item) }}\n </button>\n }\n </div>\n </div>\n }\n </mt-card>\n }\n\n <!-- Compact live scope preview -->\n <div\n class=\"flex flex-col gap-1.5 rounded-lg border border-surface bg-surface-50 px-3 py-2\"\n >\n <div class=\"flex items-center gap-2 text-xs\">\n <span class=\"shrink-0 font-semibold text-surface-700\">\n {{ t(\"delegations.scope.previewTitle\") }}:\n </span>\n @if (isPreviewing()) {\n <p-skeleton width=\"8rem\" height=\"0.7rem\"></p-skeleton>\n } @else if (preview(); as p) {\n @if (p.isValid) {\n <span class=\"min-w-0 flex-1 truncate text-surface-900\">\n {{\n getPreviewSummary(p.summary) ||\n t(\"delegations.column.scopeSummary\")\n }}\n </span>\n } @else {\n <span class=\"font-medium text-red-700\">\n {{ t(\"delegations.scope.previewInvalid\") }}\n </span>\n }\n } @else {\n <span class=\"text-surface-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </span>\n }\n </div>\n @if (preview(); as p) {\n @if (p.warnings.length > 0) {\n <ul class=\"list-inside list-disc space-y-0.5 text-xs text-amber-700\">\n @for (w of p.warnings; track $index) {\n <li>{{ w.message }}</li>\n }\n </ul>\n }\n @if (p.deniedItems.length > 0) {\n <ul class=\"list-inside list-disc space-y-0.5 text-xs text-red-700\">\n @for (d of p.deniedItems; track $index) {\n <li>\n {{ formatDeniedTarget(d.targetKey) }} /\n {{ formatDeniedOperation(d.operationKey) }} -\n {{ d.reasonCode }}\n </li>\n }\n </ul>\n }\n }\n </div>\n }\n </div>\n</ng-container>\n" }]
1916
2265
  }], ctorParameters: () => [], propDecorators: { scope: [{ type: i0.Input, args: [{ isSignal: true, alias: "scope", required: false }] }, { type: i0.Output, args: ["scopeChange"] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], adminMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "adminMode", required: false }] }], delegatorUserId: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegatorUserId", required: false }] }] } });
1917
- function humanizeDelegationDisplay(value, language) {
1918
- return (formatDelegationScopeSummary(value, language) ||
1919
- humanizeDelegationKey(typeof value === 'string' ? value : null));
1920
- }
1921
2266
 
1922
2267
  const EMPTY_SCOPE = { grants: [], metadata: {} };
1923
2268
  function getCurrentDelegatorFromLocalStorage() {
@@ -2287,7 +2632,7 @@ class DelegationForm {
2287
2632
  };
2288
2633
  }
2289
2634
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
2290
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationForm, isStandalone: true, selector: "mt-delegation-form", inputs: { delegationForEdit: { classPropertyName: "delegationForEdit", publicName: "delegationForEdit", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div\n [class]=\"\n 'flex h-full min-h-0 min-w-0 flex-col overflow-hidden ' +\n modal.contentClass\n \"\n >\n <div\n class=\"grid min-h-0 flex-1 gap-4 overflow-y-auto p-4 max-[640px]:p-3 lg:grid-cols-[minmax(0,28rem)_minmax(0,1fr)] lg:items-start\"\n >\n <section\n class=\"flex flex-col gap-4 rounded-3xl border border-surface-200 bg-surface-0 p-4 shadow-xs lg:sticky lg:top-0\"\n >\n <mt-dynamic-form\n [formConfig]=\"formConfig()\"\n [formControl]=\"delegationFormControl\"\n />\n </section>\n\n <section\n class=\"flex min-h-0 flex-col gap-4 rounded-3xl border border-surface-200 bg-surface-0 p-4 shadow-xs\"\n >\n <div class=\"flex flex-col gap-2\">\n <div\n class=\"inline-flex w-fit items-center gap-2 rounded-full bg-primary-50 px-3 py-1 text-xs font-medium text-primary-700\"\n >\n <span class=\"inline-flex size-2 rounded-full bg-primary-500\"></span>\n {{ t(\"delegations.scope.previewTitle\") }}\n </div>\n <h3 class=\"text-xl font-semibold text-surface-900\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </h3>\n @if (adminMode() && !showScopePicker()) {\n <p class=\"text-sm text-surface-500\">\n {{ t(\"delegations.form.selectDelegatorFirst\") }}\n </p>\n } @else {\n <p class=\"text-sm text-surface-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </p>\n }\n </div>\n\n @if (showScopePicker()) {\n <mt-scope-picker\n [(scope)]=\"scope\"\n [readonly]=\"readonly()\"\n [adminMode]=\"adminMode()\"\n [delegatorUserId]=\"selectedDelegatorId()\"\n ></mt-scope-picker>\n } @else {\n <div\n class=\"flex min-h-72 flex-col items-center justify-center gap-4 rounded-3xl border border-dashed border-surface-300 bg-surface-50 px-6 py-8 text-center\"\n >\n <div\n class=\"flex size-18 items-center justify-center rounded-3xl bg-surface-0 text-primary shadow-sm\"\n >\n <svg\n viewBox=\"0 0 64 64\"\n class=\"size-10\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <rect\n x=\"10\"\n y=\"12\"\n width=\"44\"\n height=\"40\"\n rx=\"12\"\n class=\"fill-primary/10 stroke-primary/35\"\n stroke-width=\"2\"\n />\n <path\n d=\"M22 28H42\"\n class=\"stroke-primary\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n />\n <path\n d=\"M22 36H34\"\n class=\"stroke-primary/70\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n />\n <circle\n cx=\"45\"\n cy=\"20\"\n r=\"7\"\n class=\"fill-surface-0 stroke-primary\"\n stroke-width=\"2\"\n />\n <path\n d=\"M45 17V23\"\n class=\"stroke-primary\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n />\n <path\n d=\"M42 20H48\"\n class=\"stroke-primary\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n />\n </svg>\n </div>\n <div class=\"space-y-1\">\n <p class=\"text-base font-medium text-surface-900\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </p>\n <p class=\"text-sm text-surface-500\">\n {{ t(\"delegations.form.selectDelegatorFirst\") }}\n </p>\n </div>\n </div>\n }\n </section>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n [label]=\"t('delegations.common.cancel')\"\n variant=\"outlined\"\n (click)=\"ref.close()\"\n styleClass=\"w-full sm:w-auto\"\n />\n @if (!readonly()) {\n <mt-button\n [label]=\"\n delegationForEdit()\n ? t('delegations.common.update')\n : t('delegations.common.create')\n \"\n [loading]=\"isSaving()\"\n [disabled]=\"!delegationFormControl.valid || !canSubmit()\"\n (click)=\"onSubmit()\"\n styleClass=\"w-full sm:w-auto\"\n />\n }\n </div>\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: DynamicForm, selector: "mt-dynamic-form", inputs: ["formConfig", "forcedHiddenFieldKeys", "preserveForcedHiddenValues", "visibleSectionKeys"], outputs: ["runtimeMessagesChange"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: ScopePicker, selector: "mt-scope-picker", inputs: ["scope", "readonly", "adminMode", "delegatorUserId"], outputs: ["scopeChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2635
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationForm, isStandalone: true, selector: "mt-delegation-form", inputs: { delegationForEdit: { classPropertyName: "delegationForEdit", publicName: "delegationForEdit", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div\n [class]=\"\n 'flex h-full min-h-0 min-w-0 flex-col overflow-hidden ' +\n modal.contentClass\n \"\n >\n <div\n class=\"grid min-h-0 flex-1 gap-4 overflow-y-auto p-4 max-[640px]:p-3 lg:grid-cols-[minmax(0,28rem)_minmax(0,1fr)] lg:items-start\"\n >\n <section\n class=\"flex flex-col gap-4 rounded-3xl border border-surface-200 bg-surface-0 p-4 shadow-xs lg:sticky lg:top-0\"\n >\n <mt-dynamic-form\n [formConfig]=\"formConfig()\"\n [formControl]=\"delegationFormControl\"\n />\n </section>\n\n <section class=\"flex min-h-0 flex-col gap-3\">\n <div class=\"flex flex-col gap-1\">\n <h3 class=\"text-xl font-semibold text-surface-900\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </h3>\n <p class=\"text-sm text-surface-500\">\n {{\n adminMode() && !showScopePicker()\n ? t(\"delegations.form.selectDelegatorFirst\")\n : t(\"delegations.scope.permissionsSubtitle\")\n }}\n </p>\n </div>\n\n @if (showScopePicker()) {\n <mt-scope-picker\n [(scope)]=\"scope\"\n [readonly]=\"readonly()\"\n [adminMode]=\"adminMode()\"\n [delegatorUserId]=\"selectedDelegatorId()\"\n ></mt-scope-picker>\n } @else {\n <div\n class=\"flex min-h-72 flex-col items-center justify-center gap-4 rounded-3xl border border-dashed border-surface-300 bg-surface-50 px-6 py-8 text-center\"\n >\n <div\n class=\"flex size-18 items-center justify-center rounded-3xl bg-surface-0 text-primary shadow-sm\"\n >\n <svg\n viewBox=\"0 0 64 64\"\n class=\"size-10\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <rect\n x=\"10\"\n y=\"12\"\n width=\"44\"\n height=\"40\"\n rx=\"12\"\n class=\"fill-primary/10 stroke-primary/35\"\n stroke-width=\"2\"\n />\n <path\n d=\"M22 28H42\"\n class=\"stroke-primary\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n />\n <path\n d=\"M22 36H34\"\n class=\"stroke-primary/70\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n />\n <circle\n cx=\"45\"\n cy=\"20\"\n r=\"7\"\n class=\"fill-surface-0 stroke-primary\"\n stroke-width=\"2\"\n />\n <path\n d=\"M45 17V23\"\n class=\"stroke-primary\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n />\n <path\n d=\"M42 20H48\"\n class=\"stroke-primary\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n />\n </svg>\n </div>\n <div class=\"space-y-1\">\n <p class=\"text-base font-medium text-surface-900\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </p>\n <p class=\"text-sm text-surface-500\">\n {{ t(\"delegations.form.selectDelegatorFirst\") }}\n </p>\n </div>\n </div>\n }\n </section>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n [label]=\"t('delegations.common.cancel')\"\n variant=\"outlined\"\n (click)=\"ref.close()\"\n styleClass=\"w-full sm:w-auto\"\n />\n @if (!readonly()) {\n <mt-button\n [label]=\"\n delegationForEdit()\n ? t('delegations.common.update')\n : t('delegations.common.create')\n \"\n [loading]=\"isSaving()\"\n [disabled]=\"!delegationFormControl.valid || !canSubmit()\"\n (click)=\"onSubmit()\"\n styleClass=\"w-full sm:w-auto\"\n />\n }\n </div>\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: DynamicForm, selector: "mt-dynamic-form", inputs: ["formConfig", "forcedHiddenFieldKeys", "preserveForcedHiddenValues", "visibleSectionKeys"], outputs: ["runtimeMessagesChange"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: ScopePicker, selector: "mt-scope-picker", inputs: ["scope", "readonly", "adminMode", "delegatorUserId"], outputs: ["scopeChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2291
2636
  }
2292
2637
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationForm, decorators: [{
2293
2638
  type: Component,
@@ -2298,7 +2643,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
2298
2643
  ReactiveFormsModule,
2299
2644
  ScopePicker,
2300
2645
  TranslocoDirective,
2301
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div\n [class]=\"\n 'flex h-full min-h-0 min-w-0 flex-col overflow-hidden ' +\n modal.contentClass\n \"\n >\n <div\n class=\"grid min-h-0 flex-1 gap-4 overflow-y-auto p-4 max-[640px]:p-3 lg:grid-cols-[minmax(0,28rem)_minmax(0,1fr)] lg:items-start\"\n >\n <section\n class=\"flex flex-col gap-4 rounded-3xl border border-surface-200 bg-surface-0 p-4 shadow-xs lg:sticky lg:top-0\"\n >\n <mt-dynamic-form\n [formConfig]=\"formConfig()\"\n [formControl]=\"delegationFormControl\"\n />\n </section>\n\n <section\n class=\"flex min-h-0 flex-col gap-4 rounded-3xl border border-surface-200 bg-surface-0 p-4 shadow-xs\"\n >\n <div class=\"flex flex-col gap-2\">\n <div\n class=\"inline-flex w-fit items-center gap-2 rounded-full bg-primary-50 px-3 py-1 text-xs font-medium text-primary-700\"\n >\n <span class=\"inline-flex size-2 rounded-full bg-primary-500\"></span>\n {{ t(\"delegations.scope.previewTitle\") }}\n </div>\n <h3 class=\"text-xl font-semibold text-surface-900\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </h3>\n @if (adminMode() && !showScopePicker()) {\n <p class=\"text-sm text-surface-500\">\n {{ t(\"delegations.form.selectDelegatorFirst\") }}\n </p>\n } @else {\n <p class=\"text-sm text-surface-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </p>\n }\n </div>\n\n @if (showScopePicker()) {\n <mt-scope-picker\n [(scope)]=\"scope\"\n [readonly]=\"readonly()\"\n [adminMode]=\"adminMode()\"\n [delegatorUserId]=\"selectedDelegatorId()\"\n ></mt-scope-picker>\n } @else {\n <div\n class=\"flex min-h-72 flex-col items-center justify-center gap-4 rounded-3xl border border-dashed border-surface-300 bg-surface-50 px-6 py-8 text-center\"\n >\n <div\n class=\"flex size-18 items-center justify-center rounded-3xl bg-surface-0 text-primary shadow-sm\"\n >\n <svg\n viewBox=\"0 0 64 64\"\n class=\"size-10\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <rect\n x=\"10\"\n y=\"12\"\n width=\"44\"\n height=\"40\"\n rx=\"12\"\n class=\"fill-primary/10 stroke-primary/35\"\n stroke-width=\"2\"\n />\n <path\n d=\"M22 28H42\"\n class=\"stroke-primary\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n />\n <path\n d=\"M22 36H34\"\n class=\"stroke-primary/70\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n />\n <circle\n cx=\"45\"\n cy=\"20\"\n r=\"7\"\n class=\"fill-surface-0 stroke-primary\"\n stroke-width=\"2\"\n />\n <path\n d=\"M45 17V23\"\n class=\"stroke-primary\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n />\n <path\n d=\"M42 20H48\"\n class=\"stroke-primary\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n />\n </svg>\n </div>\n <div class=\"space-y-1\">\n <p class=\"text-base font-medium text-surface-900\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </p>\n <p class=\"text-sm text-surface-500\">\n {{ t(\"delegations.form.selectDelegatorFirst\") }}\n </p>\n </div>\n </div>\n }\n </section>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n [label]=\"t('delegations.common.cancel')\"\n variant=\"outlined\"\n (click)=\"ref.close()\"\n styleClass=\"w-full sm:w-auto\"\n />\n @if (!readonly()) {\n <mt-button\n [label]=\"\n delegationForEdit()\n ? t('delegations.common.update')\n : t('delegations.common.create')\n \"\n [loading]=\"isSaving()\"\n [disabled]=\"!delegationFormControl.valid || !canSubmit()\"\n (click)=\"onSubmit()\"\n styleClass=\"w-full sm:w-auto\"\n />\n }\n </div>\n </div>\n</ng-container>\n" }]
2646
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div\n [class]=\"\n 'flex h-full min-h-0 min-w-0 flex-col overflow-hidden ' +\n modal.contentClass\n \"\n >\n <div\n class=\"grid min-h-0 flex-1 gap-4 overflow-y-auto p-4 max-[640px]:p-3 lg:grid-cols-[minmax(0,28rem)_minmax(0,1fr)] lg:items-start\"\n >\n <section\n class=\"flex flex-col gap-4 rounded-3xl border border-surface-200 bg-surface-0 p-4 shadow-xs lg:sticky lg:top-0\"\n >\n <mt-dynamic-form\n [formConfig]=\"formConfig()\"\n [formControl]=\"delegationFormControl\"\n />\n </section>\n\n <section class=\"flex min-h-0 flex-col gap-3\">\n <div class=\"flex flex-col gap-1\">\n <h3 class=\"text-xl font-semibold text-surface-900\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </h3>\n <p class=\"text-sm text-surface-500\">\n {{\n adminMode() && !showScopePicker()\n ? t(\"delegations.form.selectDelegatorFirst\")\n : t(\"delegations.scope.permissionsSubtitle\")\n }}\n </p>\n </div>\n\n @if (showScopePicker()) {\n <mt-scope-picker\n [(scope)]=\"scope\"\n [readonly]=\"readonly()\"\n [adminMode]=\"adminMode()\"\n [delegatorUserId]=\"selectedDelegatorId()\"\n ></mt-scope-picker>\n } @else {\n <div\n class=\"flex min-h-72 flex-col items-center justify-center gap-4 rounded-3xl border border-dashed border-surface-300 bg-surface-50 px-6 py-8 text-center\"\n >\n <div\n class=\"flex size-18 items-center justify-center rounded-3xl bg-surface-0 text-primary shadow-sm\"\n >\n <svg\n viewBox=\"0 0 64 64\"\n class=\"size-10\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n aria-hidden=\"true\"\n >\n <rect\n x=\"10\"\n y=\"12\"\n width=\"44\"\n height=\"40\"\n rx=\"12\"\n class=\"fill-primary/10 stroke-primary/35\"\n stroke-width=\"2\"\n />\n <path\n d=\"M22 28H42\"\n class=\"stroke-primary\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n />\n <path\n d=\"M22 36H34\"\n class=\"stroke-primary/70\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n />\n <circle\n cx=\"45\"\n cy=\"20\"\n r=\"7\"\n class=\"fill-surface-0 stroke-primary\"\n stroke-width=\"2\"\n />\n <path\n d=\"M45 17V23\"\n class=\"stroke-primary\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n />\n <path\n d=\"M42 20H48\"\n class=\"stroke-primary\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n />\n </svg>\n </div>\n <div class=\"space-y-1\">\n <p class=\"text-base font-medium text-surface-900\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </p>\n <p class=\"text-sm text-surface-500\">\n {{ t(\"delegations.form.selectDelegatorFirst\") }}\n </p>\n </div>\n </div>\n }\n </section>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n [label]=\"t('delegations.common.cancel')\"\n variant=\"outlined\"\n (click)=\"ref.close()\"\n styleClass=\"w-full sm:w-auto\"\n />\n @if (!readonly()) {\n <mt-button\n [label]=\"\n delegationForEdit()\n ? t('delegations.common.update')\n : t('delegations.common.create')\n \"\n [loading]=\"isSaving()\"\n [disabled]=\"!delegationFormControl.valid || !canSubmit()\"\n (click)=\"onSubmit()\"\n styleClass=\"w-full sm:w-auto\"\n />\n }\n </div>\n </div>\n</ng-container>\n" }]
2302
2647
  }], ctorParameters: () => [], propDecorators: { delegationForEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegationForEdit", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], adminMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "adminMode", required: false }] }] } });
2303
2648
 
2304
2649
  /**
@@ -2310,19 +2655,40 @@ class DelegationDetailDrawer {
2310
2655
  facade = inject(DelegationsFacade);
2311
2656
  transloco = inject(TranslocoService);
2312
2657
  ref = inject(ModalRef);
2313
- tabs = [
2314
- { key: 'overview', i18n: 'delegations.action.view' },
2315
- { key: 'scope', i18n: 'delegations.column.scopeSummary' },
2316
- ];
2317
2658
  activeTab = signal('overview', ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
2659
+ tabOptions = computed(() => [
2660
+ {
2661
+ value: 'overview',
2662
+ label: this.transloco.translate('delegations.tabs.overview'),
2663
+ },
2664
+ {
2665
+ value: 'scope',
2666
+ label: this.transloco.translate('delegations.column.scopeSummary'),
2667
+ },
2668
+ ], ...(ngDevMode ? [{ debugName: "tabOptions" }] : /* istanbul ignore next */ []));
2669
+ weekdayKeys = [
2670
+ 'sunday',
2671
+ 'monday',
2672
+ 'tuesday',
2673
+ 'wednesday',
2674
+ 'thursday',
2675
+ 'friday',
2676
+ 'saturday',
2677
+ ];
2318
2678
  isLoading = this.facade.isLoadingDetail;
2319
2679
  detail = this.facade.detail;
2320
2680
  row = computed(() => this.detail()?.row ?? null, ...(ngDevMode ? [{ debugName: "row" }] : /* istanbul ignore next */ []));
2321
2681
  ngOnInit() {
2322
2682
  this.facade.getDetail(this.delegationId(), this.adminMode());
2323
2683
  }
2324
- setTab(tab) {
2325
- this.activeTab.set(tab);
2684
+ formatSpecificDays(days) {
2685
+ if (!days?.length) {
2686
+ return this.transloco.translate('delegations.form.fullRange');
2687
+ }
2688
+ return days
2689
+ .filter((d) => d >= 0 && d < this.weekdayKeys.length)
2690
+ .map((d) => this.transloco.translate(`delegations.days.${this.weekdayKeys[d]}`))
2691
+ .join(', ');
2326
2692
  }
2327
2693
  approvalLabel() {
2328
2694
  const s = this.row()?.approval?.approvalStatus ?? 'NotRequired';
@@ -2341,7 +2707,7 @@ class DelegationDetailDrawer {
2341
2707
  return toDelegationUserEntity(party, { label });
2342
2708
  }
2343
2709
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationDetailDrawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
2344
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationDetailDrawer, isStandalone: true, selector: "mt-delegation-detail-drawer", inputs: { delegationId: { classPropertyName: "delegationId", publicName: "delegationId", isSignal: true, isRequired: true, transformFunction: null }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col h-full\">\r\n <!-- Tabs -->\r\n <div class=\"flex border-b border-gray-200 px-4\">\r\n @for (tab of tabs; track $index) {\r\n <button\r\n type=\"button\"\r\n class=\"px-3 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\r\n [class.border-primary]=\"activeTab() === tab.key\"\r\n [class.text-primary]=\"activeTab() === tab.key\"\r\n [class.border-transparent]=\"activeTab() !== tab.key\"\r\n [class.text-gray-500]=\"activeTab() !== tab.key\"\r\n (click)=\"setTab(tab.key)\"\r\n >\r\n {{ t(tab.i18n) }}\r\n </button>\r\n }\r\n </div>\r\n\r\n <!-- Body -->\r\n <div class=\"flex-1 overflow-y-auto p-4\">\r\n @if (isLoading() && !detail()) {\r\n <p-skeleton height=\"2rem\" class=\"mb-3\"></p-skeleton>\r\n <p-skeleton height=\"6rem\" class=\"mb-3\"></p-skeleton>\r\n <p-skeleton height=\"6rem\"></p-skeleton>\r\n } @else if (detail(); as d) {\r\n @if (activeTab() === \"overview\") {\r\n <div class=\"flex flex-col gap-4\">\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <div class=\"flex items-center gap-3 min-w-0\">\r\n <mt-entity-preview\r\n [data]=\"\r\n userEntity(\r\n d.row.delegator,\r\n t('delegations.column.delegatorName')\r\n )\r\n \"\r\n ></mt-entity-preview>\r\n </div>\r\n <mt-delegation-status-chip\r\n [status]=\"d.status.effectiveStatus\"\r\n ></mt-delegation-status-chip>\r\n </div>\r\n\r\n <div class=\"grid grid-cols-2 gap-4\">\r\n <div class=\"flex flex-col\">\r\n <mt-entity-preview\r\n [data]=\"\r\n userEntity(\r\n d.row.delegatedUser,\r\n t('delegations.column.delegatedTo')\r\n )\r\n \"\r\n ></mt-entity-preview>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.column.approval\") }}\r\n </span>\r\n <span class=\"text-sm text-gray-900\">{{ approvalLabel() }}</span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.column.startDate\") }}\r\n </span>\r\n <span class=\"text-sm text-gray-900\">\r\n {{ d.row.startsAtUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.column.endDate\") }}\r\n </span>\r\n <span class=\"text-sm text-gray-900\">\r\n {{ d.row.endsAtUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.form.timeZone\") }}\r\n </span>\r\n <span class=\"text-sm text-gray-900\">{{\r\n d.row.timeZoneId\r\n }}</span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.column.days\") }}\r\n </span>\r\n <span class=\"text-sm text-gray-900\">\r\n @if (d.row.dayRuleMode === \"FullRange\") {\r\n {{ t(\"delegations.form.fullRange\") }}\r\n } @else {\r\n {{ d.row.specificDays.join(\", \") }}\r\n }\r\n </span>\r\n </div>\r\n </div>\r\n\r\n @if (d.row.cancellation?.cancellationReason) {\r\n <div\r\n class=\"rounded-md border border-gray-200 bg-surface-50 p-3 text-sm text-gray-800\"\r\n >\r\n <div class=\"font-medium\">\r\n {{ t(\"delegations.form.cancellationReason\") }}\r\n </div>\r\n <div>{{ d.row.cancellation.cancellationReason }}</div>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Scope tab -->\r\n <div class=\"flex flex-col gap-3\">\r\n <div class=\"text-sm text-gray-900\">\r\n {{\r\n formatScopeSummary(d.row.scopeSummary) ||\r\n t(\"delegations.column.scopeSummary\")\r\n }}\r\n </div>\r\n <div class=\"flex flex-col gap-2\">\r\n @for (grant of d.scope.grants; track $index) {\r\n <div\r\n class=\"border border-gray-200 rounded-md px-3 py-2 flex flex-col gap-1\"\r\n >\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <span class=\"text-sm font-medium text-gray-900\">\r\n {{ getScopeTargetLabel(grant.target) }}\r\n </span>\r\n <span class=\"text-xs text-gray-500\">\r\n {{ grant.target.targetType }}\r\n </span>\r\n </div>\r\n <span\r\n class=\"text-xs px-2 py-0.5 rounded-md bg-primary-50 text-primary w-fit\"\r\n >\r\n {{ getScopeActionLabel(grant.action) }}\r\n </span>\r\n </div>\r\n }\r\n @if (d.scope.grants.length === 0) {\r\n <p class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.scope.noSelection\") }}\r\n </p>\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div\r\n class=\"border-t border-gray-200 px-4 py-3 flex items-center justify-end\"\r\n >\r\n <mt-button\r\n [label]=\"t('delegations.common.cancel')\"\r\n variant=\"outlined\"\r\n (click)=\"ref.close()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationStatusChip, selector: "mt-delegation-status-chip", inputs: ["status"] }, { kind: "pipe", type: i2.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2710
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationDetailDrawer, isStandalone: true, selector: "mt-delegation-detail-drawer", inputs: { delegationId: { classPropertyName: "delegationId", publicName: "delegationId", isSignal: true, isRequired: true, transformFunction: null }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col h-full\">\r\n <!-- Tabs -->\r\n <div class=\"border-b border-surface-200 px-4 pt-2\">\r\n <mt-tabs\r\n mode=\"underline\"\r\n [(active)]=\"activeTab\"\r\n [options]=\"tabOptions()\"\r\n fluid\r\n ></mt-tabs>\r\n </div>\r\n\r\n <!-- Body -->\r\n <div class=\"flex-1 overflow-y-auto p-4\">\r\n @if (isLoading() && !detail()) {\r\n <p-skeleton height=\"2rem\" class=\"mb-3\"></p-skeleton>\r\n <p-skeleton height=\"6rem\" class=\"mb-3\"></p-skeleton>\r\n <p-skeleton height=\"6rem\"></p-skeleton>\r\n } @else if (detail(); as d) {\r\n @if (activeTab() === \"overview\") {\r\n <div class=\"flex flex-col gap-4\">\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <div class=\"flex items-center gap-3 min-w-0\">\r\n <mt-entity-preview\r\n [data]=\"\r\n userEntity(\r\n d.row.delegator,\r\n t('delegations.column.delegatorName')\r\n )\r\n \"\r\n ></mt-entity-preview>\r\n </div>\r\n <mt-delegation-status-chip\r\n [status]=\"d.status.effectiveStatus\"\r\n ></mt-delegation-status-chip>\r\n </div>\r\n\r\n <div class=\"grid grid-cols-2 gap-4\">\r\n <div class=\"flex flex-col\">\r\n <mt-entity-preview\r\n [data]=\"\r\n userEntity(\r\n d.row.delegatedUser,\r\n t('delegations.column.delegatedTo')\r\n )\r\n \"\r\n ></mt-entity-preview>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.approval\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">{{ approvalLabel() }}</span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.startDate\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n {{ d.row.startsAtUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.endDate\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n {{ d.row.endsAtUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.form.timeZone\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">{{\r\n d.row.timeZoneId\r\n }}</span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.days\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n @if (d.row.dayRuleMode === \"FullRange\") {\r\n {{ t(\"delegations.form.fullRange\") }}\r\n } @else {\r\n {{ formatSpecificDays(d.row.specificDays) }}\r\n }\r\n </span>\r\n </div>\r\n </div>\r\n\r\n @if (d.row.cancellation?.cancellationReason) {\r\n <div\r\n class=\"rounded-md border border-surface-200 bg-surface-50 p-3 text-sm text-surface-800\"\r\n >\r\n <div class=\"font-medium\">\r\n {{ t(\"delegations.form.cancellationReason\") }}\r\n </div>\r\n <div>{{ d.row.cancellation.cancellationReason }}</div>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Scope tab -->\r\n <div class=\"flex flex-col gap-3\">\r\n <div class=\"text-sm text-surface-900\">\r\n {{\r\n formatScopeSummary(d.row.scopeSummary) ||\r\n t(\"delegations.column.scopeSummary\")\r\n }}\r\n </div>\r\n <div class=\"flex flex-col gap-2\">\r\n @for (grant of d.scope.grants; track $index) {\r\n <div\r\n class=\"border border-surface-200 rounded-md px-3 py-2 flex flex-col gap-1\"\r\n >\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <span class=\"text-sm font-medium text-surface-900\">\r\n {{ getScopeTargetLabel(grant.target) }}\r\n </span>\r\n <span class=\"text-xs text-surface-500\">\r\n {{ grant.target.targetType }}\r\n </span>\r\n </div>\r\n <span\r\n class=\"text-xs px-2 py-0.5 rounded-md bg-primary-50 text-primary w-fit\"\r\n >\r\n {{ getScopeActionLabel(grant.action) }}\r\n </span>\r\n </div>\r\n }\r\n @if (d.scope.grants.length === 0) {\r\n <p class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.scope.noSelection\") }}\r\n </p>\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div\r\n class=\"border-t border-surface-200 px-4 py-3 flex items-center justify-end\"\r\n >\r\n <mt-button\r\n [label]=\"t('delegations.common.cancel')\"\r\n variant=\"outlined\"\r\n (click)=\"ref.close()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i3.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationStatusChip, selector: "mt-delegation-status-chip", inputs: ["status"] }, { kind: "pipe", type: i1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2345
2711
  }
2346
2712
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationDetailDrawer, decorators: [{
2347
2713
  type: Component,
@@ -2350,9 +2716,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
2350
2716
  Button,
2351
2717
  EntityPreview,
2352
2718
  SkeletonModule,
2719
+ Tabs,
2353
2720
  TranslocoDirective,
2354
2721
  DelegationStatusChip,
2355
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col h-full\">\r\n <!-- Tabs -->\r\n <div class=\"flex border-b border-gray-200 px-4\">\r\n @for (tab of tabs; track $index) {\r\n <button\r\n type=\"button\"\r\n class=\"px-3 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\r\n [class.border-primary]=\"activeTab() === tab.key\"\r\n [class.text-primary]=\"activeTab() === tab.key\"\r\n [class.border-transparent]=\"activeTab() !== tab.key\"\r\n [class.text-gray-500]=\"activeTab() !== tab.key\"\r\n (click)=\"setTab(tab.key)\"\r\n >\r\n {{ t(tab.i18n) }}\r\n </button>\r\n }\r\n </div>\r\n\r\n <!-- Body -->\r\n <div class=\"flex-1 overflow-y-auto p-4\">\r\n @if (isLoading() && !detail()) {\r\n <p-skeleton height=\"2rem\" class=\"mb-3\"></p-skeleton>\r\n <p-skeleton height=\"6rem\" class=\"mb-3\"></p-skeleton>\r\n <p-skeleton height=\"6rem\"></p-skeleton>\r\n } @else if (detail(); as d) {\r\n @if (activeTab() === \"overview\") {\r\n <div class=\"flex flex-col gap-4\">\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <div class=\"flex items-center gap-3 min-w-0\">\r\n <mt-entity-preview\r\n [data]=\"\r\n userEntity(\r\n d.row.delegator,\r\n t('delegations.column.delegatorName')\r\n )\r\n \"\r\n ></mt-entity-preview>\r\n </div>\r\n <mt-delegation-status-chip\r\n [status]=\"d.status.effectiveStatus\"\r\n ></mt-delegation-status-chip>\r\n </div>\r\n\r\n <div class=\"grid grid-cols-2 gap-4\">\r\n <div class=\"flex flex-col\">\r\n <mt-entity-preview\r\n [data]=\"\r\n userEntity(\r\n d.row.delegatedUser,\r\n t('delegations.column.delegatedTo')\r\n )\r\n \"\r\n ></mt-entity-preview>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.column.approval\") }}\r\n </span>\r\n <span class=\"text-sm text-gray-900\">{{ approvalLabel() }}</span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.column.startDate\") }}\r\n </span>\r\n <span class=\"text-sm text-gray-900\">\r\n {{ d.row.startsAtUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.column.endDate\") }}\r\n </span>\r\n <span class=\"text-sm text-gray-900\">\r\n {{ d.row.endsAtUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.form.timeZone\") }}\r\n </span>\r\n <span class=\"text-sm text-gray-900\">{{\r\n d.row.timeZoneId\r\n }}</span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.column.days\") }}\r\n </span>\r\n <span class=\"text-sm text-gray-900\">\r\n @if (d.row.dayRuleMode === \"FullRange\") {\r\n {{ t(\"delegations.form.fullRange\") }}\r\n } @else {\r\n {{ d.row.specificDays.join(\", \") }}\r\n }\r\n </span>\r\n </div>\r\n </div>\r\n\r\n @if (d.row.cancellation?.cancellationReason) {\r\n <div\r\n class=\"rounded-md border border-gray-200 bg-surface-50 p-3 text-sm text-gray-800\"\r\n >\r\n <div class=\"font-medium\">\r\n {{ t(\"delegations.form.cancellationReason\") }}\r\n </div>\r\n <div>{{ d.row.cancellation.cancellationReason }}</div>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Scope tab -->\r\n <div class=\"flex flex-col gap-3\">\r\n <div class=\"text-sm text-gray-900\">\r\n {{\r\n formatScopeSummary(d.row.scopeSummary) ||\r\n t(\"delegations.column.scopeSummary\")\r\n }}\r\n </div>\r\n <div class=\"flex flex-col gap-2\">\r\n @for (grant of d.scope.grants; track $index) {\r\n <div\r\n class=\"border border-gray-200 rounded-md px-3 py-2 flex flex-col gap-1\"\r\n >\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <span class=\"text-sm font-medium text-gray-900\">\r\n {{ getScopeTargetLabel(grant.target) }}\r\n </span>\r\n <span class=\"text-xs text-gray-500\">\r\n {{ grant.target.targetType }}\r\n </span>\r\n </div>\r\n <span\r\n class=\"text-xs px-2 py-0.5 rounded-md bg-primary-50 text-primary w-fit\"\r\n >\r\n {{ getScopeActionLabel(grant.action) }}\r\n </span>\r\n </div>\r\n }\r\n @if (d.scope.grants.length === 0) {\r\n <p class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.scope.noSelection\") }}\r\n </p>\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div\r\n class=\"border-t border-gray-200 px-4 py-3 flex items-center justify-end\"\r\n >\r\n <mt-button\r\n [label]=\"t('delegations.common.cancel')\"\r\n variant=\"outlined\"\r\n (click)=\"ref.close()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%}\n"] }]
2722
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col h-full\">\r\n <!-- Tabs -->\r\n <div class=\"border-b border-surface-200 px-4 pt-2\">\r\n <mt-tabs\r\n mode=\"underline\"\r\n [(active)]=\"activeTab\"\r\n [options]=\"tabOptions()\"\r\n fluid\r\n ></mt-tabs>\r\n </div>\r\n\r\n <!-- Body -->\r\n <div class=\"flex-1 overflow-y-auto p-4\">\r\n @if (isLoading() && !detail()) {\r\n <p-skeleton height=\"2rem\" class=\"mb-3\"></p-skeleton>\r\n <p-skeleton height=\"6rem\" class=\"mb-3\"></p-skeleton>\r\n <p-skeleton height=\"6rem\"></p-skeleton>\r\n } @else if (detail(); as d) {\r\n @if (activeTab() === \"overview\") {\r\n <div class=\"flex flex-col gap-4\">\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <div class=\"flex items-center gap-3 min-w-0\">\r\n <mt-entity-preview\r\n [data]=\"\r\n userEntity(\r\n d.row.delegator,\r\n t('delegations.column.delegatorName')\r\n )\r\n \"\r\n ></mt-entity-preview>\r\n </div>\r\n <mt-delegation-status-chip\r\n [status]=\"d.status.effectiveStatus\"\r\n ></mt-delegation-status-chip>\r\n </div>\r\n\r\n <div class=\"grid grid-cols-2 gap-4\">\r\n <div class=\"flex flex-col\">\r\n <mt-entity-preview\r\n [data]=\"\r\n userEntity(\r\n d.row.delegatedUser,\r\n t('delegations.column.delegatedTo')\r\n )\r\n \"\r\n ></mt-entity-preview>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.approval\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">{{ approvalLabel() }}</span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.startDate\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n {{ d.row.startsAtUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.endDate\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n {{ d.row.endsAtUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.form.timeZone\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">{{\r\n d.row.timeZoneId\r\n }}</span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.days\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n @if (d.row.dayRuleMode === \"FullRange\") {\r\n {{ t(\"delegations.form.fullRange\") }}\r\n } @else {\r\n {{ formatSpecificDays(d.row.specificDays) }}\r\n }\r\n </span>\r\n </div>\r\n </div>\r\n\r\n @if (d.row.cancellation?.cancellationReason) {\r\n <div\r\n class=\"rounded-md border border-surface-200 bg-surface-50 p-3 text-sm text-surface-800\"\r\n >\r\n <div class=\"font-medium\">\r\n {{ t(\"delegations.form.cancellationReason\") }}\r\n </div>\r\n <div>{{ d.row.cancellation.cancellationReason }}</div>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Scope tab -->\r\n <div class=\"flex flex-col gap-3\">\r\n <div class=\"text-sm text-surface-900\">\r\n {{\r\n formatScopeSummary(d.row.scopeSummary) ||\r\n t(\"delegations.column.scopeSummary\")\r\n }}\r\n </div>\r\n <div class=\"flex flex-col gap-2\">\r\n @for (grant of d.scope.grants; track $index) {\r\n <div\r\n class=\"border border-surface-200 rounded-md px-3 py-2 flex flex-col gap-1\"\r\n >\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <span class=\"text-sm font-medium text-surface-900\">\r\n {{ getScopeTargetLabel(grant.target) }}\r\n </span>\r\n <span class=\"text-xs text-surface-500\">\r\n {{ grant.target.targetType }}\r\n </span>\r\n </div>\r\n <span\r\n class=\"text-xs px-2 py-0.5 rounded-md bg-primary-50 text-primary w-fit\"\r\n >\r\n {{ getScopeActionLabel(grant.action) }}\r\n </span>\r\n </div>\r\n }\r\n @if (d.scope.grants.length === 0) {\r\n <p class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.scope.noSelection\") }}\r\n </p>\r\n }\r\n </div>\r\n </div>\r\n }\r\n }\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div\r\n class=\"border-t border-surface-200 px-4 py-3 flex items-center justify-end\"\r\n >\r\n <mt-button\r\n [label]=\"t('delegations.common.cancel')\"\r\n variant=\"outlined\"\r\n (click)=\"ref.close()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%}\n"] }]
2356
2723
  }], propDecorators: { delegationId: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegationId", required: true }] }], adminMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "adminMode", required: false }] }] } });
2357
2724
 
2358
2725
  function defaultTrueBooleanAttribute(value) {
@@ -2503,14 +2870,19 @@ class DelegationsList {
2503
2870
  hidden: (row) => !this.has(row, 'StartSession'),
2504
2871
  },
2505
2872
  ], ...(ngDevMode ? [{ debugName: "rowActions" }] : /* istanbul ignore next */ []));
2873
+ /**
2874
+ * Table status accent colors. Kept in visual sync with the canonical palette
2875
+ * in `delegation-status-chip` (its light-mode text colors) so the list table
2876
+ * and the status chip render the same hue for each status.
2877
+ */
2506
2878
  statusColors = {
2507
- Active: '#047857',
2508
- Scheduled: '#1d4ed8',
2509
- PendingApproval: '#b45309',
2510
- InactiveToday: '#1d4ed8',
2879
+ Active: '#166534',
2880
+ Scheduled: '#1e40af',
2881
+ PendingApproval: '#854d0e',
2882
+ InactiveToday: '#1e40af',
2511
2883
  Expired: '#4b5563',
2512
- Rejected: '#b91c1c',
2513
- Cancelled: '#4b5563',
2884
+ Rejected: '#991b1b',
2885
+ Cancelled: '#374151',
2514
2886
  };
2515
2887
  statusMap = linkedSignal(() => ({
2516
2888
  Active: {
@@ -2735,7 +3107,7 @@ class DelegationsList {
2735
3107
  this.busyIds.update((ids) => on ? [...ids, key] : ids.filter((id) => id !== key));
2736
3108
  }
2737
3109
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsList, deps: [], target: i0.ɵɵFactoryTarget.Component });
2738
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationsList, isStandalone: true, selector: "mt-delegations-list", inputs: { showBreadcrumb: { classPropertyName: "showBreadcrumb", publicName: "showBreadcrumb", isSignal: true, isRequired: false, transformFunction: null }, showPageShell: { classPropertyName: "showPageShell", publicName: "showPageShell", isSignal: true, isRequired: false, transformFunction: null }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null }, surfaceTitleKey: { classPropertyName: "surfaceTitleKey", publicName: "surfaceTitleKey", isSignal: true, isRequired: false, transformFunction: null }, assignedTabLabelKey: { classPropertyName: "assignedTabLabelKey", publicName: "assignedTabLabelKey", isSignal: true, isRequired: false, transformFunction: null }, assignedDecisionTab: { classPropertyName: "assignedDecisionTab", publicName: "assignedDecisionTab", isSignal: true, isRequired: false, transformFunction: null }, assignedEmptyStateKey: { classPropertyName: "assignedEmptyStateKey", publicName: "assignedEmptyStateKey", isSignal: true, isRequired: false, transformFunction: null }, assignedStatus: { classPropertyName: "assignedStatus", publicName: "assignedStatus", isSignal: true, isRequired: false, transformFunction: null }, showApprovalTab: { classPropertyName: "showApprovalTab", publicName: "showApprovalTab", isSignal: true, isRequired: false, transformFunction: null }, approvalTabLabelKey: { classPropertyName: "approvalTabLabelKey", publicName: "approvalTabLabelKey", isSignal: true, isRequired: false, transformFunction: null }, approvalEmptyStateKey: { classPropertyName: "approvalEmptyStateKey", publicName: "approvalEmptyStateKey", isSignal: true, isRequired: false, transformFunction: null }, approvalStatus: { classPropertyName: "approvalStatus", publicName: "approvalStatus", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <ng-template #tableContent>\r\n <div class=\"flex flex-col gap-4\">\r\n @if (!adminMode()) {\r\n @let assignedLabel =\r\n assignedDecisionTab()\r\n ? t(\"delegations.action.approve\") +\r\n \" / \" +\r\n t(\"delegations.action.reject\")\r\n : t(assignedTabLabelKey());\r\n\r\n @if (showApprovalTab()) {\r\n <mt-tabs\r\n mode=\"underline\"\r\n [(active)]=\"activeTab\"\r\n [options]=\"[\r\n {\r\n value: 'my',\r\n label: t('delegations.myDelegations'),\r\n },\r\n {\r\n value: 'assigned',\r\n label: assignedLabel,\r\n },\r\n {\r\n value: 'approvals',\r\n label: t(approvalTabLabelKey()),\r\n },\r\n ]\"\r\n fluid\r\n (onChange)=\"switchTab($event)\"\r\n ></mt-tabs>\r\n } @else {\r\n <mt-tabs\r\n mode=\"underline\"\r\n [(active)]=\"activeTab\"\r\n [options]=\"[\r\n {\r\n value: 'my',\r\n label: t('delegations.myDelegations'),\r\n },\r\n {\r\n value: 'assigned',\r\n label: assignedLabel,\r\n },\r\n ]\"\r\n fluid\r\n (onChange)=\"switchTab($event)\"\r\n ></mt-tabs>\r\n }\r\n }\r\n\r\n <mt-table\r\n [data]=\"tableRows()\"\r\n [columns]=\"tableColumns()\"\r\n [actions]=\"tableActions()\"\r\n [rowActions]=\"rowActions()\"\r\n [loading]=\"isLoading()\"\r\n [noCard]=\"true\"\r\n >\r\n <ng-template #empty>\r\n <p class=\"text-sm text-surface-500\">\r\n {{ t(emptyStateKey()) }}\r\n </p>\r\n </ng-template>\r\n </mt-table>\r\n </div>\r\n </ng-template>\r\n\r\n <ng-template #adminContent>\r\n <div class=\"flex min-h-full flex-col gap-4\">\r\n @if (showBreadcrumb()) {\r\n <mt-breadcrumb [items]=\"breadcrumbItems()\"></mt-breadcrumb>\r\n }\r\n <ng-container *ngTemplateOutlet=\"tableContent\"></ng-container>\r\n </div>\r\n </ng-template>\r\n\r\n @if (adminMode()) {\r\n @if (showPageShell()) {\r\n <mt-page\r\n [title]=\"surfaceTitle()\"\r\n [avatarIcon]=\"'custom.hierarchy-structure'\"\r\n [contentClass]=\"'max-[1025px]:p-4 max-[640px]:p-3'\"\r\n [avatarStyle]=\"{\r\n '--p-avatar-background': 'var(--p-indigo-50)',\r\n '--p-avatar-color': 'var(--p-indigo-700)',\r\n }\"\r\n (backButtonClick)=\"goBack()\"\r\n backButton\r\n >\r\n <ng-container *ngTemplateOutlet=\"adminContent\"></ng-container>\r\n </mt-page>\r\n } @else {\r\n <ng-container *ngTemplateOutlet=\"adminContent\"></ng-container>\r\n }\r\n } @else {\r\n <div class=\"flex h-full flex-col gap-4 p-4\">\r\n <mt-card [title]=\"surfaceTitle()\">\r\n <ng-container *ngTemplateOutlet=\"tableContent\"></ng-container>\r\n </mt-card>\r\n </div>\r\n }\r\n</ng-container>\r\n", styles: [":host{display:block;min-width:0}:host ::ng-deep mt-table td.mt-actions-column>div{flex-wrap:nowrap;justify-content:flex-end;white-space:nowrap}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Breadcrumb, selector: "mt-breadcrumb", inputs: ["items", "styleClass"], outputs: ["onItemClick"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: Page, selector: "mt-page", inputs: ["backButton", "backButtonIcon", "avatarIcon", "avatarStyle", "avatarShape", "title", "tabs", "activeTab", "contentClass", "contentId"], outputs: ["backButtonClick", "tabChange"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "filterMode", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "exportable", "printable", "groupable", "cellClickFilter", "freezeActions", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3110
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationsList, isStandalone: true, selector: "mt-delegations-list", inputs: { showBreadcrumb: { classPropertyName: "showBreadcrumb", publicName: "showBreadcrumb", isSignal: true, isRequired: false, transformFunction: null }, showPageShell: { classPropertyName: "showPageShell", publicName: "showPageShell", isSignal: true, isRequired: false, transformFunction: null }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null }, surfaceTitleKey: { classPropertyName: "surfaceTitleKey", publicName: "surfaceTitleKey", isSignal: true, isRequired: false, transformFunction: null }, assignedTabLabelKey: { classPropertyName: "assignedTabLabelKey", publicName: "assignedTabLabelKey", isSignal: true, isRequired: false, transformFunction: null }, assignedDecisionTab: { classPropertyName: "assignedDecisionTab", publicName: "assignedDecisionTab", isSignal: true, isRequired: false, transformFunction: null }, assignedEmptyStateKey: { classPropertyName: "assignedEmptyStateKey", publicName: "assignedEmptyStateKey", isSignal: true, isRequired: false, transformFunction: null }, assignedStatus: { classPropertyName: "assignedStatus", publicName: "assignedStatus", isSignal: true, isRequired: false, transformFunction: null }, showApprovalTab: { classPropertyName: "showApprovalTab", publicName: "showApprovalTab", isSignal: true, isRequired: false, transformFunction: null }, approvalTabLabelKey: { classPropertyName: "approvalTabLabelKey", publicName: "approvalTabLabelKey", isSignal: true, isRequired: false, transformFunction: null }, approvalEmptyStateKey: { classPropertyName: "approvalEmptyStateKey", publicName: "approvalEmptyStateKey", isSignal: true, isRequired: false, transformFunction: null }, approvalStatus: { classPropertyName: "approvalStatus", publicName: "approvalStatus", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <ng-template #tableContent>\r\n <div class=\"flex flex-col gap-4\">\r\n @if (!adminMode()) {\r\n @let assignedLabel =\r\n assignedDecisionTab()\r\n ? t(\"delegations.action.approve\") +\r\n \" / \" +\r\n t(\"delegations.action.reject\")\r\n : t(assignedTabLabelKey());\r\n\r\n <mt-tabs\r\n mode=\"underline\"\r\n [(active)]=\"activeTab\"\r\n [options]=\"\r\n showApprovalTab()\r\n ? [\r\n { value: 'my', label: t('delegations.myDelegations') },\r\n { value: 'assigned', label: assignedLabel },\r\n { value: 'approvals', label: t(approvalTabLabelKey()) },\r\n ]\r\n : [\r\n { value: 'my', label: t('delegations.myDelegations') },\r\n { value: 'assigned', label: assignedLabel },\r\n ]\r\n \"\r\n fluid\r\n (onChange)=\"switchTab($event)\"\r\n ></mt-tabs>\r\n }\r\n\r\n <mt-table\r\n [data]=\"tableRows()\"\r\n [columns]=\"tableColumns()\"\r\n [actions]=\"tableActions()\"\r\n [rowActions]=\"rowActions()\"\r\n [loading]=\"isLoading()\"\r\n [noCard]=\"true\"\r\n >\r\n <ng-template #empty>\r\n <div\r\n class=\"flex min-h-64 flex-col items-center justify-center gap-4 px-6 py-10 text-center\"\r\n >\r\n <div\r\n class=\"flex size-16 items-center justify-center rounded-3xl bg-surface-0 text-primary shadow-sm\"\r\n >\r\n <svg\r\n viewBox=\"0 0 64 64\"\r\n class=\"size-9\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n aria-hidden=\"true\"\r\n >\r\n <rect\r\n x=\"10\"\r\n y=\"14\"\r\n width=\"44\"\r\n height=\"36\"\r\n rx=\"12\"\r\n class=\"fill-primary/10 stroke-primary/35\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M21 28H43\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M21 36H35\"\r\n class=\"stroke-primary/70\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n </svg>\r\n </div>\r\n <p class=\"max-w-sm text-sm text-surface-500\">\r\n {{ t(emptyStateKey()) }}\r\n </p>\r\n </div>\r\n </ng-template>\r\n </mt-table>\r\n </div>\r\n </ng-template>\r\n\r\n <ng-template #adminContent>\r\n <div class=\"flex min-h-full flex-col gap-4\">\r\n @if (showBreadcrumb()) {\r\n <mt-breadcrumb [items]=\"breadcrumbItems()\"></mt-breadcrumb>\r\n }\r\n <ng-container *ngTemplateOutlet=\"tableContent\"></ng-container>\r\n </div>\r\n </ng-template>\r\n\r\n @if (adminMode()) {\r\n @if (showPageShell()) {\r\n <mt-page\r\n [title]=\"surfaceTitle()\"\r\n [avatarIcon]=\"'custom.hierarchy-structure'\"\r\n [contentClass]=\"'max-[1025px]:p-4 max-[640px]:p-3'\"\r\n [avatarStyle]=\"{\r\n '--p-avatar-background': 'var(--p-indigo-50)',\r\n '--p-avatar-color': 'var(--p-indigo-700)',\r\n }\"\r\n (backButtonClick)=\"goBack()\"\r\n backButton\r\n >\r\n <ng-container *ngTemplateOutlet=\"adminContent\"></ng-container>\r\n </mt-page>\r\n } @else {\r\n <ng-container *ngTemplateOutlet=\"adminContent\"></ng-container>\r\n }\r\n } @else {\r\n <div class=\"flex h-full flex-col gap-4 p-4\">\r\n <mt-card [title]=\"surfaceTitle()\">\r\n <ng-container *ngTemplateOutlet=\"tableContent\"></ng-container>\r\n </mt-card>\r\n </div>\r\n }\r\n</ng-container>\r\n", styles: [":host{display:block;min-width:0}:host ::ng-deep mt-table td.mt-actions-column>div{flex-wrap:nowrap;justify-content:flex-end;white-space:nowrap}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Breadcrumb, selector: "mt-breadcrumb", inputs: ["items", "styleClass"], outputs: ["onItemClick"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: Page, selector: "mt-page", inputs: ["backButton", "backButtonIcon", "avatarIcon", "avatarStyle", "avatarShape", "title", "tabs", "activeTab", "contentClass", "contentId"], outputs: ["backButtonClick", "tabChange"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "filterMode", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "exportable", "printable", "groupable", "cellClickFilter", "freezeActions", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2739
3111
  }
2740
3112
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsList, decorators: [{
2741
3113
  type: Component,
@@ -2747,7 +3119,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
2747
3119
  Table,
2748
3120
  Tabs,
2749
3121
  TranslocoDirective,
2750
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <ng-template #tableContent>\r\n <div class=\"flex flex-col gap-4\">\r\n @if (!adminMode()) {\r\n @let assignedLabel =\r\n assignedDecisionTab()\r\n ? t(\"delegations.action.approve\") +\r\n \" / \" +\r\n t(\"delegations.action.reject\")\r\n : t(assignedTabLabelKey());\r\n\r\n @if (showApprovalTab()) {\r\n <mt-tabs\r\n mode=\"underline\"\r\n [(active)]=\"activeTab\"\r\n [options]=\"[\r\n {\r\n value: 'my',\r\n label: t('delegations.myDelegations'),\r\n },\r\n {\r\n value: 'assigned',\r\n label: assignedLabel,\r\n },\r\n {\r\n value: 'approvals',\r\n label: t(approvalTabLabelKey()),\r\n },\r\n ]\"\r\n fluid\r\n (onChange)=\"switchTab($event)\"\r\n ></mt-tabs>\r\n } @else {\r\n <mt-tabs\r\n mode=\"underline\"\r\n [(active)]=\"activeTab\"\r\n [options]=\"[\r\n {\r\n value: 'my',\r\n label: t('delegations.myDelegations'),\r\n },\r\n {\r\n value: 'assigned',\r\n label: assignedLabel,\r\n },\r\n ]\"\r\n fluid\r\n (onChange)=\"switchTab($event)\"\r\n ></mt-tabs>\r\n }\r\n }\r\n\r\n <mt-table\r\n [data]=\"tableRows()\"\r\n [columns]=\"tableColumns()\"\r\n [actions]=\"tableActions()\"\r\n [rowActions]=\"rowActions()\"\r\n [loading]=\"isLoading()\"\r\n [noCard]=\"true\"\r\n >\r\n <ng-template #empty>\r\n <p class=\"text-sm text-surface-500\">\r\n {{ t(emptyStateKey()) }}\r\n </p>\r\n </ng-template>\r\n </mt-table>\r\n </div>\r\n </ng-template>\r\n\r\n <ng-template #adminContent>\r\n <div class=\"flex min-h-full flex-col gap-4\">\r\n @if (showBreadcrumb()) {\r\n <mt-breadcrumb [items]=\"breadcrumbItems()\"></mt-breadcrumb>\r\n }\r\n <ng-container *ngTemplateOutlet=\"tableContent\"></ng-container>\r\n </div>\r\n </ng-template>\r\n\r\n @if (adminMode()) {\r\n @if (showPageShell()) {\r\n <mt-page\r\n [title]=\"surfaceTitle()\"\r\n [avatarIcon]=\"'custom.hierarchy-structure'\"\r\n [contentClass]=\"'max-[1025px]:p-4 max-[640px]:p-3'\"\r\n [avatarStyle]=\"{\r\n '--p-avatar-background': 'var(--p-indigo-50)',\r\n '--p-avatar-color': 'var(--p-indigo-700)',\r\n }\"\r\n (backButtonClick)=\"goBack()\"\r\n backButton\r\n >\r\n <ng-container *ngTemplateOutlet=\"adminContent\"></ng-container>\r\n </mt-page>\r\n } @else {\r\n <ng-container *ngTemplateOutlet=\"adminContent\"></ng-container>\r\n }\r\n } @else {\r\n <div class=\"flex h-full flex-col gap-4 p-4\">\r\n <mt-card [title]=\"surfaceTitle()\">\r\n <ng-container *ngTemplateOutlet=\"tableContent\"></ng-container>\r\n </mt-card>\r\n </div>\r\n }\r\n</ng-container>\r\n", styles: [":host{display:block;min-width:0}:host ::ng-deep mt-table td.mt-actions-column>div{flex-wrap:nowrap;justify-content:flex-end;white-space:nowrap}\n"] }]
3122
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <ng-template #tableContent>\r\n <div class=\"flex flex-col gap-4\">\r\n @if (!adminMode()) {\r\n @let assignedLabel =\r\n assignedDecisionTab()\r\n ? t(\"delegations.action.approve\") +\r\n \" / \" +\r\n t(\"delegations.action.reject\")\r\n : t(assignedTabLabelKey());\r\n\r\n <mt-tabs\r\n mode=\"underline\"\r\n [(active)]=\"activeTab\"\r\n [options]=\"\r\n showApprovalTab()\r\n ? [\r\n { value: 'my', label: t('delegations.myDelegations') },\r\n { value: 'assigned', label: assignedLabel },\r\n { value: 'approvals', label: t(approvalTabLabelKey()) },\r\n ]\r\n : [\r\n { value: 'my', label: t('delegations.myDelegations') },\r\n { value: 'assigned', label: assignedLabel },\r\n ]\r\n \"\r\n fluid\r\n (onChange)=\"switchTab($event)\"\r\n ></mt-tabs>\r\n }\r\n\r\n <mt-table\r\n [data]=\"tableRows()\"\r\n [columns]=\"tableColumns()\"\r\n [actions]=\"tableActions()\"\r\n [rowActions]=\"rowActions()\"\r\n [loading]=\"isLoading()\"\r\n [noCard]=\"true\"\r\n >\r\n <ng-template #empty>\r\n <div\r\n class=\"flex min-h-64 flex-col items-center justify-center gap-4 px-6 py-10 text-center\"\r\n >\r\n <div\r\n class=\"flex size-16 items-center justify-center rounded-3xl bg-surface-0 text-primary shadow-sm\"\r\n >\r\n <svg\r\n viewBox=\"0 0 64 64\"\r\n class=\"size-9\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n aria-hidden=\"true\"\r\n >\r\n <rect\r\n x=\"10\"\r\n y=\"14\"\r\n width=\"44\"\r\n height=\"36\"\r\n rx=\"12\"\r\n class=\"fill-primary/10 stroke-primary/35\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M21 28H43\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M21 36H35\"\r\n class=\"stroke-primary/70\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n </svg>\r\n </div>\r\n <p class=\"max-w-sm text-sm text-surface-500\">\r\n {{ t(emptyStateKey()) }}\r\n </p>\r\n </div>\r\n </ng-template>\r\n </mt-table>\r\n </div>\r\n </ng-template>\r\n\r\n <ng-template #adminContent>\r\n <div class=\"flex min-h-full flex-col gap-4\">\r\n @if (showBreadcrumb()) {\r\n <mt-breadcrumb [items]=\"breadcrumbItems()\"></mt-breadcrumb>\r\n }\r\n <ng-container *ngTemplateOutlet=\"tableContent\"></ng-container>\r\n </div>\r\n </ng-template>\r\n\r\n @if (adminMode()) {\r\n @if (showPageShell()) {\r\n <mt-page\r\n [title]=\"surfaceTitle()\"\r\n [avatarIcon]=\"'custom.hierarchy-structure'\"\r\n [contentClass]=\"'max-[1025px]:p-4 max-[640px]:p-3'\"\r\n [avatarStyle]=\"{\r\n '--p-avatar-background': 'var(--p-indigo-50)',\r\n '--p-avatar-color': 'var(--p-indigo-700)',\r\n }\"\r\n (backButtonClick)=\"goBack()\"\r\n backButton\r\n >\r\n <ng-container *ngTemplateOutlet=\"adminContent\"></ng-container>\r\n </mt-page>\r\n } @else {\r\n <ng-container *ngTemplateOutlet=\"adminContent\"></ng-container>\r\n }\r\n } @else {\r\n <div class=\"flex h-full flex-col gap-4 p-4\">\r\n <mt-card [title]=\"surfaceTitle()\">\r\n <ng-container *ngTemplateOutlet=\"tableContent\"></ng-container>\r\n </mt-card>\r\n </div>\r\n }\r\n</ng-container>\r\n", styles: [":host{display:block;min-width:0}:host ::ng-deep mt-table td.mt-actions-column>div{flex-wrap:nowrap;justify-content:flex-end;white-space:nowrap}\n"] }]
2751
3123
  }], propDecorators: { showBreadcrumb: [{ type: i0.Input, args: [{ isSignal: true, alias: "showBreadcrumb", required: false }] }], showPageShell: [{ type: i0.Input, args: [{ isSignal: true, alias: "showPageShell", required: false }] }], adminMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "adminMode", required: false }] }], surfaceTitleKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "surfaceTitleKey", required: false }] }], assignedTabLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "assignedTabLabelKey", required: false }] }], assignedDecisionTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "assignedDecisionTab", required: false }] }], assignedEmptyStateKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "assignedEmptyStateKey", required: false }] }], assignedStatus: [{ type: i0.Input, args: [{ isSignal: true, alias: "assignedStatus", required: false }] }], showApprovalTab: [{ type: i0.Input, args: [{ isSignal: true, alias: "showApprovalTab", required: false }] }], approvalTabLabelKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "approvalTabLabelKey", required: false }] }], approvalEmptyStateKey: [{ type: i0.Input, args: [{ isSignal: true, alias: "approvalEmptyStateKey", required: false }] }], approvalStatus: [{ type: i0.Input, args: [{ isSignal: true, alias: "approvalStatus", required: false }] }] } });
2752
3124
 
2753
3125
  /**
@@ -2863,5 +3235,5 @@ const appDelegationInterceptor = (req, next) => {
2863
3235
  * Generated bundle index. Do not edit.
2864
3236
  */
2865
3237
 
2866
- export { ApproveDelegation, CancelDelegation, ClearDelegationDetail, ClearScopePreview, CreateDelegationLegacy, CreateDelegationV2, DelegationDetailDrawer, DelegationForm, DelegationSessionActionKey, DelegationSessionFacade, DelegationSessionState, DelegationStatusChip, Delegations, DelegationsActionKey, DelegationsFacade, DelegationsList, DelegationsState, EndDelegationSession, GetActiveAssignedDelegations, GetAdminDelegations, GetApprovalDelegations, GetAssignedDelegations, GetDelegationDetail, GetMyDelegations, GetScopeOptions, LoadDelegationCandidates, PreviewScope, RejectDelegation, RejectDelegationDialog, ScopePicker, StartDelegationSession, StartSessionDialog, SwitchDelegationSession, TopbarDelegationMenu, UpdateDelegationLegacy, UpdateDelegationV2, appDelegationInterceptor };
3238
+ export { ApproveDelegation, CancelDelegation, ClearDelegationDetail, ClearScopePreview, CreateDelegationLegacy, CreateDelegationV2, DelegationDetailDrawer, DelegationForm, DelegationMenuPanel, DelegationSessionActionKey, DelegationSessionFacade, DelegationSessionState, DelegationStatusChip, Delegations, DelegationsActionKey, DelegationsFacade, DelegationsList, DelegationsState, EndDelegationSession, GetActiveAssignedDelegations, GetAdminDelegations, GetApprovalDelegations, GetAssignedDelegations, GetDelegationDetail, GetMyDelegations, GetScopeOptions, LoadDelegationCandidates, PreviewScope, RejectDelegation, RejectDelegationDialog, ScopePicker, StartDelegationSession, StartSessionDialog, SwitchDelegationSession, TopbarDelegationMenu, UpdateDelegationLegacy, UpdateDelegationV2, appDelegationInterceptor };
2867
3239
  //# sourceMappingURL=masterteam-delegations.mjs.map