@masterteam/delegations 0.0.35 → 0.0.37
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
|
|
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 {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
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
|
|
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 {
|
|
29
|
-
import * as
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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,17 @@ 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
|
+
* Suppress the *candidates* trigger (surface them elsewhere, e.g. the user
|
|
645
|
+
* dropdown via {@link DelegationMenuPanel}) while keeping candidate loading +
|
|
646
|
+
* the post-login prompt. The *active* "Acting on behalf of" pill stays visible
|
|
647
|
+
* even when headless — an active delegated session must always be obvious.
|
|
648
|
+
*/
|
|
649
|
+
headless = input(false, ...(ngDevMode ? [{ debugName: "headless" }] : /* istanbul ignore next */ []));
|
|
554
650
|
facade = inject(DelegationSessionFacade);
|
|
555
651
|
modal = inject(ModalService);
|
|
556
652
|
transloco = inject(TranslocoService);
|
|
557
|
-
popover = viewChild
|
|
653
|
+
popover = viewChild('popover', ...(ngDevMode ? [{ debugName: "popover" }] : /* istanbul ignore next */ []));
|
|
558
654
|
active = this.facade.active;
|
|
559
655
|
candidates = this.facade.candidates;
|
|
560
656
|
hasCandidates = this.facade.hasCandidates;
|
|
@@ -585,29 +681,22 @@ class TopbarDelegationMenu {
|
|
|
585
681
|
ngOnInit() {
|
|
586
682
|
this.facade.loadCandidates();
|
|
587
683
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
684
|
+
/** Two-letter initials for a delegator avatar (matches the user-menu pattern). */
|
|
685
|
+
initials(party) {
|
|
686
|
+
const name = party?.displayName?.trim() || party?.email?.trim() || '';
|
|
687
|
+
const parts = name.split(/\s+/).filter(Boolean);
|
|
688
|
+
if (!parts.length)
|
|
689
|
+
return '?';
|
|
690
|
+
if (parts.length === 1) {
|
|
691
|
+
return parts[0].slice(0, 2).toUpperCase();
|
|
692
|
+
}
|
|
693
|
+
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
|
593
694
|
}
|
|
594
695
|
togglePopover(event) {
|
|
595
|
-
this.popover()
|
|
596
|
-
}
|
|
597
|
-
start(row, event) {
|
|
598
|
-
event.stopPropagation();
|
|
599
|
-
this.popover().hide();
|
|
600
|
-
this.openConfirm(row, 'start');
|
|
696
|
+
this.popover()?.toggle(event);
|
|
601
697
|
}
|
|
602
|
-
|
|
603
|
-
|
|
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();
|
|
698
|
+
closePopover() {
|
|
699
|
+
this.popover()?.hide();
|
|
611
700
|
}
|
|
612
701
|
openConfirm(row, intent) {
|
|
613
702
|
this.modal.openModal(StartSessionDialog, 'dialog', {
|
|
@@ -635,21 +724,19 @@ class TopbarDelegationMenu {
|
|
|
635
724
|
});
|
|
636
725
|
}
|
|
637
726
|
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 });
|
|
727
|
+
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 <!-- The active \"Acting on behalf of\" pill stays visible even when headless\n (candidates are surfaced elsewhere, e.g. the user dropdown); an active\n delegated session must always be obvious (doc 09 session banner). -->\n @if (mode() === \"active\" || (!headless() && mode() === \"candidates\")) {\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
728
|
}
|
|
640
729
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: TopbarDelegationMenu, decorators: [{
|
|
641
730
|
type: Component,
|
|
642
731
|
args: [{ selector: 'mt-topbar-delegation-menu', standalone: true, imports: [
|
|
643
732
|
CommonModule,
|
|
644
|
-
|
|
645
|
-
EntityPreview,
|
|
733
|
+
Avatar,
|
|
646
734
|
Icon,
|
|
647
735
|
Popover,
|
|
648
|
-
RouterLink,
|
|
649
736
|
TranslocoDirective,
|
|
650
|
-
|
|
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 }] }] } });
|
|
737
|
+
DelegationMenuPanel,
|
|
738
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <!-- The active \"Acting on behalf of\" pill stays visible even when headless\n (candidates are surfaced elsewhere, e.g. the user dropdown); an active\n delegated session must always be obvious (doc 09 session banner). -->\n @if (mode() === \"active\" || (!headless() && mode() === \"candidates\")) {\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"] }]
|
|
739
|
+
}], 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
740
|
|
|
654
741
|
// ---------------------------------------------------------------------------
|
|
655
742
|
// Lists / detail
|
|
@@ -1340,11 +1427,11 @@ class RejectDelegationDialog {
|
|
|
1340
1427
|
this.ref.close(false);
|
|
1341
1428
|
}
|
|
1342
1429
|
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-
|
|
1430
|
+
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
1431
|
}
|
|
1345
1432
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: RejectDelegationDialog, decorators: [{
|
|
1346
1433
|
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-
|
|
1434
|
+
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
1435
|
}], propDecorators: { delegation: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegation", required: true }] }] } });
|
|
1349
1436
|
|
|
1350
1437
|
class Delegations {
|
|
@@ -1404,7 +1491,7 @@ const DEFAULT_SORT_ORDER = Number.MAX_SAFE_INTEGER;
|
|
|
1404
1491
|
function toArray(value) {
|
|
1405
1492
|
return Array.isArray(value) ? value : [];
|
|
1406
1493
|
}
|
|
1407
|
-
function normalizeGrant
|
|
1494
|
+
function normalizeGrant(grant) {
|
|
1408
1495
|
return {
|
|
1409
1496
|
...grant,
|
|
1410
1497
|
action: normalizeAction(grant.action),
|
|
@@ -1449,7 +1536,7 @@ function groupIdentity(groupType, groupKey, fallbackTargetKey) {
|
|
|
1449
1536
|
return `${groupType}::${groupKey || fallbackTargetKey || 'group'}`;
|
|
1450
1537
|
}
|
|
1451
1538
|
function buildHierarchyGrant(target, action) {
|
|
1452
|
-
return normalizeGrant
|
|
1539
|
+
return normalizeGrant({
|
|
1453
1540
|
applicationKey: normalizeKey(target.applicationKey) ??
|
|
1454
1541
|
normalizeKey(action.applicationKey) ??
|
|
1455
1542
|
'',
|
|
@@ -1587,7 +1674,7 @@ function getLegacyGrantableGrants(options) {
|
|
|
1587
1674
|
if (!options) {
|
|
1588
1675
|
return [];
|
|
1589
1676
|
}
|
|
1590
|
-
const legacyGrants = toArray(options.grants).map(normalizeGrant
|
|
1677
|
+
const legacyGrants = toArray(options.grants).map(normalizeGrant);
|
|
1591
1678
|
if (legacyGrants.length > 0) {
|
|
1592
1679
|
return legacyGrants;
|
|
1593
1680
|
}
|
|
@@ -1616,7 +1703,7 @@ function getLegacyGrantableGrants(options) {
|
|
|
1616
1703
|
if (!action) {
|
|
1617
1704
|
continue;
|
|
1618
1705
|
}
|
|
1619
|
-
const grant = normalizeGrant
|
|
1706
|
+
const grant = normalizeGrant({
|
|
1620
1707
|
applicationKey: applicationKey ??
|
|
1621
1708
|
normalizeKey(action.applicationKey) ??
|
|
1622
1709
|
normalizeKey(target.applicationKey) ??
|
|
@@ -1691,72 +1778,182 @@ function getDelegationGrantableGrants(options) {
|
|
|
1691
1778
|
return getDelegationGrantableGroups(options).flatMap((group) => group.targets.flatMap((targetGroup) => targetGroup.grants));
|
|
1692
1779
|
}
|
|
1693
1780
|
function hasDelegationGrantableScope(options) {
|
|
1694
|
-
return
|
|
1781
|
+
return (getDelegationPermissionTree(options).length > 0 ||
|
|
1782
|
+
getDelegationGrantableGroups(options).length > 0);
|
|
1695
1783
|
}
|
|
1696
|
-
|
|
1697
|
-
|
|
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
|
-
});
|
|
1784
|
+
/** Stable selection key for an operation leaf — independent of accessibilities. */
|
|
1785
|
+
function delegationOperationNodeKey(grant) {
|
|
1786
|
+
return `op:${targetIdentity(grant.target)}::${grant.action.operationKey}`;
|
|
1732
1787
|
}
|
|
1733
|
-
function
|
|
1788
|
+
function buildScopeTarget(node) {
|
|
1789
|
+
const moduleNode = node;
|
|
1734
1790
|
return {
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1791
|
+
applicationKey: node.applicationKey,
|
|
1792
|
+
targetType: node.targetType,
|
|
1793
|
+
targetKey: node.targetKey,
|
|
1794
|
+
name: node.name ?? null,
|
|
1795
|
+
displayName: node.name ?? null,
|
|
1796
|
+
legacyModuleId: node.legacyModuleId ?? null,
|
|
1797
|
+
targetId: node.targetId ?? null,
|
|
1798
|
+
levelId: node.levelId ?? null,
|
|
1799
|
+
levelModuleId: moduleNode.levelModuleId ?? null,
|
|
1800
|
+
domainModuleId: moduleNode.domainModuleId ?? null,
|
|
1801
|
+
moduleKey: moduleNode.moduleKey ?? null,
|
|
1802
|
+
permissionModuleType: node.permissionModuleType ?? null,
|
|
1803
|
+
permissionTargetId: node.permissionTargetId ?? null,
|
|
1739
1804
|
};
|
|
1740
1805
|
}
|
|
1741
|
-
function
|
|
1742
|
-
const
|
|
1743
|
-
.
|
|
1744
|
-
.
|
|
1745
|
-
|
|
1746
|
-
|
|
1806
|
+
function buildOperationGrant(target, operation) {
|
|
1807
|
+
const action = {
|
|
1808
|
+
applicationKey: operation.applicationKey ?? target.applicationKey,
|
|
1809
|
+
operationKey: operation.operationKey,
|
|
1810
|
+
operationKind: operation.operationKind,
|
|
1811
|
+
name: operation.name ?? null,
|
|
1812
|
+
displayName: operation.name ?? operation.displayName ?? null,
|
|
1813
|
+
permissionCommand: operation.permissionCommand ?? null,
|
|
1814
|
+
businessActionCode: operation.businessActionCode ?? null,
|
|
1815
|
+
isHighRisk: operation.isHighRisk ?? false,
|
|
1816
|
+
isDelegableSnapshot: operation.isDelegableSnapshot ?? true,
|
|
1817
|
+
sortOrder: operation.sortOrder ?? null,
|
|
1818
|
+
};
|
|
1819
|
+
return {
|
|
1820
|
+
applicationKey: target.applicationKey,
|
|
1821
|
+
target,
|
|
1822
|
+
action,
|
|
1823
|
+
accessibilities: [],
|
|
1824
|
+
dataFilters: [],
|
|
1825
|
+
constraints: [],
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
function buildOperationNodes(target, operations) {
|
|
1829
|
+
return toArray(operations)
|
|
1830
|
+
.map((operation) => {
|
|
1831
|
+
const grant = buildOperationGrant(target, operation);
|
|
1832
|
+
return {
|
|
1833
|
+
key: delegationOperationNodeKey(grant),
|
|
1834
|
+
kind: 'operation',
|
|
1835
|
+
displayName: operation.name ?? operation.displayName ?? null,
|
|
1836
|
+
fallbackKey: operation.operationKey,
|
|
1837
|
+
isHighRisk: operation.isHighRisk ?? false,
|
|
1838
|
+
grant,
|
|
1839
|
+
sortOrder: normalizeSortOrder(operation.sortOrder),
|
|
1840
|
+
};
|
|
1841
|
+
})
|
|
1842
|
+
.sort((left, right) => compareBySortThenKey(left.sortOrder, right.sortOrder, left.key, right.key))
|
|
1843
|
+
.map(({ sortOrder: _drop, ...node }) => node);
|
|
1844
|
+
}
|
|
1845
|
+
function buildModuleNode(module) {
|
|
1846
|
+
const target = buildScopeTarget(module);
|
|
1847
|
+
return {
|
|
1848
|
+
key: `mod:${targetIdentity(target)}`,
|
|
1849
|
+
kind: 'module',
|
|
1850
|
+
displayName: module.name ?? null,
|
|
1851
|
+
fallbackKey: module.targetKey,
|
|
1852
|
+
children: buildOperationNodes(target, module.operations),
|
|
1853
|
+
sortOrder: normalizeSortOrder(module.sortOrder ?? module.levelModuleId),
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1856
|
+
function buildLevelNode(level) {
|
|
1857
|
+
const target = buildScopeTarget(level);
|
|
1858
|
+
const operationNodes = buildOperationNodes(target, level.operations);
|
|
1859
|
+
const moduleNodes = toArray(level.modules)
|
|
1860
|
+
.map(buildModuleNode)
|
|
1861
|
+
.filter((node) => (node.children?.length ?? 0) > 0)
|
|
1862
|
+
.sort((left, right) => compareBySortThenKey(left.sortOrder, right.sortOrder, left.key, right.key))
|
|
1863
|
+
.map(({ sortOrder: _drop, ...node }) => node);
|
|
1864
|
+
return {
|
|
1865
|
+
key: `lvl:${targetIdentity(target)}`,
|
|
1866
|
+
kind: 'level',
|
|
1867
|
+
displayName: level.name ?? null,
|
|
1868
|
+
fallbackKey: level.targetKey,
|
|
1869
|
+
children: [...operationNodes, ...moduleNodes],
|
|
1870
|
+
sortOrder: normalizeSortOrder(level.sortOrder ?? level.levelId),
|
|
1871
|
+
};
|
|
1872
|
+
}
|
|
1873
|
+
function buildTemplateNode(group) {
|
|
1874
|
+
const levelNodes = toArray(group.levels)
|
|
1875
|
+
.map(buildLevelNode)
|
|
1876
|
+
.filter((node) => (node.children?.length ?? 0) > 0)
|
|
1877
|
+
.sort((left, right) => compareBySortThenKey(left.sortOrder, right.sortOrder, left.key, right.key))
|
|
1878
|
+
.map(({ sortOrder: _drop, ...node }) => node);
|
|
1879
|
+
return {
|
|
1880
|
+
key: `tpl:${group.groupKey}`,
|
|
1881
|
+
kind: 'template',
|
|
1882
|
+
displayName: group.name ?? null,
|
|
1883
|
+
fallbackKey: group.groupKey,
|
|
1884
|
+
children: levelNodes,
|
|
1885
|
+
sortOrder: normalizeSortOrder(group.sortOrder ?? group.templateId),
|
|
1886
|
+
};
|
|
1887
|
+
}
|
|
1888
|
+
function getDelegationPermissionTree(options) {
|
|
1889
|
+
return toArray(options?.permissions)
|
|
1890
|
+
.map(buildTemplateNode)
|
|
1891
|
+
.filter((node) => (node.children?.length ?? 0) > 0)
|
|
1892
|
+
.sort((left, right) => compareBySortThenKey(left.sortOrder, right.sortOrder, left.key, right.key))
|
|
1893
|
+
.map(({ sortOrder: _drop, ...node }) => node);
|
|
1894
|
+
}
|
|
1895
|
+
/** Back-compat: project the legacy group/target/grant shape onto the tree model. */
|
|
1896
|
+
function legacyGroupsToTree(groups) {
|
|
1897
|
+
return groups.map((group) => ({
|
|
1898
|
+
key: `grp:${group.key}`,
|
|
1899
|
+
kind: 'template',
|
|
1900
|
+
displayName: group.displayName ?? null,
|
|
1901
|
+
fallbackKey: group.groupType,
|
|
1902
|
+
children: group.targets.map((targetGroup) => ({
|
|
1903
|
+
key: `tgt:${targetGroup.key}`,
|
|
1904
|
+
kind: 'module',
|
|
1905
|
+
displayName: targetGroup.target.name ?? targetGroup.target.displayName ?? null,
|
|
1906
|
+
fallbackKey: targetGroup.target.targetKey,
|
|
1907
|
+
children: targetGroup.grants.map((grant) => ({
|
|
1908
|
+
key: delegationOperationNodeKey(grant),
|
|
1909
|
+
kind: 'operation',
|
|
1910
|
+
displayName: grant.action.name ?? grant.action.displayName ?? null,
|
|
1911
|
+
fallbackKey: grant.action.operationKey,
|
|
1912
|
+
isHighRisk: grant.action.isHighRisk ?? false,
|
|
1913
|
+
grant,
|
|
1914
|
+
})),
|
|
1915
|
+
})),
|
|
1916
|
+
}));
|
|
1917
|
+
}
|
|
1918
|
+
/**
|
|
1919
|
+
* Unified scope tree for the picker. Prefers the new `permissions` contract;
|
|
1920
|
+
* falls back to the legacy hierarchy/grants projection for older environments.
|
|
1921
|
+
*/
|
|
1922
|
+
function getDelegationScopeTree(options) {
|
|
1923
|
+
const fromPermissions = getDelegationPermissionTree(options);
|
|
1924
|
+
if (fromPermissions.length > 0) {
|
|
1925
|
+
return fromPermissions;
|
|
1747
1926
|
}
|
|
1748
|
-
|
|
1749
|
-
|
|
1927
|
+
return legacyGroupsToTree(getDelegationGrantableGroups(options));
|
|
1928
|
+
}
|
|
1929
|
+
/** All operation-leaf grants in the tree, keyed by their stable node key. */
|
|
1930
|
+
function indexDelegationTreeGrants(nodes) {
|
|
1931
|
+
const index = new Map();
|
|
1932
|
+
const walk = (list) => {
|
|
1933
|
+
for (const node of list) {
|
|
1934
|
+
if (node.kind === 'operation' && node.grant) {
|
|
1935
|
+
index.set(node.key, node.grant);
|
|
1936
|
+
}
|
|
1937
|
+
if (node.children?.length) {
|
|
1938
|
+
walk(node.children);
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
};
|
|
1942
|
+
walk(nodes);
|
|
1943
|
+
return index;
|
|
1750
1944
|
}
|
|
1945
|
+
|
|
1946
|
+
const EMPTY_SCOPE$1 = { grants: [], metadata: {} };
|
|
1751
1947
|
/**
|
|
1752
1948
|
* Hierarchy-based scope picker (doc 03). Driven entirely by `scope/options`:
|
|
1753
|
-
* - renders backend-owned
|
|
1754
|
-
*
|
|
1949
|
+
* - renders the backend-owned Template -> Level -> Module -> operation tree as a
|
|
1950
|
+
* clean, accessible checkbox tree (parents propagate to descendant operations),
|
|
1951
|
+
* - offers optional app-accessibility grants,
|
|
1755
1952
|
* - calls `scope/preview` (debounced) and surfaces summary / warnings / denied.
|
|
1756
1953
|
*
|
|
1757
|
-
* `[(scope)]` two-way binds a `DelegationScopeSelection`.
|
|
1758
|
-
*
|
|
1759
|
-
* options.
|
|
1954
|
+
* `[(scope)]` two-way binds a `DelegationScopeSelection`. Scope grants are the
|
|
1955
|
+
* single source of truth for selection; the component never invents permission
|
|
1956
|
+
* metadata — it only echoes targets/actions/accessibilities returned by options.
|
|
1760
1957
|
*/
|
|
1761
1958
|
class ScopePicker {
|
|
1762
1959
|
scope = model(EMPTY_SCOPE$1, ...(ngDevMode ? [{ debugName: "scope" }] : /* istanbul ignore next */ []));
|
|
@@ -1775,25 +1972,62 @@ class ScopePicker {
|
|
|
1775
1972
|
isLoadingOptions = this.facade.isLoadingScopeOptions;
|
|
1776
1973
|
isPreviewing = this.facade.isPreviewingScope;
|
|
1777
1974
|
errorOptions = this.facade.errorScopeOptions;
|
|
1778
|
-
groups = computed(() => getDelegationGrantableGroups(this.options()), ...(ngDevMode ? [{ debugName: "groups" }] : /* istanbul ignore next */ []));
|
|
1779
1975
|
activeLang = toSignal(this.transloco.langChanges$, {
|
|
1780
1976
|
initialValue: this.transloco.getActiveLang(),
|
|
1781
1977
|
});
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1978
|
+
// --- Permission tree ------------------------------------------------------
|
|
1979
|
+
treeModel = computed(() => getDelegationScopeTree(this.options()), ...(ngDevMode ? [{ debugName: "treeModel" }] : /* istanbul ignore next */ []));
|
|
1980
|
+
hasOptions = computed(() => this.treeModel().length > 0, ...(ngDevMode ? [{ debugName: "hasOptions" }] : /* istanbul ignore next */ []));
|
|
1981
|
+
grantIndex = computed(() => indexDelegationTreeGrants(this.treeModel()), ...(ngDevMode ? [{ debugName: "grantIndex" }] : /* istanbul ignore next */ []));
|
|
1982
|
+
/** nodeKey -> all descendant operation-leaf keys (self for operation nodes). */
|
|
1983
|
+
opKeyMap = computed(() => {
|
|
1984
|
+
const map = new Map();
|
|
1985
|
+
const collect = (node) => {
|
|
1986
|
+
if (node.kind === 'operation') {
|
|
1987
|
+
map.set(node.key, [node.key]);
|
|
1988
|
+
return [node.key];
|
|
1793
1989
|
}
|
|
1990
|
+
const keys = (node.children ?? []).flatMap(collect);
|
|
1991
|
+
map.set(node.key, keys);
|
|
1992
|
+
return keys;
|
|
1993
|
+
};
|
|
1994
|
+
this.treeModel().forEach(collect);
|
|
1995
|
+
return map;
|
|
1996
|
+
}, ...(ngDevMode ? [{ debugName: "opKeyMap" }] : /* istanbul ignore next */ []));
|
|
1997
|
+
/** Stable operation-node keys for the current scope grants. */
|
|
1998
|
+
selectedKeys = computed(() => new Set(this.scope().grants.map(delegationOperationNodeKey)), ...(ngDevMode ? [{ debugName: "selectedKeys" }] : /* istanbul ignore next */ []));
|
|
1999
|
+
selectedCount = computed(() => this.selectedKeys().size, ...(ngDevMode ? [{ debugName: "selectedCount" }] : /* istanbul ignore next */ []));
|
|
2000
|
+
searchTerm = signal('', ...(ngDevMode ? [{ debugName: "searchTerm" }] : /* istanbul ignore next */ []));
|
|
2001
|
+
expandedKeys = signal(new Set(), ...(ngDevMode ? [{ debugName: "expandedKeys" }] : /* istanbul ignore next */ []));
|
|
2002
|
+
filteredTree = computed(() => {
|
|
2003
|
+
const term = this.searchTerm().trim().toLowerCase();
|
|
2004
|
+
if (!term) {
|
|
2005
|
+
return this.treeModel();
|
|
1794
2006
|
}
|
|
1795
|
-
|
|
1796
|
-
|
|
2007
|
+
const prune = (node) => {
|
|
2008
|
+
const selfMatch = this.nodeLabel(node).toLowerCase().includes(term);
|
|
2009
|
+
const children = (node.children ?? [])
|
|
2010
|
+
.map(prune)
|
|
2011
|
+
.filter((child) => !!child);
|
|
2012
|
+
if (children.length) {
|
|
2013
|
+
return { ...node, children };
|
|
2014
|
+
}
|
|
2015
|
+
if (selfMatch) {
|
|
2016
|
+
return node;
|
|
2017
|
+
}
|
|
2018
|
+
return null;
|
|
2019
|
+
};
|
|
2020
|
+
return this.treeModel()
|
|
2021
|
+
.map(prune)
|
|
2022
|
+
.filter((node) => !!node);
|
|
2023
|
+
}, ...(ngDevMode ? [{ debugName: "filteredTree" }] : /* istanbul ignore next */ []));
|
|
2024
|
+
// --- App accessibility ----------------------------------------------------
|
|
2025
|
+
appAccessibilities = computed(() => this.options()?.appAccessibility ?? [], ...(ngDevMode ? [{ debugName: "appAccessibilities" }] : /* istanbul ignore next */ []));
|
|
2026
|
+
selectedAccessibilityKeys = signal(new Set(), ...(ngDevMode ? [{ debugName: "selectedAccessibilityKeys" }] : /* istanbul ignore next */ []));
|
|
2027
|
+
selectedAccessibilityCount = computed(() => this.selectedAccessibilityKeys().size, ...(ngDevMode ? [{ debugName: "selectedAccessibilityCount" }] : /* istanbul ignore next */ []));
|
|
2028
|
+
hasAccessibility = computed(() => this.appAccessibilities().length > 0, ...(ngDevMode ? [{ debugName: "hasAccessibility" }] : /* istanbul ignore next */ []));
|
|
2029
|
+
/** Inner view switch: permission tree vs app-accessibility grants. */
|
|
2030
|
+
activeScopeTab = signal('permissions', ...(ngDevMode ? [{ debugName: "activeScopeTab" }] : /* istanbul ignore next */ []));
|
|
1797
2031
|
constructor() {
|
|
1798
2032
|
// Load (and reload) grantable options whenever the delegator changes.
|
|
1799
2033
|
let initialized = false;
|
|
@@ -1810,10 +2044,26 @@ class ScopePicker {
|
|
|
1810
2044
|
if (lastDelegatorUserId !== delegator) {
|
|
1811
2045
|
lastDelegatorUserId = delegator;
|
|
1812
2046
|
this.scope.set(EMPTY_SCOPE$1);
|
|
1813
|
-
this.
|
|
2047
|
+
this.selectedAccessibilityKeys.set(new Set());
|
|
2048
|
+
this.searchTerm.set('');
|
|
2049
|
+
this.activeScopeTab.set('permissions');
|
|
1814
2050
|
this.facade.clearScopePreview();
|
|
1815
2051
|
}
|
|
1816
2052
|
});
|
|
2053
|
+
// Default-expand Template + Level nodes whenever options (re)load.
|
|
2054
|
+
effect(() => {
|
|
2055
|
+
const model = this.treeModel();
|
|
2056
|
+
const keys = new Set();
|
|
2057
|
+
const walk = (node) => {
|
|
2058
|
+
if (node.kind === 'template' || node.kind === 'level') {
|
|
2059
|
+
keys.add(node.key);
|
|
2060
|
+
}
|
|
2061
|
+
(node.children ?? []).forEach(walk);
|
|
2062
|
+
};
|
|
2063
|
+
model.forEach(walk);
|
|
2064
|
+
untracked(() => this.expandedKeys.set(keys));
|
|
2065
|
+
});
|
|
2066
|
+
// Debounced scope preview.
|
|
1817
2067
|
let timer = null;
|
|
1818
2068
|
effect(() => {
|
|
1819
2069
|
const s = this.scope();
|
|
@@ -1832,30 +2082,156 @@ class ScopePicker {
|
|
|
1832
2082
|
}), 300);
|
|
1833
2083
|
});
|
|
1834
2084
|
});
|
|
2085
|
+
// Initialize selected accessibilities once from the bound scope (edit).
|
|
2086
|
+
let accessibilityInitialized = false;
|
|
1835
2087
|
effect(() => {
|
|
1836
|
-
const
|
|
1837
|
-
|
|
1838
|
-
if (sameSelectionKeys(selection, selectedKeys)) {
|
|
2088
|
+
const grants = this.scope().grants;
|
|
2089
|
+
if (accessibilityInitialized || !grants.length) {
|
|
1839
2090
|
return;
|
|
1840
2091
|
}
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
2092
|
+
accessibilityInitialized = true;
|
|
2093
|
+
const keys = new Set();
|
|
2094
|
+
for (const grant of grants) {
|
|
2095
|
+
for (const fact of grant.accessibilities ?? []) {
|
|
2096
|
+
const key = typeof fact['accessibilityKey'] === 'string'
|
|
2097
|
+
? fact['accessibilityKey']
|
|
2098
|
+
: typeof fact.key === 'string'
|
|
2099
|
+
? fact.key
|
|
2100
|
+
: null;
|
|
2101
|
+
if (key)
|
|
2102
|
+
keys.add(key);
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
if (keys.size) {
|
|
2106
|
+
untracked(() => this.selectedAccessibilityKeys.set(keys));
|
|
2107
|
+
}
|
|
2108
|
+
});
|
|
2109
|
+
}
|
|
2110
|
+
// --- Expansion ------------------------------------------------------------
|
|
2111
|
+
isExpanded(node) {
|
|
2112
|
+
return this.searchTerm().trim()
|
|
2113
|
+
? true
|
|
2114
|
+
: this.expandedKeys().has(node.key);
|
|
2115
|
+
}
|
|
2116
|
+
toggleExpand(node) {
|
|
2117
|
+
if (!node.children?.length) {
|
|
2118
|
+
return;
|
|
2119
|
+
}
|
|
2120
|
+
this.expandedKeys.update((set) => {
|
|
2121
|
+
const next = new Set(set);
|
|
2122
|
+
if (next.has(node.key)) {
|
|
2123
|
+
next.delete(node.key);
|
|
2124
|
+
}
|
|
2125
|
+
else {
|
|
2126
|
+
next.add(node.key);
|
|
2127
|
+
}
|
|
2128
|
+
return next;
|
|
1844
2129
|
});
|
|
1845
2130
|
}
|
|
1846
|
-
|
|
1847
|
-
|
|
2131
|
+
hasChildren(node) {
|
|
2132
|
+
return !!node.children?.length;
|
|
2133
|
+
}
|
|
2134
|
+
// --- Selection ------------------------------------------------------------
|
|
2135
|
+
nodeState(node) {
|
|
2136
|
+
const opKeys = this.opKeyMap().get(node.key) ?? [];
|
|
2137
|
+
if (!opKeys.length) {
|
|
2138
|
+
return 'unchecked';
|
|
2139
|
+
}
|
|
2140
|
+
const selected = this.selectedKeys();
|
|
2141
|
+
let count = 0;
|
|
2142
|
+
for (const key of opKeys) {
|
|
2143
|
+
if (selected.has(key))
|
|
2144
|
+
count++;
|
|
2145
|
+
}
|
|
2146
|
+
if (count === 0)
|
|
2147
|
+
return 'unchecked';
|
|
2148
|
+
return count === opKeys.length ? 'checked' : 'indeterminate';
|
|
2149
|
+
}
|
|
2150
|
+
onRowClick(node) {
|
|
2151
|
+
if (this.hasChildren(node)) {
|
|
2152
|
+
this.toggleExpand(node);
|
|
2153
|
+
}
|
|
2154
|
+
else {
|
|
2155
|
+
this.toggle(node);
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
toggle(node) {
|
|
2159
|
+
if (this.readonly()) {
|
|
2160
|
+
return;
|
|
2161
|
+
}
|
|
2162
|
+
const opKeys = this.opKeyMap().get(node.key) ?? [];
|
|
2163
|
+
if (!opKeys.length) {
|
|
1848
2164
|
return;
|
|
1849
2165
|
}
|
|
2166
|
+
const next = new Set(this.selectedKeys());
|
|
2167
|
+
const allSelected = opKeys.every((key) => next.has(key));
|
|
2168
|
+
for (const key of opKeys) {
|
|
2169
|
+
if (allSelected) {
|
|
2170
|
+
next.delete(key);
|
|
2171
|
+
}
|
|
2172
|
+
else {
|
|
2173
|
+
next.add(key);
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
this.applySelection(next);
|
|
2177
|
+
}
|
|
2178
|
+
applySelection(selected) {
|
|
1850
2179
|
const index = this.grantIndex();
|
|
1851
|
-
const
|
|
1852
|
-
|
|
1853
|
-
.
|
|
1854
|
-
|
|
1855
|
-
...
|
|
1856
|
-
|
|
2180
|
+
const facts = this.accessibilityFacts();
|
|
2181
|
+
const grants = Array.from(selected)
|
|
2182
|
+
.map((key) => index.get(key))
|
|
2183
|
+
.filter((grant) => !!grant)
|
|
2184
|
+
.map((grant) => facts.length ? { ...grant, accessibilities: facts } : grant);
|
|
2185
|
+
this.scope.update((scope) => ({ ...scope, grants }));
|
|
2186
|
+
}
|
|
2187
|
+
clearSelection() {
|
|
2188
|
+
if (this.readonly()) {
|
|
2189
|
+
return;
|
|
2190
|
+
}
|
|
2191
|
+
this.scope.update((scope) => ({ ...scope, grants: [] }));
|
|
2192
|
+
}
|
|
2193
|
+
// --- App accessibility ----------------------------------------------------
|
|
2194
|
+
toggleAccessibility(key) {
|
|
2195
|
+
if (this.readonly()) {
|
|
2196
|
+
return;
|
|
2197
|
+
}
|
|
2198
|
+
this.selectedAccessibilityKeys.update((set) => {
|
|
2199
|
+
const next = new Set(set);
|
|
2200
|
+
if (next.has(key)) {
|
|
2201
|
+
next.delete(key);
|
|
2202
|
+
}
|
|
2203
|
+
else {
|
|
2204
|
+
next.add(key);
|
|
2205
|
+
}
|
|
2206
|
+
return next;
|
|
2207
|
+
});
|
|
2208
|
+
// Re-apply current selection so grants carry the new accessibility facts.
|
|
2209
|
+
this.applySelection(new Set(this.selectedKeys()));
|
|
2210
|
+
}
|
|
2211
|
+
isAccessibilitySelected(key) {
|
|
2212
|
+
return this.selectedAccessibilityKeys().has(key);
|
|
2213
|
+
}
|
|
2214
|
+
accessibilityFacts() {
|
|
2215
|
+
const selected = this.selectedAccessibilityKeys();
|
|
2216
|
+
if (!selected.size) {
|
|
2217
|
+
return [];
|
|
2218
|
+
}
|
|
2219
|
+
return this.appAccessibilities()
|
|
2220
|
+
.filter((item) => selected.has(item.accessibilityKey))
|
|
2221
|
+
.map((item) => ({
|
|
2222
|
+
key: item.accessibilityKey,
|
|
2223
|
+
accessibilityKey: item.accessibilityKey,
|
|
2224
|
+
name: item.name ?? null,
|
|
2225
|
+
displayName: item.name ?? null,
|
|
1857
2226
|
}));
|
|
1858
2227
|
}
|
|
2228
|
+
// --- Labels / preview -----------------------------------------------------
|
|
2229
|
+
nodeLabel(node) {
|
|
2230
|
+
return (formatDelegationDisplayValue(node.displayName ?? undefined, this.currentLanguage()) || humanizeDelegationKey(node.fallbackKey));
|
|
2231
|
+
}
|
|
2232
|
+
accessibilityLabel(item) {
|
|
2233
|
+
return (formatDelegationDisplayValue(item.name ?? undefined, this.currentLanguage()) || humanizeDelegationKey(item.accessibilityKey));
|
|
2234
|
+
}
|
|
1859
2235
|
getPreviewSummary(summary) {
|
|
1860
2236
|
return formatDelegationScopeSummary(summary, this.currentLanguage());
|
|
1861
2237
|
}
|
|
@@ -1865,59 +2241,29 @@ class ScopePicker {
|
|
|
1865
2241
|
formatDeniedTarget(value) {
|
|
1866
2242
|
return humanizeDelegationKey(value);
|
|
1867
2243
|
}
|
|
1868
|
-
|
|
1869
|
-
return
|
|
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());
|
|
2244
|
+
trackNode(_index, node) {
|
|
2245
|
+
return node.key;
|
|
1906
2246
|
}
|
|
1907
2247
|
currentLanguage() {
|
|
1908
2248
|
return this.activeLang() ?? this.transloco.getActiveLang();
|
|
1909
2249
|
}
|
|
1910
2250
|
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 });
|
|
2251
|
+
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
2252
|
}
|
|
1913
2253
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ScopePicker, decorators: [{
|
|
1914
2254
|
type: Component,
|
|
1915
|
-
args: [{ selector: 'mt-scope-picker', imports: [
|
|
2255
|
+
args: [{ selector: 'mt-scope-picker', imports: [
|
|
2256
|
+
CommonModule,
|
|
2257
|
+
FormsModule,
|
|
2258
|
+
Button,
|
|
2259
|
+
Card,
|
|
2260
|
+
Icon,
|
|
2261
|
+
Tabs,
|
|
2262
|
+
TextField,
|
|
2263
|
+
SkeletonModule,
|
|
2264
|
+
TranslocoDirective,
|
|
2265
|
+
], 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
2266
|
}], 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
2267
|
|
|
1922
2268
|
const EMPTY_SCOPE = { grants: [], metadata: {} };
|
|
1923
2269
|
function getCurrentDelegatorFromLocalStorage() {
|
|
@@ -2287,7 +2633,7 @@ class DelegationForm {
|
|
|
2287
2633
|
};
|
|
2288
2634
|
}
|
|
2289
2635
|
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
|
|
2636
|
+
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
2637
|
}
|
|
2292
2638
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationForm, decorators: [{
|
|
2293
2639
|
type: Component,
|
|
@@ -2298,7 +2644,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
2298
2644
|
ReactiveFormsModule,
|
|
2299
2645
|
ScopePicker,
|
|
2300
2646
|
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
|
|
2647
|
+
], 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
2648
|
}], 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
2649
|
|
|
2304
2650
|
/**
|
|
@@ -2310,19 +2656,40 @@ class DelegationDetailDrawer {
|
|
|
2310
2656
|
facade = inject(DelegationsFacade);
|
|
2311
2657
|
transloco = inject(TranslocoService);
|
|
2312
2658
|
ref = inject(ModalRef);
|
|
2313
|
-
tabs = [
|
|
2314
|
-
{ key: 'overview', i18n: 'delegations.action.view' },
|
|
2315
|
-
{ key: 'scope', i18n: 'delegations.column.scopeSummary' },
|
|
2316
|
-
];
|
|
2317
2659
|
activeTab = signal('overview', ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
|
|
2660
|
+
tabOptions = computed(() => [
|
|
2661
|
+
{
|
|
2662
|
+
value: 'overview',
|
|
2663
|
+
label: this.transloco.translate('delegations.tabs.overview'),
|
|
2664
|
+
},
|
|
2665
|
+
{
|
|
2666
|
+
value: 'scope',
|
|
2667
|
+
label: this.transloco.translate('delegations.column.scopeSummary'),
|
|
2668
|
+
},
|
|
2669
|
+
], ...(ngDevMode ? [{ debugName: "tabOptions" }] : /* istanbul ignore next */ []));
|
|
2670
|
+
weekdayKeys = [
|
|
2671
|
+
'sunday',
|
|
2672
|
+
'monday',
|
|
2673
|
+
'tuesday',
|
|
2674
|
+
'wednesday',
|
|
2675
|
+
'thursday',
|
|
2676
|
+
'friday',
|
|
2677
|
+
'saturday',
|
|
2678
|
+
];
|
|
2318
2679
|
isLoading = this.facade.isLoadingDetail;
|
|
2319
2680
|
detail = this.facade.detail;
|
|
2320
2681
|
row = computed(() => this.detail()?.row ?? null, ...(ngDevMode ? [{ debugName: "row" }] : /* istanbul ignore next */ []));
|
|
2321
2682
|
ngOnInit() {
|
|
2322
2683
|
this.facade.getDetail(this.delegationId(), this.adminMode());
|
|
2323
2684
|
}
|
|
2324
|
-
|
|
2325
|
-
|
|
2685
|
+
formatSpecificDays(days) {
|
|
2686
|
+
if (!days?.length) {
|
|
2687
|
+
return this.transloco.translate('delegations.form.fullRange');
|
|
2688
|
+
}
|
|
2689
|
+
return days
|
|
2690
|
+
.filter((d) => d >= 0 && d < this.weekdayKeys.length)
|
|
2691
|
+
.map((d) => this.transloco.translate(`delegations.days.${this.weekdayKeys[d]}`))
|
|
2692
|
+
.join(', ');
|
|
2326
2693
|
}
|
|
2327
2694
|
approvalLabel() {
|
|
2328
2695
|
const s = this.row()?.approval?.approvalStatus ?? 'NotRequired';
|
|
@@ -2341,7 +2708,7 @@ class DelegationDetailDrawer {
|
|
|
2341
2708
|
return toDelegationUserEntity(party, { label });
|
|
2342
2709
|
}
|
|
2343
2710
|
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=\"
|
|
2711
|
+
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
2712
|
}
|
|
2346
2713
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationDetailDrawer, decorators: [{
|
|
2347
2714
|
type: Component,
|
|
@@ -2350,9 +2717,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
2350
2717
|
Button,
|
|
2351
2718
|
EntityPreview,
|
|
2352
2719
|
SkeletonModule,
|
|
2720
|
+
Tabs,
|
|
2353
2721
|
TranslocoDirective,
|
|
2354
2722
|
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=\"
|
|
2723
|
+
], 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
2724
|
}], propDecorators: { delegationId: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegationId", required: true }] }], adminMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "adminMode", required: false }] }] } });
|
|
2357
2725
|
|
|
2358
2726
|
function defaultTrueBooleanAttribute(value) {
|
|
@@ -2503,14 +2871,19 @@ class DelegationsList {
|
|
|
2503
2871
|
hidden: (row) => !this.has(row, 'StartSession'),
|
|
2504
2872
|
},
|
|
2505
2873
|
], ...(ngDevMode ? [{ debugName: "rowActions" }] : /* istanbul ignore next */ []));
|
|
2874
|
+
/**
|
|
2875
|
+
* Table status accent colors. Kept in visual sync with the canonical palette
|
|
2876
|
+
* in `delegation-status-chip` (its light-mode text colors) so the list table
|
|
2877
|
+
* and the status chip render the same hue for each status.
|
|
2878
|
+
*/
|
|
2506
2879
|
statusColors = {
|
|
2507
|
-
Active: '#
|
|
2508
|
-
Scheduled: '#
|
|
2509
|
-
PendingApproval: '#
|
|
2510
|
-
InactiveToday: '#
|
|
2880
|
+
Active: '#166534',
|
|
2881
|
+
Scheduled: '#1e40af',
|
|
2882
|
+
PendingApproval: '#854d0e',
|
|
2883
|
+
InactiveToday: '#1e40af',
|
|
2511
2884
|
Expired: '#4b5563',
|
|
2512
|
-
Rejected: '#
|
|
2513
|
-
Cancelled: '#
|
|
2885
|
+
Rejected: '#991b1b',
|
|
2886
|
+
Cancelled: '#374151',
|
|
2514
2887
|
};
|
|
2515
2888
|
statusMap = linkedSignal(() => ({
|
|
2516
2889
|
Active: {
|
|
@@ -2735,7 +3108,7 @@ class DelegationsList {
|
|
|
2735
3108
|
this.busyIds.update((ids) => on ? [...ids, key] : ids.filter((id) => id !== key));
|
|
2736
3109
|
}
|
|
2737
3110
|
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
|
|
3111
|
+
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
3112
|
}
|
|
2740
3113
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsList, decorators: [{
|
|
2741
3114
|
type: Component,
|
|
@@ -2747,7 +3120,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
2747
3120
|
Table,
|
|
2748
3121
|
Tabs,
|
|
2749
3122
|
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
|
|
3123
|
+
], 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
3124
|
}], 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
3125
|
|
|
2753
3126
|
/**
|
|
@@ -2863,5 +3236,5 @@ const appDelegationInterceptor = (req, next) => {
|
|
|
2863
3236
|
* Generated bundle index. Do not edit.
|
|
2864
3237
|
*/
|
|
2865
3238
|
|
|
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 };
|
|
3239
|
+
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
3240
|
//# sourceMappingURL=masterteam-delegations.mjs.map
|