@masterteam/delegations 0.0.53 → 0.0.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import * as i1 from '@angular/common';
2
2
  import { DOCUMENT, CommonModule, Location } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
- import { InjectionToken, makeEnvironmentProviders, inject, Injectable, signal, computed, input, ChangeDetectionStrategy, Component, output, viewChild, effect, model, DestroyRef, untracked, booleanAttribute, numberAttribute, linkedSignal } from '@angular/core';
4
+ import { InjectionToken, makeEnvironmentProviders, inject, Injectable, signal, computed, input, ChangeDetectionStrategy, Component, output, DestroyRef, viewChild, effect, model, untracked, booleanAttribute, numberAttribute, linkedSignal } from '@angular/core';
5
5
  import { TranslocoService, TranslocoDirective } from '@jsverse/transloco';
6
6
  import { Avatar } from '@masterteam/components/avatar';
7
7
  import { ModalService } from '@masterteam/components/modal';
@@ -198,6 +198,9 @@ var __decorate$1 = (this && this.__decorate) || function (decorators, target, ke
198
198
  return c > 3 && r && Object.defineProperty(target, key, r), r;
199
199
  };
200
200
  const BASE$1 = 'identity/delegations';
201
+ /** `setTimeout` stores its delay in a signed 32-bit int; anything larger wraps
202
+ * around and fires immediately. Clamp so a long-lived session never self-ends. */
203
+ const MAX_TIMEOUT_MS = 2 ** 31 - 1;
201
204
  // Action shells matching gateway-auth type strings - loose coupling, matched by
202
205
  // `type` only so this package does not depend on @masterteam/gateway-auth.
