@masterteam/delegations 0.0.17 → 0.0.19

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.
@@ -11,10 +11,11 @@ import { Icon } from '@masterteam/icons';
11
11
  import { Popover } from 'primeng/popover';
12
12
  import { Chip } from '@masterteam/components/chip';
13
13
  import { Actions, Store, ofActionSuccessful, ofActionDispatched, Action, Selector, State, select } from '@ngxs/store';
14
- import { HttpClient, HttpParams, HttpContext } from '@angular/common/http';
14
+ import { HttpClient, HttpHeaders, HttpParams, HttpContext } from '@angular/common/http';
15
15
  import { handleApiRequest, REQUEST_CONTEXT, UserSearchFieldConfig, TextareaFieldConfig, DateFieldConfig, RadioButtonFieldConfig, MultiSelectFieldConfig, ToggleFieldConfig, ValidatorConfig } from '@masterteam/components';
16
16
  import { EMPTY, switchMap, finalize } from 'rxjs';
17
17
  import { ModalRef } from '@masterteam/components/dialog';
18
+ import { ToastService } from '@masterteam/components/toast';
18
19
  import { Page } from '@masterteam/components/page';
19
20
  import { Breadcrumb } from '@masterteam/components/breadcrumb';
20
21
  import { Table } from '@masterteam/components/table';
@@ -177,7 +178,9 @@ let DelegationSessionState = class DelegationSessionState {
177
178
  });
178
179
  }
179
180
  start(ctx, { delegation }) {
180
- const req$ = this.http.post(`${BASE$1}/delegationToken/${delegation.delegationId}`, {});
181
+ const req$ = this.http.post(`${BASE$1}/delegationToken/${delegation.delegationId}`, {}, {
182
+ headers: new HttpHeaders({ noMessage: 'true' }),
183
+ });
181
184
  return handleApiRequest({
182
185
  ctx,
183
186
  key: DelegationSessionActionKey.StartSession,
@@ -286,6 +289,117 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
286
289
  args: [{ providedIn: 'root' }]
287
290
  }] });
288
291
 
292
+ function isRecord(value) {
293
+ return !!value && typeof value === 'object' && !Array.isArray(value);
294
+ }
295
+ function readLocalizedValue(source, preferredLanguage) {
296
+ const normalizedLanguage = preferredLanguage.toLowerCase();
297
+ const baseLanguage = normalizedLanguage.split('-')[0];
298
+ const preferredKeys = normalizedLanguage.startsWith('ar')
299
+ ? ['defaultAr', 'ar', normalizedLanguage, baseLanguage, 'defaultEn', 'en']
300
+ : ['defaultEn', 'en', normalizedLanguage, baseLanguage, 'defaultAr', 'ar'];
301
+ for (const key of preferredKeys) {
302
+ const value = source[key];
303
+ if (typeof value === 'string' && value.trim()) {
304
+ return value.trim();
305
+ }
306
+ }
307
+ for (const key of ['en', 'ar', 'defaultEn', 'defaultAr']) {
308
+ const value = source[key];
309
+ if (typeof value === 'string' && value.trim()) {
310
+ return value.trim();
311
+ }
312
+ }
313
+ return null;
314
+ }
315
+ function parseJsonRecord(value) {
316
+ const trimmed = value.trim();
317
+ if (!trimmed.startsWith('{') || !trimmed.endsWith('}')) {
318
+ return null;
319
+ }
320
+ try {
321
+ const parsed = JSON.parse(trimmed);
322
+ return isRecord(parsed) ? parsed : null;
323
+ }
324
+ catch {
325
+ return null;
326
+ }
327
+ }
328
+ function resolveDisplayValue(value, preferredLanguage) {
329
+ if (typeof value === 'string') {
330
+ const trimmed = value.trim();
331
+ if (!trimmed) {
332
+ return null;
333
+ }
334
+ const parsed = parseJsonRecord(trimmed);
335
+ if (parsed) {
336
+ return (readLocalizedValue(parsed, preferredLanguage) ??
337
+ readDisplayValue(parsed, ['displayName', 'displayLabel', 'label'], preferredLanguage) ??
338
+ trimmed);
339
+ }
340
+ return trimmed;
341
+ }
342
+ if (!isRecord(value)) {
343
+ return null;
344
+ }
345
+ return (readLocalizedValue(value, preferredLanguage) ??
346
+ readDisplayValue(value, [
347
+ 'displayName',
348
+ 'displayLabel',
349
+ 'operationDisplayName',
350
+ 'operationLabel',
351
+ 'label',
352
+ 'name',
353
+ 'value',
354
+ ], preferredLanguage));
355
+ }
356
+ function readDisplayValue(source, keys, preferredLanguage) {
357
+ for (const key of keys) {
358
+ const value = resolveDisplayValue(source[key], preferredLanguage);
359
+ if (value) {
360
+ return value;
361
+ }
362
+ }
363
+ return null;
364
+ }
365
+ function resolveDisplayOrHumanizedKey(source, displayKeys, fallbackKey, preferredLanguage = 'en') {
366
+ const displayValue = readDisplayValue(source, displayKeys, preferredLanguage);
367
+ if (displayValue) {
368
+ return displayValue;
369
+ }
370
+ return humanizeDelegationKey(fallbackKey);
371
+ }
372
+ function humanizeDelegationKey(value) {
373
+ if (!value) {
374
+ return '';
375
+ }
376
+ const normalized = value
377
+ .replace(/([a-z\d])([A-Z])/g, '$1 $2')
378
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
379
+ .replace(/[._-]+/g, ' ')
380
+ .replace(/\s+/g, ' ')
381
+ .trim();
382
+ if (!normalized) {
383
+ return '';
384
+ }
385
+ return normalized.replace(/\b\w/g, (char) => char.toUpperCase());
386
+ }
387
+ function formatDelegationTargetLabel(target, preferredLanguage = 'en') {
388
+ return resolveDisplayOrHumanizedKey(target, ['displayName', 'displayLabel', 'label'], target.targetKey, preferredLanguage);
389
+ }
390
+ function formatDelegationActionLabel(action, preferredLanguage = 'en') {
391
+ return resolveDisplayOrHumanizedKey(action, [
392
+ 'displayName',
393
+ 'displayLabel',
394
+ 'operationDisplayName',
395
+ 'operationLabel',
396
+ 'label',
397
+ ], action.operationKey, preferredLanguage);
398
+ }
399
+ function formatDelegationScopeSummary(value, preferredLanguage = 'en') {
400
+ return resolveDisplayValue(value, preferredLanguage) ?? '';
401
+ }
402
+
289
403
  /**
290
404
  * Confirmation dialog before starting (or switching to) a delegated session.
291
405
  * Calls the facade, which POSTs to `delegationToken/{id}` and stores the raw
@@ -296,17 +410,25 @@ class StartSessionDialog {
296
410
  intent = input('start', ...(ngDevMode ? [{ debugName: "intent" }] : /* istanbul ignore next */ []));
297
411
  ref = inject(ModalRef);
298
412
  facade = inject(DelegationSessionFacade);
413
+ toast = inject(ToastService);
414
+ transloco = inject(TranslocoService);
299
415
  isBusy = this.facade.isStarting;
300
416
  bodyKey = computed(() => this.intent() === 'switch'
301
417
  ? 'delegations.session.switchConfirmBody'
302
418
  : 'delegations.session.startConfirmBody', ...(ngDevMode ? [{ debugName: "bodyKey" }] : /* istanbul ignore next */ []));
