@masterteam/delegations 0.0.41 → 0.0.43
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 +165 -164
- package/assets/i18n/en.json +165 -164
- package/fesm2022/masterteam-delegations.mjs +451 -157
- package/fesm2022/masterteam-delegations.mjs.map +1 -1
- package/package.json +4 -4
- package/types/masterteam-delegations.d.ts +118 -15
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i1 from '@angular/common';
|
|
2
2
|
import { DOCUMENT, CommonModule, Location } from '@angular/common';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { inject, Injectable, InjectionToken, computed, input, ChangeDetectionStrategy, Component, output, viewChild, effect, signal, model, untracked, booleanAttribute, linkedSignal } from '@angular/core';
|
|
4
|
+
import { inject, Injectable, InjectionToken, computed, input, ChangeDetectionStrategy, Component, output, viewChild, effect, signal, model, untracked, booleanAttribute, numberAttribute, linkedSignal } from '@angular/core';
|
|
5
5
|
import { TranslocoService, TranslocoDirective } from '@jsverse/transloco';
|
|
6
6
|
import { Avatar } from '@masterteam/components/avatar';
|
|
7
7
|
import { ModalService } from '@masterteam/components/modal';
|
|
@@ -336,121 +336,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
336
336
|
args: [{ providedIn: 'root' }]
|
|
337
337
|
}] });
|
|
338
338
|
|
|
339
|
-
function isRecord$1(value) {
|
|
340
|
-
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
341
|
-
}
|
|
342
|
-
function readLocalizedValue(source, preferredLanguage) {
|
|
343
|
-
const normalizedLanguage = preferredLanguage.toLowerCase();
|
|
344
|
-
const baseLanguage = normalizedLanguage.split('-')[0];
|
|
345
|
-
const preferredKeys = normalizedLanguage.startsWith('ar')
|
|
346
|
-
? ['defaultAr', 'ar', normalizedLanguage, baseLanguage, 'defaultEn', 'en']
|
|
347
|
-
: ['defaultEn', 'en', normalizedLanguage, baseLanguage, 'defaultAr', 'ar'];
|
|
348
|
-
for (const key of preferredKeys) {
|
|
349
|
-
const value = source[key];
|
|
350
|
-
if (typeof value === 'string' && value.trim()) {
|
|
351
|
-
return value.trim();
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
for (const key of ['en', 'ar', 'defaultEn', 'defaultAr']) {
|
|
355
|
-
const value = source[key];
|
|
356
|
-
if (typeof value === 'string' && value.trim()) {
|
|
357
|
-
return value.trim();
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
return null;
|
|
361
|
-
}
|
|
362
|
-
function parseJsonRecord(value) {
|
|
363
|
-
const trimmed = value.trim();
|
|
364
|
-
if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
|
|
365
|
-
return null;
|
|
366
|
-
}
|
|
367
|
-
try {
|
|
368
|
-
const parsed = JSON.parse(trimmed);
|
|
369
|
-
return isRecord$1(parsed) ? parsed : null;
|
|
370
|
-
}
|
|
371
|
-
catch {
|
|
372
|
-
return null;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
function resolveDisplayValue(value, preferredLanguage) {
|
|
376
|
-
if (typeof value === 'string') {
|
|
377
|
-
const trimmed = value.trim();
|
|
378
|
-
if (!trimmed) {
|
|
379
|
-
return null;
|
|
380
|
-
}
|
|
381
|
-
const parsed = parseJsonRecord(trimmed);
|
|
382
|
-
if (parsed) {
|
|
383
|
-
return (readLocalizedValue(parsed, preferredLanguage) ??
|
|
384
|
-
readDisplayValue(parsed, ['displayName', 'displayLabel', 'label'], preferredLanguage) ??
|
|
385
|
-
trimmed);
|
|
386
|
-
}
|
|
387
|
-
return trimmed;
|
|
388
|
-
}
|
|
389
|
-
if (!isRecord$1(value)) {
|
|
390
|
-
return null;
|
|
391
|
-
}
|
|
392
|
-
return (readLocalizedValue(value, preferredLanguage) ??
|
|
393
|
-
readDisplayValue(value, [
|
|
394
|
-
'displayName',
|
|
395
|
-
'displayLabel',
|
|
396
|
-
'operationDisplayName',
|
|
397
|
-
'operationLabel',
|
|
398
|
-
'label',
|
|
399
|
-
'name',
|
|
400
|
-
'value',
|
|
401
|
-
], preferredLanguage));
|
|
402
|
-
}
|
|
403
|
-
function readDisplayValue(source, keys, preferredLanguage) {
|
|
404
|
-
for (const key of keys) {
|
|
405
|
-
const value = resolveDisplayValue(source[key], preferredLanguage);
|
|
406
|
-
if (value) {
|
|
407
|
-
return value;
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
return null;
|
|
411
|
-
}
|
|
412
|
-
function resolveDisplayOrHumanizedKey(source, displayKeys, fallbackKey, preferredLanguage = 'en') {
|
|
413
|
-
const displayValue = readDisplayValue(source, displayKeys, preferredLanguage);
|
|
414
|
-
if (displayValue) {
|
|
415
|
-
return displayValue;
|
|
416
|
-
}
|
|
417
|
-
return humanizeDelegationKey(fallbackKey);
|
|
418
|
-
}
|
|
419
|
-
function humanizeDelegationKey(value) {
|
|
420
|
-
if (!value) {
|
|
421
|
-
return '';
|
|
422
|
-
}
|
|
423
|
-
const normalized = value
|
|
424
|
-
.replace(/([a-z\d])([A-Z])/g, '$1 $2')
|
|
425
|
-
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
426
|
-
.replace(/[._-]+/g, ' ')
|
|
427
|
-
.replace(/\s+/g, ' ')
|
|
428
|
-
.trim();
|
|
429
|
-
if (!normalized) {
|
|
430
|
-
return '';
|
|
431
|
-
}
|
|
432
|
-
return normalized.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
433
|
-
}
|
|
434
|
-
function formatDelegationDisplayValue(value, preferredLanguage = 'en') {
|
|
435
|
-
return resolveDisplayValue(value, preferredLanguage) ?? '';
|
|
436
|
-
}
|
|
437
|
-
function formatDelegationTargetLabel(target, preferredLanguage = 'en') {
|
|
438
|
-
return resolveDisplayOrHumanizedKey(target, ['name', 'displayName', 'displayLabel', 'label'], target.targetKey, preferredLanguage);
|
|
439
|
-
}
|
|
440
|
-
function formatDelegationActionLabel(action, preferredLanguage = 'en') {
|
|
441
|
-
return resolveDisplayOrHumanizedKey(action, [
|
|
442
|
-
'name',
|
|
443
|
-
'displayName',
|
|
444
|
-
'displayLabel',
|
|
445
|
-
'operationDisplayName',
|
|
446
|
-
'operationLabel',
|
|
447
|
-
'label',
|
|
448
|
-
], action.operationKey, preferredLanguage);
|
|
449
|
-
}
|
|
450
|
-
function formatDelegationScopeSummary(value, preferredLanguage = 'en') {
|
|
451
|
-
return formatDelegationDisplayValue(value, preferredLanguage);
|
|
452
|
-
}
|
|
453
|
-
|
|
454
339
|
function toDelegationUserValue(party) {
|
|
455
340
|
const displayName = party?.displayName?.trim() || party?.email?.trim() || '';
|
|
456
341
|
return {
|
|
@@ -494,9 +379,6 @@ class StartSessionDialog {
|
|
|
494
379
|
successMessageKey = computed(() => this.intent() === 'switch'
|
|
495
380
|
? 'delegations.session.switched'
|
|
496
381
|
: 'delegations.session.started', ...(ngDevMode ? [{ debugName: "successMessageKey" }] : /* istanbul ignore next */ []));
|
|
497
|
-
formatScopeSummary(summary) {
|
|
498
|
-
return formatDelegationScopeSummary(summary, this.transloco.getActiveLang());
|
|
499
|
-
}
|
|
500
382
|
userEntity(party) {
|
|
501
383
|
return toDelegationUserEntity(party, { showEmail: false });
|
|
502
384
|
}
|
|
@@ -519,11 +401,11 @@ class StartSessionDialog {
|
|
|
519
401
|
this.ref.close(false);
|
|
520
402
|
}
|
|
521
403
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: StartSessionDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
522
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.
|
|
404
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.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 </div>\n </div>\n\n <p class=\"text-sm text-surface-600 leading-relaxed\">\n {{ t(bodyKey()) }}\n </p>\n\n <div class=\"flex justify-end gap-2 pt-2 border-t border-surface-200\">\n <mt-button\n variant=\"outlined\"\n color=\"secondary\"\n [label]=\"t('delegations.common.cancel')\"\n [disabled]=\"isBusy()\"\n (click)=\"cancel()\"\n ></mt-button>\n <mt-button\n color=\"primary\"\n icon=\"user.users-check\"\n [label]=\"t(confirmLabelKey())\"\n [loading]=\"isBusy()\"\n (click)=\"confirm()\"\n ></mt-button>\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: 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 });
|
|
523
405
|
}
|
|
524
406
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: StartSessionDialog, decorators: [{
|
|
525
407
|
type: Component,
|
|
526
|
-
args: [{ selector: 'mt-start-session-dialog', imports: [CommonModule, Button, EntityPreview, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\
|
|
408
|
+
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 </div>\n </div>\n\n <p class=\"text-sm text-surface-600 leading-relaxed\">\n {{ t(bodyKey()) }}\n </p>\n\n <div class=\"flex justify-end gap-2 pt-2 border-t border-surface-200\">\n <mt-button\n variant=\"outlined\"\n color=\"secondary\"\n [label]=\"t('delegations.common.cancel')\"\n [disabled]=\"isBusy()\"\n (click)=\"cancel()\"\n ></mt-button>\n <mt-button\n color=\"primary\"\n icon=\"user.users-check\"\n [label]=\"t(confirmLabelKey())\"\n [loading]=\"isBusy()\"\n (click)=\"confirm()\"\n ></mt-button>\n </div>\n </div>\n</ng-container>\n" }]
|
|
527
409
|
}], propDecorators: { delegation: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegation", required: true }] }], intent: [{ type: i0.Input, args: [{ isSignal: true, alias: "intent", required: false }] }] } });
|
|
528
410
|
|
|
529
411
|
/**
|
|
@@ -672,7 +554,7 @@ class DelegationMenuPanel {
|
|
|
672
554
|
});
|
|
673
555
|
}
|
|
674
556
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationMenuPanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
675
|
-
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
|
|
557
|
+
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\n class=\"text-[0.625rem] font-medium uppercase text-surface-500\"\n >\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\n status=\"Active\"\n ></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\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 }\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 });
|
|
676
558
|
}
|
|
677
559
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationMenuPanel, decorators: [{
|
|
678
560
|
type: Component,
|
|
@@ -683,7 +565,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
683
565
|
RouterLink,
|
|
684
566
|
TranslocoDirective,
|
|
685
567
|
DelegationStatusChip,
|
|
686
|
-
], 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
|
|
568
|
+
], 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\n class=\"text-[0.625rem] font-medium uppercase text-surface-500\"\n >\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\n status=\"Active\"\n ></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\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 }\n</ng-container>\n" }]
|
|
687
569
|
}], 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"] }] } });
|
|
688
570
|
|
|
689
571
|
/**
|
|
@@ -1498,11 +1380,11 @@ class RejectDelegationDialog {
|
|
|
1498
1380
|
this.ref.close(false);
|
|
1499
1381
|
}
|
|
1500
1382
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: RejectDelegationDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1501
|
-
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
|
|
1383
|
+
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\r\n class=\"text-sm font-medium text-surface-800\"\r\n for=\"mt-reject-reason\"\r\n >\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 });
|
|
1502
1384
|
}
|
|
1503
1385
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: RejectDelegationDialog, decorators: [{
|
|
1504
1386
|
type: Component,
|
|
1505
|
-
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
|
|
1387
|
+
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\r\n class=\"text-sm font-medium text-surface-800\"\r\n for=\"mt-reject-reason\"\r\n >\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" }]
|
|
1506
1388
|
}], propDecorators: { delegation: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegation", required: true }] }] } });
|
|
1507
1389
|
|
|
1508
1390
|
class Delegations {
|
|
@@ -1558,6 +1440,121 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
1558
1440
|
args: [{ selector: 'mt-delegations', imports: [Page, RouterOutlet, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <mt-page\n [title]=\"t('delegations.title')\"\n [avatarIcon]=\"'custom.hierarchy-structure'\"\n [tabs]=\"tabs()\"\n [activeTab]=\"activeTab()\"\n [contentClass]=\"'max-[1025px]:p-4 max-[640px]:p-3'\"\n [avatarStyle]=\"{\n '--p-avatar-background': 'var(--p-indigo-50)',\n '--p-avatar-color': 'var(--p-indigo-700)',\n }\"\n (tabChange)=\"onTabChange($event)\"\n (backButtonClick)=\"goBack()\"\n backButton\n >\n <router-outlet />\n </mt-page>\n</ng-container>\n" }]
|
|
1559
1441
|
}], ctorParameters: () => [] });
|
|
1560
1442
|
|
|
1443
|
+
function isRecord$1(value) {
|
|
1444
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
1445
|
+
}
|
|
1446
|
+
function readLocalizedValue(source, preferredLanguage) {
|
|
1447
|
+
const normalizedLanguage = preferredLanguage.toLowerCase();
|
|
1448
|
+
const baseLanguage = normalizedLanguage.split('-')[0];
|
|
1449
|
+
const preferredKeys = normalizedLanguage.startsWith('ar')
|
|
1450
|
+
? ['defaultAr', 'ar', normalizedLanguage, baseLanguage, 'defaultEn', 'en']
|
|
1451
|
+
: ['defaultEn', 'en', normalizedLanguage, baseLanguage, 'defaultAr', 'ar'];
|
|
1452
|
+
for (const key of preferredKeys) {
|
|
1453
|
+
const value = source[key];
|
|
1454
|
+
if (typeof value === 'string' && value.trim()) {
|
|
1455
|
+
return value.trim();
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
for (const key of ['en', 'ar', 'defaultEn', 'defaultAr']) {
|
|
1459
|
+
const value = source[key];
|
|
1460
|
+
if (typeof value === 'string' && value.trim()) {
|
|
1461
|
+
return value.trim();
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
return null;
|
|
1465
|
+
}
|
|
1466
|
+
function parseJsonRecord(value) {
|
|
1467
|
+
const trimmed = value.trim();
|
|
1468
|
+
if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
|
|
1469
|
+
return null;
|
|
1470
|
+
}
|
|
1471
|
+
try {
|
|
1472
|
+
const parsed = JSON.parse(trimmed);
|
|
1473
|
+
return isRecord$1(parsed) ? parsed : null;
|
|
1474
|
+
}
|
|
1475
|
+
catch {
|
|
1476
|
+
return null;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
function resolveDisplayValue(value, preferredLanguage) {
|
|
1480
|
+
if (typeof value === 'string') {
|
|
1481
|
+
const trimmed = value.trim();
|
|
1482
|
+
if (!trimmed) {
|
|
1483
|
+
return null;
|
|
1484
|
+
}
|
|
1485
|
+
const parsed = parseJsonRecord(trimmed);
|
|
1486
|
+
if (parsed) {
|
|
1487
|
+
return (readLocalizedValue(parsed, preferredLanguage) ??
|
|
1488
|
+
readDisplayValue(parsed, ['displayName', 'displayLabel', 'label'], preferredLanguage) ??
|
|
1489
|
+
trimmed);
|
|
1490
|
+
}
|
|
1491
|
+
return trimmed;
|
|
1492
|
+
}
|
|
1493
|
+
if (!isRecord$1(value)) {
|
|
1494
|
+
return null;
|
|
1495
|
+
}
|
|
1496
|
+
return (readLocalizedValue(value, preferredLanguage) ??
|
|
1497
|
+
readDisplayValue(value, [
|
|
1498
|
+
'displayName',
|
|
1499
|
+
'displayLabel',
|
|
1500
|
+
'operationDisplayName',
|
|
1501
|
+
'operationLabel',
|
|
1502
|
+
'label',
|
|
1503
|
+
'name',
|
|
1504
|
+
'value',
|
|
1505
|
+
], preferredLanguage));
|
|
1506
|
+
}
|
|
1507
|
+
function readDisplayValue(source, keys, preferredLanguage) {
|
|
1508
|
+
for (const key of keys) {
|
|
1509
|
+
const value = resolveDisplayValue(source[key], preferredLanguage);
|
|
1510
|
+
if (value) {
|
|
1511
|
+
return value;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
return null;
|
|
1515
|
+
}
|
|
1516
|
+
function resolveDisplayOrHumanizedKey(source, displayKeys, fallbackKey, preferredLanguage = 'en') {
|
|
1517
|
+
const displayValue = readDisplayValue(source, displayKeys, preferredLanguage);
|
|
1518
|
+
if (displayValue) {
|
|
1519
|
+
return displayValue;
|
|
1520
|
+
}
|
|
1521
|
+
return humanizeDelegationKey(fallbackKey);
|
|
1522
|
+
}
|
|
1523
|
+
function humanizeDelegationKey(value) {
|
|
1524
|
+
if (!value) {
|
|
1525
|
+
return '';
|
|
1526
|
+
}
|
|
1527
|
+
const normalized = value
|
|
1528
|
+
.replace(/([a-z\d])([A-Z])/g, '$1 $2')
|
|
1529
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
|
|
1530
|
+
.replace(/[._-]+/g, ' ')
|
|
1531
|
+
.replace(/\s+/g, ' ')
|
|
1532
|
+
.trim();
|
|
1533
|
+
if (!normalized) {
|
|
1534
|
+
return '';
|
|
1535
|
+
}
|
|
1536
|
+
return normalized.replace(/\b\w/g, (char) => char.toUpperCase());
|
|
1537
|
+
}
|
|
1538
|
+
function formatDelegationDisplayValue(value, preferredLanguage = 'en') {
|
|
1539
|
+
return resolveDisplayValue(value, preferredLanguage) ?? '';
|
|
1540
|
+
}
|
|
1541
|
+
function formatDelegationTargetLabel(target, preferredLanguage = 'en') {
|
|
1542
|
+
return resolveDisplayOrHumanizedKey(target, ['name', 'displayName', 'displayLabel', 'label'], target.targetKey, preferredLanguage);
|
|
1543
|
+
}
|
|
1544
|
+
function formatDelegationActionLabel(action, preferredLanguage = 'en') {
|
|
1545
|
+
return resolveDisplayOrHumanizedKey(action, [
|
|
1546
|
+
'name',
|
|
1547
|
+
'displayName',
|
|
1548
|
+
'displayLabel',
|
|
1549
|
+
'operationDisplayName',
|
|
1550
|
+
'operationLabel',
|
|
1551
|
+
'label',
|
|
1552
|
+
], action.operationKey, preferredLanguage);
|
|
1553
|
+
}
|
|
1554
|
+
function formatDelegationScopeSummary(value, preferredLanguage = 'en') {
|
|
1555
|
+
return formatDelegationDisplayValue(value, preferredLanguage);
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1561
1558
|
const DEFAULT_SORT_ORDER = Number.MAX_SAFE_INTEGER;
|
|
1562
1559
|
function toArray(value) {
|
|
1563
1560
|
return Array.isArray(value) ? value : [];
|
|
@@ -1852,7 +1849,7 @@ function hasDelegationGrantableScope(options) {
|
|
|
1852
1849
|
return (getDelegationPermissionTree(options).length > 0 ||
|
|
1853
1850
|
getDelegationGrantableGroups(options).length > 0);
|
|
1854
1851
|
}
|
|
1855
|
-
/** Stable selection key for an operation leaf
|
|
1852
|
+
/** Stable selection key for an operation leaf, independent of accessibilities. */
|
|
1856
1853
|
function delegationOperationNodeKey(grant) {
|
|
1857
1854
|
return `op:${targetIdentity(grant.target)}::${grant.action.operationKey}`;
|
|
1858
1855
|
}
|
|
@@ -2233,9 +2230,7 @@ class ScopePicker {
|
|
|
2233
2230
|
}
|
|
2234
2231
|
// --- Expansion ------------------------------------------------------------
|
|
2235
2232
|
isExpanded(node) {
|
|
2236
|
-
return this.searchTerm().trim()
|
|
2237
|
-
? true
|
|
2238
|
-
: this.expandedKeys().has(node.key);
|
|
2233
|
+
return this.searchTerm().trim() ? true : this.expandedKeys().has(node.key);
|
|
2239
2234
|
}
|
|
2240
2235
|
toggleExpand(node) {
|
|
2241
2236
|
if (!node.children?.length) {
|
|
@@ -2413,7 +2408,7 @@ class ScopePicker {
|
|
|
2413
2408
|
return this.activeLang() ?? this.transloco.getActiveLang();
|
|
2414
2409
|
}
|
|
2415
2410
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ScopePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2416
|
-
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 <!-- Reusable tri-state checkbox box -->\n <ng-template #checkbox let-state=\"state\">\n <span\n class=\"flex size-[18px] shrink-0 items-center justify-center rounded-[5px] border transition-colors\"\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 >\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 </span>\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 <!-- Select-all header -->\n <button\n type=\"button\"\n class=\"flex w-full cursor-pointer items-center gap-2 border-b border-surface px-3 py-2 text-start hover:bg-surface-50\"\n role=\"checkbox\"\n [attr.aria-checked]=\"\n selectAllState() === 'indeterminate'\n ? 'mixed'\n : selectAllState() === 'checked'\n \"\n [disabled]=\"readonly()\"\n (click)=\"toggleAll()\"\n >\n <ng-container\n *ngTemplateOutlet=\"checkbox; context: { state: selectAllState() }\"\n ></ng-container>\n <span class=\"text-xs font-semibold uppercase tracking-wide text-surface-500\">\n {{ t(\"delegations.scope.selectAll\") }}\n </span>\n </button>\n\n <!-- Virtualized permission tree -->\n @if (visibleNodes().length > 0) {\n <cdk-virtual-scroll-viewport\n itemSize=\"40\"\n class=\"block h-[20rem]\"\n >\n <div\n *cdkVirtualFor=\"\n let item of visibleNodes();\n trackBy: trackVisible\n \"\n class=\"group flex h-10 items-center gap-2 pe-2 transition-colors hover:bg-surface-50\"\n [style.padding-inline-start.rem]=\"0.5 + item.depth * 1.25\"\n >\n @if (hasChildren(item.node)) {\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(item.node)\"\n [attr.aria-label]=\"nodeLabel(item.node)\"\n (click)=\"toggleExpand(item.node); $event.stopPropagation()\"\n >\n <mt-icon\n [icon]=\"\n isExpanded(item.node)\n ? 'arrow.chevron-down'\n : 'arrow.chevron-right'\n \"\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 nodeState(item.node) === 'indeterminate'\n ? 'mixed'\n : nodeState(item.node) === 'checked'\n \"\n [attr.aria-label]=\"nodeLabel(item.node)\"\n class=\"shrink-0\"\n [class.cursor-pointer]=\"!readonly()\"\n [disabled]=\"readonly()\"\n (click)=\"toggle(item.node); $event.stopPropagation()\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n checkbox;\n context: { state: nodeState(item.node) }\n \"\n ></ng-container>\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(item.node)\"\n >\n <span\n class=\"truncate text-sm\"\n [class.font-semibold]=\"item.node.kind === 'template'\"\n [class.text-surface-900]=\"item.node.kind === 'template'\"\n [class.font-medium]=\"\n item.node.kind === 'level' || item.node.kind === 'module'\n \"\n [class.text-surface-800]=\"\n item.node.kind === 'level' || item.node.kind === 'module'\n \"\n [class.text-surface-600]=\"item.node.kind === 'operation'\"\n >\n {{ nodeLabel(item.node) }}\n </span>\n @if (item.node.kind === \"operation\" && item.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 </cdk-virtual-scroll-viewport>\n } @else {\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 } @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: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i3.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i3.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i3.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { 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: i4.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 });
|
|
2411
|
+
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 <!-- Reusable tri-state checkbox box -->\n <ng-template #checkbox let-state=\"state\">\n <span\n class=\"flex size-[18px] shrink-0 items-center justify-center rounded-[5px] border transition-colors\"\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 >\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 </span>\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\n class=\"flex items-center gap-2 border-b border-surface px-3 py-2\"\n >\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 <!-- Select-all header -->\n <button\n type=\"button\"\n class=\"flex w-full cursor-pointer items-center gap-2 border-b border-surface px-3 py-2 text-start hover:bg-surface-50\"\n role=\"checkbox\"\n [attr.aria-checked]=\"\n selectAllState() === 'indeterminate'\n ? 'mixed'\n : selectAllState() === 'checked'\n \"\n [disabled]=\"readonly()\"\n (click)=\"toggleAll()\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n checkbox;\n context: { state: selectAllState() }\n \"\n ></ng-container>\n <span\n class=\"text-xs font-semibold uppercase tracking-wide text-surface-500\"\n >\n {{ t(\"delegations.scope.selectAll\") }}\n </span>\n </button>\n\n <!-- Virtualized permission tree -->\n @if (visibleNodes().length > 0) {\n <cdk-virtual-scroll-viewport\n itemSize=\"40\"\n class=\"block h-[20rem]\"\n >\n <div\n *cdkVirtualFor=\"\n let item of visibleNodes();\n trackBy: trackVisible\n \"\n class=\"group flex h-10 items-center gap-2 pe-2 transition-colors hover:bg-surface-50\"\n [style.padding-inline-start.rem]=\"0.5 + item.depth * 1.25\"\n >\n @if (hasChildren(item.node)) {\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(item.node)\"\n [attr.aria-label]=\"nodeLabel(item.node)\"\n (click)=\"\n toggleExpand(item.node); $event.stopPropagation()\n \"\n >\n <mt-icon\n [icon]=\"\n isExpanded(item.node)\n ? 'arrow.chevron-down'\n : 'arrow.chevron-right'\n \"\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 nodeState(item.node) === 'indeterminate'\n ? 'mixed'\n : nodeState(item.node) === 'checked'\n \"\n [attr.aria-label]=\"nodeLabel(item.node)\"\n class=\"shrink-0\"\n [class.cursor-pointer]=\"!readonly()\"\n [disabled]=\"readonly()\"\n (click)=\"toggle(item.node); $event.stopPropagation()\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n checkbox;\n context: { state: nodeState(item.node) }\n \"\n ></ng-container>\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(item.node)\"\n >\n <span\n class=\"truncate text-sm\"\n [class.font-semibold]=\"item.node.kind === 'template'\"\n [class.text-surface-900]=\"item.node.kind === 'template'\"\n [class.font-medium]=\"\n item.node.kind === 'level' ||\n item.node.kind === 'module'\n \"\n [class.text-surface-800]=\"\n item.node.kind === 'level' ||\n item.node.kind === 'module'\n \"\n [class.text-surface-600]=\"item.node.kind === 'operation'\"\n >\n {{ nodeLabel(item.node) }}\n </span>\n @if (\n item.node.kind === \"operation\" && item.node.isHighRisk\n ) {\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 </cdk-virtual-scroll-viewport>\n } @else {\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 } @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 (\n item of appAccessibilities();\n track item.accessibilityKey\n ) {\n @let selected =\n 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\n class=\"list-inside list-disc space-y-0.5 text-xs text-amber-700\"\n >\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: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i3.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i3.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i3.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { 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: i4.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 });
|
|
2417
2412
|
}
|
|
2418
2413
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ScopePicker, decorators: [{
|
|
2419
2414
|
type: Component,
|
|
@@ -2428,7 +2423,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
2428
2423
|
TextField,
|
|
2429
2424
|
SkeletonModule,
|
|
2430
2425
|
TranslocoDirective,
|
|
2431
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <!-- Reusable tri-state checkbox box -->\n <ng-template #checkbox let-state=\"state\">\n <span\n class=\"flex size-[18px] shrink-0 items-center justify-center rounded-[5px] border transition-colors\"\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 >\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 </span>\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 <!-- Select-all header -->\n <button\n type=\"button\"\n class=\"flex w-full cursor-pointer items-center gap-2 border-b border-surface px-3 py-2 text-start hover:bg-surface-50\"\n role=\"checkbox\"\n [attr.aria-checked]=\"\n selectAllState() === 'indeterminate'\n ? 'mixed'\n : selectAllState() === 'checked'\n \"\n [disabled]=\"readonly()\"\n (click)=\"toggleAll()\"\n >\n <ng-container\n *ngTemplateOutlet=\"checkbox; context: { state: selectAllState() }\"\n ></ng-container>\n <span class=\"text-xs font-semibold uppercase tracking-wide text-surface-500\">\n {{ t(\"delegations.scope.selectAll\") }}\n </span>\n </button>\n\n <!-- Virtualized permission tree -->\n @if (visibleNodes().length > 0) {\n <cdk-virtual-scroll-viewport\n itemSize=\"40\"\n class=\"block h-[20rem]\"\n >\n <div\n *cdkVirtualFor=\"\n let item of visibleNodes();\n trackBy: trackVisible\n \"\n class=\"group flex h-10 items-center gap-2 pe-2 transition-colors hover:bg-surface-50\"\n [style.padding-inline-start.rem]=\"0.5 + item.depth * 1.25\"\n >\n @if (hasChildren(item.node)) {\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(item.node)\"\n [attr.aria-label]=\"nodeLabel(item.node)\"\n (click)=\"toggleExpand(item.node); $event.stopPropagation()\"\n >\n <mt-icon\n [icon]=\"\n isExpanded(item.node)\n ? 'arrow.chevron-down'\n : 'arrow.chevron-right'\n \"\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 nodeState(item.node) === 'indeterminate'\n ? 'mixed'\n : nodeState(item.node) === 'checked'\n \"\n [attr.aria-label]=\"nodeLabel(item.node)\"\n class=\"shrink-0\"\n [class.cursor-pointer]=\"!readonly()\"\n [disabled]=\"readonly()\"\n (click)=\"toggle(item.node); $event.stopPropagation()\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n checkbox;\n context: { state: nodeState(item.node) }\n \"\n ></ng-container>\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(item.node)\"\n >\n <span\n class=\"truncate text-sm\"\n [class.font-semibold]=\"item.node.kind === 'template'\"\n [class.text-surface-900]=\"item.node.kind === 'template'\"\n [class.font-medium]=\"\n item.node.kind === 'level' || item.node.kind === 'module'\n \"\n [class.text-surface-800]=\"\n item.node.kind === 'level' || item.node.kind === 'module'\n \"\n [class.text-surface-600]=\"item.node.kind === 'operation'\"\n >\n {{ nodeLabel(item.node) }}\n </span>\n @if (item.node.kind === \"operation\" && item.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 </cdk-virtual-scroll-viewport>\n } @else {\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 } @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" }]
|
|
2426
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <!-- Reusable tri-state checkbox box -->\n <ng-template #checkbox let-state=\"state\">\n <span\n class=\"flex size-[18px] shrink-0 items-center justify-center rounded-[5px] border transition-colors\"\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 >\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 </span>\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\n class=\"flex items-center gap-2 border-b border-surface px-3 py-2\"\n >\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 <!-- Select-all header -->\n <button\n type=\"button\"\n class=\"flex w-full cursor-pointer items-center gap-2 border-b border-surface px-3 py-2 text-start hover:bg-surface-50\"\n role=\"checkbox\"\n [attr.aria-checked]=\"\n selectAllState() === 'indeterminate'\n ? 'mixed'\n : selectAllState() === 'checked'\n \"\n [disabled]=\"readonly()\"\n (click)=\"toggleAll()\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n checkbox;\n context: { state: selectAllState() }\n \"\n ></ng-container>\n <span\n class=\"text-xs font-semibold uppercase tracking-wide text-surface-500\"\n >\n {{ t(\"delegations.scope.selectAll\") }}\n </span>\n </button>\n\n <!-- Virtualized permission tree -->\n @if (visibleNodes().length > 0) {\n <cdk-virtual-scroll-viewport\n itemSize=\"40\"\n class=\"block h-[20rem]\"\n >\n <div\n *cdkVirtualFor=\"\n let item of visibleNodes();\n trackBy: trackVisible\n \"\n class=\"group flex h-10 items-center gap-2 pe-2 transition-colors hover:bg-surface-50\"\n [style.padding-inline-start.rem]=\"0.5 + item.depth * 1.25\"\n >\n @if (hasChildren(item.node)) {\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(item.node)\"\n [attr.aria-label]=\"nodeLabel(item.node)\"\n (click)=\"\n toggleExpand(item.node); $event.stopPropagation()\n \"\n >\n <mt-icon\n [icon]=\"\n isExpanded(item.node)\n ? 'arrow.chevron-down'\n : 'arrow.chevron-right'\n \"\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 nodeState(item.node) === 'indeterminate'\n ? 'mixed'\n : nodeState(item.node) === 'checked'\n \"\n [attr.aria-label]=\"nodeLabel(item.node)\"\n class=\"shrink-0\"\n [class.cursor-pointer]=\"!readonly()\"\n [disabled]=\"readonly()\"\n (click)=\"toggle(item.node); $event.stopPropagation()\"\n >\n <ng-container\n *ngTemplateOutlet=\"\n checkbox;\n context: { state: nodeState(item.node) }\n \"\n ></ng-container>\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(item.node)\"\n >\n <span\n class=\"truncate text-sm\"\n [class.font-semibold]=\"item.node.kind === 'template'\"\n [class.text-surface-900]=\"item.node.kind === 'template'\"\n [class.font-medium]=\"\n item.node.kind === 'level' ||\n item.node.kind === 'module'\n \"\n [class.text-surface-800]=\"\n item.node.kind === 'level' ||\n item.node.kind === 'module'\n \"\n [class.text-surface-600]=\"item.node.kind === 'operation'\"\n >\n {{ nodeLabel(item.node) }}\n </span>\n @if (\n item.node.kind === \"operation\" && item.node.isHighRisk\n ) {\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 </cdk-virtual-scroll-viewport>\n } @else {\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 } @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 (\n item of appAccessibilities();\n track item.accessibilityKey\n ) {\n @let selected =\n 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\n class=\"list-inside list-disc space-y-0.5 text-xs text-amber-700\"\n >\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" }]
|
|
2432
2427
|
}], 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 }] }] } });
|
|
2433
2428
|
|
|
2434
2429
|
const EMPTY_SCOPE = { grants: [], metadata: {} };
|
|
@@ -2829,7 +2824,7 @@ class DelegationForm {
|
|
|
2829
2824
|
};
|
|
2830
2825
|
}
|
|
2831
2826
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2832
|
-
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-
|
|
2827
|
+
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\">\r\n <div\r\n [class]=\"\r\n 'flex h-full min-h-0 min-w-0 flex-col overflow-hidden ' +\r\n modal.contentClass\r\n \"\r\n >\r\n <div\r\n class=\"grid min-h-0 flex-1 gap-4 overflow-y-auto p-4 max-[640px]:p-3 lg:grid-cols-2 lg:items-start\"\r\n >\r\n <section\r\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\"\r\n >\r\n <mt-dynamic-form\r\n [formConfig]=\"formConfig()\"\r\n [formControl]=\"delegationFormControl\"\r\n />\r\n </section>\r\n\r\n <section class=\"flex min-h-0 flex-col gap-3\">\r\n <div class=\"flex flex-col gap-1\">\r\n <h3 class=\"text-xl font-semibold text-surface-900\">\r\n {{ t(\"delegations.scope.permissionsTitle\") }}\r\n </h3>\r\n <p class=\"text-sm text-surface-500\">\r\n {{\r\n adminMode() && !showScopePicker()\r\n ? t(\"delegations.form.selectDelegatorFirst\")\r\n : t(\"delegations.scope.permissionsSubtitle\")\r\n }}\r\n </p>\r\n </div>\r\n\r\n @if (showScopePicker()) {\r\n <mt-scope-picker\r\n [(scope)]=\"scope\"\r\n [readonly]=\"readonly()\"\r\n [adminMode]=\"adminMode()\"\r\n [delegatorUserId]=\"selectedDelegatorId()\"\r\n ></mt-scope-picker>\r\n } @else {\r\n <div\r\n class=\"flex min-h-72 flex-col items-center justify-center gap-4 rounded-3xl border border-dashed border-surface-300 bg-surface-50 px-6 py-8 text-center\"\r\n >\r\n <div\r\n class=\"flex size-18 items-center justify-center rounded-3xl bg-surface-0 text-primary shadow-sm\"\r\n >\r\n <svg\r\n viewBox=\"0 0 64 64\"\r\n class=\"size-10\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n aria-hidden=\"true\"\r\n >\r\n <rect\r\n x=\"10\"\r\n y=\"12\"\r\n width=\"44\"\r\n height=\"40\"\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=\"M22 28H42\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M22 36H34\"\r\n class=\"stroke-primary/70\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <circle\r\n cx=\"45\"\r\n cy=\"20\"\r\n r=\"7\"\r\n class=\"fill-surface-0 stroke-primary\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M45 17V23\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"2.5\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M42 20H48\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"2.5\"\r\n stroke-linecap=\"round\"\r\n />\r\n </svg>\r\n </div>\r\n <div class=\"space-y-1\">\r\n <p class=\"text-base font-medium text-surface-900\">\r\n {{ t(\"delegations.scope.permissionsTitle\") }}\r\n </p>\r\n <p class=\"text-sm text-surface-500\">\r\n {{ t(\"delegations.form.selectDelegatorFirst\") }}\r\n </p>\r\n </div>\r\n </div>\r\n }\r\n </section>\r\n </div>\r\n </div>\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n [label]=\"t('delegations.common.cancel')\"\r\n variant=\"outlined\"\r\n (click)=\"ref.close()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n />\r\n @if (!readonly()) {\r\n <mt-button\r\n [label]=\"\r\n delegationForEdit()\r\n ? t('delegations.common.update')\r\n : t('delegations.common.create')\r\n \"\r\n [loading]=\"isSaving()\"\r\n [disabled]=\"!delegationFormControl.valid || !canSubmit()\"\r\n (click)=\"onSubmit()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n />\r\n }\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: 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 });
|
|
2833
2828
|
}
|
|
2834
2829
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationForm, decorators: [{
|
|
2835
2830
|
type: Component,
|
|
@@ -2840,11 +2835,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
2840
2835
|
ReactiveFormsModule,
|
|
2841
2836
|
ScopePicker,
|
|
2842
2837
|
TranslocoDirective,
|
|
2843
|
-
], 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-
|
|
2838
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div\r\n [class]=\"\r\n 'flex h-full min-h-0 min-w-0 flex-col overflow-hidden ' +\r\n modal.contentClass\r\n \"\r\n >\r\n <div\r\n class=\"grid min-h-0 flex-1 gap-4 overflow-y-auto p-4 max-[640px]:p-3 lg:grid-cols-2 lg:items-start\"\r\n >\r\n <section\r\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\"\r\n >\r\n <mt-dynamic-form\r\n [formConfig]=\"formConfig()\"\r\n [formControl]=\"delegationFormControl\"\r\n />\r\n </section>\r\n\r\n <section class=\"flex min-h-0 flex-col gap-3\">\r\n <div class=\"flex flex-col gap-1\">\r\n <h3 class=\"text-xl font-semibold text-surface-900\">\r\n {{ t(\"delegations.scope.permissionsTitle\") }}\r\n </h3>\r\n <p class=\"text-sm text-surface-500\">\r\n {{\r\n adminMode() && !showScopePicker()\r\n ? t(\"delegations.form.selectDelegatorFirst\")\r\n : t(\"delegations.scope.permissionsSubtitle\")\r\n }}\r\n </p>\r\n </div>\r\n\r\n @if (showScopePicker()) {\r\n <mt-scope-picker\r\n [(scope)]=\"scope\"\r\n [readonly]=\"readonly()\"\r\n [adminMode]=\"adminMode()\"\r\n [delegatorUserId]=\"selectedDelegatorId()\"\r\n ></mt-scope-picker>\r\n } @else {\r\n <div\r\n class=\"flex min-h-72 flex-col items-center justify-center gap-4 rounded-3xl border border-dashed border-surface-300 bg-surface-50 px-6 py-8 text-center\"\r\n >\r\n <div\r\n class=\"flex size-18 items-center justify-center rounded-3xl bg-surface-0 text-primary shadow-sm\"\r\n >\r\n <svg\r\n viewBox=\"0 0 64 64\"\r\n class=\"size-10\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n aria-hidden=\"true\"\r\n >\r\n <rect\r\n x=\"10\"\r\n y=\"12\"\r\n width=\"44\"\r\n height=\"40\"\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=\"M22 28H42\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M22 36H34\"\r\n class=\"stroke-primary/70\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <circle\r\n cx=\"45\"\r\n cy=\"20\"\r\n r=\"7\"\r\n class=\"fill-surface-0 stroke-primary\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M45 17V23\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"2.5\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M42 20H48\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"2.5\"\r\n stroke-linecap=\"round\"\r\n />\r\n </svg>\r\n </div>\r\n <div class=\"space-y-1\">\r\n <p class=\"text-base font-medium text-surface-900\">\r\n {{ t(\"delegations.scope.permissionsTitle\") }}\r\n </p>\r\n <p class=\"text-sm text-surface-500\">\r\n {{ t(\"delegations.form.selectDelegatorFirst\") }}\r\n </p>\r\n </div>\r\n </div>\r\n }\r\n </section>\r\n </div>\r\n </div>\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n [label]=\"t('delegations.common.cancel')\"\r\n variant=\"outlined\"\r\n (click)=\"ref.close()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n />\r\n @if (!readonly()) {\r\n <mt-button\r\n [label]=\"\r\n delegationForEdit()\r\n ? t('delegations.common.update')\r\n : t('delegations.common.create')\r\n \"\r\n [loading]=\"isSaving()\"\r\n [disabled]=\"!delegationFormControl.valid || !canSubmit()\"\r\n (click)=\"onSubmit()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n />\r\n }\r\n </div>\r\n</ng-container>\r\n" }]
|
|
2844
2839
|
}], 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 }] }] } });
|
|
2845
2840
|
|
|
2846
2841
|
/**
|
|
2847
|
-
* Read-only drawer that shows
|
|
2842
|
+
* Read-only drawer that shows `DelegationDetail` row/status plus saved scope
|
|
2843
|
+
* from `scopeView`. The persisted `scope.grants` shape is not parsed for
|
|
2844
|
+
* display; it remains a backend validation/persistence shape.
|
|
2848
2845
|
*/
|
|
2849
2846
|
class DelegationDetailDrawer {
|
|
2850
2847
|
delegationId = input.required(...(ngDevMode ? [{ debugName: "delegationId" }] : /* istanbul ignore next */ []));
|
|
@@ -2875,6 +2872,10 @@ class DelegationDetailDrawer {
|
|
|
2875
2872
|
isLoading = this.facade.isLoadingDetail;
|
|
2876
2873
|
detail = this.facade.detail;
|
|
2877
2874
|
row = computed(() => this.detail()?.row ?? null, ...(ngDevMode ? [{ debugName: "row" }] : /* istanbul ignore next */ []));
|
|
2875
|
+
scopeNodes = computed(() => this.buildScopeViewTree(this.detail()?.scopeView?.templates), ...(ngDevMode ? [{ debugName: "scopeNodes" }] : /* istanbul ignore next */ []));
|
|
2876
|
+
scopeVisibleNodes = computed(() => this.flattenScopeNodes(this.scopeNodes()), ...(ngDevMode ? [{ debugName: "scopeVisibleNodes" }] : /* istanbul ignore next */ []));
|
|
2877
|
+
selectedOperationCount = computed(() => this.scopeVisibleNodes().filter((node) => node.kind === 'operation')
|
|
2878
|
+
.length, ...(ngDevMode ? [{ debugName: "selectedOperationCount" }] : /* istanbul ignore next */ []));
|
|
2878
2879
|
ngOnInit() {
|
|
2879
2880
|
this.facade.getDetail(this.delegationId(), this.adminMode());
|
|
2880
2881
|
}
|
|
@@ -2891,20 +2892,104 @@ class DelegationDetailDrawer {
|
|
|
2891
2892
|
const s = this.row()?.approval?.approvalStatus ?? 'NotRequired';
|
|
2892
2893
|
return this.transloco.translate(`delegations.approvalStatus.${s}`);
|
|
2893
2894
|
}
|
|
2894
|
-
|
|
2895
|
-
return
|
|
2895
|
+
userEntity(party, label) {
|
|
2896
|
+
return toDelegationUserEntity(party, { label });
|
|
2896
2897
|
}
|
|
2897
|
-
|
|
2898
|
-
return
|
|
2898
|
+
hasChildren(node) {
|
|
2899
|
+
return 'children' in node && node.children.length > 0;
|
|
2899
2900
|
}
|
|
2900
|
-
|
|
2901
|
-
return
|
|
2901
|
+
trackScopeNode(_index, node) {
|
|
2902
|
+
return node.key;
|
|
2902
2903
|
}
|
|
2903
|
-
|
|
2904
|
-
return
|
|
2904
|
+
buildScopeViewTree(templates) {
|
|
2905
|
+
return this.toArray(templates)
|
|
2906
|
+
.map((template) => this.toTemplateNode(template))
|
|
2907
|
+
.filter((node) => this.hasScopeChildren(node));
|
|
2908
|
+
}
|
|
2909
|
+
flattenScopeNodes(nodes) {
|
|
2910
|
+
const out = [];
|
|
2911
|
+
const walk = (items) => {
|
|
2912
|
+
for (const node of items) {
|
|
2913
|
+
out.push(node);
|
|
2914
|
+
if ('children' in node) {
|
|
2915
|
+
walk(node.children);
|
|
2916
|
+
}
|
|
2917
|
+
}
|
|
2918
|
+
};
|
|
2919
|
+
walk(nodes);
|
|
2920
|
+
return out;
|
|
2921
|
+
}
|
|
2922
|
+
toTemplateNode(template) {
|
|
2923
|
+
const key = `tpl:${template.groupKey}`;
|
|
2924
|
+
return {
|
|
2925
|
+
kind: 'template',
|
|
2926
|
+
key,
|
|
2927
|
+
label: this.displayLabel(template.name, template.groupKey),
|
|
2928
|
+
fallbackKey: template.groupKey,
|
|
2929
|
+
isAvailable: template.isAvailable !== false,
|
|
2930
|
+
depth: 0,
|
|
2931
|
+
children: this.toArray(template.levels).flatMap((level) => this.toLevelNodes(level)),
|
|
2932
|
+
};
|
|
2933
|
+
}
|
|
2934
|
+
toLevelNodes(level) {
|
|
2935
|
+
const operationNodes = this.toArray(level.operations)
|
|
2936
|
+
.filter((operation) => operation.selected !== false)
|
|
2937
|
+
.map((operation) => this.toOperationNode(operation, level.targetKey, 2));
|
|
2938
|
+
const moduleNodes = this.toArray(level.modules)
|
|
2939
|
+
.map((module) => this.toModuleNode(module))
|
|
2940
|
+
.filter((node) => this.hasScopeChildren(node));
|
|
2941
|
+
const children = [...operationNodes, ...moduleNodes];
|
|
2942
|
+
if (children.length === 0) {
|
|
2943
|
+
return [];
|
|
2944
|
+
}
|
|
2945
|
+
return [
|
|
2946
|
+
{
|
|
2947
|
+
kind: 'level',
|
|
2948
|
+
key: `lvl:${level.applicationKey}:${level.targetKey}`,
|
|
2949
|
+
label: this.displayLabel(level.name, level.targetKey),
|
|
2950
|
+
fallbackKey: level.targetKey,
|
|
2951
|
+
isAvailable: level.isAvailable !== false,
|
|
2952
|
+
depth: 1,
|
|
2953
|
+
children,
|
|
2954
|
+
},
|
|
2955
|
+
];
|
|
2956
|
+
}
|
|
2957
|
+
toModuleNode(module) {
|
|
2958
|
+
return {
|
|
2959
|
+
kind: 'module',
|
|
2960
|
+
key: `mod:${module.applicationKey}:${module.targetKey}`,
|
|
2961
|
+
label: this.displayLabel(module.name, module.targetKey),
|
|
2962
|
+
fallbackKey: module.targetKey,
|
|
2963
|
+
isAvailable: module.isAvailable !== false,
|
|
2964
|
+
depth: 2,
|
|
2965
|
+
children: this.toArray(module.operations)
|
|
2966
|
+
.filter((operation) => operation.selected !== false)
|
|
2967
|
+
.map((operation) => this.toOperationNode(operation, module.targetKey, 3)),
|
|
2968
|
+
};
|
|
2969
|
+
}
|
|
2970
|
+
toOperationNode(operation, parentKey, depth) {
|
|
2971
|
+
return {
|
|
2972
|
+
kind: 'operation',
|
|
2973
|
+
key: `op:${parentKey}:${operation.operationKey}`,
|
|
2974
|
+
label: this.displayLabel(operation.name ?? operation.displayName, operation.operationKey),
|
|
2975
|
+
fallbackKey: operation.operationKey,
|
|
2976
|
+
isAvailable: operation.isAvailable !== false,
|
|
2977
|
+
isHighRisk: operation.isHighRisk ?? false,
|
|
2978
|
+
depth,
|
|
2979
|
+
};
|
|
2980
|
+
}
|
|
2981
|
+
displayLabel(value, fallbackKey) {
|
|
2982
|
+
return (formatDelegationDisplayValue(value, this.transloco.getActiveLang()) ||
|
|
2983
|
+
humanizeDelegationKey(fallbackKey));
|
|
2984
|
+
}
|
|
2985
|
+
toArray(value) {
|
|
2986
|
+
return Array.isArray(value) ? value : [];
|
|
2987
|
+
}
|
|
2988
|
+
hasScopeChildren(node) {
|
|
2989
|
+
return 'children' in node && node.children.length > 0;
|
|
2905
2990
|
}
|
|
2906
2991
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationDetailDrawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2907
|
-
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: i4.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 });
|
|
2992
|
+
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=\"border-b border-surface-200 px-4 pt-2\">\n <mt-tabs\n mode=\"underline\"\n [(active)]=\"activeTab\"\n [options]=\"tabOptions()\"\n fluid\n ></mt-tabs>\n </div>\n\n <!-- Body -->\n <div class=\"flex-1 overflow-y-auto p-4\">\n @if (isLoading() && !detail()) {\n <p-skeleton height=\"2rem\" class=\"mb-3\"></p-skeleton>\n <p-skeleton height=\"6rem\" class=\"mb-3\"></p-skeleton>\n <p-skeleton height=\"6rem\"></p-skeleton>\n } @else if (detail(); as d) {\n @if (activeTab() === \"overview\") {\n <div class=\"flex flex-col gap-4\">\n <div class=\"flex items-center justify-between gap-3\">\n <div class=\"flex items-center gap-3 min-w-0\">\n <mt-entity-preview\n [data]=\"\n userEntity(\n d.row.delegator,\n t('delegations.column.delegatorName')\n )\n \"\n ></mt-entity-preview>\n </div>\n <mt-delegation-status-chip\n [status]=\"d.status.effectiveStatus\"\n ></mt-delegation-status-chip>\n </div>\n\n <div class=\"grid grid-cols-2 gap-4\">\n <div class=\"flex flex-col\">\n <mt-entity-preview\n [data]=\"\n userEntity(\n d.row.delegatedUser,\n t('delegations.column.delegatedTo')\n )\n \"\n ></mt-entity-preview>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-surface-500\">\n {{ t(\"delegations.column.approval\") }}\n </span>\n <span class=\"text-sm text-surface-900\">{{\n approvalLabel()\n }}</span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-surface-500\">\n {{ t(\"delegations.column.startDate\") }}\n </span>\n <span class=\"text-sm text-surface-900\">\n {{ d.row.startsAtUtc | date: \"medium\" }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-surface-500\">\n {{ t(\"delegations.column.endDate\") }}\n </span>\n <span class=\"text-sm text-surface-900\">\n {{ d.row.endsAtUtc | date: \"medium\" }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-surface-500\">\n {{ t(\"delegations.form.timeZone\") }}\n </span>\n <span class=\"text-sm text-surface-900\">{{\n d.row.timeZoneId\n }}</span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-surface-500\">\n {{ t(\"delegations.column.days\") }}\n </span>\n <span class=\"text-sm text-surface-900\">\n @if (d.row.dayRuleMode === \"FullRange\") {\n {{ t(\"delegations.form.fullRange\") }}\n } @else {\n {{ formatSpecificDays(d.row.specificDays) }}\n }\n </span>\n </div>\n </div>\n\n @if (d.row.cancellation?.cancellationReason) {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-50 p-3 text-sm text-surface-800\"\n >\n <div class=\"font-medium\">\n {{ t(\"delegations.form.cancellationReason\") }}\n </div>\n <div>{{ d.row.cancellation.cancellationReason }}</div>\n </div>\n }\n </div>\n } @else {\n <!-- Scope tab -->\n <div\n class=\"flex flex-col overflow-hidden rounded-lg border border-surface\"\n >\n <div\n class=\"flex items-center justify-between gap-2 border-b border-surface bg-surface-50 px-3 py-2\"\n >\n <span class=\"text-xs font-semibold uppercase text-surface-500\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </span>\n @if (selectedOperationCount() > 0) {\n <span class=\"text-xs font-medium text-surface-500\">\n {{ selectedOperationCount() }}\n {{ t(\"delegations.scope.selected\") }}\n </span>\n }\n </div>\n\n @if (scopeVisibleNodes().length > 0) {\n <div class=\"flex max-h-[34rem] flex-col overflow-y-auto\">\n @for (\n node of scopeVisibleNodes();\n track trackScopeNode($index, node)\n ) {\n <div\n class=\"group flex min-h-10 items-center gap-2 pe-3 transition-colors hover:bg-surface-50\"\n [style.padding-inline-start.rem]=\"0.75 + node.depth * 1.25\"\n >\n @if (hasChildren(node)) {\n <span\n class=\"flex size-6 shrink-0 items-center justify-center rounded-md text-surface-400\"\n aria-hidden=\"true\"\n >\n <svg\n viewBox=\"0 0 20 20\"\n class=\"size-4\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M5 7L10 12L15 7\"\n stroke=\"currentColor\"\n stroke-width=\"1.8\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </span>\n } @else {\n <span class=\"size-6 shrink-0\"></span>\n }\n\n <span\n class=\"flex size-[18px] shrink-0 items-center justify-center rounded-[5px] border border-primary-500 bg-primary-500 text-white\"\n aria-hidden=\"true\"\n >\n <svg\n viewBox=\"0 0 16 16\"\n class=\"size-3\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\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 </span>\n\n <span\n class=\"min-w-0 flex-1 truncate text-sm\"\n [class.font-semibold]=\"node.kind === 'template'\"\n [class.text-surface-900]=\"node.kind === 'template'\"\n [class.font-medium]=\"\n node.kind === 'level' || node.kind === 'module'\n \"\n [class.text-surface-800]=\"\n node.kind === 'level' || node.kind === 'module'\n \"\n [class.text-surface-600]=\"node.kind === 'operation'\"\n >\n {{ node.label }}\n </span>\n\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\n @if (!node.isAvailable) {\n <span\n class=\"shrink-0 rounded-full bg-surface-100 px-2 py-0.5 text-[0.625rem] font-medium text-surface-500\"\n >\n {{ t(\"delegations.scope.unavailable\") }}\n </span>\n }\n </div>\n }\n </div>\n } @else {\n <p class=\"px-3 py-8 text-center text-sm text-surface-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </p>\n }\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n <div\n class=\"border-t border-surface-200 px-4 py-3 flex items-center justify-end\"\n >\n <mt-button\n [label]=\"t('delegations.common.cancel')\"\n variant=\"outlined\"\n (click)=\"ref.close()\"\n ></mt-button>\n </div>\n </div>\n</ng-container>\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: i4.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 });
|
|
2908
2993
|
}
|
|
2909
2994
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationDetailDrawer, decorators: [{
|
|
2910
2995
|
type: Component,
|
|
@@ -2916,7 +3001,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
2916
3001
|
Tabs,
|
|
2917
3002
|
TranslocoDirective,
|
|
2918
3003
|
DelegationStatusChip,
|
|
2919
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\
|
|
3004
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col h-full\">\n <!-- Tabs -->\n <div class=\"border-b border-surface-200 px-4 pt-2\">\n <mt-tabs\n mode=\"underline\"\n [(active)]=\"activeTab\"\n [options]=\"tabOptions()\"\n fluid\n ></mt-tabs>\n </div>\n\n <!-- Body -->\n <div class=\"flex-1 overflow-y-auto p-4\">\n @if (isLoading() && !detail()) {\n <p-skeleton height=\"2rem\" class=\"mb-3\"></p-skeleton>\n <p-skeleton height=\"6rem\" class=\"mb-3\"></p-skeleton>\n <p-skeleton height=\"6rem\"></p-skeleton>\n } @else if (detail(); as d) {\n @if (activeTab() === \"overview\") {\n <div class=\"flex flex-col gap-4\">\n <div class=\"flex items-center justify-between gap-3\">\n <div class=\"flex items-center gap-3 min-w-0\">\n <mt-entity-preview\n [data]=\"\n userEntity(\n d.row.delegator,\n t('delegations.column.delegatorName')\n )\n \"\n ></mt-entity-preview>\n </div>\n <mt-delegation-status-chip\n [status]=\"d.status.effectiveStatus\"\n ></mt-delegation-status-chip>\n </div>\n\n <div class=\"grid grid-cols-2 gap-4\">\n <div class=\"flex flex-col\">\n <mt-entity-preview\n [data]=\"\n userEntity(\n d.row.delegatedUser,\n t('delegations.column.delegatedTo')\n )\n \"\n ></mt-entity-preview>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-surface-500\">\n {{ t(\"delegations.column.approval\") }}\n </span>\n <span class=\"text-sm text-surface-900\">{{\n approvalLabel()\n }}</span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-surface-500\">\n {{ t(\"delegations.column.startDate\") }}\n </span>\n <span class=\"text-sm text-surface-900\">\n {{ d.row.startsAtUtc | date: \"medium\" }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-surface-500\">\n {{ t(\"delegations.column.endDate\") }}\n </span>\n <span class=\"text-sm text-surface-900\">\n {{ d.row.endsAtUtc | date: \"medium\" }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-surface-500\">\n {{ t(\"delegations.form.timeZone\") }}\n </span>\n <span class=\"text-sm text-surface-900\">{{\n d.row.timeZoneId\n }}</span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-surface-500\">\n {{ t(\"delegations.column.days\") }}\n </span>\n <span class=\"text-sm text-surface-900\">\n @if (d.row.dayRuleMode === \"FullRange\") {\n {{ t(\"delegations.form.fullRange\") }}\n } @else {\n {{ formatSpecificDays(d.row.specificDays) }}\n }\n </span>\n </div>\n </div>\n\n @if (d.row.cancellation?.cancellationReason) {\n <div\n class=\"rounded-md border border-surface-200 bg-surface-50 p-3 text-sm text-surface-800\"\n >\n <div class=\"font-medium\">\n {{ t(\"delegations.form.cancellationReason\") }}\n </div>\n <div>{{ d.row.cancellation.cancellationReason }}</div>\n </div>\n }\n </div>\n } @else {\n <!-- Scope tab -->\n <div\n class=\"flex flex-col overflow-hidden rounded-lg border border-surface\"\n >\n <div\n class=\"flex items-center justify-between gap-2 border-b border-surface bg-surface-50 px-3 py-2\"\n >\n <span class=\"text-xs font-semibold uppercase text-surface-500\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </span>\n @if (selectedOperationCount() > 0) {\n <span class=\"text-xs font-medium text-surface-500\">\n {{ selectedOperationCount() }}\n {{ t(\"delegations.scope.selected\") }}\n </span>\n }\n </div>\n\n @if (scopeVisibleNodes().length > 0) {\n <div class=\"flex max-h-[34rem] flex-col overflow-y-auto\">\n @for (\n node of scopeVisibleNodes();\n track trackScopeNode($index, node)\n ) {\n <div\n class=\"group flex min-h-10 items-center gap-2 pe-3 transition-colors hover:bg-surface-50\"\n [style.padding-inline-start.rem]=\"0.75 + node.depth * 1.25\"\n >\n @if (hasChildren(node)) {\n <span\n class=\"flex size-6 shrink-0 items-center justify-center rounded-md text-surface-400\"\n aria-hidden=\"true\"\n >\n <svg\n viewBox=\"0 0 20 20\"\n class=\"size-4\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\n >\n <path\n d=\"M5 7L10 12L15 7\"\n stroke=\"currentColor\"\n stroke-width=\"1.8\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n </svg>\n </span>\n } @else {\n <span class=\"size-6 shrink-0\"></span>\n }\n\n <span\n class=\"flex size-[18px] shrink-0 items-center justify-center rounded-[5px] border border-primary-500 bg-primary-500 text-white\"\n aria-hidden=\"true\"\n >\n <svg\n viewBox=\"0 0 16 16\"\n class=\"size-3\"\n fill=\"none\"\n xmlns=\"http://www.w3.org/2000/svg\"\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 </span>\n\n <span\n class=\"min-w-0 flex-1 truncate text-sm\"\n [class.font-semibold]=\"node.kind === 'template'\"\n [class.text-surface-900]=\"node.kind === 'template'\"\n [class.font-medium]=\"\n node.kind === 'level' || node.kind === 'module'\n \"\n [class.text-surface-800]=\"\n node.kind === 'level' || node.kind === 'module'\n \"\n [class.text-surface-600]=\"node.kind === 'operation'\"\n >\n {{ node.label }}\n </span>\n\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\n @if (!node.isAvailable) {\n <span\n class=\"shrink-0 rounded-full bg-surface-100 px-2 py-0.5 text-[0.625rem] font-medium text-surface-500\"\n >\n {{ t(\"delegations.scope.unavailable\") }}\n </span>\n }\n </div>\n }\n </div>\n } @else {\n <p class=\"px-3 py-8 text-center text-sm text-surface-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </p>\n }\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n <div\n class=\"border-t border-surface-200 px-4 py-3 flex items-center justify-end\"\n >\n <mt-button\n [label]=\"t('delegations.common.cancel')\"\n variant=\"outlined\"\n (click)=\"ref.close()\"\n ></mt-button>\n </div>\n </div>\n</ng-container>\n", styles: [":host{display:block;height:100%}\n"] }]
|
|
2920
3005
|
}], propDecorators: { delegationId: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegationId", required: true }] }], adminMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "adminMode", required: false }] }] } });
|
|
2921
3006
|
|
|
2922
3007
|
function defaultTrueBooleanAttribute(value) {
|
|
@@ -2925,6 +3010,24 @@ function defaultTrueBooleanAttribute(value) {
|
|
|
2925
3010
|
function defaultRouteInput(fallback) {
|
|
2926
3011
|
return (value) => value === undefined || value === null ? fallback : value;
|
|
2927
3012
|
}
|
|
3013
|
+
const DELEGATION_TABS = [
|
|
3014
|
+
'my',
|
|
3015
|
+
'assigned',
|
|
3016
|
+
'approvals',
|
|
3017
|
+
];
|
|
3018
|
+
/** Route param → a valid tab, or null when absent/unknown (host redirects to default). */
|
|
3019
|
+
function normalizeTab(value) {
|
|
3020
|
+
return DELEGATION_TABS.includes(value)
|
|
3021
|
+
? value
|
|
3022
|
+
: null;
|
|
3023
|
+
}
|
|
3024
|
+
/** Query param (string) → number, or null when absent/non-numeric. */
|
|
3025
|
+
function toNullableNumber(value) {
|
|
3026
|
+
if (value === undefined || value === null || value === '')
|
|
3027
|
+
return null;
|
|
3028
|
+
const n = numberAttribute(value, NaN);
|
|
3029
|
+
return Number.isNaN(n) ? null : n;
|
|
3030
|
+
}
|
|
2928
3031
|
/**
|
|
2929
3032
|
* Delegations portal page (doc 02, 09): two lists — My Delegations (current user
|
|
2930
3033
|
* is delegator) and Delegated To Me (current user is delegate). Rows render only
|
|
@@ -2959,10 +3062,32 @@ class DelegationsList {
|
|
|
2959
3062
|
approvalTabLabelKey = input('delegations.tabs.approvals', { ...(ngDevMode ? { debugName: "approvalTabLabelKey" } : /* istanbul ignore next */ {}), transform: defaultRouteInput('delegations.tabs.approvals') });
|
|
2960
3063
|
approvalEmptyStateKey = input('delegations.empty.pending', { ...(ngDevMode ? { debugName: "approvalEmptyStateKey" } : /* istanbul ignore next */ {}), transform: defaultRouteInput('delegations.empty.pending') });
|
|
2961
3064
|
approvalStatus = input('PendingApproval', { ...(ngDevMode ? { debugName: "approvalStatus" } : /* istanbul ignore next */ {}), transform: defaultRouteInput('PendingApproval') });
|
|
3065
|
+
/**
|
|
3066
|
+
* Route-driven mode (client self-service): the active tab is the `:tab` route
|
|
3067
|
+
* segment and the open drawer is a `?view=`/`?edit=` query param, so tabs and
|
|
3068
|
+
* drawers are deep-linkable + back-button aware. When false (admin/default),
|
|
3069
|
+
* tabs and drawers are managed internally exactly as before.
|
|
3070
|
+
*/
|
|
3071
|
+
routed = input(false, { ...(ngDevMode ? { debugName: "routed" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
|
|
3072
|
+
/** Bound from the `:tab` route param via `withComponentInputBinding()`. */
|
|
3073
|
+
tab = input(null, { ...(ngDevMode ? { debugName: "tab" } : /* istanbul ignore next */ {}), transform: normalizeTab });
|
|
3074
|
+
/** Bound from the `?view=<id>` query param (detail drawer). */
|
|
3075
|
+
view = input(null, { ...(ngDevMode ? { debugName: "view" } : /* istanbul ignore next */ {}), transform: toNullableNumber });
|
|
3076
|
+
/** Back-compatible notification deep-link aliases for opening details. */
|
|
3077
|
+
selected = input(null, { ...(ngDevMode ? { debugName: "selected" } : /* istanbul ignore next */ {}), transform: toNullableNumber });
|
|
3078
|
+
itemSelected = input(null, { ...(ngDevMode ? { debugName: "itemSelected" } : /* istanbul ignore next */ {}), transform: toNullableNumber });
|
|
3079
|
+
selectedItemId = input(null, { ...(ngDevMode ? { debugName: "selectedItemId" } : /* istanbul ignore next */ {}), transform: toNullableNumber });
|
|
3080
|
+
selectedItem = input(null, { ...(ngDevMode ? { debugName: "selectedItem" } : /* istanbul ignore next */ {}), transform: toNullableNumber });
|
|
3081
|
+
itemId = input(null, { ...(ngDevMode ? { debugName: "itemId" } : /* istanbul ignore next */ {}), transform: toNullableNumber });
|
|
3082
|
+
delegationId = input(null, { ...(ngDevMode ? { debugName: "delegationId" } : /* istanbul ignore next */ {}), transform: toNullableNumber });
|
|
3083
|
+
/** Bound from the `?edit=<id>` query param (edit drawer). */
|
|
3084
|
+
edit = input(null, { ...(ngDevMode ? { debugName: "edit" } : /* istanbul ignore next */ {}), transform: toNullableNumber });
|
|
2962
3085
|
facade = inject(DelegationsFacade);
|
|
2963
3086
|
location = inject(Location);
|
|
2964
3087
|
modal = inject(ModalService);
|
|
2965
3088
|
transloco = inject(TranslocoService);
|
|
3089
|
+
router = inject(Router);
|
|
3090
|
+
route = inject(ActivatedRoute);
|
|
2966
3091
|
breadcrumbItems = linkedSignal(() => [
|
|
2967
3092
|
{
|
|
2968
3093
|
label: '',
|
|
@@ -3007,6 +3132,13 @@ class DelegationsList {
|
|
|
3007
3132
|
: this.activeTab() === 'approvals'
|
|
3008
3133
|
? this.approvalEmptyStateKey()
|
|
3009
3134
|
: this.assignedEmptyStateKey(), ...(ngDevMode ? [{ debugName: "emptyStateKey" }] : /* istanbul ignore next */ []));
|
|
3135
|
+
detailQueryId = computed(() => this.view() ??
|
|
3136
|
+
this.selected() ??
|
|
3137
|
+
this.itemSelected() ??
|
|
3138
|
+
this.selectedItemId() ??
|
|
3139
|
+
this.selectedItem() ??
|
|
3140
|
+
this.itemId() ??
|
|
3141
|
+
this.delegationId(), ...(ngDevMode ? [{ debugName: "detailQueryId" }] : /* istanbul ignore next */ []));
|
|
3010
3142
|
tableActions = computed(() => [
|
|
3011
3143
|
{
|
|
3012
3144
|
icon: 'general.plus',
|
|
@@ -3021,14 +3153,14 @@ class DelegationsList {
|
|
|
3021
3153
|
tooltip: this.transloco.translate('delegations.action.view'),
|
|
3022
3154
|
color: 'primary',
|
|
3023
3155
|
variant: 'outlined',
|
|
3024
|
-
action: (row) => this.
|
|
3156
|
+
action: (row) => this.onView(row),
|
|
3025
3157
|
hidden: (row) => !this.has(row, 'View'),
|
|
3026
3158
|
},
|
|
3027
3159
|
{
|
|
3028
3160
|
icon: 'custom.pencil',
|
|
3029
3161
|
tooltip: this.transloco.translate('delegations.action.edit'),
|
|
3030
3162
|
color: 'primary',
|
|
3031
|
-
action: (row) => this.
|
|
3163
|
+
action: (row) => this.onEdit(row),
|
|
3032
3164
|
hidden: (row) => !this.has(row, 'Edit'),
|
|
3033
3165
|
},
|
|
3034
3166
|
{
|
|
@@ -3176,10 +3308,59 @@ class DelegationsList {
|
|
|
3176
3308
|
'friday',
|
|
3177
3309
|
'saturday',
|
|
3178
3310
|
];
|
|
3311
|
+
// Route-driven drawer refs (only used when `routed()`). The active id is
|
|
3312
|
+
// captured per-subscription so an async `onClose` can tell a user-dismiss
|
|
3313
|
+
// (route still points at the id) from a route-driven / superseded close.
|
|
3314
|
+
detailRef = null;
|
|
3315
|
+
detailOpenId = null;
|
|
3316
|
+
editRef = null;
|
|
3317
|
+
editOpenId = null;
|
|
3318
|
+
constructor() {
|
|
3319
|
+
// Tab → route sync. `ngOnInit` performs the first load; this effect only
|
|
3320
|
+
// reacts to later route changes (tab switches, Back/Forward).
|
|
3321
|
+
effect(() => {
|
|
3322
|
+
if (!this.routed())
|
|
3323
|
+
return;
|
|
3324
|
+
const t = this.tab() ?? 'my';
|
|
3325
|
+
untracked(() => {
|
|
3326
|
+
if (t !== this.activeTab()) {
|
|
3327
|
+
this.activeTab.set(t);
|
|
3328
|
+
this.loadCurrentTab();
|
|
3329
|
+
}
|
|
3330
|
+
});
|
|
3331
|
+
});
|
|
3332
|
+
// Detail (`?view=`) drawer ↔ route.
|
|
3333
|
+
effect(() => {
|
|
3334
|
+
if (!this.routed())
|
|
3335
|
+
return;
|
|
3336
|
+
const id = this.detailQueryId();
|
|
3337
|
+
untracked(() => this.syncDetailDrawer(id));
|
|
3338
|
+
});
|
|
3339
|
+
// Edit (`?edit=`) drawer ↔ route. `view` takes precedence: while a view is
|
|
3340
|
+
// open, the edit drawer is suppressed (and reopens if `view` later clears).
|
|
3341
|
+
effect(() => {
|
|
3342
|
+
if (!this.routed())
|
|
3343
|
+
return;
|
|
3344
|
+
const id = this.edit();
|
|
3345
|
+
const hasView = this.detailQueryId() != null;
|
|
3346
|
+
untracked(() => this.syncEditDrawer(hasView ? null : id));
|
|
3347
|
+
});
|
|
3348
|
+
}
|
|
3179
3349
|
ngOnInit() {
|
|
3350
|
+
if (this.routed()) {
|
|
3351
|
+
this.activeTab.set(this.tab() ?? 'my');
|
|
3352
|
+
}
|
|
3180
3353
|
this.loadCurrentTab();
|
|
3181
3354
|
}
|
|
3182
3355
|
switchTab(tab) {
|
|
3356
|
+
if (this.routed()) {
|
|
3357
|
+
// Sibling tab segment; drop any open drawer query params.
|
|
3358
|
+
void this.router.navigate(['..', tab], {
|
|
3359
|
+
relativeTo: this.route,
|
|
3360
|
+
queryParams: {},
|
|
3361
|
+
});
|
|
3362
|
+
return;
|
|
3363
|
+
}
|
|
3183
3364
|
this.activeTab.set(tab);
|
|
3184
3365
|
this.loadCurrentTab();
|
|
3185
3366
|
}
|
|
@@ -3225,16 +3406,40 @@ class DelegationsList {
|
|
|
3225
3406
|
getListedParty(row) {
|
|
3226
3407
|
return this.activeTab() === 'my' ? row.delegatedUser : row.delegator;
|
|
3227
3408
|
}
|
|
3228
|
-
|
|
3229
|
-
|
|
3409
|
+
// View/Edit entry points: in routed mode flip the URL (the sync effects open
|
|
3410
|
+
// the drawer); otherwise open imperatively as before.
|
|
3411
|
+
onView(row) {
|
|
3412
|
+
if (this.routed()) {
|
|
3413
|
+
void this.router.navigate([], {
|
|
3414
|
+
relativeTo: this.route,
|
|
3415
|
+
queryParams: { view: row.delegationId, edit: null },
|
|
3416
|
+
queryParamsHandling: 'merge',
|
|
3417
|
+
});
|
|
3418
|
+
return;
|
|
3419
|
+
}
|
|
3420
|
+
this.openDetailById(row.delegationId);
|
|
3421
|
+
}
|
|
3422
|
+
onEdit(row) {
|
|
3423
|
+
if (this.routed()) {
|
|
3424
|
+
void this.router.navigate([], {
|
|
3425
|
+
relativeTo: this.route,
|
|
3426
|
+
queryParams: { edit: row.delegationId, view: null },
|
|
3427
|
+
queryParamsHandling: 'merge',
|
|
3428
|
+
});
|
|
3429
|
+
return;
|
|
3430
|
+
}
|
|
3431
|
+
this.openForm(row);
|
|
3432
|
+
}
|
|
3433
|
+
openDetailById(delegationId) {
|
|
3434
|
+
return this.modal.openModal(DelegationDetailDrawer, 'drawer', {
|
|
3230
3435
|
header: this.transloco.translate('delegations.action.view'),
|
|
3231
|
-
styleClass: '!absolute !shadow-none !w-full !max-w-full sm:!w-[
|
|
3436
|
+
styleClass: '!absolute !shadow-none !w-full !max-w-full sm:!w-[52rem] xl:!w-[64rem]',
|
|
3232
3437
|
position: 'end',
|
|
3233
3438
|
appendTo: 'page-content',
|
|
3234
3439
|
dismissableMask: true,
|
|
3235
3440
|
dismissible: true,
|
|
3236
3441
|
inputValues: {
|
|
3237
|
-
delegationId
|
|
3442
|
+
delegationId,
|
|
3238
3443
|
adminMode: this.adminMode(),
|
|
3239
3444
|
},
|
|
3240
3445
|
});
|
|
@@ -3243,7 +3448,7 @@ class DelegationsList {
|
|
|
3243
3448
|
const ref = this.modal.openModal(DelegationForm, row ? 'drawer' : 'dialog', {
|
|
3244
3449
|
header: this.transloco.translate(row ? 'delegations.action.edit' : 'delegations.action.create'),
|
|
3245
3450
|
styleClass: row
|
|
3246
|
-
? '!absolute !shadow-none !w-full !max-w-full sm:!w-[
|
|
3451
|
+
? '!absolute !shadow-none !w-full !max-w-full sm:!w-[68rem] xl:!w-[84rem]'
|
|
3247
3452
|
: '!w-[min(98vw,88rem)] !max-w-[98vw] !h-[min(94vh,56rem)] !max-h-[94vh] !overflow-hidden',
|
|
3248
3453
|
position: row ? 'end' : '',
|
|
3249
3454
|
appendTo: row ? 'page-content' : 'body',
|
|
@@ -3255,9 +3460,98 @@ class DelegationsList {
|
|
|
3255
3460
|
adminMode: this.adminMode(),
|
|
3256
3461
|
},
|
|
3257
3462
|
});
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3463
|
+
// Routed edit reload/clear is handled by `syncEditDrawer`; create and
|
|
3464
|
+
// non-routed edit self-manage their reload here.
|
|
3465
|
+
if (!this.routed() || row == null) {
|
|
3466
|
+
ref.onClose.subscribe((saved) => {
|
|
3467
|
+
if (saved)
|
|
3468
|
+
this.reloadCurrentTab();
|
|
3469
|
+
});
|
|
3470
|
+
}
|
|
3471
|
+
return ref;
|
|
3472
|
+
}
|
|
3473
|
+
syncDetailDrawer(id) {
|
|
3474
|
+
if (id != null && this.detailOpenId !== id) {
|
|
3475
|
+
const openingId = id;
|
|
3476
|
+
this.closeDetail();
|
|
3477
|
+
this.detailOpenId = openingId;
|
|
3478
|
+
this.detailRef = this.openDetailById(openingId);
|
|
3479
|
+
this.detailRef.onClose.subscribe(() => {
|
|
3480
|
+
if (this.detailOpenId !== openingId)
|
|
3481
|
+
return; // superseded by a switch
|
|
3482
|
+
this.detailRef = null;
|
|
3483
|
+
this.detailOpenId = null;
|
|
3484
|
+
// Route still points here ⇒ the user dismissed the drawer; clear it.
|
|
3485
|
+
if (this.detailQueryId() === openingId)
|
|
3486
|
+
this.clearDetailQuery();
|
|
3487
|
+
});
|
|
3488
|
+
}
|
|
3489
|
+
else if (id == null) {
|
|
3490
|
+
this.closeDetail();
|
|
3491
|
+
}
|
|
3492
|
+
}
|
|
3493
|
+
syncEditDrawer(id) {
|
|
3494
|
+
if (id != null && this.editOpenId !== id) {
|
|
3495
|
+
const openingId = id;
|
|
3496
|
+
this.closeEdit();
|
|
3497
|
+
this.editOpenId = openingId;
|
|
3498
|
+
// The detail drawer loads its own data, but the form prefills from the
|
|
3499
|
+
// list row when present; fall back to an id-only stub on a cold deep-link
|
|
3500
|
+
// (the form then sources rowVersion from the detail it loads).
|
|
3501
|
+
const row = this.rows().find((r) => r.delegationId === openingId) ??
|
|
3502
|
+
{ delegationId: openingId };
|
|
3503
|
+
this.editRef = this.openForm(row);
|
|
3504
|
+
this.editRef.onClose.subscribe((saved) => {
|
|
3505
|
+
if (this.editOpenId !== openingId)
|
|
3506
|
+
return; // superseded
|
|
3507
|
+
this.editRef = null;
|
|
3508
|
+
this.editOpenId = null;
|
|
3509
|
+
if (saved)
|
|
3510
|
+
this.reloadCurrentTab();
|
|
3511
|
+
if (this.edit() === openingId)
|
|
3512
|
+
this.clearQuery('edit');
|
|
3513
|
+
});
|
|
3514
|
+
}
|
|
3515
|
+
else if (id == null) {
|
|
3516
|
+
this.closeEdit();
|
|
3517
|
+
}
|
|
3518
|
+
}
|
|
3519
|
+
closeDetail() {
|
|
3520
|
+
if (this.detailRef) {
|
|
3521
|
+
this.detailOpenId = null; // mark superseded so the pending onClose no-ops
|
|
3522
|
+
const ref = this.detailRef;
|
|
3523
|
+
this.detailRef = null;
|
|
3524
|
+
ref.close();
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
closeEdit() {
|
|
3528
|
+
if (this.editRef) {
|
|
3529
|
+
this.editOpenId = null;
|
|
3530
|
+
const ref = this.editRef;
|
|
3531
|
+
this.editRef = null;
|
|
3532
|
+
ref.close();
|
|
3533
|
+
}
|
|
3534
|
+
}
|
|
3535
|
+
clearQuery(param) {
|
|
3536
|
+
void this.router.navigate([], {
|
|
3537
|
+
relativeTo: this.route,
|
|
3538
|
+
queryParams: { [param]: null },
|
|
3539
|
+
queryParamsHandling: 'merge',
|
|
3540
|
+
});
|
|
3541
|
+
}
|
|
3542
|
+
clearDetailQuery() {
|
|
3543
|
+
void this.router.navigate([], {
|
|
3544
|
+
relativeTo: this.route,
|
|
3545
|
+
queryParams: {
|
|
3546
|
+
view: null,
|
|
3547
|
+
selected: null,
|
|
3548
|
+
itemSelected: null,
|
|
3549
|
+
selectedItemId: null,
|
|
3550
|
+
selectedItem: null,
|
|
3551
|
+
itemId: null,
|
|
3552
|
+
delegationId: null,
|
|
3553
|
+
},
|
|
3554
|
+
queryParamsHandling: 'merge',
|
|
3261
3555
|
});
|
|
3262
3556
|
}
|
|
3263
3557
|
approve(row) {
|
|
@@ -3304,7 +3598,7 @@ class DelegationsList {
|
|
|
3304
3598
|
this.busyIds.update((ids) => on ? [...ids, key] : ids.filter((id) => id !== key));
|
|
3305
3599
|
}
|
|
3306
3600
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsList, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3307
|
-
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 [
|
|
3601
|
+
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 }, routed: { classPropertyName: "routed", publicName: "routed", isSignal: true, isRequired: false, transformFunction: null }, tab: { classPropertyName: "tab", publicName: "tab", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, itemSelected: { classPropertyName: "itemSelected", publicName: "itemSelected", isSignal: true, isRequired: false, transformFunction: null }, selectedItemId: { classPropertyName: "selectedItemId", publicName: "selectedItemId", isSignal: true, isRequired: false, transformFunction: null }, selectedItem: { classPropertyName: "selectedItem", publicName: "selectedItem", isSignal: true, isRequired: false, transformFunction: null }, itemId: { classPropertyName: "itemId", publicName: "itemId", isSignal: true, isRequired: false, transformFunction: null }, delegationId: { classPropertyName: "delegationId", publicName: "delegationId", isSignal: true, isRequired: false, transformFunction: null }, edit: { classPropertyName: "edit", publicName: "edit", 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 });
|
|
3308
3602
|
}
|
|
3309
3603
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsList, decorators: [{
|
|
3310
3604
|
type: Component,
|
|
@@ -3316,8 +3610,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
3316
3610
|
Table,
|
|
3317
3611
|
Tabs,
|
|
3318
3612
|
TranslocoDirective,
|
|
3319
|
-
], 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 [
|
|
3320
|
-
}], 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 }] }] } });
|
|
3613
|
+
], 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"] }]
|
|
3614
|
+
}], ctorParameters: () => [], 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 }] }], routed: [{ type: i0.Input, args: [{ isSignal: true, alias: "routed", required: false }] }], tab: [{ type: i0.Input, args: [{ isSignal: true, alias: "tab", required: false }] }], view: [{ type: i0.Input, args: [{ isSignal: true, alias: "view", required: false }] }], selected: [{ type: i0.Input, args: [{ isSignal: true, alias: "selected", required: false }] }], itemSelected: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemSelected", required: false }] }], selectedItemId: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedItemId", required: false }] }], selectedItem: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedItem", required: false }] }], itemId: [{ type: i0.Input, args: [{ isSignal: true, alias: "itemId", required: false }] }], delegationId: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegationId", required: false }] }], edit: [{ type: i0.Input, args: [{ isSignal: true, alias: "edit", required: false }] }] } });
|
|
3321
3615
|
|
|
3322
3616
|
/**
|
|
3323
3617
|
* Endpoints that must NEVER carry the `app-delegation` header.
|