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