419
+ formatScopeSummary(summary) {
420
+ return formatDelegationScopeSummary(summary, this.transloco.getActiveLang());
421
+ }
303
422
  confirm() {
304
423
  const row = this.delegation();
305
424
  const op$ = this.intent() === 'switch'
306
425
  ? this.facade.switchSession(row)
307
426
  : this.facade.startSession(row);
308
427
  op$.subscribe({
309
- next: () => this.ref.close(true),
428
+ next: () => {
429
+ this.toast.success(this.transloco.translate('delegations.session.started'));
430
+ this.ref.close(true);
431
+ },
310
432
  error: () => {
311
433
  /* error surfaced via shared toast pipeline; keep dialog open */
312
434
  },
@@ -316,11 +438,11 @@ class StartSessionDialog {
316
438
  this.ref.close(false);
317
439
  }
318
440
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: StartSessionDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
319
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: StartSessionDialog, isStandalone: true, selector: "mt-start-session-dialog", inputs: { delegation: { classPropertyName: "delegation", publicName: "delegation", isSignal: true, isRequired: true, transformFunction: null }, intent: { classPropertyName: "intent", publicName: "intent", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col gap-4 p-2\">\n <div class=\"flex items-start gap-3\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-12! h-12! text-lg! bg-surface-200!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <div class=\"text-base font-medium text-gray-900 truncate\">\n {{ delegation().delegator.displayName }}\n </div>\n @if (delegation().delegator.email) {\n <div class=\"text-xs text-gray-500 truncate\">\n {{ delegation().delegator.email }}\n </div>\n }\n @if (delegation().scopeSummary) {\n <div class=\"text-xs text-gray-500 mt-1\">\n {{ t(\"delegations.column.scopeSummary\") }}:\n {{ delegation().scopeSummary }}\n </div>\n }\n </div>\n </div>\n\n <p class=\"text-sm text-gray-600 leading-relaxed\">\n {{ t(bodyKey()) }}\n </p>\n\n <div class=\"flex justify-end gap-2 pt-2 border-t border-gray-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('delegations.action.startSession')\"\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: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { 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: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
441
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: StartSessionDialog, isStandalone: true, selector: "mt-start-session-dialog", inputs: { delegation: { classPropertyName: "delegation", publicName: "delegation", isSignal: true, isRequired: true, transformFunction: null }, intent: { classPropertyName: "intent", publicName: "intent", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col gap-4 p-2\">\n <div class=\"flex items-start gap-3\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-12! h-12! text-lg! bg-surface-200!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <div class=\"text-base font-medium text-gray-900 truncate\">\n {{ delegation().delegator.displayName }}\n </div>\n @if (delegation().delegator.email) {\n <div class=\"text-xs text-gray-500 truncate\">\n {{ delegation().delegator.email }}\n </div>\n }\n @if (delegation().scopeSummary) {\n <div class=\"text-xs text-gray-500 mt-1\">\n {{ t(\"delegations.column.scopeSummary\") }}:\n {{ formatScopeSummary(delegation().scopeSummary) }}\n </div>\n }\n </div>\n </div>\n\n <p class=\"text-sm text-gray-600 leading-relaxed\">\n {{ t(bodyKey()) }}\n </p>\n\n <div class=\"flex justify-end gap-2 pt-2 border-t border-gray-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('delegations.action.startSession')\"\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: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { 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: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
320
442
  }
321
443
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: StartSessionDialog, decorators: [{
322
444
  type: Component,
323
- args: [{ selector: 'mt-start-session-dialog', standalone: true, imports: [CommonModule, Avatar, Button, 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 <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-12! h-12! text-lg! bg-surface-200!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <div class=\"text-base font-medium text-gray-900 truncate\">\n {{ delegation().delegator.displayName }}\n </div>\n @if (delegation().delegator.email) {\n <div class=\"text-xs text-gray-500 truncate\">\n {{ delegation().delegator.email }}\n </div>\n }\n @if (delegation().scopeSummary) {\n <div class=\"text-xs text-gray-500 mt-1\">\n {{ t(\"delegations.column.scopeSummary\") }}:\n {{ delegation().scopeSummary }}\n </div>\n }\n </div>\n </div>\n\n <p class=\"text-sm text-gray-600 leading-relaxed\">\n {{ t(bodyKey()) }}\n </p>\n\n <div class=\"flex justify-end gap-2 pt-2 border-t border-gray-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('delegations.action.startSession')\"\n [loading]=\"isBusy()\"\n (click)=\"confirm()\"\n ></mt-button>\n </div>\n </div>\n</ng-container>\n" }]
445
+ args: [{ selector: 'mt-start-session-dialog', standalone: true, imports: [CommonModule, Avatar, Button, 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 <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-12! h-12! text-lg! bg-surface-200!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <div class=\"text-base font-medium text-gray-900 truncate\">\n {{ delegation().delegator.displayName }}\n </div>\n @if (delegation().delegator.email) {\n <div class=\"text-xs text-gray-500 truncate\">\n {{ delegation().delegator.email }}\n </div>\n }\n @if (delegation().scopeSummary) {\n <div class=\"text-xs text-gray-500 mt-1\">\n {{ t(\"delegations.column.scopeSummary\") }}:\n {{ formatScopeSummary(delegation().scopeSummary) }}\n </div>\n }\n </div>\n </div>\n\n <p class=\"text-sm text-gray-600 leading-relaxed\">\n {{ t(bodyKey()) }}\n </p>\n\n <div class=\"flex justify-end gap-2 pt-2 border-t border-gray-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('delegations.action.startSession')\"\n [loading]=\"isBusy()\"\n (click)=\"confirm()\"\n ></mt-button>\n </div>\n </div>\n</ng-container>\n" }]
324
446
  }], propDecorators: { delegation: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegation", required: true }] }], intent: [{ type: i0.Input, args: [{ isSignal: true, alias: "intent", required: false }] }] } });
325
447
 
326
448
  /**
@@ -351,6 +473,9 @@ class TopbarDelegationMenu {
351
473
  return 'candidates';
352
474
  return 'hidden';
353
475
  }, ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
476
+ formatScopeSummary(summary) {
477
+ return formatDelegationScopeSummary(summary, this.transloco.getActiveLang());
478
+ }
354
479
  togglePopover(event) {
355
480
  this.popover().toggle(event);
356
481
  }
@@ -379,7 +504,7 @@ class TopbarDelegationMenu {
379
504
  });
380
505
  }
381
506
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: TopbarDelegationMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
382
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: TopbarDelegationMenu, isStandalone: true, selector: "mt-topbar-delegation-menu", inputs: { managePath: { classPropertyName: "managePath", publicName: "managePath", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "popover", first: true, predicate: ["popover"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t\">\n @if (mode() !== \"hidden\") {\n <div class=\"mt-delegation-trigger flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"mt-delegation-trigger__button flex items-center gap-2 px-2 py-1 rounded-full cursor-pointer hover:bg-surface-100\"\n [attr.aria-label]=\"t('delegations.title')\"\n (click)=\"togglePopover($event)\"\n >\n @if (mode() === \"active\") {\n <mt-icon\n icon=\"user.users-check\"\n styleClass=\"text-lg text-primary\"\n ></mt-icon>\n @if (!compact()) {\n <span class=\"mt-delegation-trigger__label text-sm font-medium\">\n {{\n t(\"delegations.session.banner\", {\n delegatorName: onBehalfOf()?.displayName,\n })\n }}\n </span>\n }\n <mt-delegation-status-chip\n status=\"Active\"\n ></mt-delegation-status-chip>\n } @else {\n <mt-icon\n icon=\"user.users-plus\"\n styleClass=\"text-lg text-primary\"\n ></mt-icon>\n @if (!compact()) {\n <span class=\"mt-delegation-trigger__label text-sm font-medium\">\n {{ t(\"delegations.session.availableTitle\") }}\n </span>\n }\n }\n </button>\n </div>\n\n <p-popover #popover [pt]=\"{ content: { class: 'p-0!' } }\">\n <div class=\"flex flex-col min-w-80 max-w-96 p-0\">\n @if (mode() === \"active\" && active(); as session) {\n <!-- STATE C \u2014 active session -->\n <div\n class=\"flex items-start gap-3 border-b border-gray-200 px-4 py-4\"\n >\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-11! h-11! text-xl! text-gray-600! bg-surface-300!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0 flex-1\">\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\n </div>\n <div class=\"text-sm font-medium text-gray-900 truncate\">\n {{ onBehalfOf()?.displayName }}\n </div>\n <div class=\"text-xs text-gray-500 mt-1\">\n {{\n t(\"delegations.session.executedBy\", {\n actualUserName: executedBy()?.displayName,\n })\n }}\n </div>\n @if (session.delegation.scopeSummary) {\n <div\n class=\"text-xs text-gray-500 mt-1 truncate\"\n [title]=\"session.delegation.scopeSummary\"\n >\n {{ t(\"delegations.column.scopeSummary\") }}:\n {{ session.delegation.scopeSummary }}\n </div>\n }\n </div>\n </div>\n\n <div class=\"flex flex-col gap-1 px-2 py-2\">\n <mt-button\n variant=\"text\"\n icon=\"arrow.arrow-left\"\n [label]=\"t('delegations.action.endSession')\"\n styleClass=\"w-full justify-start\"\n (click)=\"endSession($event)\"\n ></mt-button>\n </div>\n\n @if (candidates().length > 1) {\n <div class=\"border-t border-gray-200 pt-2 px-2 pb-2\">\n <div class=\"text-xs text-gray-500 px-2 pb-1\">\n {{ t(\"delegations.session.switchToAnother\") }}\n </div>\n @for (cand of candidates(); track cand.delegationId) {\n @if (cand.delegationId !== session.delegation.delegationId) {\n <button\n type=\"button\"\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer\"\n (click)=\"switchTo(cand, $event)\"\n >\n <div class=\"flex items-center gap-2 min-w-0\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-7! h-7! text-sm! bg-surface-200!\"\n ></mt-avatar>\n <span class=\"text-sm text-gray-900 truncate\">\n {{ cand.delegator.displayName }}\n </span>\n </div>\n <mt-icon\n icon=\"arrow.switch-horizontal-01\"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n </button>\n }\n }\n </div>\n }\n } @else if (mode() === \"candidates\") {\n <!-- STATE B \u2014 candidates available -->\n <div class=\"border-b border-gray-200 px-4 py-3\">\n <div class=\"text-sm font-medium text-gray-900\">\n {{ t(\"delegations.session.youCanActAs\") }}\n </div>\n <div class=\"text-xs text-gray-500 mt-1\">\n {{ t(\"delegations.session.chooseDelegatorHint\") }}\n </div>\n </div>\n <div class=\"flex flex-col gap-1 px-2 py-2 max-h-72 overflow-y-auto\">\n @for (cand of candidates(); track cand.delegationId) {\n <button\n type=\"button\"\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer text-start\"\n (click)=\"start(cand, $event)\"\n >\n <div class=\"flex items-center gap-2 min-w-0 flex-1\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-9! h-9! text-base! bg-surface-200!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <span class=\"text-sm font-medium text-gray-900 truncate\">\n {{ cand.delegator.displayName }}\n </span>\n @if (cand.scopeSummary) {\n <span\n class=\"text-xs text-gray-500 truncate\"\n [title]=\"cand.scopeSummary\"\n >\n {{ cand.scopeSummary }}\n </span>\n }\n </div>\n </div>\n <mt-icon\n icon=\"arrow.arrow-right\"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n </button>\n }\n </div>\n }\n\n <!-- Footer -->\n <div class=\"border-t border-gray-200 px-2 py-2\">\n <a\n [routerLink]=\"managePath()\"\n (click)=\"popover().hide()\"\n class=\"flex items-center gap-2 px-3 py-2 rounded-md text-gray-700 hover:bg-surface-100 text-sm cursor-pointer\"\n >\n <mt-icon\n icon=\"general.settings-02\"\n styleClass=\"text-base\"\n ></mt-icon>\n <span>{{ t(\"delegations.session.manage\") }}</span>\n </a>\n </div>\n </div>\n </p-popover>\n }\n</ng-container>\n", styles: [":host{display:inline-flex;align-items:center}.mt-delegation-trigger__button{transition:background-color .2s ease-out}.mt-delegation-trigger__label{max-width:16rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host-context(.dark) .mt-delegation-trigger__button:hover{background-color:#94a3b826}\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: 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: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationStatusChip, selector: "mt-delegation-status-chip", inputs: ["status"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
507
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: TopbarDelegationMenu, isStandalone: true, selector: "mt-topbar-delegation-menu", inputs: { managePath: { classPropertyName: "managePath", publicName: "managePath", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "popover", first: true, predicate: ["popover"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t\">\n @if (mode() !== \"hidden\") {\n <div class=\"mt-delegation-trigger flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"mt-delegation-trigger__button flex min-w-0 max-w-full items-center gap-2 rounded-full px-2 py-1 hover:bg-surface-100 cursor-pointer\"\n [attr.aria-label]=\"t('delegations.title')\"\n [attr.title]=\"\n mode() === 'active'\n ? t('delegations.session.banner', {\n delegatorName: onBehalfOf()?.displayName,\n })\n : null\n \"\n (click)=\"togglePopover($event)\"\n >\n @if (mode() === \"active\") {\n <mt-icon\n icon=\"user.users-check\"\n styleClass=\"text-lg text-primary\"\n ></mt-icon>\n @if (!compact()) {\n <span class=\"mt-delegation-trigger__label text-sm font-medium\">\n {{\n t(\"delegations.session.banner\", {\n delegatorName: onBehalfOf()?.displayName,\n })\n }}\n </span>\n }\n <mt-delegation-status-chip\n status=\"Active\"\n ></mt-delegation-status-chip>\n } @else {\n <mt-icon\n icon=\"user.users-plus\"\n styleClass=\"text-lg text-primary\"\n ></mt-icon>\n @if (!compact()) {\n <span class=\"mt-delegation-trigger__label text-sm font-medium\">\n {{ t(\"delegations.session.availableTitle\") }}\n </span>\n }\n }\n </button>\n </div>\n\n <p-popover #popover [pt]=\"{ content: { class: 'p-0!' } }\">\n <div class=\"flex flex-col min-w-80 max-w-96 p-0\">\n @if (mode() === \"active\" && active(); as session) {\n <!-- STATE C \u2014 active session -->\n <div\n class=\"flex items-start gap-3 border-b border-gray-200 px-4 py-4\"\n >\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-11! h-11! text-xl! text-gray-600! bg-surface-300!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0 flex-1\">\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\n </div>\n <div class=\"text-sm font-medium text-gray-900 truncate\">\n {{ onBehalfOf()?.displayName }}\n </div>\n <div class=\"text-xs text-gray-500 mt-1\">\n {{\n t(\"delegations.session.executedBy\", {\n actualUserName: executedBy()?.displayName,\n })\n }}\n </div>\n @if (session.delegation.scopeSummary) {\n @let summary =\n formatScopeSummary(session.delegation.scopeSummary);\n <div\n class=\"text-xs text-gray-500 mt-1 truncate\"\n [title]=\"summary\"\n >\n {{ t(\"delegations.column.scopeSummary\") }}:\n {{ summary }}\n </div>\n }\n </div>\n </div>\n\n <div class=\"flex flex-col gap-1 px-2 py-2\">\n <mt-button\n variant=\"text\"\n icon=\"arrow.arrow-left\"\n [label]=\"t('delegations.action.endSession')\"\n styleClass=\"w-full justify-start\"\n (click)=\"endSession($event)\"\n ></mt-button>\n </div>\n\n @if (candidates().length > 1) {\n <div class=\"border-t border-gray-200 pt-2 px-2 pb-2\">\n <div class=\"text-xs text-gray-500 px-2 pb-1\">\n {{ t(\"delegations.session.switchToAnother\") }}\n </div>\n @for (cand of candidates(); track cand.delegationId) {\n @if (cand.delegationId !== session.delegation.delegationId) {\n <button\n type=\"button\"\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer\"\n (click)=\"switchTo(cand, $event)\"\n >\n <div class=\"flex items-center gap-2 min-w-0\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-7! h-7! text-sm! bg-surface-200!\"\n ></mt-avatar>\n <span class=\"text-sm text-gray-900 truncate\">\n {{ cand.delegator.displayName }}\n </span>\n </div>\n <mt-icon\n icon=\"arrow.switch-horizontal-01\"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n </button>\n }\n }\n </div>\n }\n } @else if (mode() === \"candidates\") {\n <!-- STATE B \u2014 candidates available -->\n <div class=\"border-b border-gray-200 px-4 py-3\">\n <div class=\"text-sm font-medium text-gray-900\">\n {{ t(\"delegations.session.youCanActAs\") }}\n </div>\n <div class=\"text-xs text-gray-500 mt-1\">\n {{ t(\"delegations.session.chooseDelegatorHint\") }}\n </div>\n </div>\n <div class=\"flex flex-col gap-1 px-2 py-2 max-h-72 overflow-y-auto\">\n @for (cand of candidates(); track cand.delegationId) {\n <button\n type=\"button\"\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer text-start\"\n (click)=\"start(cand, $event)\"\n >\n <div class=\"flex items-center gap-2 min-w-0 flex-1\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-9! h-9! text-base! bg-surface-200!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <span class=\"text-sm font-medium text-gray-900 truncate\">\n {{ cand.delegator.displayName }}\n </span>\n @if (cand.scopeSummary) {\n @let summary = formatScopeSummary(cand.scopeSummary);\n <span\n class=\"text-xs text-gray-500 truncate\"\n [title]=\"summary\"\n >\n {{ summary }}\n </span>\n }\n </div>\n </div>\n <mt-icon\n icon=\"arrow.arrow-right\"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n </button>\n }\n </div>\n }\n\n <!-- Footer -->\n <div class=\"border-t border-gray-200 px-2 py-2\">\n <a\n [routerLink]=\"managePath()\"\n (click)=\"popover().hide()\"\n class=\"flex items-center gap-2 px-3 py-2 rounded-md text-gray-700 hover:bg-surface-100 text-sm cursor-pointer\"\n >\n <mt-icon\n icon=\"general.settings-02\"\n styleClass=\"text-base\"\n ></mt-icon>\n <span>{{ t(\"delegations.session.manage\") }}</span>\n </a>\n </div>\n </div>\n </p-popover>\n }\n</ng-container>\n", styles: [":host{display:inline-flex;align-items:center;min-width:0;max-width:100%}.mt-delegation-trigger{min-width:0;max-width:100%}.mt-delegation-trigger__button{transition:background-color .2s ease-out;min-width:0;max-width:100%}.mt-delegation-trigger__label{max-width:min(16rem,28vw);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media(max-width:1200px){.mt-delegation-trigger__label{max-width:10rem}}@media(max-width:1024px){.mt-delegation-trigger__button{gap:.375rem;padding-inline:.375rem}.mt-delegation-trigger__label{display:none}}:host-context(.dark) .mt-delegation-trigger__button:hover{background-color:#94a3b826}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { 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: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationStatusChip, selector: "mt-delegation-status-chip", inputs: ["status"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
383
508
  }
384
509
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: TopbarDelegationMenu, decorators: [{
385
510
  type: Component,
@@ -392,7 +517,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
392
517
  RouterLink,
393
518
  TranslocoDirective,
394
519
  DelegationStatusChip,
395
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n @if (mode() !== \"hidden\") {\n <div class=\"mt-delegation-trigger flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"mt-delegation-trigger__button flex items-center gap-2 px-2 py-1 rounded-full cursor-pointer hover:bg-surface-100\"\n [attr.aria-label]=\"t('delegations.title')\"\n (click)=\"togglePopover($event)\"\n >\n @if (mode() === \"active\") {\n <mt-icon\n icon=\"user.users-check\"\n styleClass=\"text-lg text-primary\"\n ></mt-icon>\n @if (!compact()) {\n <span class=\"mt-delegation-trigger__label text-sm font-medium\">\n {{\n t(\"delegations.session.banner\", {\n delegatorName: onBehalfOf()?.displayName,\n })\n }}\n </span>\n }\n <mt-delegation-status-chip\n status=\"Active\"\n ></mt-delegation-status-chip>\n } @else {\n <mt-icon\n icon=\"user.users-plus\"\n styleClass=\"text-lg text-primary\"\n ></mt-icon>\n @if (!compact()) {\n <span class=\"mt-delegation-trigger__label text-sm font-medium\">\n {{ t(\"delegations.session.availableTitle\") }}\n </span>\n }\n }\n </button>\n </div>\n\n <p-popover #popover [pt]=\"{ content: { class: 'p-0!' } }\">\n <div class=\"flex flex-col min-w-80 max-w-96 p-0\">\n @if (mode() === \"active\" && active(); as session) {\n <!-- STATE C \u2014 active session -->\n <div\n class=\"flex items-start gap-3 border-b border-gray-200 px-4 py-4\"\n >\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-11! h-11! text-xl! text-gray-600! bg-surface-300!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0 flex-1\">\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\n </div>\n <div class=\"text-sm font-medium text-gray-900 truncate\">\n {{ onBehalfOf()?.displayName }}\n </div>\n <div class=\"text-xs text-gray-500 mt-1\">\n {{\n t(\"delegations.session.executedBy\", {\n actualUserName: executedBy()?.displayName,\n })\n }}\n </div>\n @if (session.delegation.scopeSummary) {\n <div\n class=\"text-xs text-gray-500 mt-1 truncate\"\n [title]=\"session.delegation.scopeSummary\"\n >\n {{ t(\"delegations.column.scopeSummary\") }}:\n {{ session.delegation.scopeSummary }}\n </div>\n }\n </div>\n </div>\n\n <div class=\"flex flex-col gap-1 px-2 py-2\">\n <mt-button\n variant=\"text\"\n icon=\"arrow.arrow-left\"\n [label]=\"t('delegations.action.endSession')\"\n styleClass=\"w-full justify-start\"\n (click)=\"endSession($event)\"\n ></mt-button>\n </div>\n\n @if (candidates().length > 1) {\n <div class=\"border-t border-gray-200 pt-2 px-2 pb-2\">\n <div class=\"text-xs text-gray-500 px-2 pb-1\">\n {{ t(\"delegations.session.switchToAnother\") }}\n </div>\n @for (cand of candidates(); track cand.delegationId) {\n @if (cand.delegationId !== session.delegation.delegationId) {\n <button\n type=\"button\"\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer\"\n (click)=\"switchTo(cand, $event)\"\n >\n <div class=\"flex items-center gap-2 min-w-0\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-7! h-7! text-sm! bg-surface-200!\"\n ></mt-avatar>\n <span class=\"text-sm text-gray-900 truncate\">\n {{ cand.delegator.displayName }}\n </span>\n </div>\n <mt-icon\n icon=\"arrow.switch-horizontal-01\"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n </button>\n }\n }\n </div>\n }\n } @else if (mode() === \"candidates\") {\n <!-- STATE B \u2014 candidates available -->\n <div class=\"border-b border-gray-200 px-4 py-3\">\n <div class=\"text-sm font-medium text-gray-900\">\n {{ t(\"delegations.session.youCanActAs\") }}\n </div>\n <div class=\"text-xs text-gray-500 mt-1\">\n {{ t(\"delegations.session.chooseDelegatorHint\") }}\n </div>\n </div>\n <div class=\"flex flex-col gap-1 px-2 py-2 max-h-72 overflow-y-auto\">\n @for (cand of candidates(); track cand.delegationId) {\n <button\n type=\"button\"\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer text-start\"\n (click)=\"start(cand, $event)\"\n >\n <div class=\"flex items-center gap-2 min-w-0 flex-1\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-9! h-9! text-base! bg-surface-200!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <span class=\"text-sm font-medium text-gray-900 truncate\">\n {{ cand.delegator.displayName }}\n </span>\n @if (cand.scopeSummary) {\n <span\n class=\"text-xs text-gray-500 truncate\"\n [title]=\"cand.scopeSummary\"\n >\n {{ cand.scopeSummary }}\n </span>\n }\n </div>\n </div>\n <mt-icon\n icon=\"arrow.arrow-right\"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n </button>\n }\n </div>\n }\n\n <!-- Footer -->\n <div class=\"border-t border-gray-200 px-2 py-2\">\n <a\n [routerLink]=\"managePath()\"\n (click)=\"popover().hide()\"\n class=\"flex items-center gap-2 px-3 py-2 rounded-md text-gray-700 hover:bg-surface-100 text-sm cursor-pointer\"\n >\n <mt-icon\n icon=\"general.settings-02\"\n styleClass=\"text-base\"\n ></mt-icon>\n <span>{{ t(\"delegations.session.manage\") }}</span>\n </a>\n </div>\n </div>\n </p-popover>\n }\n</ng-container>\n", styles: [":host{display:inline-flex;align-items:center}.mt-delegation-trigger__button{transition:background-color .2s ease-out}.mt-delegation-trigger__label{max-width:16rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host-context(.dark) .mt-delegation-trigger__button:hover{background-color:#94a3b826}\n"] }]
520
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n @if (mode() !== \"hidden\") {\n <div class=\"mt-delegation-trigger flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"mt-delegation-trigger__button flex min-w-0 max-w-full items-center gap-2 rounded-full px-2 py-1 hover:bg-surface-100 cursor-pointer\"\n [attr.aria-label]=\"t('delegations.title')\"\n [attr.title]=\"\n mode() === 'active'\n ? t('delegations.session.banner', {\n delegatorName: onBehalfOf()?.displayName,\n })\n : null\n \"\n (click)=\"togglePopover($event)\"\n >\n @if (mode() === \"active\") {\n <mt-icon\n icon=\"user.users-check\"\n styleClass=\"text-lg text-primary\"\n ></mt-icon>\n @if (!compact()) {\n <span class=\"mt-delegation-trigger__label text-sm font-medium\">\n {{\n t(\"delegations.session.banner\", {\n delegatorName: onBehalfOf()?.displayName,\n })\n }}\n </span>\n }\n <mt-delegation-status-chip\n status=\"Active\"\n ></mt-delegation-status-chip>\n } @else {\n <mt-icon\n icon=\"user.users-plus\"\n styleClass=\"text-lg text-primary\"\n ></mt-icon>\n @if (!compact()) {\n <span class=\"mt-delegation-trigger__label text-sm font-medium\">\n {{ t(\"delegations.session.availableTitle\") }}\n </span>\n }\n }\n </button>\n </div>\n\n <p-popover #popover [pt]=\"{ content: { class: 'p-0!' } }\">\n <div class=\"flex flex-col min-w-80 max-w-96 p-0\">\n @if (mode() === \"active\" && active(); as session) {\n <!-- STATE C \u2014 active session -->\n <div\n class=\"flex items-start gap-3 border-b border-gray-200 px-4 py-4\"\n >\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-11! h-11! text-xl! text-gray-600! bg-surface-300!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0 flex-1\">\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\n </div>\n <div class=\"text-sm font-medium text-gray-900 truncate\">\n {{ onBehalfOf()?.displayName }}\n </div>\n <div class=\"text-xs text-gray-500 mt-1\">\n {{\n t(\"delegations.session.executedBy\", {\n actualUserName: executedBy()?.displayName,\n })\n }}\n </div>\n @if (session.delegation.scopeSummary) {\n @let summary =\n formatScopeSummary(session.delegation.scopeSummary);\n <div\n class=\"text-xs text-gray-500 mt-1 truncate\"\n [title]=\"summary\"\n >\n {{ t(\"delegations.column.scopeSummary\") }}:\n {{ summary }}\n </div>\n }\n </div>\n </div>\n\n <div class=\"flex flex-col gap-1 px-2 py-2\">\n <mt-button\n variant=\"text\"\n icon=\"arrow.arrow-left\"\n [label]=\"t('delegations.action.endSession')\"\n styleClass=\"w-full justify-start\"\n (click)=\"endSession($event)\"\n ></mt-button>\n </div>\n\n @if (candidates().length > 1) {\n <div class=\"border-t border-gray-200 pt-2 px-2 pb-2\">\n <div class=\"text-xs text-gray-500 px-2 pb-1\">\n {{ t(\"delegations.session.switchToAnother\") }}\n </div>\n @for (cand of candidates(); track cand.delegationId) {\n @if (cand.delegationId !== session.delegation.delegationId) {\n <button\n type=\"button\"\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer\"\n (click)=\"switchTo(cand, $event)\"\n >\n <div class=\"flex items-center gap-2 min-w-0\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-7! h-7! text-sm! bg-surface-200!\"\n ></mt-avatar>\n <span class=\"text-sm text-gray-900 truncate\">\n {{ cand.delegator.displayName }}\n </span>\n </div>\n <mt-icon\n icon=\"arrow.switch-horizontal-01\"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n </button>\n }\n }\n </div>\n }\n } @else if (mode() === \"candidates\") {\n <!-- STATE B \u2014 candidates available -->\n <div class=\"border-b border-gray-200 px-4 py-3\">\n <div class=\"text-sm font-medium text-gray-900\">\n {{ t(\"delegations.session.youCanActAs\") }}\n </div>\n <div class=\"text-xs text-gray-500 mt-1\">\n {{ t(\"delegations.session.chooseDelegatorHint\") }}\n </div>\n </div>\n <div class=\"flex flex-col gap-1 px-2 py-2 max-h-72 overflow-y-auto\">\n @for (cand of candidates(); track cand.delegationId) {\n <button\n type=\"button\"\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer text-start\"\n (click)=\"start(cand, $event)\"\n >\n <div class=\"flex items-center gap-2 min-w-0 flex-1\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-9! h-9! text-base! bg-surface-200!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <span class=\"text-sm font-medium text-gray-900 truncate\">\n {{ cand.delegator.displayName }}\n </span>\n @if (cand.scopeSummary) {\n @let summary = formatScopeSummary(cand.scopeSummary);\n <span\n class=\"text-xs text-gray-500 truncate\"\n [title]=\"summary\"\n >\n {{ summary }}\n </span>\n }\n </div>\n </div>\n <mt-icon\n icon=\"arrow.arrow-right\"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n </button>\n }\n </div>\n }\n\n <!-- Footer -->\n <div class=\"border-t border-gray-200 px-2 py-2\">\n <a\n [routerLink]=\"managePath()\"\n (click)=\"popover().hide()\"\n class=\"flex items-center gap-2 px-3 py-2 rounded-md text-gray-700 hover:bg-surface-100 text-sm cursor-pointer\"\n >\n <mt-icon\n icon=\"general.settings-02\"\n styleClass=\"text-base\"\n ></mt-icon>\n <span>{{ t(\"delegations.session.manage\") }}</span>\n </a>\n </div>\n </div>\n </p-popover>\n }\n</ng-container>\n", styles: [":host{display:inline-flex;align-items:center;min-width:0;max-width:100%}.mt-delegation-trigger{min-width:0;max-width:100%}.mt-delegation-trigger__button{transition:background-color .2s ease-out;min-width:0;max-width:100%}.mt-delegation-trigger__label{max-width:min(16rem,28vw);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media(max-width:1200px){.mt-delegation-trigger__label{max-width:10rem}}@media(max-width:1024px){.mt-delegation-trigger__button{gap:.375rem;padding-inline:.375rem}.mt-delegation-trigger__label{display:none}}:host-context(.dark) .mt-delegation-trigger__button:hover{background-color:#94a3b826}\n"] }]
396
521
  }], propDecorators: { managePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "managePath", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], popover: [{ type: i0.ViewChild, args: ['popover', { isSignal: true }] }] } });
397
522
 
398
523
  // ---------------------------------------------------------------------------
@@ -643,6 +768,7 @@ let DelegationsState = class DelegationsState {
643
768
  // Scope
644
769
  // ============================================================================
645
770
  getScopeOptions(ctx, { delegatorUserId }) {
771
+ ctx.patchState({ scopeOptions: null, scopePreview: null });
646
772
  let params = new HttpParams();
647
773
  if (delegatorUserId)
648
774
  params = params.set('delegatorUserId', delegatorUserId);
@@ -868,6 +994,7 @@ class DelegationsFacade {
868
994
  errorMy = computed(() => this.errors()[DelegationsActionKey.GetMy] ?? null, ...(ngDevMode ? [{ debugName: "errorMy" }] : /* istanbul ignore next */ []));
869
995
  errorAssigned = computed(() => this.errors()[DelegationsActionKey.GetAssigned] ?? null, ...(ngDevMode ? [{ debugName: "errorAssigned" }] : /* istanbul ignore next */ []));
870
996
  errorDetail = computed(() => this.errors()[DelegationsActionKey.GetDetail] ?? null, ...(ngDevMode ? [{ debugName: "errorDetail" }] : /* istanbul ignore next */ []));
997
+ errorScopeOptions = computed(() => this.errors()[DelegationsActionKey.GetScopeOptions] ?? null, ...(ngDevMode ? [{ debugName: "errorScopeOptions" }] : /* istanbul ignore next */ []));
871
998
  errorSave = computed(() => this.errors()[DelegationsActionKey.Create] ??
872
999
  this.errors()[DelegationsActionKey.Update] ??
873
1000
  null, ...(ngDevMode ? [{ debugName: "errorSave" }] : /* istanbul ignore next */ []));
@@ -985,6 +1112,179 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
985
1112
  args: [{ selector: 'mt-delegations', imports: [Page, RouterOutlet, TranslocoDirective], template: "<ng-container *transloco=\"let t\">\n <mt-page\n [title]=\"t('delegations.title')\"\n [avatarIcon]=\"'custom.hierarchy-structure'\"\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 (backButtonClick)=\"goBack()\"\n backButton\n >\n <router-outlet />\n </mt-page>\n</ng-container>\n" }]
986
1113
  }] });
987
1114
 
1115
+ function toArray(value) {
1116
+ return Array.isArray(value) ? value : [];
1117
+ }
1118
+ function normalizeGrant$1(grant) {
1119
+ return {
1120
+ ...grant,
1121
+ accessibilities: toArray(grant.accessibilities),
1122
+ dataFilters: toArray(grant.dataFilters),
1123
+ constraints: toArray(grant.constraints),
1124
+ };
1125
+ }
1126
+ function normalizeKey(value) {
1127
+ if (typeof value === 'string') {
1128
+ const trimmed = value.trim();
1129
+ return trimmed || null;
1130
+ }
1131
+ if (typeof value === 'number' && Number.isFinite(value)) {
1132
+ return String(value);
1133
+ }
1134
+ return null;
1135
+ }
1136
+ function matchesTarget(target, rule) {
1137
+ return (target.targetType === rule.targetType &&
1138
+ target.targetKey === rule.targetKey &&
1139
+ (!rule.applicationKey ||
1140
+ !target.applicationKey ||
1141
+ target.applicationKey === rule.applicationKey));
1142
+ }
1143
+ function matchesAction(action, rule, applicationKey) {
1144
+ return (action.operationKey === rule.operationKey &&
1145
+ (!rule.applicationKey ||
1146
+ !action.applicationKey ||
1147
+ action.applicationKey === rule.applicationKey) &&
1148
+ (!applicationKey ||
1149
+ !action.applicationKey ||
1150
+ action.applicationKey === applicationKey));
1151
+ }
1152
+ function factLookupKeys(fact) {
1153
+ const candidates = [
1154
+ fact.key,
1155
+ fact.code,
1156
+ fact.id,
1157
+ fact['accessibilityKey'],
1158
+ fact['dataFilterKey'],
1159
+ fact['constraintKey'],
1160
+ fact['permissionKey'],
1161
+ fact['pageKey'],
1162
+ fact['value'],
1163
+ ];
1164
+ return candidates
1165
+ .map(normalizeKey)
1166
+ .filter((value) => !!value);
1167
+ }
1168
+ function buildFactIndex(facts) {
1169
+ const index = new Map();
1170
+ for (const fact of facts) {
1171
+ for (const key of factLookupKeys(fact)) {
1172
+ if (!index.has(key)) {
1173
+ index.set(key, fact);
1174
+ }
1175
+ }
1176
+ }
1177
+ return index;
1178
+ }
1179
+ function lookupFacts(keys, index) {
1180
+ if (!Array.isArray(keys) || keys.length === 0) {
1181
+ return [];
1182
+ }
1183
+ const resolved = [];
1184
+ const seen = new Set();
1185
+ for (const rawKey of keys) {
1186
+ const key = normalizeKey(rawKey);
1187
+ if (!key || seen.has(key)) {
1188
+ continue;
1189
+ }
1190
+ const match = index.get(key);
1191
+ if (!match) {
1192
+ continue;
1193
+ }
1194
+ seen.add(key);
1195
+ resolved.push(match);
1196
+ }
1197
+ return resolved;
1198
+ }
1199
+ function ruleKeys(rule, preferredKeys) {
1200
+ for (const preferredKey of preferredKeys) {
1201
+ const value = rule[preferredKey];
1202
+ if (Array.isArray(value)) {
1203
+ return value.map((item) => String(item));
1204
+ }
1205
+ }
1206
+ return null;
1207
+ }
1208
+ function grantIdentity(grant) {
1209
+ return JSON.stringify({
1210
+ app: grant.applicationKey,
1211
+ target: [
1212
+ grant.target.applicationKey,
1213
+ grant.target.targetType,
1214
+ grant.target.targetKey,
1215
+ grant.target.moduleKey ?? null,
1216
+ grant.target.permissionModuleType ?? null,
1217
+ grant.target.permissionTargetId ?? null,
1218
+ ],
1219
+ action: [
1220
+ grant.action.applicationKey,
1221
+ grant.action.operationKey,
1222
+ grant.action.operationKind,
1223
+ grant.action.permissionCommand ?? null,
1224
+ grant.action.businessActionCode ?? null,
1225
+ ],
1226
+ accessibilities: grant.accessibilities.map(factLookupKeys),
1227
+ dataFilters: grant.dataFilters.map(factLookupKeys),
1228
+ constraints: grant.constraints.map(factLookupKeys),
1229
+ });
1230
+ }
1231
+ function getDelegationGrantableGrants(options) {
1232
+ if (!options) {
1233
+ return [];
1234
+ }
1235
+ const legacyGrants = toArray(options.grants).map(normalizeGrant$1);
1236
+ if (legacyGrants.length > 0) {
1237
+ return legacyGrants;
1238
+ }
1239
+ const targets = toArray(options.targets);
1240
+ const operations = toArray(options.operations);
1241
+ const combinationRules = toArray(options.combinationRules);
1242
+ if (targets.length === 0 ||
1243
+ operations.length === 0 ||
1244
+ combinationRules.length === 0) {
1245
+ return [];
1246
+ }
1247
+ const accessibilityIndex = buildFactIndex(toArray(options.accessibilities));
1248
+ const dataFilterIndex = buildFactIndex(toArray(options.dataFilters));
1249
+ const constraintIndex = buildFactIndex(toArray(options.constraints));
1250
+ const grants = [];
1251
+ const seen = new Set();
1252
+ for (const rule of combinationRules) {
1253
+ const target = targets.find((item) => matchesTarget(item, rule));
1254
+ if (!target) {
1255
+ continue;
1256
+ }
1257
+ const applicationKey = normalizeKey(rule.applicationKey) ??
1258
+ normalizeKey(target.applicationKey) ??
1259
+ null;
1260
+ const action = operations.find((item) => matchesAction(item, rule, applicationKey ?? undefined));
1261
+ if (!action) {
1262
+ continue;
1263
+ }
1264
+ const grant = normalizeGrant$1({
1265
+ applicationKey: applicationKey ??
1266
+ normalizeKey(action.applicationKey) ??
1267
+ normalizeKey(target.applicationKey) ??
1268
+ '',
1269
+ target,
1270
+ action,
1271
+ accessibilities: lookupFacts(ruleKeys(rule, ['accessibilityKeys', 'accessibilities']), accessibilityIndex),
1272
+ dataFilters: lookupFacts(ruleKeys(rule, ['dataFilterKeys', 'dataFilters', 'filterKeys']), dataFilterIndex),
1273
+ constraints: lookupFacts(ruleKeys(rule, ['constraintKeys', 'constraints']), constraintIndex),
1274
+ });
1275
+ const identity = grantIdentity(grant);
1276
+ if (seen.has(identity)) {
1277
+ continue;
1278
+ }
1279
+ seen.add(identity);
1280
+ grants.push(grant);
1281
+ }
1282
+ return grants;
1283
+ }
1284
+ function hasDelegationGrantableScope(options) {
1285
+ return getDelegationGrantableGrants(options).length > 0;
1286
+ }
1287
+
988
1288
  const EMPTY_SCOPE$1 = { grants: [], metadata: {} };
989
1289
  function targetKey(t) {
990
1290
  return `${t.applicationKey}::${t.targetType}::${t.targetKey}`;
@@ -1051,13 +1351,15 @@ class ScopePicker {
1051
1351
  */
1052
1352
  delegatorUserId = input(undefined, ...(ngDevMode ? [{ debugName: "delegatorUserId" }] : /* istanbul ignore next */ []));
1053
1353
  facade = inject(DelegationsFacade);
1354
+ transloco = inject(TranslocoService);
1054
1355
  options = this.facade.scopeOptions;
1055
1356
  preview = this.facade.scopePreview;
1056
1357
  isLoadingOptions = this.facade.isLoadingScopeOptions;
1057
1358
  isPreviewing = this.facade.isPreviewingScope;
1359
+ errorOptions = this.facade.errorScopeOptions;
1058
1360
  /** Grantable options grouped by target. */
1059
1361
  groups = computed(() => {
1060
- const grants = this.options()?.grants ?? [];
1362
+ const grants = getDelegationGrantableGrants(this.options());
1061
1363
  const map = new Map();
1062
1364
  for (const g of grants) {
1063
1365
  const key = targetKey(g.target);
@@ -1076,9 +1378,22 @@ class ScopePicker {
1076
1378
  expanded = signal(new Set(), ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
1077
1379
  constructor() {
1078
1380
  // Load (and reload) grantable options whenever the delegator changes.
1381
+ let initialized = false;
1382
+ let lastDelegatorUserId;
1079
1383
  effect(() => {
1080
1384
  const delegator = this.delegatorUserId();
1081
1385
  untracked(() => this.facade.loadScopeOptions(delegator));
1386
+ if (!initialized) {
1387
+ initialized = true;
1388
+ lastDelegatorUserId = delegator;
1389
+ return;
1390
+ }
1391
+ if (lastDelegatorUserId !== delegator) {
1392
+ lastDelegatorUserId = delegator;
1393
+ this.scope.set(EMPTY_SCOPE$1);
1394
+ this.expanded.set(new Set());
1395
+ this.facade.clearScopePreview();
1396
+ }
1082
1397
  });
1083
1398
  let timer = null;
1084
1399
  effect(() => {
@@ -1108,6 +1423,21 @@ class ScopePicker {
1108
1423
  isSelected(grant) {
1109
1424
  return this.selectedKeys().has(grantKey(grant));
1110
1425
  }
1426
+ getTargetLabel(target) {
1427
+ return formatDelegationTargetLabel(target, this.currentLanguage());
1428
+ }
1429
+ getActionLabel(grant) {
1430
+ return formatDelegationActionLabel(grant.action, this.currentLanguage());
1431
+ }
1432
+ getPreviewSummary(summary) {
1433
+ return formatDelegationScopeSummary(summary, this.currentLanguage());
1434
+ }
1435
+ formatDeniedOperation(value) {
1436
+ return humanizeDelegationKey(value);
1437
+ }
1438
+ formatDeniedTarget(value) {
1439
+ return humanizeDelegationKey(value);
1440
+ }
1111
1441
  toggleGrant(grant) {
1112
1442
  if (this.readonly())
1113
1443
  return;
@@ -1124,8 +1454,11 @@ class ScopePicker {
1124
1454
  const keys = this.selectedKeys();
1125
1455
  return group.grants.filter((g) => keys.has(grantKey(g))).length;
1126
1456
  }
1457
+ currentLanguage() {
1458
+ return this.transloco.getActiveLang();
1459
+ }
1127
1460
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ScopePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
1128
- 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 }, delegatorUserId: { classPropertyName: "delegatorUserId", publicName: "delegatorUserId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { scope: "scopeChange" }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div class=\"mt-scope-picker flex flex-col gap-4\">\n @if (isLoadingOptions() && !options()) {\n <p-skeleton height=\"2rem\"></p-skeleton>\n <p-skeleton height=\"6rem\"></p-skeleton>\n <p-skeleton height=\"6rem\"></p-skeleton>\n } @else if (options()) {\n <!-- Targets + actions -->\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-sm font-medium text-gray-800\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </h4>\n\n @if (groups().length === 0) {\n <p class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noGrantableOptions\") }}\n </p>\n }\n\n @for (group of groups(); track group.key) {\n <div class=\"border border-gray-200 rounded-md overflow-hidden\">\n <button\n type=\"button\"\n class=\"flex items-center justify-between w-full px-3 py-2 hover:bg-surface-50 cursor-pointer\"\n (click)=\"toggleAccordion(group)\"\n >\n <div class=\"flex items-center gap-2 min-w-0\">\n <mt-icon\n [icon]=\"\n isExpanded(group)\n ? 'arrow.chevron-down'\n : 'arrow.chevron-right'\n \"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n <span class=\"text-sm text-gray-900 truncate\">\n {{ group.target.displayName || group.target.targetKey }}\n </span>\n <span class=\"text-xs text-gray-400\">\n {{ group.target.targetType }}\n </span>\n </div>\n @if (selectedCount(group); as count) {\n @if (count > 0) {\n <span\n class=\"text-xs text-primary font-medium px-2 py-0.5 rounded-full bg-primary-50\"\n >\n {{ count }}\n </span>\n }\n }\n </button>\n\n @if (isExpanded(group)) {\n <div class=\"flex flex-col gap-1 px-3 py-2 bg-surface-50/40\">\n @for (grant of group.grants; track $index) {\n <label\n class=\"flex items-center gap-2 px-2 py-1.5 rounded-md cursor-pointer hover:bg-surface-100\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(grant)\"\n [disabled]=\"readonly()\"\n (change)=\"toggleGrant(grant)\"\n />\n <span class=\"text-sm\">\n {{ grant.action.operationKey }}\n </span>\n @if (grant.action.isHighRisk) {\n <mt-icon\n icon=\"alert.alert-triangle\"\n styleClass=\"text-sm text-amber-500\"\n [pTooltip]=\"t('delegations.scope.highRisk')\"\n tooltipPosition=\"top\"\n ></mt-icon>\n }\n </label>\n }\n </div>\n }\n </div>\n }\n </section>\n\n <!-- Preview -->\n <section\n class=\"flex flex-col gap-1 px-3 py-2 rounded-md border border-dashed border-gray-300 bg-surface-50\"\n >\n <div class=\"flex items-center justify-between gap-2\">\n <div class=\"text-xs font-medium text-gray-700\">\n {{ t(\"delegations.scope.previewTitle\") }}\n </div>\n @if (isPreviewing()) {\n <p-skeleton width=\"6rem\" height=\"0.75rem\"></p-skeleton>\n }\n </div>\n @if (preview(); as p) {\n @if (p.isValid) {\n <div class=\"text-sm text-gray-900\">\n {{ p.summary || t(\"delegations.column.scopeSummary\") }}\n </div>\n } @else {\n <div class=\"text-sm text-red-700\">\n {{ t(\"delegations.scope.previewInvalid\") }}\n </div>\n }\n @if (p.warnings.length > 0) {\n <ul class=\"text-xs text-amber-700 list-disc list-inside\">\n @for (w of p.warnings; track w.message) {\n <li>{{ w.message }}</li>\n }\n </ul>\n }\n @if (p.deniedItems.length > 0) {\n <ul class=\"text-xs text-red-700 list-disc list-inside\">\n @for (d of p.deniedItems; track d.operationKey) {\n <li>\n {{ d.targetKey }} / {{ d.operationKey }} \u2014\n {{ d.reasonCode }}\n </li>\n }\n </ul>\n }\n } @else {\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </div>\n }\n </section>\n }\n </div>\n</ng-container>\n", styles: [":host{display:block}.mt-scope-picker input[type=checkbox]{accent-color:var(--p-primary-color, currentColor)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i2.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1461
+ 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 }, delegatorUserId: { classPropertyName: "delegatorUserId", publicName: "delegatorUserId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { scope: "scopeChange" }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div class=\"mt-scope-picker flex flex-col gap-4\">\n @if (isLoadingOptions()) {\n <p-skeleton height=\"2rem\"></p-skeleton>\n <p-skeleton height=\"6rem\"></p-skeleton>\n <p-skeleton height=\"6rem\"></p-skeleton>\n } @else {\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-sm font-medium text-gray-800\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </h4>\n\n @if (errorOptions(); as errorMessage) {\n <div\n class=\"rounded-md border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-700\"\n >\n {{ errorMessage }}\n </div>\n }\n\n @if (groups().length === 0 && !errorOptions()) {\n <p class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noGrantableOptions\") }}\n </p>\n }\n\n @for (group of groups(); track group.key) {\n <div class=\"overflow-hidden rounded-md border border-gray-200\">\n <button\n type=\"button\"\n class=\"flex w-full cursor-pointer items-center justify-between px-3 py-2 hover:bg-surface-50\"\n (click)=\"toggleAccordion(group)\"\n >\n <div class=\"flex min-w-0 items-center gap-2\">\n <mt-icon\n [icon]=\"\n isExpanded(group)\n ? 'arrow.chevron-down'\n : 'arrow.chevron-right'\n \"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n <span class=\"truncate text-sm text-gray-900\">\n {{ getTargetLabel(group.target) }}\n </span>\n <span class=\"text-xs text-gray-400\">\n {{ group.target.targetType }}\n </span>\n </div>\n @if (selectedCount(group); as count) {\n @if (count > 0) {\n <span\n class=\"rounded-full bg-primary-50 px-2 py-0.5 text-xs font-medium text-primary\"\n >\n {{ count }}\n </span>\n }\n }\n </button>\n\n @if (isExpanded(group)) {\n <div class=\"flex flex-col gap-1 bg-surface-50/40 px-3 py-2\">\n @for (grant of group.grants; track $index) {\n <label\n class=\"flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 hover:bg-surface-100\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(grant)\"\n [disabled]=\"readonly()\"\n (change)=\"toggleGrant(grant)\"\n />\n <span class=\"text-sm\">\n {{ getActionLabel(grant) }}\n </span>\n @if (grant.action.isHighRisk) {\n <mt-icon\n icon=\"alert.alert-triangle\"\n styleClass=\"text-sm text-amber-500\"\n [pTooltip]=\"t('delegations.scope.highRisk')\"\n tooltipPosition=\"top\"\n ></mt-icon>\n }\n </label>\n }\n </div>\n }\n </div>\n }\n </section>\n\n <section\n class=\"flex flex-col gap-1 rounded-md border border-dashed border-gray-300 bg-surface-50 px-3 py-2\"\n >\n <div class=\"flex items-center justify-between gap-2\">\n <div class=\"text-xs font-medium text-gray-700\">\n {{ t(\"delegations.scope.previewTitle\") }}\n </div>\n @if (isPreviewing()) {\n <p-skeleton width=\"6rem\" height=\"0.75rem\"></p-skeleton>\n }\n </div>\n @if (preview(); as p) {\n @if (p.isValid) {\n <div class=\"text-sm text-gray-900\">\n {{\n getPreviewSummary(p.summary) ||\n t(\"delegations.column.scopeSummary\")\n }}\n </div>\n } @else {\n <div class=\"text-sm text-red-700\">\n {{ t(\"delegations.scope.previewInvalid\") }}\n </div>\n }\n @if (p.warnings.length > 0) {\n <ul class=\"list-inside list-disc text-xs text-amber-700\">\n @for (w of p.warnings; track w.message) {\n <li>{{ w.message }}</li>\n }\n </ul>\n }\n @if (p.deniedItems.length > 0) {\n <ul class=\"list-inside list-disc text-xs text-red-700\">\n @for (d of p.deniedItems; track d.operationKey) {\n <li>\n {{ formatDeniedTarget(d.targetKey) }} /\n {{ formatDeniedOperation(d.operationKey) }} -\n {{ d.reasonCode }}\n </li>\n }\n </ul>\n }\n } @else {\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </div>\n }\n </section>\n }\n </div>\n</ng-container>\n", styles: [":host{display:block}.mt-scope-picker input[type=checkbox]{accent-color:var(--p-primary-color, currentColor)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i2.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1129
1462
  }
1130
1463
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ScopePicker, decorators: [{
1131
1464
  type: Component,
@@ -1135,10 +1468,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1135
1468
  SkeletonModule,
1136
1469
  TooltipModule,
1137
1470
  TranslocoDirective,
1138
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div class=\"mt-scope-picker flex flex-col gap-4\">\n @if (isLoadingOptions() && !options()) {\n <p-skeleton height=\"2rem\"></p-skeleton>\n <p-skeleton height=\"6rem\"></p-skeleton>\n <p-skeleton height=\"6rem\"></p-skeleton>\n } @else if (options()) {\n <!-- Targets + actions -->\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-sm font-medium text-gray-800\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </h4>\n\n @if (groups().length === 0) {\n <p class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noGrantableOptions\") }}\n </p>\n }\n\n @for (group of groups(); track group.key) {\n <div class=\"border border-gray-200 rounded-md overflow-hidden\">\n <button\n type=\"button\"\n class=\"flex items-center justify-between w-full px-3 py-2 hover:bg-surface-50 cursor-pointer\"\n (click)=\"toggleAccordion(group)\"\n >\n <div class=\"flex items-center gap-2 min-w-0\">\n <mt-icon\n [icon]=\"\n isExpanded(group)\n ? 'arrow.chevron-down'\n : 'arrow.chevron-right'\n \"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n <span class=\"text-sm text-gray-900 truncate\">\n {{ group.target.displayName || group.target.targetKey }}\n </span>\n <span class=\"text-xs text-gray-400\">\n {{ group.target.targetType }}\n </span>\n </div>\n @if (selectedCount(group); as count) {\n @if (count > 0) {\n <span\n class=\"text-xs text-primary font-medium px-2 py-0.5 rounded-full bg-primary-50\"\n >\n {{ count }}\n </span>\n }\n }\n </button>\n\n @if (isExpanded(group)) {\n <div class=\"flex flex-col gap-1 px-3 py-2 bg-surface-50/40\">\n @for (grant of group.grants; track $index) {\n <label\n class=\"flex items-center gap-2 px-2 py-1.5 rounded-md cursor-pointer hover:bg-surface-100\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(grant)\"\n [disabled]=\"readonly()\"\n (change)=\"toggleGrant(grant)\"\n />\n <span class=\"text-sm\">\n {{ grant.action.operationKey }}\n </span>\n @if (grant.action.isHighRisk) {\n <mt-icon\n icon=\"alert.alert-triangle\"\n styleClass=\"text-sm text-amber-500\"\n [pTooltip]=\"t('delegations.scope.highRisk')\"\n tooltipPosition=\"top\"\n ></mt-icon>\n }\n </label>\n }\n </div>\n }\n </div>\n }\n </section>\n\n <!-- Preview -->\n <section\n class=\"flex flex-col gap-1 px-3 py-2 rounded-md border border-dashed border-gray-300 bg-surface-50\"\n >\n <div class=\"flex items-center justify-between gap-2\">\n <div class=\"text-xs font-medium text-gray-700\">\n {{ t(\"delegations.scope.previewTitle\") }}\n </div>\n @if (isPreviewing()) {\n <p-skeleton width=\"6rem\" height=\"0.75rem\"></p-skeleton>\n }\n </div>\n @if (preview(); as p) {\n @if (p.isValid) {\n <div class=\"text-sm text-gray-900\">\n {{ p.summary || t(\"delegations.column.scopeSummary\") }}\n </div>\n } @else {\n <div class=\"text-sm text-red-700\">\n {{ t(\"delegations.scope.previewInvalid\") }}\n </div>\n }\n @if (p.warnings.length > 0) {\n <ul class=\"text-xs text-amber-700 list-disc list-inside\">\n @for (w of p.warnings; track w.message) {\n <li>{{ w.message }}</li>\n }\n </ul>\n }\n @if (p.deniedItems.length > 0) {\n <ul class=\"text-xs text-red-700 list-disc list-inside\">\n @for (d of p.deniedItems; track d.operationKey) {\n <li>\n {{ d.targetKey }} / {{ d.operationKey }} \u2014\n {{ d.reasonCode }}\n </li>\n }\n </ul>\n }\n } @else {\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </div>\n }\n </section>\n }\n </div>\n</ng-container>\n", styles: [":host{display:block}.mt-scope-picker input[type=checkbox]{accent-color:var(--p-primary-color, currentColor)}\n"] }]
1471
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div class=\"mt-scope-picker flex flex-col gap-4\">\n @if (isLoadingOptions()) {\n <p-skeleton height=\"2rem\"></p-skeleton>\n <p-skeleton height=\"6rem\"></p-skeleton>\n <p-skeleton height=\"6rem\"></p-skeleton>\n } @else {\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-sm font-medium text-gray-800\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </h4>\n\n @if (errorOptions(); as errorMessage) {\n <div\n class=\"rounded-md border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-700\"\n >\n {{ errorMessage }}\n </div>\n }\n\n @if (groups().length === 0 && !errorOptions()) {\n <p class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noGrantableOptions\") }}\n </p>\n }\n\n @for (group of groups(); track group.key) {\n <div class=\"overflow-hidden rounded-md border border-gray-200\">\n <button\n type=\"button\"\n class=\"flex w-full cursor-pointer items-center justify-between px-3 py-2 hover:bg-surface-50\"\n (click)=\"toggleAccordion(group)\"\n >\n <div class=\"flex min-w-0 items-center gap-2\">\n <mt-icon\n [icon]=\"\n isExpanded(group)\n ? 'arrow.chevron-down'\n : 'arrow.chevron-right'\n \"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n <span class=\"truncate text-sm text-gray-900\">\n {{ getTargetLabel(group.target) }}\n </span>\n <span class=\"text-xs text-gray-400\">\n {{ group.target.targetType }}\n </span>\n </div>\n @if (selectedCount(group); as count) {\n @if (count > 0) {\n <span\n class=\"rounded-full bg-primary-50 px-2 py-0.5 text-xs font-medium text-primary\"\n >\n {{ count }}\n </span>\n }\n }\n </button>\n\n @if (isExpanded(group)) {\n <div class=\"flex flex-col gap-1 bg-surface-50/40 px-3 py-2\">\n @for (grant of group.grants; track $index) {\n <label\n class=\"flex cursor-pointer items-center gap-2 rounded-md px-2 py-1.5 hover:bg-surface-100\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(grant)\"\n [disabled]=\"readonly()\"\n (change)=\"toggleGrant(grant)\"\n />\n <span class=\"text-sm\">\n {{ getActionLabel(grant) }}\n </span>\n @if (grant.action.isHighRisk) {\n <mt-icon\n icon=\"alert.alert-triangle\"\n styleClass=\"text-sm text-amber-500\"\n [pTooltip]=\"t('delegations.scope.highRisk')\"\n tooltipPosition=\"top\"\n ></mt-icon>\n }\n </label>\n }\n </div>\n }\n </div>\n }\n </section>\n\n <section\n class=\"flex flex-col gap-1 rounded-md border border-dashed border-gray-300 bg-surface-50 px-3 py-2\"\n >\n <div class=\"flex items-center justify-between gap-2\">\n <div class=\"text-xs font-medium text-gray-700\">\n {{ t(\"delegations.scope.previewTitle\") }}\n </div>\n @if (isPreviewing()) {\n <p-skeleton width=\"6rem\" height=\"0.75rem\"></p-skeleton>\n }\n </div>\n @if (preview(); as p) {\n @if (p.isValid) {\n <div class=\"text-sm text-gray-900\">\n {{\n getPreviewSummary(p.summary) ||\n t(\"delegations.column.scopeSummary\")\n }}\n </div>\n } @else {\n <div class=\"text-sm text-red-700\">\n {{ t(\"delegations.scope.previewInvalid\") }}\n </div>\n }\n @if (p.warnings.length > 0) {\n <ul class=\"list-inside list-disc text-xs text-amber-700\">\n @for (w of p.warnings; track w.message) {\n <li>{{ w.message }}</li>\n }\n </ul>\n }\n @if (p.deniedItems.length > 0) {\n <ul class=\"list-inside list-disc text-xs text-red-700\">\n @for (d of p.deniedItems; track d.operationKey) {\n <li>\n {{ formatDeniedTarget(d.targetKey) }} /\n {{ formatDeniedOperation(d.operationKey) }} -\n {{ d.reasonCode }}\n </li>\n }\n </ul>\n }\n } @else {\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </div>\n }\n </section>\n }\n </div>\n</ng-container>\n", styles: [":host{display:block}.mt-scope-picker input[type=checkbox]{accent-color:var(--p-primary-color, currentColor)}\n"] }]
1139
1472
  }], 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 }] }], delegatorUserId: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegatorUserId", required: false }] }] } });
1140
1473
 
1141
1474
  const EMPTY_SCOPE = { grants: [], metadata: {} };
1475
+ function extractUserId(value) {
1476
+ if (typeof value === 'string') {
1477
+ return value.trim() || undefined;
1478
+ }
1479
+ if (!value || typeof value !== 'object') {
1480
+ return undefined;
1481
+ }
1482
+ const record = value;
1483
+ for (const key of ['userId', 'id']) {
1484
+ const candidate = record[key];
1485
+ if (typeof candidate === 'string' && candidate.trim()) {
1486
+ return candidate.trim();
1487
+ }
1488
+ }
1489
+ return undefined;
1490
+ }
1142
1491
  /**
1143
1492
  * Delegation create/edit form (doc 04). Uses the v2 explicit-scope endpoints so
1144
1493
  * the persisted `DelegationScopeSelection` matches what scope/preview validated.
@@ -1175,11 +1524,7 @@ class DelegationForm {
1175
1524
  selectedDelegatorId = computed(() => {
1176
1525
  if (!this.adminMode())
1177
1526
  return undefined;
1178
- const v = this.formValue();
1179
- return (v?.delegateFrom?.userId ??
1180
- v?.delegateFrom?.id ??
1181
- v?.delegateFrom ??
1182
- undefined);
1527
+ return extractUserId(this.formValue()?.delegateFrom);
1183
1528
  }, ...(ngDevMode ? [{ debugName: "selectedDelegatorId" }] : /* istanbul ignore next */ []));
1184
1529
  /** In admin mode the scope picker only loads once a delegator is chosen. */
1185
1530
  showScopePicker = computed(() => !this.adminMode() || !!this.selectedDelegatorId(), ...(ngDevMode ? [{ debugName: "showScopePicker" }] : /* istanbul ignore next */ []));
@@ -1359,7 +1704,7 @@ class DelegationForm {
1359
1704
  delegateFrom: this.adminMode()
1360
1705
  ? (this.selectedDelegatorId() ?? null)
1361
1706
  : null,
1362
- delegateTo: v?.delegateTo?.userId ?? v?.delegateTo?.id ?? v?.delegateTo,
1707
+ delegateTo: extractUserId(v?.delegateTo) ?? '',
1363
1708
  description: v?.description,
1364
1709
  delegateFromDateTime: v?.delegateFromDateTime,
1365
1710
  delegateToDateTime: v?.delegateToDateTime,
@@ -1429,8 +1774,17 @@ class DelegationDetailDrawer {
1429
1774
  const s = this.row()?.approval?.approvalStatus ?? 'NotRequired';
1430
1775
  return this.transloco.translate(`delegations.approvalStatus.${s}`);
1431
1776
  }
1777
+ getScopeTargetLabel(target) {
1778
+ return formatDelegationTargetLabel(target, this.transloco.getActiveLang());
1779
+ }
1780
+ getScopeActionLabel(action) {
1781
+ return formatDelegationActionLabel(action, this.transloco.getActiveLang());
1782
+ }
1783
+ formatScopeSummary(summary) {
1784
+ return formatDelegationScopeSummary(summary, this.transloco.getActiveLang());
1785
+ }
1432
1786
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationDetailDrawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
1433
- 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 } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col h-full\">\n <!-- Tabs -->\n <div class=\"flex border-b border-gray-200 px-4\">\n @for (tab of tabs; track tab.key) {\n <button\n type=\"button\"\n class=\"px-3 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\n [class.border-primary]=\"activeTab() === tab.key\"\n [class.text-primary]=\"activeTab() === tab.key\"\n [class.border-transparent]=\"activeTab() !== tab.key\"\n [class.text-gray-500]=\"activeTab() !== tab.key\"\n (click)=\"setTab(tab.key)\"\n >\n {{ t(tab.i18n) }}\n </button>\n }\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-avatar\n icon=\"user.user-01\"\n styleClass=\"w-12! h-12! text-lg!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.delegatorName\") }}\n </div>\n <div class=\"text-base font-medium text-gray-900 truncate\">\n {{ d.row.delegator.displayName }}\n </div>\n @if (d.row.delegator.email) {\n <div class=\"text-xs text-gray-500 truncate\">\n {{ d.row.delegator.email }}\n </div>\n }\n </div>\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 <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.delegatedTo\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.delegatedUser.displayName }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.approval\") }}\n </span>\n <span class=\"text-sm text-gray-900\">{{ approvalLabel() }}</span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.startDate\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.startsAtUtc | date: \"medium\" }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.endDate\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.endsAtUtc | date: \"medium\" }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.form.timeZone\") }}\n </span>\n <span class=\"text-sm text-gray-900\">{{\n d.row.timeZoneId\n }}</span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.days\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n @if (d.row.dayRuleMode === \"FullRange\") {\n {{ t(\"delegations.form.fullRange\") }}\n } @else {\n {{ d.row.specificDays.join(\", \") }}\n }\n </span>\n </div>\n </div>\n\n @if (d.row.cancellation?.cancellationReason) {\n <div\n class=\"rounded-md border border-gray-200 bg-surface-50 p-3 text-sm text-gray-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 class=\"flex flex-col gap-3\">\n <div class=\"text-sm text-gray-900\">\n {{ d.row.scopeSummary || t(\"delegations.column.scopeSummary\") }}\n </div>\n <div class=\"flex flex-col gap-2\">\n @for (grant of d.scope.grants; track $index) {\n <div\n class=\"border border-gray-200 rounded-md px-3 py-2 flex flex-col gap-1\"\n >\n <div class=\"flex items-center justify-between gap-2\">\n <span class=\"text-sm font-medium text-gray-900\">\n {{ grant.target.displayName || grant.target.targetKey }}\n </span>\n <span class=\"text-xs text-gray-500\">\n {{ grant.target.targetType }}\n </span>\n </div>\n <span\n class=\"text-xs px-2 py-0.5 rounded-md bg-primary-50 text-primary w-fit\"\n >\n {{ grant.action.operationKey }}\n </span>\n </div>\n }\n @if (d.scope.grants.length === 0) {\n <p class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </p>\n }\n </div>\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n <div\n class=\"border-t border-gray-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: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { 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: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationStatusChip, selector: "mt-delegation-status-chip", inputs: ["status"] }, { kind: "pipe", type: i2$1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1787
+ 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 } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col h-full\">\n <!-- Tabs -->\n <div class=\"flex border-b border-gray-200 px-4\">\n @for (tab of tabs; track tab.key) {\n <button\n type=\"button\"\n class=\"px-3 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\n [class.border-primary]=\"activeTab() === tab.key\"\n [class.text-primary]=\"activeTab() === tab.key\"\n [class.border-transparent]=\"activeTab() !== tab.key\"\n [class.text-gray-500]=\"activeTab() !== tab.key\"\n (click)=\"setTab(tab.key)\"\n >\n {{ t(tab.i18n) }}\n </button>\n }\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-avatar\n icon=\"user.user-01\"\n styleClass=\"w-12! h-12! text-lg!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.delegatorName\") }}\n </div>\n <div class=\"text-base font-medium text-gray-900 truncate\">\n {{ d.row.delegator.displayName }}\n </div>\n @if (d.row.delegator.email) {\n <div class=\"text-xs text-gray-500 truncate\">\n {{ d.row.delegator.email }}\n </div>\n }\n </div>\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 <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.delegatedTo\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.delegatedUser.displayName }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.approval\") }}\n </span>\n <span class=\"text-sm text-gray-900\">{{ approvalLabel() }}</span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.startDate\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.startsAtUtc | date: \"medium\" }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.endDate\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.endsAtUtc | date: \"medium\" }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.form.timeZone\") }}\n </span>\n <span class=\"text-sm text-gray-900\">{{\n d.row.timeZoneId\n }}</span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.days\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n @if (d.row.dayRuleMode === \"FullRange\") {\n {{ t(\"delegations.form.fullRange\") }}\n } @else {\n {{ d.row.specificDays.join(\", \") }}\n }\n </span>\n </div>\n </div>\n\n @if (d.row.cancellation?.cancellationReason) {\n <div\n class=\"rounded-md border border-gray-200 bg-surface-50 p-3 text-sm text-gray-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 class=\"flex flex-col gap-3\">\n <div class=\"text-sm text-gray-900\">\n {{\n formatScopeSummary(d.row.scopeSummary) ||\n t(\"delegations.column.scopeSummary\")\n }}\n </div>\n <div class=\"flex flex-col gap-2\">\n @for (grant of d.scope.grants; track $index) {\n <div\n class=\"border border-gray-200 rounded-md px-3 py-2 flex flex-col gap-1\"\n >\n <div class=\"flex items-center justify-between gap-2\">\n <span class=\"text-sm font-medium text-gray-900\">\n {{ getScopeTargetLabel(grant.target) }}\n </span>\n <span class=\"text-xs text-gray-500\">\n {{ grant.target.targetType }}\n </span>\n </div>\n <span\n class=\"text-xs px-2 py-0.5 rounded-md bg-primary-50 text-primary w-fit\"\n >\n {{ getScopeActionLabel(grant.action) }}\n </span>\n </div>\n }\n @if (d.scope.grants.length === 0) {\n <p class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </p>\n }\n </div>\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n <div\n class=\"border-t border-gray-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: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { 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: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationStatusChip, selector: "mt-delegation-status-chip", inputs: ["status"] }, { kind: "pipe", type: i2$1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1434
1788
  }
1435
1789
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationDetailDrawer, decorators: [{
1436
1790
  type: Component,
@@ -1441,7 +1795,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1441
1795
  SkeletonModule,
1442
1796
  TranslocoDirective,
1443
1797
  DelegationStatusChip,
1444
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col h-full\">\n <!-- Tabs -->\n <div class=\"flex border-b border-gray-200 px-4\">\n @for (tab of tabs; track tab.key) {\n <button\n type=\"button\"\n class=\"px-3 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\n [class.border-primary]=\"activeTab() === tab.key\"\n [class.text-primary]=\"activeTab() === tab.key\"\n [class.border-transparent]=\"activeTab() !== tab.key\"\n [class.text-gray-500]=\"activeTab() !== tab.key\"\n (click)=\"setTab(tab.key)\"\n >\n {{ t(tab.i18n) }}\n </button>\n }\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-avatar\n icon=\"user.user-01\"\n styleClass=\"w-12! h-12! text-lg!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.delegatorName\") }}\n </div>\n <div class=\"text-base font-medium text-gray-900 truncate\">\n {{ d.row.delegator.displayName }}\n </div>\n @if (d.row.delegator.email) {\n <div class=\"text-xs text-gray-500 truncate\">\n {{ d.row.delegator.email }}\n </div>\n }\n </div>\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 <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.delegatedTo\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.delegatedUser.displayName }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.approval\") }}\n </span>\n <span class=\"text-sm text-gray-900\">{{ approvalLabel() }}</span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.startDate\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.startsAtUtc | date: \"medium\" }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.endDate\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.endsAtUtc | date: \"medium\" }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.form.timeZone\") }}\n </span>\n <span class=\"text-sm text-gray-900\">{{\n d.row.timeZoneId\n }}</span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.days\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n @if (d.row.dayRuleMode === \"FullRange\") {\n {{ t(\"delegations.form.fullRange\") }}\n } @else {\n {{ d.row.specificDays.join(\", \") }}\n }\n </span>\n </div>\n </div>\n\n @if (d.row.cancellation?.cancellationReason) {\n <div\n class=\"rounded-md border border-gray-200 bg-surface-50 p-3 text-sm text-gray-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 class=\"flex flex-col gap-3\">\n <div class=\"text-sm text-gray-900\">\n {{ d.row.scopeSummary || t(\"delegations.column.scopeSummary\") }}\n </div>\n <div class=\"flex flex-col gap-2\">\n @for (grant of d.scope.grants; track $index) {\n <div\n class=\"border border-gray-200 rounded-md px-3 py-2 flex flex-col gap-1\"\n >\n <div class=\"flex items-center justify-between gap-2\">\n <span class=\"text-sm font-medium text-gray-900\">\n {{ grant.target.displayName || grant.target.targetKey }}\n </span>\n <span class=\"text-xs text-gray-500\">\n {{ grant.target.targetType }}\n </span>\n </div>\n <span\n class=\"text-xs px-2 py-0.5 rounded-md bg-primary-50 text-primary w-fit\"\n >\n {{ grant.action.operationKey }}\n </span>\n </div>\n }\n @if (d.scope.grants.length === 0) {\n <p class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </p>\n }\n </div>\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n <div\n class=\"border-t border-gray-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"] }]
1798
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col h-full\">\n <!-- Tabs -->\n <div class=\"flex border-b border-gray-200 px-4\">\n @for (tab of tabs; track tab.key) {\n <button\n type=\"button\"\n class=\"px-3 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\n [class.border-primary]=\"activeTab() === tab.key\"\n [class.text-primary]=\"activeTab() === tab.key\"\n [class.border-transparent]=\"activeTab() !== tab.key\"\n [class.text-gray-500]=\"activeTab() !== tab.key\"\n (click)=\"setTab(tab.key)\"\n >\n {{ t(tab.i18n) }}\n </button>\n }\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-avatar\n icon=\"user.user-01\"\n styleClass=\"w-12! h-12! text-lg!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.delegatorName\") }}\n </div>\n <div class=\"text-base font-medium text-gray-900 truncate\">\n {{ d.row.delegator.displayName }}\n </div>\n @if (d.row.delegator.email) {\n <div class=\"text-xs text-gray-500 truncate\">\n {{ d.row.delegator.email }}\n </div>\n }\n </div>\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 <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.delegatedTo\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.delegatedUser.displayName }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.approval\") }}\n </span>\n <span class=\"text-sm text-gray-900\">{{ approvalLabel() }}</span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.startDate\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.startsAtUtc | date: \"medium\" }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.endDate\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.endsAtUtc | date: \"medium\" }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.form.timeZone\") }}\n </span>\n <span class=\"text-sm text-gray-900\">{{\n d.row.timeZoneId\n }}</span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.days\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n @if (d.row.dayRuleMode === \"FullRange\") {\n {{ t(\"delegations.form.fullRange\") }}\n } @else {\n {{ d.row.specificDays.join(\", \") }}\n }\n </span>\n </div>\n </div>\n\n @if (d.row.cancellation?.cancellationReason) {\n <div\n class=\"rounded-md border border-gray-200 bg-surface-50 p-3 text-sm text-gray-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 class=\"flex flex-col gap-3\">\n <div class=\"text-sm text-gray-900\">\n {{\n formatScopeSummary(d.row.scopeSummary) ||\n t(\"delegations.column.scopeSummary\")\n }}\n </div>\n <div class=\"flex flex-col gap-2\">\n @for (grant of d.scope.grants; track $index) {\n <div\n class=\"border border-gray-200 rounded-md px-3 py-2 flex flex-col gap-1\"\n >\n <div class=\"flex items-center justify-between gap-2\">\n <span class=\"text-sm font-medium text-gray-900\">\n {{ getScopeTargetLabel(grant.target) }}\n </span>\n <span class=\"text-xs text-gray-500\">\n {{ grant.target.targetType }}\n </span>\n </div>\n <span\n class=\"text-xs px-2 py-0.5 rounded-md bg-primary-50 text-primary w-fit\"\n >\n {{ getScopeActionLabel(grant.action) }}\n </span>\n </div>\n }\n @if (d.scope.grants.length === 0) {\n <p class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </p>\n }\n </div>\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n <div\n class=\"border-t border-gray-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"] }]
1445
1799
  }], propDecorators: { delegationId: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegationId", required: true }] }] } });
1446
1800
 
1447
1801
  /**
@@ -1506,7 +1860,7 @@ class DelegationsList {
1506
1860
  canCreate = computed(() => {
1507
1861
  if (this.adminMode())
1508
1862
  return true;
1509
- return (this.scopeOptions()?.grants?.length ?? 0) > 0;
1863
+ return hasDelegationGrantableScope(this.scopeOptions());
1510
1864
  }, ...(ngDevMode ? [{ debugName: "canCreate" }] : /* istanbul ignore next */ []));
