@masterteam/delegations 0.0.34 → 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.
- package/assets/delegations.css +1 -1
- package/assets/i18n/ar.json +163 -155
- package/assets/i18n/en.json +163 -155
- package/fesm2022/masterteam-delegations.mjs +672 -262
- package/fesm2022/masterteam-delegations.mjs.map +1 -1
- package/package.json +2 -2
- package/types/masterteam-delegations.d.ts +188 -50
|
@@ -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\">\n <div class=\"flex flex-col gap-4 p-2\">\n <div class=\"flex items-start gap-3\">\n <div class=\"flex flex-col min-w-0\">\n <mt-entity-preview\n [data]=\"userEntity(delegation().delegator)\"\n ></mt-entity-preview>\n @if (delegation().scopeSummary) {\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\">\n <div class=\"flex flex-col gap-4 p-2\">\n <div class=\"flex items-start gap-3\">\n <div class=\"flex flex-col min-w-0\">\n <mt-entity-preview\n [data]=\"userEntity(delegation().delegator)\"\n ></mt-entity-preview>\n @if (delegation().scopeSummary) {\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\">\n <div class=\"flex flex-col gap-4 p-2\">\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\">\n <div class=\"flex flex-col gap-4 p-2\">\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');
|
|
601
|
-
}
|
|
602
|
-
switchTo(row, event) {
|
|
603
|
-
event.stopPropagation();
|
|
604
|
-
this.popover().hide();
|
|
605
|
-
this.openConfirm(row, 'switch');
|
|
695
|
+
this.popover()?.toggle(event);
|
|
606
696
|
}
|
|
607
|
-
|
|
608
|
-
|
|
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\">\n @if (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 <div class=\"mt-delegation-trigger flex items-center gap-2\">\n <button\n type=\"button\"\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\"\n [class.mt-delegation-trigger__button--active]=\"mode() === 'active'\"\n [attr.aria-label]=\"\n mode() === 'active'\n ? t('delegations.session.banner', {\n delegatorName: onBehalfOf()?.displayName,\n }) +\n '. ' +\n t('delegations.session.executedBy', {\n actualUserName: executedBy()?.displayName,\n })\n : t('delegations.session.availableTitle')\n \"\n [attr.title]=\"\n mode() === 'active'\n ? t('delegations.session.banner', {\n delegatorName: onBehalfOf()?.displayName,\n })\n : null\n \"\n (click)=\"togglePopover($event)\"\n >\n @if (mode() === \"active\") {\n <mt-icon\n icon=\"user.users-check\"\n styleClass=\"text-lg text-primary\"\n ></mt-icon>\n @if (!compact()) {\n <span\n class=\"mt-delegation-trigger__label flex min-w-0 flex-col text-start leading-tight\"\n >\n <span class=\"truncate text-sm font-medium\">\n {{\n t(\"delegations.session.banner\", {\n delegatorName: onBehalfOf()?.displayName,\n })\n }}\n </span>\n <span class=\"truncate text-[0.6875rem] font-normal text-gray-500\">\n {{\n t(\"delegations.session.executedBy\", {\n actualUserName: executedBy()?.displayName,\n })\n }}\n </span>\n </span>\n }\n <mt-delegation-status-chip\n status=\"Active\"\n ></mt-delegation-status-chip>\n } @else {\n <mt-icon\n icon=\"user.users-plus\"\n styleClass=\"text-lg text-primary\"\n ></mt-icon>\n @if (!compact()) {\n <span class=\"mt-delegation-trigger__label text-sm font-medium\">\n {{ t(\"delegations.session.availableTitle\") }}\n </span>\n }\n }\n </button>\n </div>\n\n <p-popover #popover [pt]=\"{ content: { class: 'p-0!' } }\">\n <div class=\"flex flex-col min-w-80 max-w-96 p-0\">\n @if (mode() === \"active\" && active(); as session) {\n <!-- STATE C \u2014 active session -->\n <div\n class=\"flex items-start gap-3 border-b border-gray-200 px-4 py-4\"\n >\n <div class=\"flex min-w-0 flex-1 flex-col\">\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\n </div>\n <mt-entity-preview\n [data]=\"userEntity(onBehalfOf())\"\n ></mt-entity-preview>\n <div class=\"text-xs text-gray-500 mt-1\">\n {{\n t(\"delegations.session.executedBy\", {\n actualUserName: executedBy()?.displayName,\n })\n }}\n </div>\n @if (session.delegation.scopeSummary) {\n @let summary =\n formatScopeSummary(session.delegation.scopeSummary);\n <div\n class=\"text-xs text-gray-500 mt-1 truncate\"\n [title]=\"summary\"\n >\n {{ t(\"delegations.column.scopeSummary\") }}:\n {{ summary }}\n </div>\n }\n </div>\n </div>\n\n <div class=\"flex flex-col gap-1 px-2 py-2\">\n <mt-button\n variant=\"text\"\n icon=\"arrow.arrow-left\"\n [label]=\"t('delegations.action.endSession')\"\n styleClass=\"w-full justify-start\"\n (click)=\"endSession($event)\"\n ></mt-button>\n </div>\n\n @if (candidates().length > 1) {\n <div class=\"border-t border-gray-200 pt-2 px-2 pb-2\">\n <div class=\"text-xs text-gray-500 px-2 pb-1\">\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 items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer\"\n [attr.aria-label]=\"\n t('delegations.action.switchSession') +\n ': ' +\n cand.delegator.displayName\n \"\n (click)=\"switchTo(cand, $event)\"\n >\n <div class=\"min-w-0 flex-1\">\n <mt-entity-preview\n [data]=\"userEntity(cand.delegator)\"\n ></mt-entity-preview>\n </div>\n <mt-icon\n icon=\"arrow.switch-horizontal-01\"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n </button>\n }\n }\n </div>\n }\n } @else if (mode() === \"candidates\") {\n <!-- STATE B \u2014 candidates available -->\n <div class=\"border-b border-gray-200 px-4 py-3\">\n <div class=\"text-sm font-medium text-gray-900\">\n {{ t(\"delegations.session.youCanActAs\") }}\n </div>\n <div class=\"text-xs text-gray-500 mt-1\">\n {{ t(\"delegations.session.chooseDelegatorHint\") }}\n </div>\n </div>\n <div class=\"flex flex-col gap-1 px-2 py-2 max-h-72 overflow-y-auto\">\n @for (cand of candidates(); track $index) {\n <button\n type=\"button\"\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\"\n [attr.aria-label]=\"\n t('delegations.action.startSession') +\n ': ' +\n cand.delegator.displayName\n \"\n (click)=\"start(cand, $event)\"\n >\n <div class=\"flex min-w-0 flex-1 flex-col gap-1\">\n <mt-entity-preview\n [data]=\"userEntity(cand.delegator)\"\n ></mt-entity-preview>\n @if (cand.scopeSummary) {\n @let summary = formatScopeSummary(cand.scopeSummary);\n <span\n class=\"text-xs text-gray-500 truncate\"\n [title]=\"summary\"\n >\n {{ summary }}\n </span>\n }\n </div>\n <mt-icon\n icon=\"arrow.arrow-right\"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n </button>\n }\n </div>\n }\n\n <!-- Footer -->\n @if (showManageLink()) {\n <div class=\"border-t border-gray-200 px-2 py-2\">\n <a\n [routerLink]=\"managePath()\"\n (click)=\"popover().hide()\"\n class=\"flex items-center gap-2 px-3 py-2 rounded-md text-gray-700 hover:bg-surface-100 text-sm cursor-pointer\"\n >\n <mt-icon\n icon=\"general.settings-02\"\n styleClass=\"text-base\"\n ></mt-icon>\n <span>{{ t(\"delegations.session.manage\") }}</span>\n </a>\n </div>\n }\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{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\">\n @if (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 <div class=\"mt-delegation-trigger flex items-center
|
|
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
|
|
@@ -668,6 +754,13 @@ class GetAssignedDelegations {
|
|
|
668
754
|
this.query = query;
|
|
669
755
|
}
|
|
670
756
|
}
|
|
757
|
+
class GetApprovalDelegations {
|
|
758
|
+
query;
|
|
759
|
+
static type = '[Delegations] Get Approval Delegations';
|
|
760
|
+
constructor(query = {}) {
|
|
761
|
+
this.query = query;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
671
764
|
class GetAdminDelegations {
|
|
672
765
|
query;
|
|
673
766
|
static type = '[Delegations] Get Admin Delegations';
|
|
@@ -797,6 +890,7 @@ var DelegationsActionKey;
|
|
|
797
890
|
(function (DelegationsActionKey) {
|
|
798
891
|
DelegationsActionKey["GetMy"] = "getMy";
|
|
799
892
|
DelegationsActionKey["GetAssigned"] = "getAssigned";
|
|
893
|
+
DelegationsActionKey["GetApprovals"] = "getApprovals";
|
|
800
894
|
DelegationsActionKey["GetAdmin"] = "getAdmin";
|
|
801
895
|
DelegationsActionKey["GetActive"] = "getActive";
|
|
802
896
|
DelegationsActionKey["GetDetail"] = "getDetail";
|
|
@@ -851,6 +945,9 @@ let DelegationsState = class DelegationsState {
|
|
|
851
945
|
static getAssigned(state) {
|
|
852
946
|
return state.assigned;
|
|
853
947
|
}
|
|
948
|
+
static getApprovals(state) {
|
|
949
|
+
return state.approvals;
|
|
950
|
+
}
|
|
854
951
|
static getAdmin(state) {
|
|
855
952
|
return state.admin;
|
|
856
953
|
}
|
|
@@ -895,6 +992,15 @@ let DelegationsState = class DelegationsState {
|
|
|
895
992
|
onSuccess: (response) => ({ assigned: response.data }),
|
|
896
993
|
});
|
|
897
994
|
}
|
|
995
|
+
getApprovals(ctx, { query }) {
|
|
996
|
+
const req$ = this.http.get(`${BASE}/assigned`, { params: buildListParams(query) });
|
|
997
|
+
return handleApiRequest({
|
|
998
|
+
ctx,
|
|
999
|
+
key: DelegationsActionKey.GetApprovals,
|
|
1000
|
+
request$: req$,
|
|
1001
|
+
onSuccess: (response) => ({ approvals: response.data }),
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
898
1004
|
getAdmin(ctx, { query }) {
|
|
899
1005
|
const req$ = this.http.get(`${BASE}/All`, { params: buildListParams(query) });
|
|
900
1006
|
return handleApiRequest({
|
|
@@ -1066,6 +1172,9 @@ __decorate([
|
|
|
1066
1172
|
__decorate([
|
|
1067
1173
|
Action(GetAssignedDelegations)
|
|
1068
1174
|
], DelegationsState.prototype, "getAssigned", null);
|
|
1175
|
+
__decorate([
|
|
1176
|
+
Action(GetApprovalDelegations)
|
|
1177
|
+
], DelegationsState.prototype, "getApprovals", null);
|
|
1069
1178
|
__decorate([
|
|
1070
1179
|
Action(GetAdminDelegations)
|
|
1071
1180
|
], DelegationsState.prototype, "getAdmin", null);
|
|
@@ -1114,6 +1223,9 @@ __decorate([
|
|
|
1114
1223
|
__decorate([
|
|
1115
1224
|
Selector()
|
|
1116
1225
|
], DelegationsState, "getAssigned", null);
|
|
1226
|
+
__decorate([
|
|
1227
|
+
Selector()
|
|
1228
|
+
], DelegationsState, "getApprovals", null);
|
|
1117
1229
|
__decorate([
|
|
1118
1230
|
Selector()
|
|
1119
1231
|
], DelegationsState, "getAdmin", null);
|
|
@@ -1141,6 +1253,7 @@ DelegationsState = __decorate([
|
|
|
1141
1253
|
defaults: {
|
|
1142
1254
|
my: null,
|
|
1143
1255
|
assigned: null,
|
|
1256
|
+
approvals: null,
|
|
1144
1257
|
admin: null,
|
|
1145
1258
|
active: null,
|
|
1146
1259
|
detail: null,
|
|
@@ -1153,7 +1266,7 @@ DelegationsState = __decorate([
|
|
|
1153
1266
|
], DelegationsState);
|
|
1154
1267
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsState, decorators: [{
|
|
1155
1268
|
type: Injectable
|
|
1156
|
-
}], propDecorators: { getMy: [], getAssigned: [], getAdmin: [], getActive: [], getDetail: [], clearDetail: [], getScopeOptions: [], previewScope: [], clearScopePreview: [], createLegacy: [], createV2: [], updateLegacy: [], updateV2: [], approve: [], reject: [], cancel: [] } });
|
|
1269
|
+
}], propDecorators: { getMy: [], getAssigned: [], getApprovals: [], getAdmin: [], getActive: [], getDetail: [], clearDetail: [], getScopeOptions: [], previewScope: [], clearScopePreview: [], createLegacy: [], createV2: [], updateLegacy: [], updateV2: [], approve: [], reject: [], cancel: [] } });
|
|
1157
1270
|
|
|
1158
1271
|
class DelegationsFacade {
|
|
1159
1272
|
store = inject(Store);
|
|
@@ -1162,6 +1275,7 @@ class DelegationsFacade {
|
|
|
1162
1275
|
// ---------------------------------------------------------------------------
|
|
1163
1276
|
myPage = select(DelegationsState.getMy);
|
|
1164
1277
|
assignedPage = select(DelegationsState.getAssigned);
|
|
1278
|
+
approvalsPage = select(DelegationsState.getApprovals);
|
|
1165
1279
|
adminPage = select(DelegationsState.getAdmin);
|
|
1166
1280
|
activePage = select(DelegationsState.getActive);
|
|
1167
1281
|
detail = select(DelegationsState.getDetail);
|
|
@@ -1174,6 +1288,7 @@ class DelegationsFacade {
|
|
|
1174
1288
|
// ---------------------------------------------------------------------------
|
|
1175
1289
|
myItems = computed(() => this.myPage()?.items ?? [], ...(ngDevMode ? [{ debugName: "myItems" }] : /* istanbul ignore next */ []));
|
|
1176
1290
|
assignedItems = computed(() => this.assignedPage()?.items ?? [], ...(ngDevMode ? [{ debugName: "assignedItems" }] : /* istanbul ignore next */ []));
|
|
1291
|
+
approvalItems = computed(() => this.approvalsPage()?.items ?? [], ...(ngDevMode ? [{ debugName: "approvalItems" }] : /* istanbul ignore next */ []));
|
|
1177
1292
|
adminItems = computed(() => this.adminPage()?.items ?? [], ...(ngDevMode ? [{ debugName: "adminItems" }] : /* istanbul ignore next */ []));
|
|
1178
1293
|
activeItems = computed(() => this.activePage()?.items ?? [], ...(ngDevMode ? [{ debugName: "activeItems" }] : /* istanbul ignore next */ []));
|
|
1179
1294
|
// ---------------------------------------------------------------------------
|
|
@@ -1181,6 +1296,7 @@ class DelegationsFacade {
|
|
|
1181
1296
|
// ---------------------------------------------------------------------------
|
|
1182
1297
|
isLoadingMy = computed(() => this.loadingActive().includes(DelegationsActionKey.GetMy), ...(ngDevMode ? [{ debugName: "isLoadingMy" }] : /* istanbul ignore next */ []));
|
|
1183
1298
|
isLoadingAssigned = computed(() => this.loadingActive().includes(DelegationsActionKey.GetAssigned), ...(ngDevMode ? [{ debugName: "isLoadingAssigned" }] : /* istanbul ignore next */ []));
|
|
1299
|
+
isLoadingApprovals = computed(() => this.loadingActive().includes(DelegationsActionKey.GetApprovals), ...(ngDevMode ? [{ debugName: "isLoadingApprovals" }] : /* istanbul ignore next */ []));
|
|
1184
1300
|
isLoadingAdmin = computed(() => this.loadingActive().includes(DelegationsActionKey.GetAdmin), ...(ngDevMode ? [{ debugName: "isLoadingAdmin" }] : /* istanbul ignore next */ []));
|
|
1185
1301
|
isLoadingActive = computed(() => this.loadingActive().includes(DelegationsActionKey.GetActive), ...(ngDevMode ? [{ debugName: "isLoadingActive" }] : /* istanbul ignore next */ []));
|
|
1186
1302
|
isLoadingDetail = computed(() => this.loadingActive().includes(DelegationsActionKey.GetDetail), ...(ngDevMode ? [{ debugName: "isLoadingDetail" }] : /* istanbul ignore next */ []));
|
|
@@ -1196,6 +1312,7 @@ class DelegationsFacade {
|
|
|
1196
1312
|
// ---------------------------------------------------------------------------
|
|
1197
1313
|
errorMy = computed(() => this.errors()[DelegationsActionKey.GetMy] ?? null, ...(ngDevMode ? [{ debugName: "errorMy" }] : /* istanbul ignore next */ []));
|
|
1198
1314
|
errorAssigned = computed(() => this.errors()[DelegationsActionKey.GetAssigned] ?? null, ...(ngDevMode ? [{ debugName: "errorAssigned" }] : /* istanbul ignore next */ []));
|
|
1315
|
+
errorApprovals = computed(() => this.errors()[DelegationsActionKey.GetApprovals] ?? null, ...(ngDevMode ? [{ debugName: "errorApprovals" }] : /* istanbul ignore next */ []));
|
|
1199
1316
|
errorAdmin = computed(() => this.errors()[DelegationsActionKey.GetAdmin] ?? null, ...(ngDevMode ? [{ debugName: "errorAdmin" }] : /* istanbul ignore next */ []));
|
|
1200
1317
|
errorDetail = computed(() => this.errors()[DelegationsActionKey.GetDetail] ?? null, ...(ngDevMode ? [{ debugName: "errorDetail" }] : /* istanbul ignore next */ []));
|
|
1201
1318
|
errorScopeOptions = computed(() => this.errors()[DelegationsActionKey.GetScopeOptions] ?? null, ...(ngDevMode ? [{ debugName: "errorScopeOptions" }] : /* istanbul ignore next */ []));
|
|
@@ -1211,6 +1328,9 @@ class DelegationsFacade {
|
|
|
1211
1328
|
getAssigned(query = {}) {
|
|
1212
1329
|
return this.store.dispatch(new GetAssignedDelegations(query));
|
|
1213
1330
|
}
|
|
1331
|
+
getApprovals(query = {}) {
|
|
1332
|
+
return this.store.dispatch(new GetApprovalDelegations(query));
|
|
1333
|
+
}
|
|
1214
1334
|
getAdmin(query = {}) {
|
|
1215
1335
|
return this.store.dispatch(new GetAdminDelegations(query));
|
|
1216
1336
|
}
|
|
@@ -1306,11 +1426,11 @@ class RejectDelegationDialog {
|
|
|
1306
1426
|
this.ref.close(false);
|
|
1307
1427
|
}
|
|
1308
1428
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: RejectDelegationDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1309
|
-
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\">\n <div class=\"flex flex-col gap-4 p-2\">\n <div class=\"flex flex-col gap-1\">\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 });
|
|
1310
1430
|
}
|
|
1311
1431
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: RejectDelegationDialog, decorators: [{
|
|
1312
1432
|
type: Component,
|
|
1313
|
-
args: [{ selector: 'mt-reject-delegation-dialog', imports: [CommonModule, Button, EntityPreview, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col gap-4 p-2\">\n <div class=\"flex flex-col gap-1\">\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" }]
|
|
1314
1434
|
}], propDecorators: { delegation: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegation", required: true }] }] } });
|
|
1315
1435
|
|
|
1316
1436
|
class Delegations {
|
|
@@ -1370,7 +1490,7 @@ const DEFAULT_SORT_ORDER = Number.MAX_SAFE_INTEGER;
|
|
|
1370
1490
|
function toArray(value) {
|
|
1371
1491
|
return Array.isArray(value) ? value : [];
|
|
1372
1492
|
}
|
|
1373
|
-
function normalizeGrant
|
|
1493
|
+
function normalizeGrant(grant) {
|
|
1374
1494
|
return {
|
|
1375
1495
|
...grant,
|
|
1376
1496
|
action: normalizeAction(grant.action),
|
|
@@ -1415,7 +1535,7 @@ function groupIdentity(groupType, groupKey, fallbackTargetKey) {
|
|
|
1415
1535
|
return `${groupType}::${groupKey || fallbackTargetKey || 'group'}`;
|
|
1416
1536
|
}
|
|
1417
1537
|
function buildHierarchyGrant(target, action) {
|
|
1418
|
-
return normalizeGrant
|
|
1538
|
+
return normalizeGrant({
|
|
1419
1539
|
applicationKey: normalizeKey(target.applicationKey) ??
|
|
1420
1540
|
normalizeKey(action.applicationKey) ??
|
|
1421
1541
|
'',
|
|
@@ -1553,7 +1673,7 @@ function getLegacyGrantableGrants(options) {
|
|
|
1553
1673
|
if (!options) {
|
|
1554
1674
|
return [];
|
|
1555
1675
|
}
|
|
1556
|
-
const legacyGrants = toArray(options.grants).map(normalizeGrant
|
|
1676
|
+
const legacyGrants = toArray(options.grants).map(normalizeGrant);
|
|
1557
1677
|
if (legacyGrants.length > 0) {
|
|
1558
1678
|
return legacyGrants;
|
|
1559
1679
|
}
|
|
@@ -1582,7 +1702,7 @@ function getLegacyGrantableGrants(options) {
|
|
|
1582
1702
|
if (!action) {
|
|
1583
1703
|
continue;
|
|
1584
1704
|
}
|
|
1585
|
-
const grant = normalizeGrant
|
|
1705
|
+
const grant = normalizeGrant({
|
|
1586
1706
|
applicationKey: applicationKey ??
|
|
1587
1707
|
normalizeKey(action.applicationKey) ??
|
|
1588
1708
|
normalizeKey(target.applicationKey) ??
|
|
@@ -1657,72 +1777,182 @@ function getDelegationGrantableGrants(options) {
|
|
|
1657
1777
|
return getDelegationGrantableGroups(options).flatMap((group) => group.targets.flatMap((targetGroup) => targetGroup.grants));
|
|
1658
1778
|
}
|
|
1659
1779
|
function hasDelegationGrantableScope(options) {
|
|
1660
|
-
return
|
|
1780
|
+
return (getDelegationPermissionTree(options).length > 0 ||
|
|
1781
|
+
getDelegationGrantableGroups(options).length > 0);
|
|
1661
1782
|
}
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
* Full grant identity. Two grants with the same target/action but different
|
|
1666
|
-
* accessibilities, data filters, or constraints are DIFFERENT grants and must
|
|
1667
|
-
* not collapse (backend scope contract). Prefer a backend-provided stable
|
|
1668
|
-
* `key`; otherwise hash the complete grant shape.
|
|
1669
|
-
*/
|
|
1670
|
-
function grantKey(g) {
|
|
1671
|
-
if (g.key)
|
|
1672
|
-
return g.key;
|
|
1673
|
-
return JSON.stringify({
|
|
1674
|
-
app: g.applicationKey,
|
|
1675
|
-
target: [
|
|
1676
|
-
g.target.applicationKey,
|
|
1677
|
-
g.target.targetType,
|
|
1678
|
-
g.target.targetKey,
|
|
1679
|
-
g.target.permissionModuleType ?? null,
|
|
1680
|
-
g.target.permissionTargetId ?? null,
|
|
1681
|
-
g.target.legacyModuleId ?? null,
|
|
1682
|
-
g.target.levelId ?? null,
|
|
1683
|
-
g.target.levelModuleId ?? null,
|
|
1684
|
-
g.target.domainModuleId ?? null,
|
|
1685
|
-
g.target.moduleKey ?? null,
|
|
1686
|
-
],
|
|
1687
|
-
action: [
|
|
1688
|
-
g.action.applicationKey,
|
|
1689
|
-
g.action.operationKey,
|
|
1690
|
-
g.action.operationKind,
|
|
1691
|
-
g.action.permissionCommand ?? null,
|
|
1692
|
-
g.action.businessActionCode ?? null,
|
|
1693
|
-
],
|
|
1694
|
-
accessibilities: g.accessibilities ?? [],
|
|
1695
|
-
dataFilters: g.dataFilters ?? [],
|
|
1696
|
-
constraints: g.constraints ?? [],
|
|
1697
|
-
});
|
|
1783
|
+
/** Stable selection key for an operation leaf — independent of accessibilities. */
|
|
1784
|
+
function delegationOperationNodeKey(grant) {
|
|
1785
|
+
return `op:${targetIdentity(grant.target)}::${grant.action.operationKey}`;
|
|
1698
1786
|
}
|
|
1699
|
-
function
|
|
1787
|
+
function buildScopeTarget(node) {
|
|
1788
|
+
const moduleNode = node;
|
|
1700
1789
|
return {
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
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,
|
|
1705
1803
|
};
|
|
1706
1804
|
}
|
|
1707
|
-
function
|
|
1708
|
-
const
|
|
1709
|
-
.
|
|
1710
|
-
.
|
|
1711
|
-
|
|
1712
|
-
|
|
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;
|
|
1713
1925
|
}
|
|
1714
|
-
|
|
1715
|
-
return leftKeys.every((key) => rightSet.has(key));
|
|
1926
|
+
return legacyGroupsToTree(getDelegationGrantableGroups(options));
|
|
1716
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;
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
const EMPTY_SCOPE$1 = { grants: [], metadata: {} };
|
|
1717
1946
|
/**
|
|
1718
1947
|
* Hierarchy-based scope picker (doc 03). Driven entirely by `scope/options`:
|
|
1719
|
-
* - renders backend-owned
|
|
1720
|
-
*
|
|
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,
|
|
1721
1951
|
* - calls `scope/preview` (debounced) and surfaces summary / warnings / denied.
|
|
1722
1952
|
*
|
|
1723
|
-
* `[(scope)]` two-way binds a `DelegationScopeSelection`.
|
|
1724
|
-
*
|
|
1725
|
-
* 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.
|
|
1726
1956
|
*/
|
|
1727
1957
|
class ScopePicker {
|
|
1728
1958
|
scope = model(EMPTY_SCOPE$1, ...(ngDevMode ? [{ debugName: "scope" }] : /* istanbul ignore next */ []));
|
|
@@ -1741,25 +1971,62 @@ class ScopePicker {
|
|
|
1741
1971
|
isLoadingOptions = this.facade.isLoadingScopeOptions;
|
|
1742
1972
|
isPreviewing = this.facade.isPreviewingScope;
|
|
1743
1973
|
errorOptions = this.facade.errorScopeOptions;
|
|
1744
|
-
groups = computed(() => getDelegationGrantableGroups(this.options()), ...(ngDevMode ? [{ debugName: "groups" }] : /* istanbul ignore next */ []));
|
|
1745
1974
|
activeLang = toSignal(this.transloco.langChanges$, {
|
|
1746
1975
|
initialValue: this.transloco.getActiveLang(),
|
|
1747
1976
|
});
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
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];
|
|
1759
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();
|
|
1760
2005
|
}
|
|
1761
|
-
|
|
1762
|
-
|
|
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 */ []));
|
|
1763
2030
|
constructor() {
|
|
1764
2031
|
// Load (and reload) grantable options whenever the delegator changes.
|
|
1765
2032
|
let initialized = false;
|
|
@@ -1776,10 +2043,26 @@ class ScopePicker {
|
|
|
1776
2043
|
if (lastDelegatorUserId !== delegator) {
|
|
1777
2044
|
lastDelegatorUserId = delegator;
|
|
1778
2045
|
this.scope.set(EMPTY_SCOPE$1);
|
|
1779
|
-
this.
|
|
2046
|
+
this.selectedAccessibilityKeys.set(new Set());
|
|
2047
|
+
this.searchTerm.set('');
|
|
2048
|
+
this.activeScopeTab.set('permissions');
|
|
1780
2049
|
this.facade.clearScopePreview();
|
|
1781
2050
|
}
|
|
1782
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.
|
|
1783
2066
|
let timer = null;
|
|
1784
2067
|
effect(() => {
|
|
1785
2068
|
const s = this.scope();
|
|
@@ -1798,30 +2081,156 @@ class ScopePicker {
|
|
|
1798
2081
|
}), 300);
|
|
1799
2082
|
});
|
|
1800
2083
|
});
|
|
2084
|
+
// Initialize selected accessibilities once from the bound scope (edit).
|
|
2085
|
+
let accessibilityInitialized = false;
|
|
1801
2086
|
effect(() => {
|
|
1802
|
-
const
|
|
1803
|
-
|
|
1804
|
-
if (sameSelectionKeys(selection, selectedKeys)) {
|
|
2087
|
+
const grants = this.scope().grants;
|
|
2088
|
+
if (accessibilityInitialized || !grants.length) {
|
|
1805
2089
|
return;
|
|
1806
2090
|
}
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
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
|
+
}
|
|
1810
2107
|
});
|
|
1811
2108
|
}
|
|
1812
|
-
|
|
1813
|
-
|
|
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) {
|
|
1814
2117
|
return;
|
|
1815
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;
|
|
2128
|
+
});
|
|
2129
|
+
}
|
|
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) {
|
|
2163
|
+
return;
|
|
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) {
|
|
1816
2178
|
const index = this.grantIndex();
|
|
1817
|
-
const
|
|
1818
|
-
|
|
1819
|
-
.
|
|
1820
|
-
|
|
1821
|
-
...
|
|
1822
|
-
|
|
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,
|
|
1823
2225
|
}));
|
|
1824
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
|
+
}
|
|
1825
2234
|
getPreviewSummary(summary) {
|
|
1826
2235
|
return formatDelegationScopeSummary(summary, this.currentLanguage());
|
|
1827
2236
|
}
|
|
@@ -1831,59 +2240,29 @@ class ScopePicker {
|
|
|
1831
2240
|
formatDeniedTarget(value) {
|
|
1832
2241
|
return humanizeDelegationKey(value);
|
|
1833
2242
|
}
|
|
1834
|
-
|
|
1835
|
-
return
|
|
1836
|
-
}
|
|
1837
|
-
targetGrantCount(targetGroup) {
|
|
1838
|
-
return targetGroup.grants.length;
|
|
1839
|
-
}
|
|
1840
|
-
buildTreeNodes(groups) {
|
|
1841
|
-
return groups.map((group) => ({
|
|
1842
|
-
key: `group:${group.key}`,
|
|
1843
|
-
label: `${this.getGroupLabel(group)} (${this.groupTargetCount(group)})`,
|
|
1844
|
-
selectable: !this.readonly(),
|
|
1845
|
-
expanded: true,
|
|
1846
|
-
children: group.targets.map((targetGroup) => this.buildTargetNode(targetGroup)),
|
|
1847
|
-
}));
|
|
1848
|
-
}
|
|
1849
|
-
buildTargetNode(targetGroup) {
|
|
1850
|
-
return {
|
|
1851
|
-
key: `target:${targetGroup.key}`,
|
|
1852
|
-
label: `${this.getTargetLabel(targetGroup)} (${this.targetGrantCount(targetGroup)})`,
|
|
1853
|
-
selectable: !this.readonly(),
|
|
1854
|
-
expanded: true,
|
|
1855
|
-
children: targetGroup.grants.map((grant) => ({
|
|
1856
|
-
key: grantKey(grant),
|
|
1857
|
-
label: this.getActionLabel(grant),
|
|
1858
|
-
selectable: !this.readonly(),
|
|
1859
|
-
})),
|
|
1860
|
-
};
|
|
1861
|
-
}
|
|
1862
|
-
getGroupLabel(group) {
|
|
1863
|
-
return group.displayName
|
|
1864
|
-
? humanizeDelegationDisplay(group.displayName, this.currentLanguage())
|
|
1865
|
-
: humanizeDelegationKey(group.groupType);
|
|
1866
|
-
}
|
|
1867
|
-
getTargetLabel(targetGroup) {
|
|
1868
|
-
return formatDelegationTargetLabel(targetGroup.target, this.currentLanguage());
|
|
1869
|
-
}
|
|
1870
|
-
getActionLabel(grant) {
|
|
1871
|
-
return formatDelegationActionLabel(grant.action, this.currentLanguage());
|
|
2243
|
+
trackNode(_index, node) {
|
|
2244
|
+
return node.key;
|
|
1872
2245
|
}
|
|
1873
2246
|
currentLanguage() {
|
|
1874
2247
|
return this.activeLang() ?? this.transloco.getActiveLang();
|
|
1875
2248
|
}
|
|
1876
2249
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ScopePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1877
|
-
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 <div class=\"flex flex-col gap-4\">\n @if (isLoadingOptions()) {\n <div class=\"grid gap-4 2xl:grid-cols-[minmax(0,1fr)_20rem]\">\n <section\n class=\"flex flex-col gap-4 rounded-3xl border border-surface-200 bg-surface-0 p-4 shadow-xs\"\n >\n <div class=\"flex flex-col gap-2\">\n <p-skeleton width=\"9rem\" height=\"1rem\"></p-skeleton>\n <p-skeleton width=\"20rem\" height=\"0.9rem\"></p-skeleton>\n </div>\n\n <p-skeleton height=\"3rem\"></p-skeleton>\n\n <div class=\"grid gap-3\">\n @for (item of [0, 1, 2, 3]; track $index) {\n <div class=\"rounded-2xl border border-surface-200 p-4\">\n <div class=\"flex items-center justify-between gap-3\">\n <p-skeleton width=\"12rem\" height=\"1rem\"></p-skeleton>\n <p-skeleton width=\"1.5rem\" height=\"1.5rem\"></p-skeleton>\n </div>\n </div>\n }\n </div>\n </section>\n\n <section\n class=\"flex flex-col gap-4 rounded-3xl border border-dashed border-surface-300 bg-surface-50 p-4\"\n >\n <div class=\"flex flex-col gap-2\">\n <p-skeleton width=\"8rem\" height=\"1rem\"></p-skeleton>\n <p-skeleton width=\"100%\" height=\"0.85rem\"></p-skeleton>\n </div>\n\n <div class=\"flex flex-col gap-3\">\n @for (item of [0, 1, 2]; track $index) {\n <p-skeleton height=\"1rem\"></p-skeleton>\n }\n </div>\n </section>\n </div>\n } @else {\n <section class=\"flex flex-col gap-3\">\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 (groups().length === 0 && !errorOptions()) {\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=\"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 <circle\n cx=\"46\"\n cy=\"19\"\n r=\"6\"\n class=\"fill-surface-0 stroke-primary\"\n stroke-width=\"2\"\n />\n <path\n d=\"M43 16L49 22\"\n class=\"stroke-primary\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n />\n <path\n d=\"M49 16L43 22\"\n class=\"stroke-primary\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n />\n </svg>\n </div>\n\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 }\n\n @if (groups().length > 0) {\n <div\n class=\"rounded-3xl border border-surface-200 bg-surface-0 p-3 shadow-xs\"\n >\n <mt-tree\n [value]=\"treeNodes()\"\n [(selection)]=\"treeSelection\"\n selectionMode=\"checkbox\"\n [loading]=\"isLoadingOptions()\"\n [propagateSelectionDown]=\"true\"\n [propagateSelectionUp]=\"true\"\n (action)=\"onTreeAction($event)\"\n [filter]=\"true\"\n filterMode=\"lenient\"\n scrollHeight=\"28rem\"\n [virtualScroll]=\"true\"\n [virtualScrollItemSize]=\"72\"\n />\n </div>\n }\n </section>\n\n <section\n class=\"flex flex-col gap-3 rounded-3xl border border-dashed border-surface-300 bg-surface-50 px-4 py-4\"\n >\n <div class=\"flex items-center justify-between gap-2\">\n <div class=\"text-base font-medium text-surface-800\">\n {{ t(\"delegations.scope.previewTitle\") }}\n </div>\n @if (isPreviewing()) {\n <p-skeleton width=\"6rem\" height=\"0.75rem\"></p-skeleton>\n }\n </div>\n @if (preview(); as p) {\n @if (p.isValid) {\n <div class=\"text-sm font-medium text-surface-900\">\n {{\n getPreviewSummary(p.summary) ||\n t(\"delegations.column.scopeSummary\")\n }}\n </div>\n } @else {\n <div class=\"text-sm font-medium text-red-700\">\n {{ t(\"delegations.scope.previewInvalid\") }}\n </div>\n }\n @if (p.warnings.length > 0) {\n <ul class=\"list-inside list-disc space-y-1 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-1 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 } @else {\n <div class=\"text-xs text-surface-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </div>\n }\n </section>\n }\n </div>\n</ng-container>\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 });
|
|
1878
2251
|
}
|
|
1879
2252
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ScopePicker, decorators: [{
|
|
1880
2253
|
type: Component,
|
|
1881
|
-
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" }]
|
|
1882
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 }] }] } });
|
|
1883
|
-
function humanizeDelegationDisplay(value, language) {
|
|
1884
|
-
return (formatDelegationScopeSummary(value, language) ||
|
|
1885
|
-
humanizeDelegationKey(typeof value === 'string' ? value : null));
|
|
1886
|
-
}
|
|
1887
2266
|
|
|
1888
2267
|
const EMPTY_SCOPE = { grants: [], metadata: {} };
|
|
1889
2268
|
function getCurrentDelegatorFromLocalStorage() {
|
|
@@ -2253,7 +2632,7 @@ class DelegationForm {
|
|
|
2253
2632
|
};
|
|
2254
2633
|
}
|
|
2255
2634
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2256
|
-
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 });
|
|
2257
2636
|
}
|
|
2258
2637
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationForm, decorators: [{
|
|
2259
2638
|
type: Component,
|
|
@@ -2264,7 +2643,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
2264
2643
|
ReactiveFormsModule,
|
|
2265
2644
|
ScopePicker,
|
|
2266
2645
|
TranslocoDirective,
|
|
2267
|
-
], 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" }]
|
|
2268
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 }] }] } });
|
|
2269
2648
|
|
|
2270
2649
|
/**
|
|
@@ -2276,19 +2655,40 @@ class DelegationDetailDrawer {
|
|
|
2276
2655
|
facade = inject(DelegationsFacade);
|
|
2277
2656
|
transloco = inject(TranslocoService);
|
|
2278
2657
|
ref = inject(ModalRef);
|
|
2279
|
-
tabs = [
|
|
2280
|
-
{ key: 'overview', i18n: 'delegations.action.view' },
|
|
2281
|
-
{ key: 'scope', i18n: 'delegations.column.scopeSummary' },
|
|
2282
|
-
];
|
|
2283
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
|
+
];
|
|
2284
2678
|
isLoading = this.facade.isLoadingDetail;
|
|
2285
2679
|
detail = this.facade.detail;
|
|
2286
2680
|
row = computed(() => this.detail()?.row ?? null, ...(ngDevMode ? [{ debugName: "row" }] : /* istanbul ignore next */ []));
|
|
2287
2681
|
ngOnInit() {
|
|
2288
2682
|
this.facade.getDetail(this.delegationId(), this.adminMode());
|
|
2289
2683
|
}
|
|
2290
|
-
|
|
2291
|
-
|
|
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(', ');
|
|
2292
2692
|
}
|
|
2293
2693
|
approvalLabel() {
|
|
2294
2694
|
const s = this.row()?.approval?.approvalStatus ?? 'NotRequired';
|
|
@@ -2307,7 +2707,7 @@ class DelegationDetailDrawer {
|
|
|
2307
2707
|
return toDelegationUserEntity(party, { label });
|
|
2308
2708
|
}
|
|
2309
2709
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationDetailDrawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2310
|
-
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\">\n <div class=\"flex flex-col h-full\">\n <!-- Tabs -->\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 });
|
|
2311
2711
|
}
|
|
2312
2712
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationDetailDrawer, decorators: [{
|
|
2313
2713
|
type: Component,
|
|
@@ -2316,9 +2716,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
2316
2716
|
Button,
|
|
2317
2717
|
EntityPreview,
|
|
2318
2718
|
SkeletonModule,
|
|
2719
|
+
Tabs,
|
|
2319
2720
|
TranslocoDirective,
|
|
2320
2721
|
DelegationStatusChip,
|
|
2321
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col h-full\">\n <!-- Tabs -->\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"] }]
|
|
2322
2723
|
}], propDecorators: { delegationId: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegationId", required: true }] }], adminMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "adminMode", required: false }] }] } });
|
|
2323
2724
|
|
|
2324
2725
|
function defaultTrueBooleanAttribute(value) {
|
|
@@ -2385,12 +2786,16 @@ class DelegationsList {
|
|
|
2385
2786
|
? this.facade.isLoadingAdmin()
|
|
2386
2787
|
: this.activeTab() === 'my'
|
|
2387
2788
|
? this.facade.isLoadingMy()
|
|
2388
|
-
: this.
|
|
2789
|
+
: this.activeTab() === 'approvals'
|
|
2790
|
+
? this.facade.isLoadingApprovals()
|
|
2791
|
+
: this.facade.isLoadingAssigned(), ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
|
|
2389
2792
|
rows = computed(() => this.adminMode()
|
|
2390
2793
|
? this.facade.adminItems()
|
|
2391
2794
|
: this.activeTab() === 'my'
|
|
2392
2795
|
? this.facade.myItems()
|
|
2393
|
-
: this.
|
|
2796
|
+
: this.activeTab() === 'approvals'
|
|
2797
|
+
? this.facade.approvalItems()
|
|
2798
|
+
: this.facade.assignedItems(), ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
|
|
2394
2799
|
tableRows = computed(() => this.rows().map((row) => ({
|
|
2395
2800
|
...row,
|
|
2396
2801
|
listedUser: toDelegationUserValue(this.getListedParty(row)),
|
|
@@ -2465,14 +2870,19 @@ class DelegationsList {
|
|
|
2465
2870
|
hidden: (row) => !this.has(row, 'StartSession'),
|
|
2466
2871
|
},
|
|
2467
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
|
+
*/
|
|
2468
2878
|
statusColors = {
|
|
2469
|
-
Active: '#
|
|
2470
|
-
Scheduled: '#
|
|
2471
|
-
PendingApproval: '#
|
|
2472
|
-
InactiveToday: '#
|
|
2879
|
+
Active: '#166534',
|
|
2880
|
+
Scheduled: '#1e40af',
|
|
2881
|
+
PendingApproval: '#854d0e',
|
|
2882
|
+
InactiveToday: '#1e40af',
|
|
2473
2883
|
Expired: '#4b5563',
|
|
2474
|
-
Rejected: '#
|
|
2475
|
-
Cancelled: '#
|
|
2884
|
+
Rejected: '#991b1b',
|
|
2885
|
+
Cancelled: '#374151',
|
|
2476
2886
|
};
|
|
2477
2887
|
statusMap = linkedSignal(() => ({
|
|
2478
2888
|
Active: {
|
|
@@ -2588,9 +2998,9 @@ class DelegationsList {
|
|
|
2588
2998
|
this.facade.getMy(query);
|
|
2589
2999
|
}
|
|
2590
3000
|
else if (this.activeTab() === 'approvals') {
|
|
2591
|
-
this.facade.
|
|
3001
|
+
this.facade.getApprovals({
|
|
2592
3002
|
...query,
|
|
2593
|
-
status:
|
|
3003
|
+
status: this.approvalStatus(),
|
|
2594
3004
|
});
|
|
2595
3005
|
}
|
|
2596
3006
|
else {
|
|
@@ -2697,7 +3107,7 @@ class DelegationsList {
|
|
|
2697
3107
|
this.busyIds.update((ids) => on ? [...ids, key] : ids.filter((id) => id !== key));
|
|
2698
3108
|
}
|
|
2699
3109
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsList, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2700
|
-
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\">\n <ng-template #tableContent>\n <div class=\"flex flex-col gap-4\">\n @if (!adminMode()) {\n @let assignedLabel =\n assignedDecisionTab()\n ? t(\"delegations.action.approve\") +\n \" / \" +\n t(\"delegations.action.reject\")\n : t(assignedTabLabelKey());\n\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 });
|
|
2701
3111
|
}
|
|
2702
3112
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsList, decorators: [{
|
|
2703
3113
|
type: Component,
|
|
@@ -2709,7 +3119,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
2709
3119
|
Table,
|
|
2710
3120
|
Tabs,
|
|
2711
3121
|
TranslocoDirective,
|
|
2712
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <ng-template #tableContent>\n <div class=\"flex flex-col gap-4\">\n @if (!adminMode()) {\n @let assignedLabel =\n assignedDecisionTab()\n ? t(\"delegations.action.approve\") +\n \" / \" +\n t(\"delegations.action.reject\")\n : t(assignedTabLabelKey());\n\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"] }]
|
|
2713
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 }] }] } });
|
|
2714
3124
|
|
|
2715
3125
|
/**
|
|
@@ -2825,5 +3235,5 @@ const appDelegationInterceptor = (req, next) => {
|
|
|
2825
3235
|
* Generated bundle index. Do not edit.
|
|
2826
3236
|
*/
|
|
2827
3237
|
|
|
2828
|
-
export { ApproveDelegation, CancelDelegation, ClearDelegationDetail, ClearScopePreview, CreateDelegationLegacy, CreateDelegationV2, DelegationDetailDrawer, DelegationForm, DelegationSessionActionKey, DelegationSessionFacade, DelegationSessionState, DelegationStatusChip, Delegations, DelegationsActionKey, DelegationsFacade, DelegationsList, DelegationsState, EndDelegationSession, GetActiveAssignedDelegations, GetAdminDelegations, 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 };
|
|
2829
3239
|
//# sourceMappingURL=masterteam-delegations.mjs.map
|