203
206
  class GatewayLoginSuccessShell {
@@ -244,6 +247,14 @@ let DelegationSessionState = class DelegationSessionState {
244
247
  vault = inject(DelegationTokenVault);
245
248
  runtime = inject(DELEGATION_RUNTIME_CONFIG);
246
249
  expiryTimer = null;
250
+ /**
251
+ * Bumped whenever candidates are cleared (logout / user / tenant / app
252
+ * switch). A `LoadDelegationCandidates` response that was already in flight
253
+ * when the actor went away resolves against a stale generation and is
254
+ * dropped, so the previous user's delegators can never repopulate the menu —
255
+ * or open the availability prompt — after sign-out.
256
+ */
257
+ candidatesGeneration = 0;
247
258
  constructor() {
248
259
  this.actions$
249
260
  .pipe(ofActionSuccessful(GatewayLoginSuccessShell))
@@ -289,6 +300,7 @@ let DelegationSessionState = class DelegationSessionState {
289
300
  // Actions
290
301
  // ---------------------------------------------------------------------------
291
302
  loadCandidates(ctx, { page, pageSize, append }) {
303
+ const generation = this.candidatesGeneration;
292
304
  const req$ = this.http.get(`${BASE$1}/user/activedelegations`, {
293
305
  params: new HttpParams().set('page', page).set('pageSize', pageSize),
294
306
  });
@@ -297,6 +309,10 @@ let DelegationSessionState = class DelegationSessionState {
297
309
  key: DelegationSessionActionKey.LoadCandidates,
298
310
  request$: req$,
299
311
  onSuccess: (response) => {
312
+ // The actor changed while this request was in flight — discard it.
313
+ if (generation !== this.candidatesGeneration) {
314
+ return;
315
+ }
300
316
  const next = (response.data?.items ?? []).filter(canStart);
301
317
  const current = append ? ctx.getState().candidates : [];
302
318
  const candidates = [...current, ...next].filter((row, index, rows) => rows.findIndex((candidate) => candidate.delegationId === row.delegationId) === index);
@@ -347,18 +363,27 @@ let DelegationSessionState = class DelegationSessionState {
347
363
  end(ctx, { reason }) {
348
364
  // Client-local only — there is no server end-session endpoint (doc 05).
349
365
  const previous = ctx.getState().active;
366
+ const clearCandidates = shouldClearCandidates(reason);
367
+ // Logout / user / tenant / app switch must always drop the candidate list,
368
+ // even when no delegated session was ever started. Leaving it behind kept
369
+ // the previous actor's delegators in memory across sign-out, which is both
370
+ // a leak into the next session and the reason the availability prompt could
371
+ // reopen over the login page.
372
+ if (clearCandidates) {
373
+ this.candidatesGeneration++;
374
+ ctx.patchState({
375
+ candidates: [],
376
+ candidatesTotalCount: 0,
377
+ candidatesPage: 1,
378
+ });
379
+ }
350
380
  if (!previous && !this.vault.token()) {
351
381
  return EMPTY;
352
382
  }
353
383
  this.clearExpiry();
354
384
  this.vault.clear();
355
- ctx.patchState({
356
- active: null,
357
- ...(shouldClearCandidates(reason)
358
- ? { candidates: [], candidatesTotalCount: 0, candidatesPage: 1 }
359
- : {}),
360
- });
361
- return from(Promise.resolve(this.runtime.onContextChanged({ previous, current: null, reason }))).pipe(switchMap(() => shouldClearCandidates(reason)
385
+ ctx.patchState({ active: null });
386
+ return from(Promise.resolve(this.runtime.onContextChanged({ previous, current: null, reason }))).pipe(switchMap(() => clearCandidates
362
387
  ? EMPTY
363
388
  : this.store.dispatch(new LoadDelegationCandidates())));
364
389
  }
@@ -368,7 +393,15 @@ let DelegationSessionState = class DelegationSessionState {
368
393
  }
369
394
  scheduleExpiry(active) {
370
395
  this.clearExpiry();
371
- const delay = Math.max(0, Date.parse(active.expiresAtUtc) - Date.now());
396
+ const expiresAt = Date.parse(active.expiresAtUtc ?? '');
397
+ // A missing or unparseable expiry used to produce a NaN delay, which
398
+ // `setTimeout` coerces to 0 — the session ended on the very next tick and
399
+ // the user silently fell back to their own identity. Fail open instead and
400
+ // let the backend's `delegationReasonCode` end the session (doc 05).
401
+ if (!Number.isFinite(expiresAt)) {
402
+ return;
403
+ }
404
+ const delay = Math.min(Math.max(0, expiresAt - Date.now()), MAX_TIMEOUT_MS);
372
405
  this.expiryTimer = setTimeout(() => this.store.dispatch(new EndDelegationSession('Expired')), delay);
373
406
  }
374
407
  clearExpiry() {
@@ -520,6 +553,8 @@ function toDelegationUserEntity(party, options = {}) {
520
553
  class StartSessionDialog {
521
554
  delegation = input.required(...(ngDevMode ? [{ debugName: "delegation" }] : /* istanbul ignore next */ []));
522
555
  intent = input('start', ...(ngDevMode ? [{ debugName: "intent" }] : /* istanbul ignore next */ []));
556
+ /** Supplies the shared `mt-modal-content` / `mt-modal-footer` shell classes. */
557
+ modal = inject(ModalService);
523
558
  ref = inject(ModalRef);
524
559
  facade = inject(DelegationSessionFacade);
525
560
  toast = inject(ToastService);
@@ -556,11 +591,11 @@ class StartSessionDialog {
556
591
  this.ref.close(false);
557
592
  }
558
593
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: StartSessionDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
559
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.8", type: StartSessionDialog, isStandalone: true, selector: "mt-start-session-dialog", inputs: { delegation: { classPropertyName: "delegation", publicName: "delegation", isSignal: true, isRequired: true, transformFunction: null }, intent: { classPropertyName: "intent", publicName: "intent", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col gap-4 p-2\">\r\n <div class=\"flex items-start gap-3\">\r\n <div class=\"flex flex-col min-w-0\">\r\n <mt-entity-preview\r\n [data]=\"userEntity(delegation().delegator)\"\r\n ></mt-entity-preview>\r\n </div>\r\n </div>\r\n\r\n <p class=\"text-sm text-surface-600 leading-relaxed\">\r\n {{ t(bodyKey()) }}\r\n </p>\r\n\r\n <div class=\"flex justify-end gap-2 pt-2 border-t border-surface-200\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.cancel')\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"cancel()\"\r\n ></mt-button>\r\n <mt-button\r\n color=\"primary\"\r\n icon=\"user.users-check\"\r\n [label]=\"t(confirmLabelKey())\"\r\n [loading]=\"isBusy()\"\r\n (click)=\"confirm()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
594
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.8", type: StartSessionDialog, isStandalone: true, selector: "mt-start-session-dialog", inputs: { delegation: { classPropertyName: "delegation", publicName: "delegation", isSignal: true, isRequired: true, transformFunction: null }, intent: { classPropertyName: "intent", publicName: "intent", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\n >\n <mt-entity-preview\n [data]=\"userEntity(delegation().delegator)\"\n ></mt-entity-preview>\n\n <p class=\"text-sm leading-relaxed text-surface-600\">\n {{ t(bodyKey()) }}\n </p>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n variant=\"outlined\"\n color=\"secondary\"\n [label]=\"t('delegations.common.cancel')\"\n [disabled]=\"isBusy()\"\n (click)=\"cancel()\"\n ></mt-button>\n <mt-button\n color=\"primary\"\n icon=\"user.users-check\"\n [label]=\"t(confirmLabelKey())\"\n [loading]=\"isBusy()\"\n (click)=\"confirm()\"\n ></mt-button>\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
560
595
  }
561
596
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: StartSessionDialog, decorators: [{
562
597
  type: Component,
563
- args: [{ selector: 'mt-start-session-dialog', imports: [CommonModule, Button, EntityPreview, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col gap-4 p-2\">\r\n <div class=\"flex items-start gap-3\">\r\n <div class=\"flex flex-col min-w-0\">\r\n <mt-entity-preview\r\n [data]=\"userEntity(delegation().delegator)\"\r\n ></mt-entity-preview>\r\n </div>\r\n </div>\r\n\r\n <p class=\"text-sm text-surface-600 leading-relaxed\">\r\n {{ t(bodyKey()) }}\r\n </p>\r\n\r\n <div class=\"flex justify-end gap-2 pt-2 border-t border-surface-200\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.cancel')\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"cancel()\"\r\n ></mt-button>\r\n <mt-button\r\n color=\"primary\"\r\n icon=\"user.users-check\"\r\n [label]=\"t(confirmLabelKey())\"\r\n [loading]=\"isBusy()\"\r\n (click)=\"confirm()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n" }]
598
+ args: [{ selector: 'mt-start-session-dialog', imports: [CommonModule, Button, EntityPreview, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\n >\n <mt-entity-preview\n [data]=\"userEntity(delegation().delegator)\"\n ></mt-entity-preview>\n\n <p class=\"text-sm leading-relaxed text-surface-600\">\n {{ t(bodyKey()) }}\n </p>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n variant=\"outlined\"\n color=\"secondary\"\n [label]=\"t('delegations.common.cancel')\"\n [disabled]=\"isBusy()\"\n (click)=\"cancel()\"\n ></mt-button>\n <mt-button\n color=\"primary\"\n icon=\"user.users-check\"\n [label]=\"t(confirmLabelKey())\"\n [loading]=\"isBusy()\"\n (click)=\"confirm()\"\n ></mt-button>\n </div>\n</ng-container>\n" }]
564
599
  }], propDecorators: { delegation: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegation", required: true }] }], intent: [{ type: i0.Input, args: [{ isSignal: true, alias: "intent", required: false }] }] } });
565
600
 
566
601
  /**
@@ -569,6 +604,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
569
604
  */
570
605
  class DelegationCandidatesPromptDialog {
571
606
  candidates = input.required(...(ngDevMode ? [{ debugName: "candidates" }] : /* istanbul ignore next */ []));
607
+ /** Supplies the shared `mt-modal-content` / `mt-modal-footer` shell classes. */
608
+ modal = inject(ModalService);
572
609
  ref = inject(ModalRef);
573
610
  userEntity(party) {
574
611
  return toDelegationUserEntity(party, { showEmail: false });
@@ -580,11 +617,11 @@ class DelegationCandidatesPromptDialog {
580
617
  this.ref.close(null);
581
618
  }
582
619
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationCandidatesPromptDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
583
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationCandidatesPromptDialog, isStandalone: true, selector: "mt-delegation-candidates-prompt-dialog", inputs: { candidates: { classPropertyName: "candidates", publicName: "candidates", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col gap-4 p-2\">\r\n <p class=\"text-sm leading-relaxed text-surface-600\">\r\n {{ t(\"delegations.session.promptBody\") }}\r\n </p>\r\n\r\n <div class=\"flex max-h-80 flex-col gap-2 overflow-y-auto pr-1\">\r\n @for (cand of candidates(); track cand.delegationId) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-center justify-between gap-3 rounded-2xl border border-solid border-surface-200 bg-surface-0 px-3 py-3 text-start transition-colors hover:border-primary-300 hover:bg-primary-50/50 focus-visible:border-primary-400 focus-visible:outline-none\"\r\n [attr.aria-label]=\"\r\n t('delegations.session.promptStartAria', {\r\n delegatorName: cand.delegator.displayName,\r\n })\r\n \"\r\n (click)=\"select(cand)\"\r\n >\r\n <span class=\"flex min-w-0 items-center gap-3\">\r\n <mt-entity-preview\r\n [data]=\"userEntity(cand.delegator)\"\r\n ></mt-entity-preview>\r\n </span>\r\n <span\r\n class=\"inline-flex shrink-0 items-center gap-1.5 text-sm font-medium text-primary\"\r\n >\r\n <span class=\"hidden sm:inline\">\r\n {{ t(\"delegations.action.startSession\") }}\r\n </span>\r\n <mt-icon icon=\"arrow.arrow-right\" class=\"text-base\"></mt-icon>\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n\r\n <div class=\"flex justify-end border-t border-surface-200 pt-3\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.close')\"\r\n (click)=\"close()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
620
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationCandidatesPromptDialog, isStandalone: true, selector: "mt-delegation-candidates-prompt-dialog", inputs: { candidates: { classPropertyName: "candidates", publicName: "candidates", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\n >\n <p class=\"text-sm leading-relaxed text-surface-600\">\n {{ t(\"delegations.session.promptBody\") }}\n </p>\n\n <div class=\"flex flex-col gap-2\">\n @for (cand of candidates(); track cand.delegationId) {\n <button\n type=\"button\"\n class=\"flex w-full cursor-pointer items-center justify-between gap-3 rounded-lg border border-solid border-surface-200 bg-surface-0 px-3 py-2.5 text-start transition-colors hover:border-primary-300 hover:bg-primary-50/50 focus-visible:border-primary-400 focus-visible:outline-none\"\n [attr.aria-label]=\"\n t('delegations.session.promptStartAria', {\n delegatorName: cand.delegator.displayName,\n })\n \"\n (click)=\"select(cand)\"\n >\n <span class=\"flex min-w-0 items-center gap-3\">\n <mt-entity-preview\n [data]=\"userEntity(cand.delegator)\"\n ></mt-entity-preview>\n </span>\n <span\n class=\"inline-flex shrink-0 items-center gap-1.5 text-sm font-medium text-primary\"\n >\n <span class=\"hidden sm:inline\">\n {{ t(\"delegations.action.startSession\") }}\n </span>\n <mt-icon icon=\"arrow.arrow-right\" class=\"text-base\"></mt-icon>\n </span>\n </button>\n }\n </div>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n variant=\"outlined\"\n color=\"secondary\"\n [label]=\"t('delegations.common.close')\"\n (click)=\"close()\"\n ></mt-button>\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
584
621
  }
585
622
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationCandidatesPromptDialog, decorators: [{
586
623
  type: Component,
587
- args: [{ selector: 'mt-delegation-candidates-prompt-dialog', imports: [CommonModule, Button, EntityPreview, Icon, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col gap-4 p-2\">\r\n <p class=\"text-sm leading-relaxed text-surface-600\">\r\n {{ t(\"delegations.session.promptBody\") }}\r\n </p>\r\n\r\n <div class=\"flex max-h-80 flex-col gap-2 overflow-y-auto pr-1\">\r\n @for (cand of candidates(); track cand.delegationId) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-center justify-between gap-3 rounded-2xl border border-solid border-surface-200 bg-surface-0 px-3 py-3 text-start transition-colors hover:border-primary-300 hover:bg-primary-50/50 focus-visible:border-primary-400 focus-visible:outline-none\"\r\n [attr.aria-label]=\"\r\n t('delegations.session.promptStartAria', {\r\n delegatorName: cand.delegator.displayName,\r\n })\r\n \"\r\n (click)=\"select(cand)\"\r\n >\r\n <span class=\"flex min-w-0 items-center gap-3\">\r\n <mt-entity-preview\r\n [data]=\"userEntity(cand.delegator)\"\r\n ></mt-entity-preview>\r\n </span>\r\n <span\r\n class=\"inline-flex shrink-0 items-center gap-1.5 text-sm font-medium text-primary\"\r\n >\r\n <span class=\"hidden sm:inline\">\r\n {{ t(\"delegations.action.startSession\") }}\r\n </span>\r\n <mt-icon icon=\"arrow.arrow-right\" class=\"text-base\"></mt-icon>\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n\r\n <div class=\"flex justify-end border-t border-surface-200 pt-3\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.close')\"\r\n (click)=\"close()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n" }]
624
+ args: [{ selector: 'mt-delegation-candidates-prompt-dialog', imports: [CommonModule, Button, EntityPreview, Icon, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\n >\n <p class=\"text-sm leading-relaxed text-surface-600\">\n {{ t(\"delegations.session.promptBody\") }}\n </p>\n\n <div class=\"flex flex-col gap-2\">\n @for (cand of candidates(); track cand.delegationId) {\n <button\n type=\"button\"\n class=\"flex w-full cursor-pointer items-center justify-between gap-3 rounded-lg border border-solid border-surface-200 bg-surface-0 px-3 py-2.5 text-start transition-colors hover:border-primary-300 hover:bg-primary-50/50 focus-visible:border-primary-400 focus-visible:outline-none\"\n [attr.aria-label]=\"\n t('delegations.session.promptStartAria', {\n delegatorName: cand.delegator.displayName,\n })\n \"\n (click)=\"select(cand)\"\n >\n <span class=\"flex min-w-0 items-center gap-3\">\n <mt-entity-preview\n [data]=\"userEntity(cand.delegator)\"\n ></mt-entity-preview>\n </span>\n <span\n class=\"inline-flex shrink-0 items-center gap-1.5 text-sm font-medium text-primary\"\n >\n <span class=\"hidden sm:inline\">\n {{ t(\"delegations.action.startSession\") }}\n </span>\n <mt-icon icon=\"arrow.arrow-right\" class=\"text-base\"></mt-icon>\n </span>\n </button>\n }\n </div>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n variant=\"outlined\"\n color=\"secondary\"\n [label]=\"t('delegations.common.close')\"\n (click)=\"close()\"\n ></mt-button>\n </div>\n</ng-container>\n" }]
588
625
  }], propDecorators: { candidates: [{ type: i0.Input, args: [{ isSignal: true, alias: "candidates", required: true }] }] } });
589
626
 
590
627
  const STATUS_VISUAL = {
@@ -761,6 +798,7 @@ class TopbarDelegationMenu {
761
798
  facade = inject(DelegationSessionFacade);
762
799
  modal = inject(ModalService);
763
800
  transloco = inject(TranslocoService);
801
+ destroyRef = inject(DestroyRef);
764
802
  popover = viewChild('popover', ...(ngDevMode ? [{ debugName: "popover" }] : /* istanbul ignore next */ []));
765
803
  active = this.facade.active;
766
804
  candidates = this.facade.candidates;
@@ -771,6 +809,14 @@ class TopbarDelegationMenu {
771
809
  promptPending = false;
772
810
  promptRefreshQueued = false;
773
811
  promptFlowOpen = false;
812
+ destroyed = false;
813
+ /**
814
+ * Dialogs opened through `ModalService` live on `document.body`, so they
815
+ * outlive this component. Held so the whole prompt flow can be torn down when
816
+ * the authenticated shell goes away (sign-out) instead of stranding an
817
+ * "act on behalf of" dialog over the login page.
818
+ */
819
+ promptRefs = [];
774
820
  mode = computed(() => {
775
821
  if (this.active())
776
822
  return 'active';
@@ -789,6 +835,17 @@ class TopbarDelegationMenu {
789
835
  }
790
836
  this.checkPromptCandidates(candidates);
791
837
  });
838
+ // Candidates emptying mid-flow means the actor is gone (sign-out, user /
839
+ // tenant / app switch). Nothing in the prompt is valid any more.
840
+ effect(() => {
841
+ if (!this.candidates().length) {
842
+ this.closePromptFlow();
843
+ }
844
+ });
845
+ this.destroyRef.onDestroy(() => {
846
+ this.destroyed = true;
847
+ this.closePromptFlow();
848
+ });
792
849
  }
793
850
  ngOnInit() {
794
851
  this.facade.loadCandidates();
@@ -819,7 +876,13 @@ class TopbarDelegationMenu {
819
876
  void this.facade
820
877
  .claimPromptCandidates(candidates)
821
878
  .then((unseen) => {
822
- if (unseen.length && !this.active()) {
879
+ // `claimUnseen` is async (SubtleCrypto). The user can sign out inside
880
+ // that window, which destroys this controller and empties the candidate
881
+ // list — opening then would drop the dialog on the login page.
882
+ if (unseen.length &&
883
+ !this.destroyed &&
884
+ !this.active() &&
885
+ this.candidates().length) {
823
886
  this.openCandidatesPrompt(unseen);
824
887
  }
825
888
  })
@@ -828,7 +891,10 @@ class TopbarDelegationMenu {
828
891
  if (this.promptRefreshQueued) {
829
892
  this.promptRefreshQueued = false;
830
893
  const latest = this.candidates();
831
- if (this.promptOnCandidates() && !this.active() && latest.length) {
894
+ if (!this.destroyed &&
895
+ this.promptOnCandidates() &&
896
+ !this.active() &&
897
+ latest.length) {
832
898
  this.checkPromptCandidates(latest);
833
899
  }
834
900
  }
@@ -854,22 +920,46 @@ class TopbarDelegationMenu {
854
920
  dismissible: true,
855
921
  inputValues: { candidates },
856
922
  });
923
+ this.trackPromptRef(ref);
857
924
  ref.onClose.subscribe((row) => {
858
- if (row) {
859
- this.openConfirm(row, 'start').onClose.subscribe(() => this.finishPromptFlow());
925
+ if (row && !this.destroyed) {
926
+ this.trackPromptRef(this.openConfirm(row, 'start')).onClose.subscribe(() => this.finishPromptFlow());
860
927
  }
861
928
  else {
862
929
  this.finishPromptFlow();
863
930
  }
864
931
  });
865
932
  }
933
+ trackPromptRef(ref) {
934
+ this.promptRefs.push(ref);
935
+ return ref;
936
+ }
937
+ /** Tears down anything the prompt flow left on `document.body`. */
938
+ closePromptFlow() {
939
+ const refs = this.promptRefs;
940
+ this.promptRefs = [];
941
+ this.promptFlowOpen = false;
942
+ this.promptRefreshQueued = false;
943
+ for (const ref of refs) {
944
+ try {
945
+ ref.close();
946
+ }
947
+ catch {
948
+ // best-effort: a ref already closed by the user throws nothing useful
949
+ }
950
+ }
951
+ }
866
952
  finishPromptFlow() {
867
953
  this.promptFlowOpen = false;
954
+ this.promptRefs = [];
868
955
  if (!this.promptRefreshQueued)
869
956
  return;
870
957
  this.promptRefreshQueued = false;
871
958
  const latest = this.candidates();
872
- if (this.promptOnCandidates() && !this.active() && latest.length) {
959
+ if (!this.destroyed &&
960
+ this.promptOnCandidates() &&
961
+ !this.active() &&
962
+ latest.length) {
873
963
  this.checkPromptCandidates(latest);
874
964
  }
875
965
  }
@@ -1604,6 +1694,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1604
1694
  */
1605
1695
  class RejectDelegationDialog {
1606
1696
  delegation = input.required(...(ngDevMode ? [{ debugName: "delegation" }] : /* istanbul ignore next */ []));
1697
+ /** Supplies the shared `mt-modal-content` / `mt-modal-footer` shell classes. */
1698
+ modal = inject(ModalService);
1607
1699
  ref = inject(ModalRef);
1608
1700
  facade = inject(DelegationsFacade);
1609
1701
  reason = signal('', ...(ngDevMode ? [{ debugName: "reason" }] : /* istanbul ignore next */ []));
@@ -1637,11 +1729,11 @@ class RejectDelegationDialog {
1637
1729
  this.ref.close(false);
1638
1730
  }
1639
1731
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: RejectDelegationDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
1640
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: RejectDelegationDialog, isStandalone: true, selector: "mt-reject-delegation-dialog", inputs: { delegation: { classPropertyName: "delegation", publicName: "delegation", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col gap-4 p-2\">\r\n <div class=\"flex flex-col gap-1\">\r\n <div class=\"text-sm text-surface-700\">\r\n {{ t(\"delegations.confirm.reject\") }}\r\n </div>\r\n <mt-entity-preview\r\n [data]=\"userEntity(delegation().delegator)\"\r\n ></mt-entity-preview>\r\n </div>\r\n\r\n <div class=\"flex flex-col gap-1\">\r\n <label\r\n class=\"text-sm font-medium text-surface-800\"\r\n for=\"mt-reject-reason\"\r\n >\r\n {{ t(\"delegations.form.rejectionReason\") }}\r\n <span class=\"text-red-600\">*</span>\r\n </label>\r\n <textarea\r\n id=\"mt-reject-reason\"\r\n rows=\"3\"\r\n class=\"w-full rounded-md border border-surface-300 px-3 py-2 text-sm focus:border-primary focus:outline-none\"\r\n [class.border-red-400]=\"submitted() && !isValid()\"\r\n [value]=\"reason()\"\r\n (input)=\"onReasonInput($event)\"\r\n [attr.aria-invalid]=\"submitted() && !isValid()\"\r\n [placeholder]=\"t('delegations.form.rejectionReason')\"\r\n ></textarea>\r\n @if (submitted() && !isValid()) {\r\n <span class=\"text-xs text-red-600\">\r\n {{ t(\"delegations.form.rejectReasonRequired\") }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <div class=\"flex justify-end gap-2 pt-2 border-t border-surface-200\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.cancel')\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"cancel()\"\r\n ></mt-button>\r\n <mt-button\r\n color=\"danger\"\r\n icon=\"general.x-close\"\r\n [label]=\"t('delegations.action.reject')\"\r\n [loading]=\"isBusy()\"\r\n [disabled]=\"!isValid()\"\r\n (click)=\"confirm()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1732
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: RejectDelegationDialog, isStandalone: true, selector: "mt-reject-delegation-dialog", inputs: { delegation: { classPropertyName: "delegation", publicName: "delegation", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\n >\n <div class=\"flex flex-col gap-1\">\n <div class=\"text-sm text-surface-700\">\n {{ t(\"delegations.confirm.reject\") }}\n </div>\n <mt-entity-preview\n [data]=\"userEntity(delegation().delegator)\"\n ></mt-entity-preview>\n </div>\n\n <div class=\"flex flex-col gap-1\">\n <label\n class=\"text-sm font-medium text-surface-800\"\n for=\"mt-reject-reason\"\n >\n {{ t(\"delegations.form.rejectionReason\") }}\n <span class=\"text-red-600\">*</span>\n </label>\n <textarea\n id=\"mt-reject-reason\"\n rows=\"3\"\n class=\"w-full rounded-md border border-surface-300 px-3 py-2 text-sm focus:border-primary focus:outline-none\"\n [class.border-red-400]=\"submitted() && !isValid()\"\n [value]=\"reason()\"\n (input)=\"onReasonInput($event)\"\n [attr.aria-invalid]=\"submitted() && !isValid()\"\n [placeholder]=\"t('delegations.form.rejectionReason')\"\n ></textarea>\n @if (submitted() && !isValid()) {\n <span class=\"text-xs text-red-600\">\n {{ t(\"delegations.form.rejectReasonRequired\") }}\n </span>\n }\n </div>\n </div>\n\n <div [class]=\"modal.footerClass\">\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=\"danger\"\n icon=\"general.x-close\"\n [label]=\"t('delegations.action.reject')\"\n [loading]=\"isBusy()\"\n [disabled]=\"!isValid()\"\n (click)=\"confirm()\"\n ></mt-button>\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1641
1733
  }
1642
1734
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: RejectDelegationDialog, decorators: [{
1643
1735
  type: Component,
1644
- args: [{ selector: 'mt-reject-delegation-dialog', imports: [CommonModule, Button, EntityPreview, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col gap-4 p-2\">\r\n <div class=\"flex flex-col gap-1\">\r\n <div class=\"text-sm text-surface-700\">\r\n {{ t(\"delegations.confirm.reject\") }}\r\n </div>\r\n <mt-entity-preview\r\n [data]=\"userEntity(delegation().delegator)\"\r\n ></mt-entity-preview>\r\n </div>\r\n\r\n <div class=\"flex flex-col gap-1\">\r\n <label\r\n class=\"text-sm font-medium text-surface-800\"\r\n for=\"mt-reject-reason\"\r\n >\r\n {{ t(\"delegations.form.rejectionReason\") }}\r\n <span class=\"text-red-600\">*</span>\r\n </label>\r\n <textarea\r\n id=\"mt-reject-reason\"\r\n rows=\"3\"\r\n class=\"w-full rounded-md border border-surface-300 px-3 py-2 text-sm focus:border-primary focus:outline-none\"\r\n [class.border-red-400]=\"submitted() && !isValid()\"\r\n [value]=\"reason()\"\r\n (input)=\"onReasonInput($event)\"\r\n [attr.aria-invalid]=\"submitted() && !isValid()\"\r\n [placeholder]=\"t('delegations.form.rejectionReason')\"\r\n ></textarea>\r\n @if (submitted() && !isValid()) {\r\n <span class=\"text-xs text-red-600\">\r\n {{ t(\"delegations.form.rejectReasonRequired\") }}\r\n </span>\r\n }\r\n </div>\r\n\r\n <div class=\"flex justify-end gap-2 pt-2 border-t border-surface-200\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.cancel')\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"cancel()\"\r\n ></mt-button>\r\n <mt-button\r\n color=\"danger\"\r\n icon=\"general.x-close\"\r\n [label]=\"t('delegations.action.reject')\"\r\n [loading]=\"isBusy()\"\r\n [disabled]=\"!isValid()\"\r\n (click)=\"confirm()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n" }]
1736
+ args: [{ selector: 'mt-reject-delegation-dialog', imports: [CommonModule, Button, EntityPreview, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\n >\n <div class=\"flex flex-col gap-1\">\n <div class=\"text-sm text-surface-700\">\n {{ t(\"delegations.confirm.reject\") }}\n </div>\n <mt-entity-preview\n [data]=\"userEntity(delegation().delegator)\"\n ></mt-entity-preview>\n </div>\n\n <div class=\"flex flex-col gap-1\">\n <label\n class=\"text-sm font-medium text-surface-800\"\n for=\"mt-reject-reason\"\n >\n {{ t(\"delegations.form.rejectionReason\") }}\n <span class=\"text-red-600\">*</span>\n </label>\n <textarea\n id=\"mt-reject-reason\"\n rows=\"3\"\n class=\"w-full rounded-md border border-surface-300 px-3 py-2 text-sm focus:border-primary focus:outline-none\"\n [class.border-red-400]=\"submitted() && !isValid()\"\n [value]=\"reason()\"\n (input)=\"onReasonInput($event)\"\n [attr.aria-invalid]=\"submitted() && !isValid()\"\n [placeholder]=\"t('delegations.form.rejectionReason')\"\n ></textarea>\n @if (submitted() && !isValid()) {\n <span class=\"text-xs text-red-600\">\n {{ t(\"delegations.form.rejectReasonRequired\") }}\n </span>\n }\n </div>\n </div>\n\n <div [class]=\"modal.footerClass\">\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=\"danger\"\n icon=\"general.x-close\"\n [label]=\"t('delegations.action.reject')\"\n [loading]=\"isBusy()\"\n [disabled]=\"!isValid()\"\n (click)=\"confirm()\"\n ></mt-button>\n </div>\n</ng-container>\n" }]
1645
1737
  }], propDecorators: { delegation: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegation", required: true }] }] } });
1646
1738
 
1647
1739
  class Delegations {
@@ -2677,7 +2769,7 @@ class ScopePicker {
2677
2769
  return this.activeLang() ?? this.transloco.getActiveLang();
2678
2770
  }
2679
2771
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ScopePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
2680
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ScopePicker, isStandalone: true, selector: "mt-scope-picker", inputs: { scope: { classPropertyName: "scope", publicName: "scope", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null }, delegatorUserId: { classPropertyName: "delegatorUserId", publicName: "delegatorUserId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { scope: "scopeChange" }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <!-- Reusable tri-state checkbox box -->\r\n <ng-template #checkbox let-state=\"state\">\r\n <span\r\n class=\"flex size-[18px] shrink-0 items-center justify-center rounded-[5px] border transition-colors\"\r\n [class.border-primary-500]=\"state !== 'unchecked'\"\r\n [class.bg-primary-500]=\"state !== 'unchecked'\"\r\n [class.text-white]=\"state !== 'unchecked'\"\r\n [class.border-surface-300]=\"state === 'unchecked'\"\r\n [class.bg-surface-0]=\"state === 'unchecked'\"\r\n >\r\n @if (state === \"checked\") {\r\n <svg\r\n viewBox=\"0 0 16 16\"\r\n class=\"size-3\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n aria-hidden=\"true\"\r\n >\r\n <path\r\n d=\"M3.5 8.5L6.5 11.5L12.5 4.5\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n />\r\n </svg>\r\n } @else if (state === \"indeterminate\") {\r\n <span class=\"h-[2px] w-2.5 rounded-full bg-white\"></span>\r\n }\r\n </span>\r\n </ng-template>\r\n\r\n <div class=\"flex flex-col gap-3\">\r\n @if (isLoadingOptions()) {\r\n <mt-card [paddingless]=\"true\">\r\n <div class=\"flex flex-col gap-3 p-4\">\r\n <p-skeleton height=\"2.5rem\"></p-skeleton>\r\n @for (item of [0, 1, 2, 3, 4, 5]; track $index) {\r\n <p-skeleton height=\"2rem\"></p-skeleton>\r\n }\r\n </div>\r\n </mt-card>\r\n } @else {\r\n @if (errorOptions(); as errorMessage) {\r\n <div\r\n class=\"rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700\"\r\n >\r\n {{ errorMessage }}\r\n </div>\r\n }\r\n\r\n @if (!hasOptions() && !errorOptions()) {\r\n <mt-card [paddingless]=\"true\">\r\n <div\r\n class=\"flex min-h-72 flex-col items-center justify-center gap-4 px-6 py-8 text-center\"\r\n >\r\n <div\r\n class=\"flex size-16 items-center justify-center rounded-3xl bg-surface-50 text-primary\"\r\n >\r\n <svg\r\n viewBox=\"0 0 64 64\"\r\n class=\"size-9\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n aria-hidden=\"true\"\r\n >\r\n <rect\r\n x=\"10\"\r\n y=\"12\"\r\n width=\"44\"\r\n height=\"38\"\r\n rx=\"12\"\r\n class=\"fill-primary/10 stroke-primary/35\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M21 27H43\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M21 35H37\"\r\n class=\"stroke-primary/70\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n </svg>\r\n </div>\r\n <div class=\"space-y-1\">\r\n <p class=\"text-base font-medium text-surface-900\">\r\n {{ t(\"delegations.scope.permissionsTitle\") }}\r\n </p>\r\n <p class=\"text-sm text-surface-500\">\r\n {{ t(\"delegations.scope.noGrantableOptions\") }}\r\n </p>\r\n </div>\r\n </div>\r\n </mt-card>\r\n }\r\n\r\n @if (hasOptions()) {\r\n <mt-card [paddingless]=\"true\" class=\"overflow-hidden\">\r\n <!-- Inner view switch: Permissions / Pages & accessibility -->\r\n @if (hasAccessibility()) {\r\n <div class=\"border-b border-surface px-3 pt-2.5 pb-0\">\r\n <mt-tabs\r\n mode=\"underline\"\r\n [(active)]=\"activeScopeTab\"\r\n [options]=\"[\r\n {\r\n value: 'permissions',\r\n label: t('delegations.scope.permissionsTab'),\r\n badge: selectedCount() || null,\r\n },\r\n {\r\n value: 'accessibility',\r\n label: t('delegations.scope.accessibilityTitle'),\r\n badge: selectedAccessibilityCount() || null,\r\n },\r\n ]\"\r\n fluid\r\n ></mt-tabs>\r\n </div>\r\n }\r\n\r\n @if (activeScopeTab() === \"permissions\" || !hasAccessibility()) {\r\n <!-- Toolbar: table-style search + clear -->\r\n <div\r\n class=\"flex items-center gap-2 border-b border-surface px-3 py-2\"\r\n >\r\n <div class=\"min-w-0 flex-1\">\r\n <mt-text-field\r\n [ngModel]=\"searchTerm()\"\r\n (ngModelChange)=\"searchTerm.set($event)\"\r\n icon=\"general.search-lg\"\r\n [placeholder]=\"t('delegations.scope.searchPlaceholder')\"\r\n ></mt-text-field>\r\n </div>\r\n @if (selectedCount() > 0) {\r\n <span class=\"shrink-0 text-xs font-medium text-surface-500\">\r\n {{ selectedCount() }} {{ t(\"delegations.scope.selected\") }}\r\n </span>\r\n <mt-button\r\n variant=\"text\"\r\n size=\"small\"\r\n [label]=\"t('delegations.scope.clear')\"\r\n [disabled]=\"readonly()\"\r\n (click)=\"clearSelection()\"\r\n ></mt-button>\r\n }\r\n </div>\r\n\r\n <!-- Select-all header -->\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-center gap-2 border-b border-surface px-3 py-2 text-start hover:bg-surface-50\"\r\n role=\"checkbox\"\r\n [attr.aria-checked]=\"\r\n selectAllState() === 'indeterminate'\r\n ? 'mixed'\r\n : selectAllState() === 'checked'\r\n \"\r\n [disabled]=\"readonly()\"\r\n (click)=\"toggleAll()\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n checkbox;\r\n context: { state: selectAllState() }\r\n \"\r\n ></ng-container>\r\n <span\r\n class=\"text-xs font-semibold uppercase tracking-wide text-surface-500\"\r\n >\r\n {{ t(\"delegations.scope.selectAll\") }}\r\n </span>\r\n </button>\r\n\r\n <!-- Virtualized permission tree -->\r\n @if (visibleNodes().length > 0) {\r\n <cdk-virtual-scroll-viewport\r\n itemSize=\"40\"\r\n class=\"block h-[20rem]\"\r\n >\r\n <div\r\n *cdkVirtualFor=\"\r\n let item of visibleNodes();\r\n trackBy: trackVisible\r\n \"\r\n class=\"group flex h-10 items-center gap-2 pe-2 transition-colors hover:bg-surface-50\"\r\n [style.padding-inline-start.rem]=\"0.5 + item.depth * 1.25\"\r\n >\r\n @if (hasChildren(item.node)) {\r\n <button\r\n type=\"button\"\r\n class=\"flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-surface-400 hover:bg-surface-100 hover:text-surface-600\"\r\n [attr.aria-expanded]=\"isExpanded(item.node)\"\r\n [attr.aria-label]=\"nodeLabel(item.node)\"\r\n (click)=\"\r\n toggleExpand(item.node); $event.stopPropagation()\r\n \"\r\n >\r\n <mt-icon\r\n [icon]=\"\r\n isExpanded(item.node)\r\n ? 'arrow.chevron-down'\r\n : 'arrow.chevron-right'\r\n \"\r\n styleClass=\"text-base\"\r\n ></mt-icon>\r\n </button>\r\n } @else {\r\n <span class=\"size-6 shrink-0\"></span>\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n role=\"checkbox\"\r\n [attr.aria-checked]=\"\r\n nodeState(item.node) === 'indeterminate'\r\n ? 'mixed'\r\n : nodeState(item.node) === 'checked'\r\n \"\r\n [attr.aria-label]=\"nodeLabel(item.node)\"\r\n class=\"shrink-0\"\r\n [class.cursor-pointer]=\"!readonly()\"\r\n [disabled]=\"readonly()\"\r\n (click)=\"toggle(item.node); $event.stopPropagation()\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n checkbox;\r\n context: { state: nodeState(item.node) }\r\n \"\r\n ></ng-container>\r\n </button>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"flex min-w-0 flex-1 cursor-pointer items-center gap-2 text-start\"\r\n (click)=\"onRowClick(item.node)\"\r\n >\r\n <span\r\n class=\"truncate text-sm\"\r\n [class.font-semibold]=\"item.node.kind === 'template'\"\r\n [class.text-surface-900]=\"item.node.kind === 'template'\"\r\n [class.font-medium]=\"\r\n item.node.kind === 'level' ||\r\n item.node.kind === 'module'\r\n \"\r\n [class.text-surface-800]=\"\r\n item.node.kind === 'level' ||\r\n item.node.kind === 'module'\r\n \"\r\n [class.text-surface-600]=\"item.node.kind === 'operation'\"\r\n >\r\n {{ nodeLabel(item.node) }}\r\n </span>\r\n @if (\r\n item.node.kind === \"operation\" && item.node.isHighRisk\r\n ) {\r\n <span\r\n class=\"shrink-0 rounded-full bg-amber-100 px-2 py-0.5 text-[0.625rem] font-medium text-amber-700\"\r\n >\r\n {{ t(\"delegations.scope.highRisk\") }}\r\n </span>\r\n }\r\n </button>\r\n </div>\r\n </cdk-virtual-scroll-viewport>\r\n } @else {\r\n <p class=\"px-3 py-8 text-center text-sm text-surface-500\">\r\n {{ t(\"components.table.no-data-found\") }}\r\n </p>\r\n }\r\n } @else {\r\n <!-- App accessibility -->\r\n <div class=\"flex flex-col gap-3 p-4\">\r\n <p class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.scope.accessibilityHint\") }}\r\n </p>\r\n <div class=\"flex flex-wrap gap-2\">\r\n @for (\r\n item of appAccessibilities();\r\n track item.accessibilityKey\r\n ) {\r\n @let selected =\r\n isAccessibilitySelected(item.accessibilityKey);\r\n <button\r\n type=\"button\"\r\n class=\"inline-flex cursor-pointer items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs font-medium transition-colors\"\r\n [class.border-primary-400]=\"selected\"\r\n [class.bg-primary-50]=\"selected\"\r\n [class.text-primary-700]=\"selected\"\r\n [class.border-surface-200]=\"!selected\"\r\n [class.text-surface-600]=\"!selected\"\r\n [class.hover:border-primary-300]=\"!readonly()\"\r\n [disabled]=\"readonly()\"\r\n [attr.aria-pressed]=\"selected\"\r\n (click)=\"toggleAccessibility(item.accessibilityKey)\"\r\n >\r\n <span\r\n class=\"inline-flex size-1.5 rounded-full\"\r\n [class.bg-primary-500]=\"selected\"\r\n [class.bg-surface-300]=\"!selected\"\r\n ></span>\r\n {{ accessibilityLabel(item) }}\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </mt-card>\r\n }\r\n\r\n <!-- Compact live scope preview -->\r\n <div\r\n class=\"flex flex-col gap-1.5 rounded-lg border border-surface bg-surface-50 px-3 py-2\"\r\n >\r\n <div class=\"flex items-center gap-2 text-xs\">\r\n <span class=\"shrink-0 font-semibold text-surface-700\">\r\n {{ t(\"delegations.scope.previewTitle\") }}:\r\n </span>\r\n @if (isPreviewing()) {\r\n <p-skeleton width=\"8rem\" height=\"0.7rem\"></p-skeleton>\r\n } @else if (preview(); as p) {\r\n @if (p.isValid) {\r\n <span class=\"min-w-0 flex-1 truncate text-surface-900\">\r\n {{\r\n getPreviewSummary(p.summary) ||\r\n t(\"delegations.column.scopeSummary\")\r\n }}\r\n </span>\r\n } @else {\r\n <span class=\"font-medium text-red-700\">\r\n {{ t(\"delegations.scope.previewInvalid\") }}\r\n </span>\r\n }\r\n } @else {\r\n <span class=\"text-surface-500\">\r\n {{ t(\"delegations.scope.noSelection\") }}\r\n </span>\r\n }\r\n </div>\r\n @if (preview(); as p) {\r\n @if (p.warnings.length > 0) {\r\n <ul\r\n class=\"list-inside list-disc space-y-0.5 text-xs text-amber-700\"\r\n >\r\n @for (w of p.warnings; track $index) {\r\n <li>{{ w.message }}</li>\r\n }\r\n </ul>\r\n }\r\n @if (p.deniedItems.length > 0) {\r\n <ul class=\"list-inside list-disc space-y-0.5 text-xs text-red-700\">\r\n @for (d of p.deniedItems; track $index) {\r\n <li>\r\n {{ formatDeniedTarget(d.targetKey) }} /\r\n {{ formatDeniedOperation(d.operationKey) }} -\r\n {{ d.reasonCode }}\r\n </li>\r\n }\r\n </ul>\r\n }\r\n }\r\n </div>\r\n }\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i3.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i3.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i3.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "maxLength", "icon", "iconPosition"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i4.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2772
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ScopePicker, isStandalone: true, selector: "mt-scope-picker", inputs: { scope: { classPropertyName: "scope", publicName: "scope", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null }, delegatorUserId: { classPropertyName: "delegatorUserId", publicName: "delegatorUserId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { scope: "scopeChange" }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <!-- Reusable tri-state checkbox box -->\r\n <ng-template #checkbox let-state=\"state\">\r\n <span\r\n class=\"flex size-[18px] shrink-0 items-center justify-center rounded-[5px] border transition-colors\"\r\n [class.border-primary-500]=\"state !== 'unchecked'\"\r\n [class.bg-primary-500]=\"state !== 'unchecked'\"\r\n [class.text-white]=\"state !== 'unchecked'\"\r\n [class.border-surface-300]=\"state === 'unchecked'\"\r\n [class.bg-surface-0]=\"state === 'unchecked'\"\r\n >\r\n @if (state === \"checked\") {\r\n <svg\r\n viewBox=\"0 0 16 16\"\r\n class=\"size-3\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n aria-hidden=\"true\"\r\n >\r\n <path\r\n d=\"M3.5 8.5L6.5 11.5L12.5 4.5\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n />\r\n </svg>\r\n } @else if (state === \"indeterminate\") {\r\n <span class=\"h-[2px] w-2.5 rounded-full bg-white\"></span>\r\n }\r\n </span>\r\n </ng-template>\r\n\r\n <div class=\"flex flex-col gap-3\">\r\n @if (isLoadingOptions()) {\r\n <mt-card [paddingless]=\"true\">\r\n <div class=\"flex flex-col gap-3 p-4\">\r\n <p-skeleton height=\"2.5rem\"></p-skeleton>\r\n @for (item of [0, 1, 2, 3, 4, 5]; track $index) {\r\n <p-skeleton height=\"2rem\"></p-skeleton>\r\n }\r\n </div>\r\n </mt-card>\r\n } @else {\r\n @if (errorOptions(); as errorMessage) {\r\n <div\r\n class=\"rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700\"\r\n >\r\n {{ errorMessage }}\r\n </div>\r\n }\r\n\r\n @if (!hasOptions() && !errorOptions()) {\r\n <mt-card [paddingless]=\"true\">\r\n <div\r\n class=\"flex min-h-72 flex-col items-center justify-center gap-4 px-6 py-8 text-center\"\r\n >\r\n <div\r\n class=\"flex size-16 items-center justify-center rounded-3xl bg-surface-50 text-primary\"\r\n >\r\n <svg\r\n viewBox=\"0 0 64 64\"\r\n class=\"size-9\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n aria-hidden=\"true\"\r\n >\r\n <rect\r\n x=\"10\"\r\n y=\"12\"\r\n width=\"44\"\r\n height=\"38\"\r\n rx=\"12\"\r\n class=\"fill-primary/10 stroke-primary/35\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M21 27H43\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M21 35H37\"\r\n class=\"stroke-primary/70\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n </svg>\r\n </div>\r\n <div class=\"space-y-1\">\r\n <p class=\"text-base font-medium text-surface-900\">\r\n {{ t(\"delegations.scope.permissionsTitle\") }}\r\n </p>\r\n <p class=\"text-sm text-surface-500\">\r\n {{ t(\"delegations.scope.noGrantableOptions\") }}\r\n </p>\r\n </div>\r\n </div>\r\n </mt-card>\r\n }\r\n\r\n @if (hasOptions()) {\r\n <mt-card [paddingless]=\"true\" class=\"overflow-hidden\">\r\n <!-- Inner view switch: Permissions / Pages & accessibility -->\r\n @if (hasAccessibility()) {\r\n <div class=\"border-b border-surface px-3 pt-2.5 pb-0\">\r\n <mt-tabs\r\n mode=\"underline\"\r\n [(active)]=\"activeScopeTab\"\r\n [options]=\"[\r\n {\r\n value: 'permissions',\r\n label: t('delegations.scope.permissionsTab'),\r\n badge: selectedCount() || null,\r\n },\r\n {\r\n value: 'accessibility',\r\n label: t('delegations.scope.accessibilityTitle'),\r\n badge: selectedAccessibilityCount() || null,\r\n },\r\n ]\"\r\n fluid\r\n ></mt-tabs>\r\n </div>\r\n }\r\n\r\n @if (activeScopeTab() === \"permissions\" || !hasAccessibility()) {\r\n <!-- Toolbar: table-style search + clear -->\r\n <div\r\n class=\"flex items-center gap-2 border-b border-surface px-3 py-2\"\r\n >\r\n <div class=\"min-w-0 flex-1\">\r\n <mt-text-field\r\n [ngModel]=\"searchTerm()\"\r\n (ngModelChange)=\"searchTerm.set($event)\"\r\n icon=\"general.search-lg\"\r\n [placeholder]=\"t('delegations.scope.searchPlaceholder')\"\r\n ></mt-text-field>\r\n </div>\r\n @if (selectedCount() > 0) {\r\n <span class=\"shrink-0 text-xs font-medium text-surface-500\">\r\n {{ selectedCount() }} {{ t(\"delegations.scope.selected\") }}\r\n </span>\r\n <mt-button\r\n variant=\"text\"\r\n size=\"small\"\r\n [label]=\"t('delegations.scope.clear')\"\r\n [disabled]=\"readonly()\"\r\n (click)=\"clearSelection()\"\r\n ></mt-button>\r\n }\r\n </div>\r\n\r\n <!-- Select-all header -->\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-center gap-2 border-b border-surface px-3 py-2 text-start hover:bg-surface-50\"\r\n role=\"checkbox\"\r\n [attr.aria-checked]=\"\r\n selectAllState() === 'indeterminate'\r\n ? 'mixed'\r\n : selectAllState() === 'checked'\r\n \"\r\n [disabled]=\"readonly()\"\r\n (click)=\"toggleAll()\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n checkbox;\r\n context: { state: selectAllState() }\r\n \"\r\n ></ng-container>\r\n <span\r\n class=\"text-xs font-semibold uppercase tracking-wide text-surface-500\"\r\n >\r\n {{ t(\"delegations.scope.selectAll\") }}\r\n </span>\r\n </button>\r\n\r\n <!-- Virtualized permission tree -->\r\n @if (visibleNodes().length > 0) {\r\n <cdk-virtual-scroll-viewport\r\n itemSize=\"40\"\r\n class=\"block h-[20rem]\"\r\n >\r\n <div\r\n *cdkVirtualFor=\"\r\n let item of visibleNodes();\r\n trackBy: trackVisible\r\n \"\r\n class=\"group flex h-10 items-center gap-2 pe-2 transition-colors hover:bg-surface-50\"\r\n [style.padding-inline-start.rem]=\"0.5 + item.depth * 1.25\"\r\n >\r\n @if (hasChildren(item.node)) {\r\n <button\r\n type=\"button\"\r\n class=\"flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-surface-400 hover:bg-surface-100 hover:text-surface-600\"\r\n [attr.aria-expanded]=\"isExpanded(item.node)\"\r\n [attr.aria-label]=\"nodeLabel(item.node)\"\r\n (click)=\"\r\n toggleExpand(item.node); $event.stopPropagation()\r\n \"\r\n >\r\n <mt-icon\r\n [icon]=\"\r\n isExpanded(item.node)\r\n ? 'arrow.chevron-down'\r\n : 'arrow.chevron-right'\r\n \"\r\n styleClass=\"text-base\"\r\n ></mt-icon>\r\n </button>\r\n } @else {\r\n <span class=\"size-6 shrink-0\"></span>\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n role=\"checkbox\"\r\n [attr.aria-checked]=\"\r\n nodeState(item.node) === 'indeterminate'\r\n ? 'mixed'\r\n : nodeState(item.node) === 'checked'\r\n \"\r\n [attr.aria-label]=\"nodeLabel(item.node)\"\r\n class=\"shrink-0\"\r\n [class.cursor-pointer]=\"!readonly()\"\r\n [disabled]=\"readonly()\"\r\n (click)=\"toggle(item.node); $event.stopPropagation()\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n checkbox;\r\n context: { state: nodeState(item.node) }\r\n \"\r\n ></ng-container>\r\n </button>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"flex min-w-0 flex-1 cursor-pointer items-center gap-2 text-start\"\r\n (click)=\"onRowClick(item.node)\"\r\n >\r\n <span\r\n class=\"truncate text-sm\"\r\n [class.font-semibold]=\"item.node.kind === 'template'\"\r\n [class.text-surface-900]=\"item.node.kind === 'template'\"\r\n [class.font-medium]=\"\r\n item.node.kind === 'level' ||\r\n item.node.kind === 'module'\r\n \"\r\n [class.text-surface-800]=\"\r\n item.node.kind === 'level' ||\r\n item.node.kind === 'module'\r\n \"\r\n [class.text-surface-600]=\"item.node.kind === 'operation'\"\r\n >\r\n {{ nodeLabel(item.node) }}\r\n </span>\r\n @if (\r\n item.node.kind === \"operation\" && item.node.isHighRisk\r\n ) {\r\n <span\r\n class=\"shrink-0 rounded-full bg-amber-100 px-2 py-0.5 text-[0.625rem] font-medium text-amber-700\"\r\n >\r\n {{ t(\"delegations.scope.highRisk\") }}\r\n </span>\r\n }\r\n </button>\r\n </div>\r\n </cdk-virtual-scroll-viewport>\r\n } @else {\r\n <p class=\"px-3 py-8 text-center text-sm text-surface-500\">\r\n {{ t(\"components.table.no-data-found\") }}\r\n </p>\r\n }\r\n } @else {\r\n <!-- App accessibility -->\r\n <div class=\"flex flex-col gap-3 p-4\">\r\n <p class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.scope.accessibilityHint\") }}\r\n </p>\r\n <div class=\"flex flex-wrap gap-2\">\r\n @for (\r\n item of appAccessibilities();\r\n track item.accessibilityKey\r\n ) {\r\n @let selected =\r\n isAccessibilitySelected(item.accessibilityKey);\r\n <button\r\n type=\"button\"\r\n class=\"inline-flex cursor-pointer items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs font-medium transition-colors\"\r\n [class.border-primary-400]=\"selected\"\r\n [class.bg-primary-50]=\"selected\"\r\n [class.text-primary-700]=\"selected\"\r\n [class.border-surface-200]=\"!selected\"\r\n [class.text-surface-600]=\"!selected\"\r\n [class.hover:border-primary-300]=\"!readonly()\"\r\n [disabled]=\"readonly()\"\r\n [attr.aria-pressed]=\"selected\"\r\n (click)=\"toggleAccessibility(item.accessibilityKey)\"\r\n >\r\n <span\r\n class=\"inline-flex size-1.5 rounded-full\"\r\n [class.bg-primary-500]=\"selected\"\r\n [class.bg-surface-300]=\"!selected\"\r\n ></span>\r\n {{ accessibilityLabel(item) }}\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </mt-card>\r\n }\r\n\r\n <!-- Compact live scope preview -->\r\n <div\r\n class=\"flex flex-col gap-1.5 rounded-lg border border-surface bg-surface-50 px-3 py-2\"\r\n >\r\n <div class=\"flex items-center gap-2 text-xs\">\r\n <span class=\"shrink-0 font-semibold text-surface-700\">\r\n {{ t(\"delegations.scope.previewTitle\") }}:\r\n </span>\r\n @if (isPreviewing()) {\r\n <p-skeleton width=\"8rem\" height=\"0.7rem\"></p-skeleton>\r\n } @else if (preview(); as p) {\r\n @if (p.isValid) {\r\n <span class=\"min-w-0 flex-1 truncate text-surface-900\">\r\n {{\r\n getPreviewSummary(p.summary) ||\r\n t(\"delegations.column.scopeSummary\")\r\n }}\r\n </span>\r\n } @else {\r\n <span class=\"font-medium text-red-700\">\r\n {{ t(\"delegations.scope.previewInvalid\") }}\r\n </span>\r\n }\r\n } @else {\r\n <span class=\"text-surface-500\">\r\n {{ t(\"delegations.scope.noSelection\") }}\r\n </span>\r\n }\r\n </div>\r\n @if (preview(); as p) {\r\n @if (p.warnings.length > 0) {\r\n <ul\r\n class=\"list-inside list-disc space-y-0.5 text-xs text-amber-700\"\r\n >\r\n @for (w of p.warnings; track $index) {\r\n <li>{{ w.message }}</li>\r\n }\r\n </ul>\r\n }\r\n @if (p.deniedItems.length > 0) {\r\n <ul class=\"list-inside list-disc space-y-0.5 text-xs text-red-700\">\r\n @for (d of p.deniedItems; track $index) {\r\n <li>\r\n {{ formatDeniedTarget(d.targetKey) }} /\r\n {{ formatDeniedOperation(d.operationKey) }} -\r\n {{ d.reasonCode }}\r\n </li>\r\n }\r\n </ul>\r\n }\r\n }\r\n </div>\r\n }\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i3.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i3.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i3.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "maxLength", "icon", "iconPosition"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i4.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
2681
2773
  }
2682
2774
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ScopePicker, decorators: [{
2683
2775
  type: Component,
@@ -3133,7 +3225,7 @@ class DelegationForm {
3133
3225
  };
3134
3226
  }
3135
3227
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
3136
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationForm, isStandalone: true, selector: "mt-delegation-form", inputs: { delegationForEdit: { classPropertyName: "delegationForEdit", publicName: "delegationForEdit", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div\r\n [class]=\"\r\n 'flex h-full min-h-0 min-w-0 flex-col overflow-hidden ' +\r\n modal.contentClass\r\n \"\r\n >\r\n <div\r\n class=\"grid min-h-0 flex-1 gap-4 overflow-y-auto p-4 max-[640px]:p-3 lg:grid-cols-2 lg:items-start\"\r\n >\r\n <section\r\n class=\"flex flex-col gap-4 rounded-3xl border border-surface-200 bg-surface-0 p-4 shadow-xs lg:sticky lg:top-0\"\r\n >\r\n <mt-dynamic-form\r\n [formConfig]=\"formConfig()\"\r\n [formControl]=\"delegationFormControl\"\r\n />\r\n </section>\r\n\r\n <section class=\"flex min-h-0 flex-col gap-3\">\r\n <div class=\"flex flex-col gap-1\">\r\n <h3 class=\"text-xl font-semibold text-surface-900\">\r\n {{ t(\"delegations.scope.permissionsTitle\") }}\r\n </h3>\r\n <p class=\"text-sm text-surface-500\">\r\n {{\r\n adminMode() && !showScopePicker()\r\n ? t(\"delegations.form.selectDelegatorFirst\")\r\n : t(\"delegations.scope.permissionsSubtitle\")\r\n }}\r\n </p>\r\n </div>\r\n\r\n @if (showScopePicker()) {\r\n <mt-scope-picker\r\n [(scope)]=\"scope\"\r\n [readonly]=\"readonly()\"\r\n [adminMode]=\"adminMode()\"\r\n [delegatorUserId]=\"selectedDelegatorId()\"\r\n ></mt-scope-picker>\r\n } @else {\r\n <div\r\n class=\"flex min-h-72 flex-col items-center justify-center gap-4 rounded-3xl border border-dashed border-surface-300 bg-surface-50 px-6 py-8 text-center\"\r\n >\r\n <div\r\n class=\"flex size-18 items-center justify-center rounded-3xl bg-surface-0 text-primary shadow-sm\"\r\n >\r\n <svg\r\n viewBox=\"0 0 64 64\"\r\n class=\"size-10\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n aria-hidden=\"true\"\r\n >\r\n <rect\r\n x=\"10\"\r\n y=\"12\"\r\n width=\"44\"\r\n height=\"40\"\r\n rx=\"12\"\r\n class=\"fill-primary/10 stroke-primary/35\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M22 28H42\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M22 36H34\"\r\n class=\"stroke-primary/70\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <circle\r\n cx=\"45\"\r\n cy=\"20\"\r\n r=\"7\"\r\n class=\"fill-surface-0 stroke-primary\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M45 17V23\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"2.5\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M42 20H48\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"2.5\"\r\n stroke-linecap=\"round\"\r\n />\r\n </svg>\r\n </div>\r\n <div class=\"space-y-1\">\r\n <p class=\"text-base font-medium text-surface-900\">\r\n {{ t(\"delegations.scope.permissionsTitle\") }}\r\n </p>\r\n <p class=\"text-sm text-surface-500\">\r\n {{ t(\"delegations.form.selectDelegatorFirst\") }}\r\n </p>\r\n </div>\r\n </div>\r\n }\r\n </section>\r\n </div>\r\n </div>\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n [label]=\"t('delegations.common.cancel')\"\r\n variant=\"outlined\"\r\n (click)=\"ref.close()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n />\r\n @if (!readonly()) {\r\n <mt-button\r\n [label]=\"\r\n delegationForEdit()\r\n ? t('delegations.common.update')\r\n : t('delegations.common.create')\r\n \"\r\n [loading]=\"isSaving()\"\r\n [disabled]=\"!delegationFormControl.valid || !canSubmit()\"\r\n (click)=\"onSubmit()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n />\r\n }\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: DynamicForm, selector: "mt-dynamic-form", inputs: ["formConfig", "forcedHiddenFieldKeys", "forcedDisabledFieldKeys", "preserveForcedHiddenValues", "visibleSectionKeys", "externalValues"], outputs: ["runtimeMessagesChange"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: ScopePicker, selector: "mt-scope-picker", inputs: ["scope", "readonly", "adminMode", "delegatorUserId"], outputs: ["scopeChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3228
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationForm, isStandalone: true, selector: "mt-delegation-form", inputs: { delegationForEdit: { classPropertyName: "delegationForEdit", publicName: "delegationForEdit", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div\r\n [class]=\"\r\n 'flex h-full min-h-0 min-w-0 flex-col overflow-hidden ' +\r\n modal.contentClass\r\n \"\r\n >\r\n <div\r\n class=\"grid min-h-0 flex-1 gap-4 overflow-y-auto p-4 max-[640px]:p-3 lg:grid-cols-2 lg:items-start\"\r\n >\r\n <section\r\n class=\"flex flex-col gap-4 rounded-3xl border border-surface-200 bg-surface-0 p-4 shadow-xs lg:sticky lg:top-0\"\r\n >\r\n <mt-dynamic-form\r\n [formConfig]=\"formConfig()\"\r\n [formControl]=\"delegationFormControl\"\r\n />\r\n </section>\r\n\r\n <section class=\"flex min-h-0 flex-col gap-3\">\r\n <div class=\"flex flex-col gap-1\">\r\n <h3 class=\"text-xl font-semibold text-surface-900\">\r\n {{ t(\"delegations.scope.permissionsTitle\") }}\r\n </h3>\r\n <p class=\"text-sm text-surface-500\">\r\n {{\r\n adminMode() && !showScopePicker()\r\n ? t(\"delegations.form.selectDelegatorFirst\")\r\n : t(\"delegations.scope.permissionsSubtitle\")\r\n }}\r\n </p>\r\n </div>\r\n\r\n @if (showScopePicker()) {\r\n <mt-scope-picker\r\n [(scope)]=\"scope\"\r\n [readonly]=\"readonly()\"\r\n [adminMode]=\"adminMode()\"\r\n [delegatorUserId]=\"selectedDelegatorId()\"\r\n ></mt-scope-picker>\r\n } @else {\r\n <div\r\n class=\"flex min-h-72 flex-col items-center justify-center gap-4 rounded-3xl border border-dashed border-surface-300 bg-surface-50 px-6 py-8 text-center\"\r\n >\r\n <div\r\n class=\"flex size-18 items-center justify-center rounded-3xl bg-surface-0 text-primary shadow-sm\"\r\n >\r\n <svg\r\n viewBox=\"0 0 64 64\"\r\n class=\"size-10\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n aria-hidden=\"true\"\r\n >\r\n <rect\r\n x=\"10\"\r\n y=\"12\"\r\n width=\"44\"\r\n height=\"40\"\r\n rx=\"12\"\r\n class=\"fill-primary/10 stroke-primary/35\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M22 28H42\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M22 36H34\"\r\n class=\"stroke-primary/70\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <circle\r\n cx=\"45\"\r\n cy=\"20\"\r\n r=\"7\"\r\n class=\"fill-surface-0 stroke-primary\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M45 17V23\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"2.5\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M42 20H48\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"2.5\"\r\n stroke-linecap=\"round\"\r\n />\r\n </svg>\r\n </div>\r\n <div class=\"space-y-1\">\r\n <p class=\"text-base font-medium text-surface-900\">\r\n {{ t(\"delegations.scope.permissionsTitle\") }}\r\n </p>\r\n <p class=\"text-sm text-surface-500\">\r\n {{ t(\"delegations.form.selectDelegatorFirst\") }}\r\n </p>\r\n </div>\r\n </div>\r\n }\r\n </section>\r\n </div>\r\n </div>\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n [label]=\"t('delegations.common.cancel')\"\r\n variant=\"outlined\"\r\n (click)=\"ref.close()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n />\r\n @if (!readonly()) {\r\n <mt-button\r\n [label]=\"\r\n delegationForEdit()\r\n ? t('delegations.common.update')\r\n : t('delegations.common.create')\r\n \"\r\n [loading]=\"isSaving()\"\r\n [disabled]=\"!delegationFormControl.valid || !canSubmit()\"\r\n (click)=\"onSubmit()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n />\r\n }\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: DynamicForm, selector: "mt-dynamic-form", inputs: ["formConfig", "forcedHiddenFieldKeys", "forcedDisabledFieldKeys", "preserveForcedHiddenValues", "visibleSectionKeys", "externalValues"], outputs: ["runtimeMessagesChange"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: ScopePicker, selector: "mt-scope-picker", inputs: ["scope", "readonly", "adminMode", "delegatorUserId"], outputs: ["scopeChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3137
3229
  }
3138
3230
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationForm, decorators: [{
3139
3231
  type: Component,
@@ -3298,7 +3390,7 @@ class DelegationDetailDrawer {
3298
3390
  return 'children' in node && node.children.length > 0;
3299
3391
  }
3300
3392
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationDetailDrawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
3301
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationDetailDrawer, isStandalone: true, selector: "mt-delegation-detail-drawer", inputs: { delegationId: { classPropertyName: "delegationId", publicName: "delegationId", isSignal: true, isRequired: true, transformFunction: null }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col h-full\">\r\n <!-- Tabs -->\r\n <div class=\"border-b border-surface-200 px-4 pt-2\">\r\n <mt-tabs\r\n mode=\"underline\"\r\n [(active)]=\"activeTab\"\r\n [options]=\"tabOptions()\"\r\n fluid\r\n ></mt-tabs>\r\n </div>\r\n\r\n <!-- Body -->\r\n <div class=\"flex-1 overflow-y-auto p-4\">\r\n @if (isLoading() && !detail()) {\r\n <p-skeleton height=\"2rem\" class=\"mb-3\"></p-skeleton>\r\n <p-skeleton height=\"6rem\" class=\"mb-3\"></p-skeleton>\r\n <p-skeleton height=\"6rem\"></p-skeleton>\r\n } @else if (detail(); as d) {\r\n @if (activeTab() === \"overview\") {\r\n <div class=\"flex flex-col gap-4\">\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <div class=\"flex items-center gap-3 min-w-0\">\r\n <mt-entity-preview\r\n [data]=\"\r\n userEntity(\r\n d.row.delegator,\r\n t('delegations.column.delegatorName')\r\n )\r\n \"\r\n ></mt-entity-preview>\r\n </div>\r\n <mt-delegation-status-chip\r\n [status]=\"d.status.effectiveStatus\"\r\n [reasonCode]=\"d.status.reasonCode\"\r\n ></mt-delegation-status-chip>\r\n </div>\r\n\r\n <div class=\"grid grid-cols-2 gap-4\">\r\n <div class=\"flex flex-col\">\r\n <mt-entity-preview\r\n [data]=\"\r\n userEntity(\r\n d.row.delegatedUser,\r\n t('delegations.column.delegatedTo')\r\n )\r\n \"\r\n ></mt-entity-preview>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.approval\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">{{\r\n approvalLabel()\r\n }}</span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.startDate\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n {{ d.row.startsAtUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.endDate\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n {{ d.row.endsAtUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.form.timeZone\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">{{\r\n d.row.timeZoneId\r\n }}</span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.days\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n @if (d.row.dayRuleMode === \"FullRange\") {\r\n {{ t(\"delegations.form.fullRange\") }}\r\n } @else {\r\n {{ formatSpecificDays(d.row.specificDays) }}\r\n }\r\n </span>\r\n </div>\r\n </div>\r\n\r\n @if (d.row.cancellation?.cancellationReason) {\r\n <div\r\n class=\"rounded-md border border-surface-200 bg-surface-50 p-3 text-sm text-surface-800\"\r\n >\r\n <div class=\"font-medium\">\r\n {{ t(\"delegations.form.cancellationReason\") }}\r\n </div>\r\n <div>{{ d.row.cancellation.cancellationReason }}</div>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Scope tab -->\r\n <div\r\n class=\"flex flex-col overflow-hidden rounded-lg border border-surface\"\r\n >\r\n <div\r\n class=\"flex items-center justify-between gap-2 border-b border-surface bg-surface-50 px-3 py-2\"\r\n >\r\n <span class=\"text-xs font-semibold uppercase text-surface-500\">\r\n {{ t(\"delegations.scope.permissionsTitle\") }}\r\n </span>\r\n @if (selectedOperationCount() > 0) {\r\n <span class=\"text-xs font-medium text-surface-500\">\r\n {{ selectedOperationCount() }}\r\n {{ t(\"delegations.scope.selected\") }}\r\n </span>\r\n }\r\n </div>\r\n\r\n @if (scopeVisibleNodes().length > 0) {\r\n <div class=\"flex max-h-[34rem] flex-col overflow-y-auto\">\r\n @for (\r\n node of scopeVisibleNodes();\r\n track trackScopeNode($index, node)\r\n ) {\r\n <div\r\n class=\"group flex min-h-10 items-center gap-2 pe-3 transition-colors hover:bg-surface-50\"\r\n [style.padding-inline-start.rem]=\"0.75 + node.depth * 1.25\"\r\n >\r\n @if (hasChildren(node)) {\r\n <span\r\n class=\"flex size-6 shrink-0 items-center justify-center rounded-md text-surface-400\"\r\n aria-hidden=\"true\"\r\n >\r\n <svg\r\n viewBox=\"0 0 20 20\"\r\n class=\"size-4\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path\r\n d=\"M5 7L10 12L15 7\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"1.8\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n />\r\n </svg>\r\n </span>\r\n } @else {\r\n <span class=\"size-6 shrink-0\"></span>\r\n }\r\n\r\n <span\r\n class=\"flex size-[18px] shrink-0 items-center justify-center rounded-[5px] border border-primary-500 bg-primary-500 text-white\"\r\n aria-hidden=\"true\"\r\n >\r\n <svg\r\n viewBox=\"0 0 16 16\"\r\n class=\"size-3\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path\r\n d=\"M3.5 8.5L6.5 11.5L12.5 4.5\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n />\r\n </svg>\r\n </span>\r\n\r\n <span\r\n class=\"min-w-0 flex-1 truncate text-sm\"\r\n [class.font-semibold]=\"node.kind === 'template'\"\r\n [class.text-surface-900]=\"node.kind === 'template'\"\r\n [class.font-medium]=\"\r\n node.kind === 'level' || node.kind === 'module'\r\n \"\r\n [class.text-surface-800]=\"\r\n node.kind === 'level' || node.kind === 'module'\r\n \"\r\n [class.text-surface-600]=\"node.kind === 'operation'\"\r\n >\r\n {{ node.label }}\r\n </span>\r\n\r\n @if (node.kind === \"operation\" && node.isHighRisk) {\r\n <span\r\n class=\"shrink-0 rounded-full bg-amber-100 px-2 py-0.5 text-[0.625rem] font-medium text-amber-700\"\r\n >\r\n {{ t(\"delegations.scope.highRisk\") }}\r\n </span>\r\n }\r\n\r\n @if (!node.isAvailable) {\r\n <span\r\n class=\"shrink-0 rounded-full bg-surface-100 px-2 py-0.5 text-[0.625rem] font-medium text-surface-500\"\r\n >\r\n {{ t(\"delegations.scope.unavailable\") }}\r\n </span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <p class=\"px-3 py-8 text-center text-sm text-surface-500\">\r\n {{ t(\"delegations.scope.noSelection\") }}\r\n </p>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div\r\n class=\"border-t border-surface-200 px-4 py-3 flex items-center justify-end\"\r\n >\r\n <mt-button\r\n [label]=\"t('delegations.common.cancel')\"\r\n variant=\"outlined\"\r\n (click)=\"ref.close()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i4.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationStatusChip, selector: "mt-delegation-status-chip", inputs: ["status", "reasonCode"] }, { kind: "pipe", type: i1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3393
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationDetailDrawer, isStandalone: true, selector: "mt-delegation-detail-drawer", inputs: { delegationId: { classPropertyName: "delegationId", publicName: "delegationId", isSignal: true, isRequired: true, transformFunction: null }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col h-full\">\r\n <!-- Tabs -->\r\n <div class=\"border-b border-surface-200 px-4 pt-2\">\r\n <mt-tabs\r\n mode=\"underline\"\r\n [(active)]=\"activeTab\"\r\n [options]=\"tabOptions()\"\r\n fluid\r\n ></mt-tabs>\r\n </div>\r\n\r\n <!-- Body -->\r\n <div class=\"flex-1 overflow-y-auto p-4\">\r\n @if (isLoading() && !detail()) {\r\n <p-skeleton height=\"2rem\" class=\"mb-3\"></p-skeleton>\r\n <p-skeleton height=\"6rem\" class=\"mb-3\"></p-skeleton>\r\n <p-skeleton height=\"6rem\"></p-skeleton>\r\n } @else if (detail(); as d) {\r\n @if (activeTab() === \"overview\") {\r\n <div class=\"flex flex-col gap-4\">\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <div class=\"flex items-center gap-3 min-w-0\">\r\n <mt-entity-preview\r\n [data]=\"\r\n userEntity(\r\n d.row.delegator,\r\n t('delegations.column.delegatorName')\r\n )\r\n \"\r\n ></mt-entity-preview>\r\n </div>\r\n <mt-delegation-status-chip\r\n [status]=\"d.status.effectiveStatus\"\r\n [reasonCode]=\"d.status.reasonCode\"\r\n ></mt-delegation-status-chip>\r\n </div>\r\n\r\n <div class=\"grid grid-cols-2 gap-4\">\r\n <div class=\"flex flex-col\">\r\n <mt-entity-preview\r\n [data]=\"\r\n userEntity(\r\n d.row.delegatedUser,\r\n t('delegations.column.delegatedTo')\r\n )\r\n \"\r\n ></mt-entity-preview>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.approval\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">{{\r\n approvalLabel()\r\n }}</span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.startDate\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n {{ d.row.startsAtUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.endDate\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n {{ d.row.endsAtUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.form.timeZone\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">{{\r\n d.row.timeZoneId\r\n }}</span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.days\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n @if (d.row.dayRuleMode === \"FullRange\") {\r\n {{ t(\"delegations.form.fullRange\") }}\r\n } @else {\r\n {{ formatSpecificDays(d.row.specificDays) }}\r\n }\r\n </span>\r\n </div>\r\n </div>\r\n\r\n @if (d.row.cancellation?.cancellationReason) {\r\n <div\r\n class=\"rounded-md border border-surface-200 bg-surface-50 p-3 text-sm text-surface-800\"\r\n >\r\n <div class=\"font-medium\">\r\n {{ t(\"delegations.form.cancellationReason\") }}\r\n </div>\r\n <div>{{ d.row.cancellation.cancellationReason }}</div>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Scope tab -->\r\n <div\r\n class=\"flex flex-col overflow-hidden rounded-lg border border-surface\"\r\n >\r\n <div\r\n class=\"flex items-center justify-between gap-2 border-b border-surface bg-surface-50 px-3 py-2\"\r\n >\r\n <span class=\"text-xs font-semibold uppercase text-surface-500\">\r\n {{ t(\"delegations.scope.permissionsTitle\") }}\r\n </span>\r\n @if (selectedOperationCount() > 0) {\r\n <span class=\"text-xs font-medium text-surface-500\">\r\n {{ selectedOperationCount() }}\r\n {{ t(\"delegations.scope.selected\") }}\r\n </span>\r\n }\r\n </div>\r\n\r\n @if (scopeVisibleNodes().length > 0) {\r\n <div class=\"flex max-h-[34rem] flex-col overflow-y-auto\">\r\n @for (\r\n node of scopeVisibleNodes();\r\n track trackScopeNode($index, node)\r\n ) {\r\n <div\r\n class=\"group flex min-h-10 items-center gap-2 pe-3 transition-colors hover:bg-surface-50\"\r\n [style.padding-inline-start.rem]=\"0.75 + node.depth * 1.25\"\r\n >\r\n @if (hasChildren(node)) {\r\n <span\r\n class=\"flex size-6 shrink-0 items-center justify-center rounded-md text-surface-400\"\r\n aria-hidden=\"true\"\r\n >\r\n <svg\r\n viewBox=\"0 0 20 20\"\r\n class=\"size-4\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path\r\n d=\"M5 7L10 12L15 7\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"1.8\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n />\r\n </svg>\r\n </span>\r\n } @else {\r\n <span class=\"size-6 shrink-0\"></span>\r\n }\r\n\r\n <span\r\n class=\"flex size-[18px] shrink-0 items-center justify-center rounded-[5px] border border-primary-500 bg-primary-500 text-white\"\r\n aria-hidden=\"true\"\r\n >\r\n <svg\r\n viewBox=\"0 0 16 16\"\r\n class=\"size-3\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path\r\n d=\"M3.5 8.5L6.5 11.5L12.5 4.5\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n />\r\n </svg>\r\n </span>\r\n\r\n <span\r\n class=\"min-w-0 flex-1 truncate text-sm\"\r\n [class.font-semibold]=\"node.kind === 'template'\"\r\n [class.text-surface-900]=\"node.kind === 'template'\"\r\n [class.font-medium]=\"\r\n node.kind === 'level' || node.kind === 'module'\r\n \"\r\n [class.text-surface-800]=\"\r\n node.kind === 'level' || node.kind === 'module'\r\n \"\r\n [class.text-surface-600]=\"node.kind === 'operation'\"\r\n >\r\n {{ node.label }}\r\n </span>\r\n\r\n @if (node.kind === \"operation\" && node.isHighRisk) {\r\n <span\r\n class=\"shrink-0 rounded-full bg-amber-100 px-2 py-0.5 text-[0.625rem] font-medium text-amber-700\"\r\n >\r\n {{ t(\"delegations.scope.highRisk\") }}\r\n </span>\r\n }\r\n\r\n @if (!node.isAvailable) {\r\n <span\r\n class=\"shrink-0 rounded-full bg-surface-100 px-2 py-0.5 text-[0.625rem] font-medium text-surface-500\"\r\n >\r\n {{ t(\"delegations.scope.unavailable\") }}\r\n </span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <p class=\"px-3 py-8 text-center text-sm text-surface-500\">\r\n {{ t(\"delegations.scope.noSelection\") }}\r\n </p>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div\r\n class=\"border-t border-surface-200 px-4 py-3 flex items-center justify-end\"\r\n >\r\n <mt-button\r\n [label]=\"t('delegations.common.cancel')\"\r\n variant=\"outlined\"\r\n (click)=\"ref.close()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i4.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationStatusChip, selector: "mt-delegation-status-chip", inputs: ["status", "reasonCode"] }, { kind: "pipe", type: i1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3302
3394
  }
3303
3395
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationDetailDrawer, decorators: [{
3304
3396
  type: Component,
@@ -3941,7 +4033,7 @@ class DelegationsList {
3941
4033
  this.busyIds.update((ids) => on ? [...ids, key] : ids.filter((id) => id !== key));
3942
4034
  }
3943
4035
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsList, deps: [], target: i0.ɵɵFactoryTarget.Component });
3944
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationsList, isStandalone: true, selector: "mt-delegations-list", inputs: { showBreadcrumb: { classPropertyName: "showBreadcrumb", publicName: "showBreadcrumb", isSignal: true, isRequired: false, transformFunction: null }, showPageShell: { classPropertyName: "showPageShell", publicName: "showPageShell", isSignal: true, isRequired: false, transformFunction: null }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null }, surfaceTitleKey: { classPropertyName: "surfaceTitleKey", publicName: "surfaceTitleKey", isSignal: true, isRequired: false, transformFunction: null }, assignedTabLabelKey: { classPropertyName: "assignedTabLabelKey", publicName: "assignedTabLabelKey", isSignal: true, isRequired: false, transformFunction: null }, assignedDecisionTab: { classPropertyName: "assignedDecisionTab", publicName: "assignedDecisionTab", isSignal: true, isRequired: false, transformFunction: null }, assignedEmptyStateKey: { classPropertyName: "assignedEmptyStateKey", publicName: "assignedEmptyStateKey", isSignal: true, isRequired: false, transformFunction: null }, assignedStatus: { classPropertyName: "assignedStatus", publicName: "assignedStatus", isSignal: true, isRequired: false, transformFunction: null }, showApprovalTab: { classPropertyName: "showApprovalTab", publicName: "showApprovalTab", isSignal: true, isRequired: false, transformFunction: null }, approvalTabLabelKey: { classPropertyName: "approvalTabLabelKey", publicName: "approvalTabLabelKey", isSignal: true, isRequired: false, transformFunction: null }, approvalEmptyStateKey: { classPropertyName: "approvalEmptyStateKey", publicName: "approvalEmptyStateKey", isSignal: true, isRequired: false, transformFunction: null }, approvalStatus: { classPropertyName: "approvalStatus", publicName: "approvalStatus", isSignal: true, isRequired: false, transformFunction: null }, routed: { classPropertyName: "routed", publicName: "routed", isSignal: true, isRequired: false, transformFunction: null }, tab: { classPropertyName: "tab", publicName: "tab", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, itemSelected: { classPropertyName: "itemSelected", publicName: "itemSelected", isSignal: true, isRequired: false, transformFunction: null }, selectedItemId: { classPropertyName: "selectedItemId", publicName: "selectedItemId", isSignal: true, isRequired: false, transformFunction: null }, selectedItem: { classPropertyName: "selectedItem", publicName: "selectedItem", isSignal: true, isRequired: false, transformFunction: null }, itemId: { classPropertyName: "itemId", publicName: "itemId", isSignal: true, isRequired: false, transformFunction: null }, delegationId: { classPropertyName: "delegationId", publicName: "delegationId", isSignal: true, isRequired: false, transformFunction: null }, edit: { classPropertyName: "edit", publicName: "edit", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <ng-template #tableContent>\r\n <div class=\"flex flex-col gap-4\">\r\n @if (!adminMode()) {\r\n @let assignedLabel =\r\n assignedDecisionTab()\r\n ? t(\"delegations.action.approve\") +\r\n \" / \" +\r\n t(\"delegations.action.reject\")\r\n : t(assignedTabLabelKey());\r\n\r\n <mt-tabs\r\n mode=\"underline\"\r\n [active]=\"activeTab()\"\r\n [options]=\"\r\n showApprovalTab()\r\n ? [\r\n { value: 'my', label: t('delegations.myDelegations') },\r\n { value: 'assigned', label: assignedLabel },\r\n { value: 'approvals', label: t(approvalTabLabelKey()) },\r\n ]\r\n : [\r\n { value: 'my', label: t('delegations.myDelegations') },\r\n { value: 'assigned', label: assignedLabel },\r\n ]\r\n \"\r\n fluid\r\n (onChange)=\"switchTab($event)\"\r\n ></mt-tabs>\r\n }\r\n\r\n <mt-table\r\n [data]=\"tableRows()\"\r\n [columns]=\"tableColumns()\"\r\n [actions]=\"tableActions()\"\r\n [rowActions]=\"rowActions()\"\r\n [loading]=\"isLoading()\"\r\n [lazy]=\"true\"\r\n [lazyTotalRecords]=\"currentPage()?.totalCount ?? 0\"\r\n dataKey=\"delegationId\"\r\n [noCard]=\"true\"\r\n (lazyLoad)=\"onLazyLoad($event)\"\r\n >\r\n <ng-template #empty>\r\n <div\r\n class=\"flex min-h-64 flex-col items-center justify-center gap-4 px-6 py-10 text-center\"\r\n >\r\n <div\r\n class=\"flex size-16 items-center justify-center rounded-3xl bg-surface-0 text-primary shadow-sm\"\r\n >\r\n <svg\r\n viewBox=\"0 0 64 64\"\r\n class=\"size-9\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n aria-hidden=\"true\"\r\n >\r\n <rect\r\n x=\"10\"\r\n y=\"14\"\r\n width=\"44\"\r\n height=\"36\"\r\n rx=\"12\"\r\n class=\"fill-primary/10 stroke-primary/35\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M21 28H43\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M21 36H35\"\r\n class=\"stroke-primary/70\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n </svg>\r\n </div>\r\n <p class=\"max-w-sm text-sm text-surface-500\">\r\n {{ t(emptyStateKey()) }}\r\n </p>\r\n </div>\r\n </ng-template>\r\n </mt-table>\r\n </div>\r\n </ng-template>\r\n\r\n <ng-template #adminContent>\r\n <div class=\"flex min-h-full flex-col gap-4\">\r\n @if (showBreadcrumb()) {\r\n <mt-breadcrumb [items]=\"breadcrumbItems()\"></mt-breadcrumb>\r\n }\r\n <ng-container *ngTemplateOutlet=\"tableContent\"></ng-container>\r\n </div>\r\n </ng-template>\r\n\r\n @if (adminMode()) {\r\n @if (showPageShell()) {\r\n <mt-page\r\n [title]=\"surfaceTitle()\"\r\n [avatarIcon]=\"'custom.hierarchy-structure'\"\r\n [contentClass]=\"'max-[1025px]:p-4 max-[640px]:p-3'\"\r\n [avatarStyle]=\"{\r\n '--p-avatar-background': 'var(--p-indigo-50)',\r\n '--p-avatar-color': 'var(--p-indigo-700)',\r\n }\"\r\n (backButtonClick)=\"goBack()\"\r\n backButton\r\n >\r\n <ng-container *ngTemplateOutlet=\"adminContent\"></ng-container>\r\n </mt-page>\r\n } @else {\r\n <ng-container *ngTemplateOutlet=\"adminContent\"></ng-container>\r\n }\r\n } @else {\r\n <div class=\"flex h-full flex-col gap-4 ps-4 pt-4\">\r\n <mt-card class=\"rounded-e-none!\" [title]=\"surfaceTitle()\">\r\n <ng-container *ngTemplateOutlet=\"tableContent\"></ng-container>\r\n </mt-card>\r\n </div>\r\n }\r\n</ng-container>\r\n", styles: [":host{display:block;min-width:0}:host ::ng-deep mt-table td.mt-actions-column>div{flex-wrap:nowrap;justify-content:flex-end;white-space:nowrap}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Breadcrumb, selector: "mt-breadcrumb", inputs: ["items", "styleClass"], outputs: ["onItemClick"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: Page, selector: "mt-page", inputs: ["backButton", "backButtonIcon", "avatarIcon", "avatarStyle", "avatarShape", "title", "tabs", "activeTab", "contentClass", "contentId"], outputs: ["backButtonClick", "tabChange"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "filterMode", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "persistStateExclude", "exportable", "printable", "groupable", "groupCountMap", "cellClickFilter", "freezeActions", "virtualScroll", "virtualScrollItemSize", "scrollHeight", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy", "sortField", "sortDirection"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange", "sortFieldChange", "sortDirectionChange"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4036
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationsList, isStandalone: true, selector: "mt-delegations-list", inputs: { showBreadcrumb: { classPropertyName: "showBreadcrumb", publicName: "showBreadcrumb", isSignal: true, isRequired: false, transformFunction: null }, showPageShell: { classPropertyName: "showPageShell", publicName: "showPageShell", isSignal: true, isRequired: false, transformFunction: null }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null }, surfaceTitleKey: { classPropertyName: "surfaceTitleKey", publicName: "surfaceTitleKey", isSignal: true, isRequired: false, transformFunction: null }, assignedTabLabelKey: { classPropertyName: "assignedTabLabelKey", publicName: "assignedTabLabelKey", isSignal: true, isRequired: false, transformFunction: null }, assignedDecisionTab: { classPropertyName: "assignedDecisionTab", publicName: "assignedDecisionTab", isSignal: true, isRequired: false, transformFunction: null }, assignedEmptyStateKey: { classPropertyName: "assignedEmptyStateKey", publicName: "assignedEmptyStateKey", isSignal: true, isRequired: false, transformFunction: null }, assignedStatus: { classPropertyName: "assignedStatus", publicName: "assignedStatus", isSignal: true, isRequired: false, transformFunction: null }, showApprovalTab: { classPropertyName: "showApprovalTab", publicName: "showApprovalTab", isSignal: true, isRequired: false, transformFunction: null }, approvalTabLabelKey: { classPropertyName: "approvalTabLabelKey", publicName: "approvalTabLabelKey", isSignal: true, isRequired: false, transformFunction: null }, approvalEmptyStateKey: { classPropertyName: "approvalEmptyStateKey", publicName: "approvalEmptyStateKey", isSignal: true, isRequired: false, transformFunction: null }, approvalStatus: { classPropertyName: "approvalStatus", publicName: "approvalStatus", isSignal: true, isRequired: false, transformFunction: null }, routed: { classPropertyName: "routed", publicName: "routed", isSignal: true, isRequired: false, transformFunction: null }, tab: { classPropertyName: "tab", publicName: "tab", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, itemSelected: { classPropertyName: "itemSelected", publicName: "itemSelected", isSignal: true, isRequired: false, transformFunction: null }, selectedItemId: { classPropertyName: "selectedItemId", publicName: "selectedItemId", isSignal: true, isRequired: false, transformFunction: null }, selectedItem: { classPropertyName: "selectedItem", publicName: "selectedItem", isSignal: true, isRequired: false, transformFunction: null }, itemId: { classPropertyName: "itemId", publicName: "itemId", isSignal: true, isRequired: false, transformFunction: null }, delegationId: { classPropertyName: "delegationId", publicName: "delegationId", isSignal: true, isRequired: false, transformFunction: null }, edit: { classPropertyName: "edit", publicName: "edit", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <ng-template #tableContent>\r\n <div class=\"flex flex-col gap-4\">\r\n @if (!adminMode()) {\r\n @let assignedLabel =\r\n assignedDecisionTab()\r\n ? t(\"delegations.action.approve\") +\r\n \" / \" +\r\n t(\"delegations.action.reject\")\r\n : t(assignedTabLabelKey());\r\n\r\n <mt-tabs\r\n mode=\"underline\"\r\n [active]=\"activeTab()\"\r\n [options]=\"\r\n showApprovalTab()\r\n ? [\r\n { value: 'my', label: t('delegations.myDelegations') },\r\n { value: 'assigned', label: assignedLabel },\r\n { value: 'approvals', label: t(approvalTabLabelKey()) },\r\n ]\r\n : [\r\n { value: 'my', label: t('delegations.myDelegations') },\r\n { value: 'assigned', label: assignedLabel },\r\n ]\r\n \"\r\n fluid\r\n (onChange)=\"switchTab($event)\"\r\n ></mt-tabs>\r\n }\r\n\r\n <mt-table\r\n [data]=\"tableRows()\"\r\n [columns]=\"tableColumns()\"\r\n [actions]=\"tableActions()\"\r\n [rowActions]=\"rowActions()\"\r\n [loading]=\"isLoading()\"\r\n [lazy]=\"true\"\r\n [lazyTotalRecords]=\"currentPage()?.totalCount ?? 0\"\r\n dataKey=\"delegationId\"\r\n [noCard]=\"true\"\r\n (lazyLoad)=\"onLazyLoad($event)\"\r\n >\r\n <ng-template #empty>\r\n <div\r\n class=\"flex min-h-64 flex-col items-center justify-center gap-4 px-6 py-10 text-center\"\r\n >\r\n <div\r\n class=\"flex size-16 items-center justify-center rounded-3xl bg-surface-0 text-primary shadow-sm\"\r\n >\r\n <svg\r\n viewBox=\"0 0 64 64\"\r\n class=\"size-9\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n aria-hidden=\"true\"\r\n >\r\n <rect\r\n x=\"10\"\r\n y=\"14\"\r\n width=\"44\"\r\n height=\"36\"\r\n rx=\"12\"\r\n class=\"fill-primary/10 stroke-primary/35\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M21 28H43\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M21 36H35\"\r\n class=\"stroke-primary/70\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n </svg>\r\n </div>\r\n <p class=\"max-w-sm text-sm text-surface-500\">\r\n {{ t(emptyStateKey()) }}\r\n </p>\r\n </div>\r\n </ng-template>\r\n </mt-table>\r\n </div>\r\n </ng-template>\r\n\r\n <ng-template #adminContent>\r\n <div class=\"flex min-h-full flex-col gap-4\">\r\n @if (showBreadcrumb()) {\r\n <mt-breadcrumb [items]=\"breadcrumbItems()\"></mt-breadcrumb>\r\n }\r\n <ng-container *ngTemplateOutlet=\"tableContent\"></ng-container>\r\n </div>\r\n </ng-template>\r\n\r\n @if (adminMode()) {\r\n @if (showPageShell()) {\r\n <mt-page\r\n [title]=\"surfaceTitle()\"\r\n [avatarIcon]=\"'custom.hierarchy-structure'\"\r\n [contentClass]=\"'max-[1025px]:p-4 max-[640px]:p-3'\"\r\n [avatarStyle]=\"{\r\n '--p-avatar-background': 'var(--p-indigo-50)',\r\n '--p-avatar-color': 'var(--p-indigo-700)',\r\n }\"\r\n (backButtonClick)=\"goBack()\"\r\n backButton\r\n >\r\n <ng-container *ngTemplateOutlet=\"adminContent\"></ng-container>\r\n </mt-page>\r\n } @else {\r\n <ng-container *ngTemplateOutlet=\"adminContent\"></ng-container>\r\n }\r\n } @else {\r\n <div class=\"flex h-full flex-col gap-4 ps-4 pt-4\">\r\n <mt-card class=\"rounded-e-none!\" [title]=\"surfaceTitle()\">\r\n <ng-container *ngTemplateOutlet=\"tableContent\"></ng-container>\r\n </mt-card>\r\n </div>\r\n }\r\n</ng-container>\r\n", styles: [":host{display:block;min-width:0}:host ::ng-deep mt-table td.mt-actions-column>div{flex-wrap:nowrap;justify-content:flex-end;white-space:nowrap}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Breadcrumb, selector: "mt-breadcrumb", inputs: ["items", "styleClass"], outputs: ["onItemClick"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: Page, selector: "mt-page", inputs: ["backButton", "backButtonIcon", "avatarIcon", "avatarStyle", "avatarShape", "title", "tabs", "activeTab", "contentClass", "contentId"], outputs: ["backButtonClick", "tabChange"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "filterMode", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "persistStateExclude", "exportable", "printable", "groupable", "groupCountMap", "cellClickFilter", "freezeActions", "virtualScroll", "virtualScrollItemSize", "scrollHeight", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "emptyTitle", "emptyDescription", "emptyActionLabel", "emptyActionIcon", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy", "sortField", "sortDirection"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "emptyAction", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange", "sortFieldChange", "sortDirectionChange"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
3945
4037
  }
3946
4038
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsList, decorators: [{
3947
4039
  type: Component,