1511
1865
  tableActions = computed(() => this.canCreate()
1512
1866
  ? [
@@ -1663,6 +2017,9 @@ class DelegationsList {
1663
2017
  }
1664
2018
  return this.transloco.translate('delegations.form.fullRange');
1665
2019
  }
2020
+ formatScopeSummary(value) {
2021
+ return formatDelegationScopeSummary(value, this.transloco.getActiveLang());
2022
+ }
1666
2023
  viewDetails(row) {
1667
2024
  this.modal.openModal(DelegationDetailDrawer, 'drawer', {
1668
2025
  header: this.transloco.translate('delegations.action.view'),
@@ -1739,7 +2096,7 @@ class DelegationsList {
1739
2096
  this.busyIds.update((ids) => on ? [...ids, key] : ids.filter((id) => id !== key));
1740
2097
  }
1741
2098
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsList, deps: [], target: i0.ɵɵFactoryTarget.Component });
1742
- 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 }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "statusCol", first: true, predicate: ["statusCol"], descendants: true, isSignal: true }, { propertyName: "userCol", first: true, predicate: ["userCol"], descendants: true, isSignal: true }, { propertyName: "scopeCol", first: true, predicate: ["scopeCol"], descendants: true, isSignal: true }, { propertyName: "daysCol", first: true, predicate: ["daysCol"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col h-full gap-2 p-4\">\n @if (showBreadcrumb()) {\n <mt-breadcrumb [items]=\"breadcrumbItems()\"></mt-breadcrumb>\n }\n\n <div class=\"flex items-center gap-2 border-b border-gray-200\">\n @for (tab of tabs(); track tab.value) {\n <button\n type=\"button\"\n class=\"px-4 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\n [class.border-primary]=\"activeTab() === tab.value\"\n [class.text-primary]=\"activeTab() === tab.value\"\n [class.border-transparent]=\"activeTab() !== tab.value\"\n [class.text-gray-500]=\"activeTab() !== tab.value\"\n (click)=\"switchTab(tab.value)\"\n >\n {{ tab.label }}\n </button>\n }\n </div>\n\n <mt-table\n [data]=\"rows()\"\n [columns]=\"tableColumns()\"\n [actions]=\"tableActions()\"\n [rowActions]=\"rowActions()\"\n [freezeActions]=\"true\"\n [loading]=\"isLoading()\"\n >\n <ng-template #statusCol let-row>\n <mt-delegation-status-chip\n [status]=\"row.effectiveStatus\"\n ></mt-delegation-status-chip>\n </ng-template>\n\n <ng-template #userCol let-row>\n @let party = activeTab() === \"my\" ? row.delegatedUser : row.delegator;\n <div class=\"flex items-center gap-2\">\n <mt-avatar icon=\"user.user-01\" styleClass=\"w-8! h-8!\"></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <span class=\"text-sm text-gray-900 truncate\">\n {{ party?.displayName }}\n </span>\n @if (party?.email) {\n <span class=\"text-xs text-gray-500 truncate\">\n {{ party.email }}\n </span>\n }\n </div>\n </div>\n </ng-template>\n\n <ng-template #scopeCol let-row>\n <span class=\"text-sm text-gray-700\" [title]=\"row.scopeSummary\">\n {{ row.scopeSummary || \"\u2014\" }}\n </span>\n </ng-template>\n\n <ng-template #daysCol let-row>\n <span class=\"text-sm text-gray-700\">{{ formatDays(row) }}</span>\n </ng-template>\n </mt-table>\n </div>\n</ng-container>\n", styles: [":host{display:block;min-width:0}:host ::ng-deep mt-table .p-datatable-table{min-width:72rem}:host ::ng-deep mt-table .p-datatable-header>div,:host ::ng-deep mt-table .p-datatable-header>div>div{gap:.75rem;min-width:0;flex-wrap:wrap}:host ::ng-deep mt-table .p-datatable-header mt-tabs{display:block;max-width:100%;overflow-x:auto;padding-bottom:.25rem}:host ::ng-deep mt-table .p-datatable-header mt-text-field{display:block;flex:1 1 16rem;min-width:min(100%,16rem)}:host ::ng-deep mt-table .p-datatable-header .p-inputtext,:host ::ng-deep mt-table .p-datatable-header .p-inputwrapper,:host ::ng-deep mt-table .p-datatable-header .p-selectbutton{width:100%;max-width:100%}@media(max-width:1024px){:host ::ng-deep mt-table .p-datatable-header>div{flex-direction:column;align-items:stretch}:host ::ng-deep mt-table .p-datatable-header>div>div{width:100%;justify-content:flex-start}:host ::ng-deep mt-table .p-datatable-header mt-text-field{flex-basis:100%;min-width:0}}@media(max-width:768px){:host ::ng-deep mt-table .p-datatable-table{min-width:64rem}}\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: Breadcrumb, selector: "mt-breadcrumb", inputs: ["items", "styleClass"], outputs: ["onItemClick"] }, { 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: "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 });
2099
+ 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 }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "statusCol", first: true, predicate: ["statusCol"], descendants: true, isSignal: true }, { propertyName: "userCol", first: true, predicate: ["userCol"], descendants: true, isSignal: true }, { propertyName: "scopeCol", first: true, predicate: ["scopeCol"], descendants: true, isSignal: true }, { propertyName: "daysCol", first: true, predicate: ["daysCol"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col h-full gap-2 p-4\">\n @if (showBreadcrumb()) {\n <mt-breadcrumb [items]=\"breadcrumbItems()\"></mt-breadcrumb>\n }\n\n <div class=\"flex items-center gap-2 border-b border-gray-200\">\n @for (tab of tabs(); track tab.value) {\n <button\n type=\"button\"\n class=\"px-4 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\n [class.border-primary]=\"activeTab() === tab.value\"\n [class.text-primary]=\"activeTab() === tab.value\"\n [class.border-transparent]=\"activeTab() !== tab.value\"\n [class.text-gray-500]=\"activeTab() !== tab.value\"\n (click)=\"switchTab(tab.value)\"\n >\n {{ tab.label }}\n </button>\n }\n </div>\n\n <mt-table\n [data]=\"rows()\"\n [columns]=\"tableColumns()\"\n [actions]=\"tableActions()\"\n [rowActions]=\"rowActions()\"\n [freezeActions]=\"true\"\n [loading]=\"isLoading()\"\n >\n <ng-template #statusCol let-row>\n <mt-delegation-status-chip\n [status]=\"row.effectiveStatus\"\n ></mt-delegation-status-chip>\n </ng-template>\n\n <ng-template #userCol let-row>\n @let party = activeTab() === \"my\" ? row.delegatedUser : row.delegator;\n <div class=\"flex items-center gap-2\">\n <mt-avatar icon=\"user.user-01\" styleClass=\"w-8! h-8!\"></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <span class=\"text-sm text-gray-900 truncate\">\n {{ party?.displayName }}\n </span>\n @if (party?.email) {\n <span class=\"text-xs text-gray-500 truncate\">\n {{ party.email }}\n </span>\n }\n </div>\n </div>\n </ng-template>\n\n <ng-template #scopeCol let-row>\n @let summary = formatScopeSummary(row.scopeSummary);\n <span class=\"text-sm text-gray-700\" [title]=\"summary\">\n {{ summary || \"\u2014\" }}\n </span>\n </ng-template>\n\n <ng-template #daysCol let-row>\n <span class=\"text-sm text-gray-700\">{{ formatDays(row) }}</span>\n </ng-template>\n </mt-table>\n </div>\n</ng-container>\n", styles: [":host{display:block;min-width:0}:host ::ng-deep mt-table .p-datatable-table{min-width:72rem}:host ::ng-deep mt-table .p-datatable-header>div,:host ::ng-deep mt-table .p-datatable-header>div>div{gap:.75rem;min-width:0;flex-wrap:wrap}:host ::ng-deep mt-table .p-datatable-header mt-tabs{display:block;max-width:100%;overflow-x:auto;padding-bottom:.25rem}:host ::ng-deep mt-table .p-datatable-header mt-text-field{display:block;flex:1 1 16rem;min-width:min(100%,16rem)}:host ::ng-deep mt-table .p-datatable-header .p-inputtext,:host ::ng-deep mt-table .p-datatable-header .p-inputwrapper,:host ::ng-deep mt-table .p-datatable-header .p-selectbutton{width:100%;max-width:100%}@media(max-width:1024px){:host ::ng-deep mt-table .p-datatable-header>div{flex-direction:column;align-items:stretch}:host ::ng-deep mt-table .p-datatable-header>div>div{width:100%;justify-content:flex-start}:host ::ng-deep mt-table .p-datatable-header mt-text-field{flex-basis:100%;min-width:0}}@media(max-width:768px){:host ::ng-deep mt-table .p-datatable-table{min-width:64rem}}\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: Breadcrumb, selector: "mt-breadcrumb", inputs: ["items", "styleClass"], outputs: ["onItemClick"] }, { 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: "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 });
1743
2100
  }
1744
2101
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsList, decorators: [{
1745
2102
  type: Component,
@@ -1750,7 +2107,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1750
2107
  Table,
1751
2108
  TranslocoDirective,
1752
2109
  DelegationStatusChip,
1753
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col h-full gap-2 p-4\">\n @if (showBreadcrumb()) {\n <mt-breadcrumb [items]=\"breadcrumbItems()\"></mt-breadcrumb>\n }\n\n <div class=\"flex items-center gap-2 border-b border-gray-200\">\n @for (tab of tabs(); track tab.value) {\n <button\n type=\"button\"\n class=\"px-4 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\n [class.border-primary]=\"activeTab() === tab.value\"\n [class.text-primary]=\"activeTab() === tab.value\"\n [class.border-transparent]=\"activeTab() !== tab.value\"\n [class.text-gray-500]=\"activeTab() !== tab.value\"\n (click)=\"switchTab(tab.value)\"\n >\n {{ tab.label }}\n </button>\n }\n </div>\n\n <mt-table\n [data]=\"rows()\"\n [columns]=\"tableColumns()\"\n [actions]=\"tableActions()\"\n [rowActions]=\"rowActions()\"\n [freezeActions]=\"true\"\n [loading]=\"isLoading()\"\n >\n <ng-template #statusCol let-row>\n <mt-delegation-status-chip\n [status]=\"row.effectiveStatus\"\n ></mt-delegation-status-chip>\n </ng-template>\n\n <ng-template #userCol let-row>\n @let party = activeTab() === \"my\" ? row.delegatedUser : row.delegator;\n <div class=\"flex items-center gap-2\">\n <mt-avatar icon=\"user.user-01\" styleClass=\"w-8! h-8!\"></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <span class=\"text-sm text-gray-900 truncate\">\n {{ party?.displayName }}\n </span>\n @if (party?.email) {\n <span class=\"text-xs text-gray-500 truncate\">\n {{ party.email }}\n </span>\n }\n </div>\n </div>\n </ng-template>\n\n <ng-template #scopeCol let-row>\n <span class=\"text-sm text-gray-700\" [title]=\"row.scopeSummary\">\n {{ row.scopeSummary || \"\u2014\" }}\n </span>\n </ng-template>\n\n <ng-template #daysCol let-row>\n <span class=\"text-sm text-gray-700\">{{ formatDays(row) }}</span>\n </ng-template>\n </mt-table>\n </div>\n</ng-container>\n", styles: [":host{display:block;min-width:0}:host ::ng-deep mt-table .p-datatable-table{min-width:72rem}:host ::ng-deep mt-table .p-datatable-header>div,:host ::ng-deep mt-table .p-datatable-header>div>div{gap:.75rem;min-width:0;flex-wrap:wrap}:host ::ng-deep mt-table .p-datatable-header mt-tabs{display:block;max-width:100%;overflow-x:auto;padding-bottom:.25rem}:host ::ng-deep mt-table .p-datatable-header mt-text-field{display:block;flex:1 1 16rem;min-width:min(100%,16rem)}:host ::ng-deep mt-table .p-datatable-header .p-inputtext,:host ::ng-deep mt-table .p-datatable-header .p-inputwrapper,:host ::ng-deep mt-table .p-datatable-header .p-selectbutton{width:100%;max-width:100%}@media(max-width:1024px){:host ::ng-deep mt-table .p-datatable-header>div{flex-direction:column;align-items:stretch}:host ::ng-deep mt-table .p-datatable-header>div>div{width:100%;justify-content:flex-start}:host ::ng-deep mt-table .p-datatable-header mt-text-field{flex-basis:100%;min-width:0}}@media(max-width:768px){:host ::ng-deep mt-table .p-datatable-table{min-width:64rem}}\n"] }]
2110
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col h-full gap-2 p-4\">\n @if (showBreadcrumb()) {\n <mt-breadcrumb [items]=\"breadcrumbItems()\"></mt-breadcrumb>\n }\n\n <div class=\"flex items-center gap-2 border-b border-gray-200\">\n @for (tab of tabs(); track tab.value) {\n <button\n type=\"button\"\n class=\"px-4 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\n [class.border-primary]=\"activeTab() === tab.value\"\n [class.text-primary]=\"activeTab() === tab.value\"\n [class.border-transparent]=\"activeTab() !== tab.value\"\n [class.text-gray-500]=\"activeTab() !== tab.value\"\n (click)=\"switchTab(tab.value)\"\n >\n {{ tab.label }}\n </button>\n }\n </div>\n\n <mt-table\n [data]=\"rows()\"\n [columns]=\"tableColumns()\"\n [actions]=\"tableActions()\"\n [rowActions]=\"rowActions()\"\n [freezeActions]=\"true\"\n [loading]=\"isLoading()\"\n >\n <ng-template #statusCol let-row>\n <mt-delegation-status-chip\n [status]=\"row.effectiveStatus\"\n ></mt-delegation-status-chip>\n </ng-template>\n\n <ng-template #userCol let-row>\n @let party = activeTab() === \"my\" ? row.delegatedUser : row.delegator;\n <div class=\"flex items-center gap-2\">\n <mt-avatar icon=\"user.user-01\" styleClass=\"w-8! h-8!\"></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <span class=\"text-sm text-gray-900 truncate\">\n {{ party?.displayName }}\n </span>\n @if (party?.email) {\n <span class=\"text-xs text-gray-500 truncate\">\n {{ party.email }}\n </span>\n }\n </div>\n </div>\n </ng-template>\n\n <ng-template #scopeCol let-row>\n @let summary = formatScopeSummary(row.scopeSummary);\n <span class=\"text-sm text-gray-700\" [title]=\"summary\">\n {{ summary || \"\u2014\" }}\n </span>\n </ng-template>\n\n <ng-template #daysCol let-row>\n <span class=\"text-sm text-gray-700\">{{ formatDays(row) }}</span>\n </ng-template>\n </mt-table>\n </div>\n</ng-container>\n", styles: [":host{display:block;min-width:0}:host ::ng-deep mt-table .p-datatable-table{min-width:72rem}:host ::ng-deep mt-table .p-datatable-header>div,:host ::ng-deep mt-table .p-datatable-header>div>div{gap:.75rem;min-width:0;flex-wrap:wrap}:host ::ng-deep mt-table .p-datatable-header mt-tabs{display:block;max-width:100%;overflow-x:auto;padding-bottom:.25rem}:host ::ng-deep mt-table .p-datatable-header mt-text-field{display:block;flex:1 1 16rem;min-width:min(100%,16rem)}:host ::ng-deep mt-table .p-datatable-header .p-inputtext,:host ::ng-deep mt-table .p-datatable-header .p-inputwrapper,:host ::ng-deep mt-table .p-datatable-header .p-selectbutton{width:100%;max-width:100%}@media(max-width:1024px){:host ::ng-deep mt-table .p-datatable-header>div{flex-direction:column;align-items:stretch}:host ::ng-deep mt-table .p-datatable-header>div>div{width:100%;justify-content:flex-start}:host ::ng-deep mt-table .p-datatable-header mt-text-field{flex-basis:100%;min-width:0}}@media(max-width:768px){:host ::ng-deep mt-table .p-datatable-table{min-width:64rem}}\n"] }]
1754
2111
  }], propDecorators: { showBreadcrumb: [{ type: i0.Input, args: [{ isSignal: true, alias: "showBreadcrumb", required: false }] }], adminMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "adminMode", required: false }] }], statusCol: [{ type: i0.ViewChild, args: ['statusCol', { isSignal: true }] }], userCol: [{ type: i0.ViewChild, args: ['userCol', { isSignal: true }] }], scopeCol: [{ type: i0.ViewChild, args: ['scopeCol', { isSignal: true }] }], daysCol: [{ type: i0.ViewChild, args: ['daysCol', { isSignal: true }] }] } });
1755
2112
 
1756
2113
  /**