@masterteam/delegations 0.1.0 → 0.2.0

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,4 +1,4 @@
1
- import * as i1 from '@angular/common';
1
+ import * as i2$1 from '@angular/common';
2
2
  import { CommonModule } from '@angular/common';
3
3
  import * as i0 from '@angular/core';
4
4
  import { input, computed, Component, inject, Injectable, ChangeDetectionStrategy, viewChild, model, signal, effect, untracked, linkedSignal } from '@angular/core';
@@ -13,137 +13,104 @@ import { Chip } from '@masterteam/components/chip';
13
13
  import { Actions, Store, ofActionSuccessful, ofActionDispatched, Action, Selector, State, select } from '@ngxs/store';
14
14
  import { HttpClient, HttpParams, HttpContext } from '@angular/common/http';
15
15
  import { handleApiRequest, REQUEST_CONTEXT, UserSearchFieldConfig, TextareaFieldConfig, DateFieldConfig, RadioButtonFieldConfig, MultiSelectFieldConfig, ToggleFieldConfig, ValidatorConfig } from '@masterteam/components';
16
- import { EMPTY, catchError, switchMap, finalize } from 'rxjs';
16
+ import { EMPTY, switchMap, finalize } from 'rxjs';
17
17
  import { ModalRef } from '@masterteam/components/dialog';
18
18
  import { Page } from '@masterteam/components/page';
19
19
  import { Breadcrumb } from '@masterteam/components/breadcrumb';
20
20
  import { Table } from '@masterteam/components/table';
21
- import * as i1$2 from '@angular/forms';
21
+ import * as i1$1 from '@angular/forms';
22
22
  import { FormControl, ReactiveFormsModule } from '@angular/forms';
23
23
  import { toSignal } from '@angular/core/rxjs-interop';
24
24
  import { DynamicForm } from '@masterteam/forms/dynamic-form';
25
- import * as i1$1 from 'primeng/skeleton';
25
+ import * as i1 from 'primeng/skeleton';
26
26
  import { SkeletonModule } from 'primeng/skeleton';
27
27
  import * as i2 from 'primeng/tooltip';
28
28
  import { TooltipModule } from 'primeng/tooltip';
29
29
 
30
30
  const STATUS_VISUAL = {
31
31
  Active: {
32
- i18nKey: 'delegations.active',
32
+ i18nKey: 'delegations.status.active',
33
33
  styleClass: 'mt-status-chip mt-status-chip--active',
34
34
  },
35
35
  Scheduled: {
36
- i18nKey: 'delegations.scheduled',
36
+ i18nKey: 'delegations.status.scheduled',
37
37
  styleClass: 'mt-status-chip mt-status-chip--scheduled',
38
38
  },
39
39
  PendingApproval: {
40
- i18nKey: 'delegations.pending-approval',
40
+ i18nKey: 'delegations.status.pendingApproval',
41
41
  styleClass: 'mt-status-chip mt-status-chip--pending',
42
42
  },
43
+ InactiveToday: {
44
+ i18nKey: 'delegations.status.inactiveToday',
45
+ styleClass: 'mt-status-chip mt-status-chip--scheduled',
46
+ },
43
47
  Expired: {
44
- i18nKey: 'delegations.expired',
48
+ i18nKey: 'delegations.status.expired',
45
49
  styleClass: 'mt-status-chip mt-status-chip--expired',
46
50
  },
47
51
  Rejected: {
48
- i18nKey: 'delegations.rejected',
52
+ i18nKey: 'delegations.status.rejected',
49
53
  styleClass: 'mt-status-chip mt-status-chip--rejected',
50
54
  },
51
55
  Cancelled: {
52
- i18nKey: 'delegations.cancelled',
56
+ i18nKey: 'delegations.status.cancelled',
53
57
  styleClass: 'mt-status-chip mt-status-chip--cancelled',
54
58
  },
55
59
  };
56
60
  class DelegationStatusChip {
57
61
  status = input.required(...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
58
- visual = computed(() => STATUS_VISUAL[this.status()], ...(ngDevMode ? [{ debugName: "visual" }] : /* istanbul ignore next */ []));
62
+ visual = computed(() => STATUS_VISUAL[this.status()] ?? STATUS_VISUAL.Scheduled, ...(ngDevMode ? [{ debugName: "visual" }] : /* istanbul ignore next */ []));
59
63
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationStatusChip, deps: [], target: i0.ɵɵFactoryTarget.Component });
60
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.8", type: DelegationStatusChip, isStandalone: true, selector: "mt-delegation-status-chip", inputs: { status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
61
- <ng-container *transloco="let t">
62
- @let v = visual();
63
- <mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
64
- </ng-container>
64
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.8", type: DelegationStatusChip, isStandalone: true, selector: "mt-delegation-status-chip", inputs: { status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
65
+ <ng-container *transloco="let t">
66
+ @let v = visual();
67
+ <mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
68
+ </ng-container>
65
69
  `, isInline: true, styles: [":host{display:inline-flex}:host ::ng-deep .mt-status-chip{font-weight:500;font-size:.75rem;border-radius:9999px;padding-inline:.625rem;padding-block:.125rem}:host ::ng-deep .mt-status-chip--active{background-color:#dcfce7;color:#166534}:host ::ng-deep .mt-status-chip--scheduled{background-color:#dbeafe;color:#1e40af}:host ::ng-deep .mt-status-chip--pending{background-color:#fef9c3;color:#854d0e}:host ::ng-deep .mt-status-chip--expired{background-color:#f3f4f6;color:#4b5563}:host ::ng-deep .mt-status-chip--rejected{background-color:#fee2e2;color:#991b1b}:host ::ng-deep .mt-status-chip--cancelled{background-color:#e5e7eb;color:#374151}:host-context(.dark) ::ng-deep .mt-status-chip--active{background-color:#22c55e33;color:#86efac}:host-context(.dark) ::ng-deep .mt-status-chip--scheduled{background-color:#3b82f633;color:#93c5fd}:host-context(.dark) ::ng-deep .mt-status-chip--pending{background-color:#eab30833;color:#fde047}:host-context(.dark) ::ng-deep .mt-status-chip--expired{background-color:#94a3b833;color:#cbd5e1}:host-context(.dark) ::ng-deep .mt-status-chip--rejected{background-color:#ef444433;color:#fca5a5}:host-context(.dark) ::ng-deep .mt-status-chip--cancelled{background-color:#94a3b826;color:#cbd5e1}\n"], dependencies: [{ kind: "component", type: Chip, selector: "mt-chip", inputs: ["label", "icon", "image", "removable", "removeIcon", "styleClass"], outputs: ["onRemove", "onImageError"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] });
66
70
  }
67
71
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationStatusChip, decorators: [{
68
72
  type: Component,
69
- args: [{ selector: 'mt-delegation-status-chip', standalone: true, imports: [Chip, TranslocoDirective], template: `
70
- <ng-container *transloco="let t">
71
- @let v = visual();
72
- <mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
73
- </ng-container>
73
+ args: [{ selector: 'mt-delegation-status-chip', standalone: true, imports: [Chip, TranslocoDirective], template: `
74
+ <ng-container *transloco="let t">
75
+ @let v = visual();
76
+ <mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
77
+ </ng-container>
74
78
  `, styles: [":host{display:inline-flex}:host ::ng-deep .mt-status-chip{font-weight:500;font-size:.75rem;border-radius:9999px;padding-inline:.625rem;padding-block:.125rem}:host ::ng-deep .mt-status-chip--active{background-color:#dcfce7;color:#166534}:host ::ng-deep .mt-status-chip--scheduled{background-color:#dbeafe;color:#1e40af}:host ::ng-deep .mt-status-chip--pending{background-color:#fef9c3;color:#854d0e}:host ::ng-deep .mt-status-chip--expired{background-color:#f3f4f6;color:#4b5563}:host ::ng-deep .mt-status-chip--rejected{background-color:#fee2e2;color:#991b1b}:host ::ng-deep .mt-status-chip--cancelled{background-color:#e5e7eb;color:#374151}:host-context(.dark) ::ng-deep .mt-status-chip--active{background-color:#22c55e33;color:#86efac}:host-context(.dark) ::ng-deep .mt-status-chip--scheduled{background-color:#3b82f633;color:#93c5fd}:host-context(.dark) ::ng-deep .mt-status-chip--pending{background-color:#eab30833;color:#fde047}:host-context(.dark) ::ng-deep .mt-status-chip--expired{background-color:#94a3b833;color:#cbd5e1}:host-context(.dark) ::ng-deep .mt-status-chip--rejected{background-color:#ef444433;color:#fca5a5}:host-context(.dark) ::ng-deep .mt-status-chip--cancelled{background-color:#94a3b826;color:#cbd5e1}\n"] }]
75
79
  }], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: true }] }] } });
76
80
 
77
- /** Reads sessionStorage + login candidates, restores any tab-local active session. */
78
- class BootstrapDelegationSession {
79
- static type = '[DelegationSession] Bootstrap';
81
+ /** Fetch the active delegations the current user may start (user/activedelegations). */
82
+ class LoadDelegationCandidates {
83
+ static type = '[DelegationSession] Load Candidates';
80
84
  }
81
- /** Replaces the candidate pool (called after LoginSuccess or when user.delegations changes). */
82
- class SetDelegationCandidates {
83
- candidates;
84
- static type = '[DelegationSession] Set Candidates';
85
- constructor(candidates) {
86
- this.candidates = candidates;
87
- }
88
- }
89
- /** Start a delegated session for the given assignment. */
85
+ /** Start a delegated session for the given assignment row. */
90
86
  class StartDelegationSession {
91
- delegationId;
87
+ delegation;
92
88
  static type = '[DelegationSession] Start';
93
- constructor(delegationId) {
94
- this.delegationId = delegationId;
89
+ constructor(delegation) {
90
+ this.delegation = delegation;
95
91
  }
96
92
  }
97
- /** End the current delegated session (server + local). */
93
+ /** End the current delegated session. Client-local only (doc 05). */
98
94
  class EndDelegationSession {
99
- options;
95
+ reason;
100
96
  static type = '[DelegationSession] End';
101
- constructor(options = {}) {
102
- this.options = options;
97
+ constructor(reason = 'Manual') {
98
+ this.reason = reason;
103
99
  }
104
100
  }
105
- /** Atomically end current + start another. */
101
+ /** Switch directly from the current session to another delegation. */
106
102
  class SwitchDelegationSession {
107
- delegationId;
103
+ delegation;
108
104
  static type = '[DelegationSession] Switch';
109
- constructor(delegationId) {
110
- this.delegationId = delegationId;
111
- }
112
- }
113
- /** Refresh the delegation access token via refresh-token rotation. */
114
- class RefreshDelegationSession {
115
- static type = '[DelegationSession] Refresh';
116
- }
117
- /** Verify the current session against the server (used after bootstrap). */
118
- class VerifyDelegationSession {
119
- static type = '[DelegationSession] Verify';
120
- }
121
- /** Surface a recoverable session error from any pipeline (e.g. 403 codes). */
122
- class DelegationSessionInvalidated {
123
- reason;
124
- static type = '[DelegationSession] Invalidated';
125
- constructor(reason) {
126
- this.reason = reason;
105
+ constructor(delegation) {
106
+ this.delegation = delegation;
127
107
  }
128
108
  }
129
109
 
130
- const DELEGATION_SESSION_STORAGE_KEY = 'mt.delegation.session.v1';
131
- function toActiveSession(pair) {
132
- return {
133
- accessToken: pair.accessToken,
134
- accessTokenExpiresAtUtc: pair.accessTokenExpiresAtUtc,
135
- refreshToken: pair.refreshToken,
136
- refreshTokenExpiresAtUtc: pair.refreshTokenExpiresAtUtc,
137
- delegation: pair.delegation,
138
- };
139
- }
140
110
  var DelegationSessionActionKey;
141
111
  (function (DelegationSessionActionKey) {
142
- DelegationSessionActionKey["Bootstrap"] = "bootstrap";
112
+ DelegationSessionActionKey["LoadCandidates"] = "loadCandidates";
143
113
  DelegationSessionActionKey["StartSession"] = "startSession";
144
- DelegationSessionActionKey["EndSession"] = "endSession";
145
- DelegationSessionActionKey["RefreshSession"] = "refreshSession";
146
- DelegationSessionActionKey["GetSession"] = "getSession";
147
114
  })(DelegationSessionActionKey || (DelegationSessionActionKey = {}));
148
115
 
149
116
  var __decorate$1 = (this && this.__decorate) || function (decorators, target, key, desc) {
@@ -153,74 +120,29 @@ var __decorate$1 = (this && this.__decorate) || function (decorators, target, ke
153
120
  return c > 3 && r && Object.defineProperty(target, key, r), r;
154
121
  };
155
122
  const BASE$1 = 'identity/delegations';
156
- // ---------------------------------------------------------------------------
157
- // Action shells for loose coupling with gateway-auth (matched by `type` string).
158
- // ---------------------------------------------------------------------------
123
+ // Action shells matching gateway-auth type strings — loose coupling, matched by
124
+ // `type` only so this package does not depend on @masterteam/gateway-auth.
159
125
  class GatewayLoginSuccessShell {
160
126
  static type = '[Auth] Login Success';
161
127
  }
162
128
  class GatewayLogoutShell {
163
129
  static type = '[Auth] Logout';
164
130
  }
165
- function readStored() {
166
- try {
167
- const raw = sessionStorage.getItem(DELEGATION_SESSION_STORAGE_KEY);
168
- if (!raw)
169
- return null;
170
- const parsed = JSON.parse(raw);
171
- if (!parsed?.accessToken || !parsed?.accessTokenExpiresAtUtc)
172
- return null;
173
- if (new Date(parsed.accessTokenExpiresAtUtc).getTime() < Date.now()) {
174
- sessionStorage.removeItem(DELEGATION_SESSION_STORAGE_KEY);
175
- return null;
176
- }
177
- return parsed;
178
- }
179
- catch {
180
- return null;
181
- }
182
- }
183
- function writeStored(value) {
184
- try {
185
- if (value) {
186
- sessionStorage.setItem(DELEGATION_SESSION_STORAGE_KEY, JSON.stringify(value));
187
- }
188
- else {
189
- sessionStorage.removeItem(DELEGATION_SESSION_STORAGE_KEY);
190
- }
191
- }
192
- catch {
193
- /* sessionStorage may be unavailable in some embeds; ignore */
194
- }
195
- }
196
- /**
197
- * Filters a delegation list to "candidates the user could activate".
198
- * Active + StartSession in allowedActions is the runtime gate.
199
- */
200
- function pickCandidates(delegations) {
201
- if (!Array.isArray(delegations))
202
- return [];
203
- return delegations.filter((d) => d.status === 'Active' && d.allowedActions?.includes('StartSession'));
131
+ function canStart(row) {
132
+ return (row.effectiveStatus === 'Active' &&
133
+ row.allowedActions?.includes('StartSession'));
204
134
  }
205
135
  let DelegationSessionState = class DelegationSessionState {
206
136
  http = inject(HttpClient);
207
137
  actions$ = inject(Actions);
208
138
  store = inject(Store);
209
139
  constructor() {
210
- // ---- Bridge to gateway-auth lifecycle (loose coupling via Actions stream) ----
211
- // We use local action *shells* matching gateway-auth's type strings to avoid
212
- // a hard project dependency on @masterteam/gateway-auth. NGXS matches actions
213
- // by the static `type` field, so any class with the same type string works.
214
140
  this.actions$
215
141
  .pipe(ofActionSuccessful(GatewayLoginSuccessShell))
216
- .subscribe(() => {
217
- this.store.dispatch(new BootstrapDelegationSession());
218
- });
142
+ .subscribe(() => this.store.dispatch(new LoadDelegationCandidates()));
219
143
  this.actions$
220
144
  .pipe(ofActionDispatched(GatewayLogoutShell))
221
- .subscribe(() => {
222
- this.store.dispatch(new EndDelegationSession({ silent: true }));
223
- });
145
+ .subscribe(() => this.store.dispatch(new EndDelegationSession('Logout')));
224
146
  }
225
147
  // ---------------------------------------------------------------------------
226
148
  // Selectors
@@ -243,116 +165,44 @@ let DelegationSessionState = class DelegationSessionState {
243
165
  // ---------------------------------------------------------------------------
244
166
  // Actions
245
167
  // ---------------------------------------------------------------------------
246
- bootstrap(ctx) {
247
- // 1. Hydrate active session from sessionStorage (tab-local).
248
- const stored = readStored();
249
- ctx.patchState({ active: stored });
250
- // 2. Hydrate candidates from gateway-auth user.delegations (loose lookup).
251
- const authUser = this.store.snapshot()['auth'];
252
- const userDelegations = authUser?.user
253
- ?.delegations ?? [];
254
- ctx.patchState({ candidates: pickCandidates(userDelegations) });
255
- // 3. If we had a stored session, verify it against the server.
256
- if (stored) {
257
- this.store.dispatch(new VerifyDelegationSession());
258
- }
259
- }
260
- setCandidates(ctx, { candidates }) {
261
- ctx.patchState({ candidates });
262
- }
263
- verify(ctx) {
264
- const req$ = this.http.get(`${BASE$1}/session`);
168
+ loadCandidates(ctx) {
169
+ const req$ = this.http.get(`${BASE$1}/user/activedelegations`);
265
170
  return handleApiRequest({
266
171
  ctx,
267
- key: DelegationSessionActionKey.GetSession,
172
+ key: DelegationSessionActionKey.LoadCandidates,
268
173
  request$: req$,
269
- onSuccess: (response) => {
270
- if (!response.data) {
271
- writeStored(null);
272
- return { active: null };
273
- }
274
- const current = ctx.getState().active;
275
- if (current && response.data.id === current.delegation.id) {
276
- // Server confirms the same delegation — keep token, just refresh summary.
277
- const updated = {
278
- ...current,
279
- delegation: response.data,
280
- };
281
- writeStored(updated);
282
- return { active: updated };
283
- }
284
- // Mismatch — drop local session.
285
- writeStored(null);
286
- return { active: null };
287
- },
174
+ onSuccess: (response) => ({
175
+ candidates: (response.data?.items ?? []).filter(canStart),
176
+ }),
288
177
  });
289
178
  }
290
- start(ctx, { delegationId }) {
291
- const req$ = this.http.post(`${BASE$1}/${delegationId}/token`, {});
179
+ start(ctx, { delegation }) {
180
+ const req$ = this.http.post(`${BASE$1}/delegationToken/${delegation.delegationId}`, {});
292
181
  return handleApiRequest({
293
182
  ctx,
294
183
  key: DelegationSessionActionKey.StartSession,
295
184
  request$: req$,
296
- onSuccess: (response) => {
297
- const active = toActiveSession(response.data);
298
- writeStored(active);
299
- return { active };
300
- },
185
+ onSuccess: (response) => ({
186
+ active: { token: response.data, delegation },
187
+ }),
301
188
  });
302
189
  }
303
- end(ctx, { options }) {
304
- const state = ctx.getState();
305
- // Always clear local state immediately.
306
- writeStored(null);
190
+ end(ctx) {
191
+ // Client-local only — there is no server end-session endpoint (doc 05).
307
192
  ctx.patchState({ active: null });
308
- if (!state.active || options.silent) {
309
- return EMPTY;
310
- }
311
- // Fire-and-forget the server end call; failures don't roll back local clear.
312
- return this.http
313
- .post(`${BASE$1}/session/end`, {})
314
- .pipe(catchError(() => EMPTY));
193
+ return EMPTY;
315
194
  }
316
- switch(ctx, { delegationId }) {
195
+ switch(ctx, { delegation }) {
317
196
  return this.store
318
- .dispatch(new EndDelegationSession({ silent: true }))
319
- .pipe(switchMap(() => this.store.dispatch(new StartDelegationSession(delegationId))));
320
- }
321
- refresh(ctx) {
322
- const current = ctx.getState().active;
323
- if (!current)
324
- return EMPTY;
325
- const req$ = this.http.post(`${BASE$1}/token/refresh`, { refreshToken: current.refreshToken });
326
- return handleApiRequest({
327
- ctx,
328
- key: DelegationSessionActionKey.RefreshSession,
329
- request$: req$,
330
- onSuccess: (response) => {
331
- const active = toActiveSession(response.data);
332
- writeStored(active);
333
- return { active };
334
- },
335
- });
336
- }
337
- invalidated(ctx) {
338
- // Same effect as EndDelegationSession({ silent: true }), but as a distinct
339
- // signal so UI can react with a toast or modal.
340
- writeStored(null);
341
- ctx.patchState({ active: null });
342
- return EMPTY;
197
+ .dispatch(new EndDelegationSession('Manual'))
198
+ .pipe(switchMap(() => this.store.dispatch(new StartDelegationSession(delegation))));
343
199
  }
344
200
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionState, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
345
201
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionState });
346
202
  };
347
203
  __decorate$1([
348
- Action(BootstrapDelegationSession)
349
- ], DelegationSessionState.prototype, "bootstrap", null);
350
- __decorate$1([
351
- Action(SetDelegationCandidates)
352
- ], DelegationSessionState.prototype, "setCandidates", null);
353
- __decorate$1([
354
- Action(VerifyDelegationSession)
355
- ], DelegationSessionState.prototype, "verify", null);
204
+ Action(LoadDelegationCandidates)
205
+ ], DelegationSessionState.prototype, "loadCandidates", null);
356
206
  __decorate$1([
357
207
  Action(StartDelegationSession)
358
208
  ], DelegationSessionState.prototype, "start", null);
@@ -362,12 +212,6 @@ __decorate$1([
362
212
  __decorate$1([
363
213
  Action(SwitchDelegationSession)
364
214
  ], DelegationSessionState.prototype, "switch", null);
365
- __decorate$1([
366
- Action(RefreshDelegationSession)
367
- ], DelegationSessionState.prototype, "refresh", null);
368
- __decorate$1([
369
- Action(DelegationSessionInvalidated)
370
- ], DelegationSessionState.prototype, "invalidated", null);
371
215
  __decorate$1([
372
216
  Selector()
373
217
  ], DelegationSessionState, "getActive", null);
@@ -396,7 +240,7 @@ DelegationSessionState = __decorate$1([
396
240
  ], DelegationSessionState);
397
241
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionState, decorators: [{
398
242
  type: Injectable
399
- }], ctorParameters: () => [], propDecorators: { bootstrap: [], setCandidates: [], verify: [], start: [], end: [], switch: [], refresh: [], invalidated: [] } });
243
+ }], ctorParameters: () => [], propDecorators: { loadCandidates: [], start: [], end: [], switch: [] } });
400
244
 
401
245
  class DelegationSessionFacade {
402
246
  store = inject(Store);
@@ -408,41 +252,31 @@ class DelegationSessionFacade {
408
252
  isDelegated = select(DelegationSessionState.isDelegated);
409
253
  loadingActive = select(DelegationSessionState.getLoadingActive);
410
254
  // ---------------------------------------------------------------------------
411
- // Derived (used by interceptor + topbar menu)
255
+ // Derived (interceptor + topbar)
412
256
  // ---------------------------------------------------------------------------
413
- delegator = computed(() => this.active()?.delegation.delegator ?? null, ...(ngDevMode ? [{ debugName: "delegator" }] : /* istanbul ignore next */ []));
414
- accessToken = computed(() => this.active()?.accessToken ?? null, ...(ngDevMode ? [{ debugName: "accessToken" }] : /* istanbul ignore next */ []));
415
- accessTokenExpiresAtUtc = computed(() => this.active()?.accessTokenExpiresAtUtc ?? null, ...(ngDevMode ? [{ debugName: "accessTokenExpiresAtUtc" }] : /* istanbul ignore next */ []));
257
+ /** Raw delegation token for the `app-delegation` header. */
258
+ token = computed(() => this.active()?.token ?? null, ...(ngDevMode ? [{ debugName: "token" }] : /* istanbul ignore next */ []));
259
+ /** On-behalf-of (delegator). */
260
+ onBehalfOf = computed(() => this.active()?.delegation.delegator ?? null, ...(ngDevMode ? [{ debugName: "onBehalfOf" }] : /* istanbul ignore next */ []));
261
+ /** Executed-by (actual logged-in / delegated user). */
262
+ executedBy = computed(() => this.active()?.delegation.delegatedUser ?? null, ...(ngDevMode ? [{ debugName: "executedBy" }] : /* istanbul ignore next */ []));
416
263
  hasCandidates = computed(() => this.candidates().length > 0, ...(ngDevMode ? [{ debugName: "hasCandidates" }] : /* istanbul ignore next */ []));
417
- isStartingSession = computed(() => this.loadingActive().includes(DelegationSessionActionKey.StartSession), ...(ngDevMode ? [{ debugName: "isStartingSession" }] : /* istanbul ignore next */ []));
418
- isEndingSession = computed(() => this.loadingActive().includes(DelegationSessionActionKey.EndSession), ...(ngDevMode ? [{ debugName: "isEndingSession" }] : /* istanbul ignore next */ []));
419
- isRefreshingSession = computed(() => this.loadingActive().includes(DelegationSessionActionKey.RefreshSession), ...(ngDevMode ? [{ debugName: "isRefreshingSession" }] : /* istanbul ignore next */ []));
264
+ isStarting = computed(() => this.loadingActive().includes(DelegationSessionActionKey.StartSession), ...(ngDevMode ? [{ debugName: "isStarting" }] : /* istanbul ignore next */ []));
265
+ isLoadingCandidates = computed(() => this.loadingActive().includes(DelegationSessionActionKey.LoadCandidates), ...(ngDevMode ? [{ debugName: "isLoadingCandidates" }] : /* istanbul ignore next */ []));
420
266
  // ---------------------------------------------------------------------------
421
267
  // Dispatchers
422
268
  // ---------------------------------------------------------------------------
423
- bootstrap() {
424
- return this.store.dispatch(new BootstrapDelegationSession());
425
- }
426
- setCandidates(candidates) {
427
- return this.store.dispatch(new SetDelegationCandidates(candidates));
428
- }
429
- startSession(delegationId) {
430
- return this.store.dispatch(new StartDelegationSession(delegationId));
269
+ loadCandidates() {
270
+ return this.store.dispatch(new LoadDelegationCandidates());
431
271
  }
432
- endSession(opts = {}) {
433
- return this.store.dispatch(new EndDelegationSession(opts));
272
+ startSession(delegation) {
273
+ return this.store.dispatch(new StartDelegationSession(delegation));
434
274
  }
435
- switchSession(delegationId) {
436
- return this.store.dispatch(new SwitchDelegationSession(delegationId));
275
+ switchSession(delegation) {
276
+ return this.store.dispatch(new SwitchDelegationSession(delegation));
437
277
  }
438
- refreshSession() {
439
- return this.store.dispatch(new RefreshDelegationSession());
440
- }
441
- verifySession() {
442
- return this.store.dispatch(new VerifyDelegationSession());
443
- }
444
- invalidate(reason = 'Unknown') {
445
- return this.store.dispatch(new DelegationSessionInvalidated(reason));
278
+ endSession(reason = 'Manual') {
279
+ return this.store.dispatch(new EndDelegationSession(reason));
446
280
  }
447
281
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionFacade, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
448
282
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionFacade, providedIn: 'root' });
@@ -453,30 +287,28 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
453
287
  }] });
454
288
 
455
289
  /**
456
- * Confirmation dialog opened by the topbar menu before starting (or switching to)
457
- * a delegated session. Calls the facade and closes itself on success.
290
+ * Confirmation dialog before starting (or switching to) a delegated session.
291
+ * Calls the facade, which POSTs to `delegationToken/{id}` and stores the raw
292
+ * token in memory only.
458
293
  */
459
294
  class StartSessionDialog {
460
- candidate = input.required(...(ngDevMode ? [{ debugName: "candidate" }] : /* istanbul ignore next */ []));
295
+ delegation = input.required(...(ngDevMode ? [{ debugName: "delegation" }] : /* istanbul ignore next */ []));
461
296
  intent = input('start', ...(ngDevMode ? [{ debugName: "intent" }] : /* istanbul ignore next */ []));
462
297
  ref = inject(ModalRef);
463
298
  facade = inject(DelegationSessionFacade);
464
- isBusy = computed(() => this.facade.isStartingSession() || this.facade.isEndingSession(), ...(ngDevMode ? [{ debugName: "isBusy" }] : /* istanbul ignore next */ []));
465
- title = computed(() => this.intent() === 'switch'
466
- ? 'delegations.switch-session-confirm-title'
467
- : 'delegations.start-session-confirm-title', ...(ngDevMode ? [{ debugName: "title" }] : /* istanbul ignore next */ []));
468
- body = computed(() => this.intent() === 'switch'
469
- ? 'delegations.switch-session-confirm-body'
470
- : 'delegations.start-session-confirm-body', ...(ngDevMode ? [{ debugName: "body" }] : /* istanbul ignore next */ []));
299
+ isBusy = this.facade.isStarting;
300
+ bodyKey = computed(() => this.intent() === 'switch'
301
+ ? 'delegations.session.switchConfirmBody'
302
+ : 'delegations.session.startConfirmBody', ...(ngDevMode ? [{ debugName: "bodyKey" }] : /* istanbul ignore next */ []));
471
303
  confirm() {
472
- const id = this.candidate().id;
304
+ const row = this.delegation();
473
305
  const op$ = this.intent() === 'switch'
474
- ? this.facade.switchSession(id)
475
- : this.facade.startSession(id);
306
+ ? this.facade.switchSession(row)
307
+ : this.facade.startSession(row);
476
308
  op$.subscribe({
477
309
  next: () => this.ref.close(true),
478
310
  error: () => {
479
- /* facade publishes the error to the toast pipeline; just stay open */
311
+ /* error surfaced via shared toast pipeline; keep dialog open */
480
312
  },
481
313
  });
482
314
  }
@@ -484,27 +316,24 @@ class StartSessionDialog {
484
316
  this.ref.close(false);
485
317
  }
486
318
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: StartSessionDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
487
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: StartSessionDialog, isStandalone: true, selector: "mt-start-session-dialog", inputs: { candidate: { classPropertyName: "candidate", publicName: "candidate", 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 <mt-avatar\r\n [image]=\"candidate().delegator.photo || ''\"\r\n icon=\"user.user-01\"\r\n styleClass=\"w-12! h-12! text-lg! bg-surface-200!\"\r\n ></mt-avatar>\r\n <div class=\"flex flex-col min-w-0\">\r\n <div class=\"text-base font-medium text-gray-900 truncate\">\r\n {{ candidate().delegator.displayName }}\r\n </div>\r\n @if (candidate().delegator.email) {\r\n <div class=\"text-xs text-gray-500 truncate\">\r\n {{ candidate().delegator.email }}\r\n </div>\r\n }\r\n @if (candidate().scopeSummary) {\r\n <div class=\"text-xs text-gray-500 mt-1\">\r\n {{ t(\"delegations.scope\") }}: {{ candidate().scopeSummary }}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <p class=\"text-sm text-gray-600 leading-relaxed\">\r\n {{ t(body()) }}\r\n </p>\r\n\r\n <div class=\"flex justify-end gap-2 pt-2 border-t border-gray-200\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('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]=\"\r\n intent() === 'switch'\r\n ? t('delegations.switch-session')\r\n : t('delegations.start-session')\r\n \"\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: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
319
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: StartSessionDialog, isStandalone: true, selector: "mt-start-session-dialog", inputs: { delegation: { classPropertyName: "delegation", publicName: "delegation", isSignal: true, isRequired: true, transformFunction: null }, intent: { classPropertyName: "intent", publicName: "intent", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col gap-4 p-2\">\n <div class=\"flex items-start gap-3\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-12! h-12! text-lg! bg-surface-200!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <div class=\"text-base font-medium text-gray-900 truncate\">\n {{ delegation().delegator.displayName }}\n </div>\n @if (delegation().delegator.email) {\n <div class=\"text-xs text-gray-500 truncate\">\n {{ delegation().delegator.email }}\n </div>\n }\n @if (delegation().scopeSummary) {\n <div class=\"text-xs text-gray-500 mt-1\">\n {{ t(\"delegations.column.scopeSummary\") }}:\n {{ delegation().scopeSummary }}\n </div>\n }\n </div>\n </div>\n\n <p class=\"text-sm text-gray-600 leading-relaxed\">\n {{ t(bodyKey()) }}\n </p>\n\n <div class=\"flex justify-end gap-2 pt-2 border-t border-gray-200\">\n <mt-button\n variant=\"outlined\"\n color=\"secondary\"\n [label]=\"t('delegations.common.cancel')\"\n [disabled]=\"isBusy()\"\n (click)=\"cancel()\"\n ></mt-button>\n <mt-button\n color=\"primary\"\n icon=\"user.users-check\"\n [label]=\"t('delegations.action.startSession')\"\n [loading]=\"isBusy()\"\n (click)=\"confirm()\"\n ></mt-button>\n </div>\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
488
320
  }
489
321
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: StartSessionDialog, decorators: [{
490
322
  type: Component,
491
- args: [{ selector: 'mt-start-session-dialog', standalone: true, imports: [CommonModule, Avatar, Button, 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 <mt-avatar\r\n [image]=\"candidate().delegator.photo || ''\"\r\n icon=\"user.user-01\"\r\n styleClass=\"w-12! h-12! text-lg! bg-surface-200!\"\r\n ></mt-avatar>\r\n <div class=\"flex flex-col min-w-0\">\r\n <div class=\"text-base font-medium text-gray-900 truncate\">\r\n {{ candidate().delegator.displayName }}\r\n </div>\r\n @if (candidate().delegator.email) {\r\n <div class=\"text-xs text-gray-500 truncate\">\r\n {{ candidate().delegator.email }}\r\n </div>\r\n }\r\n @if (candidate().scopeSummary) {\r\n <div class=\"text-xs text-gray-500 mt-1\">\r\n {{ t(\"delegations.scope\") }}: {{ candidate().scopeSummary }}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n\r\n <p class=\"text-sm text-gray-600 leading-relaxed\">\r\n {{ t(body()) }}\r\n </p>\r\n\r\n <div class=\"flex justify-end gap-2 pt-2 border-t border-gray-200\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('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]=\"\r\n intent() === 'switch'\r\n ? t('delegations.switch-session')\r\n : t('delegations.start-session')\r\n \"\r\n [loading]=\"isBusy()\"\r\n (click)=\"confirm()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n" }]
492
- }], propDecorators: { candidate: [{ type: i0.Input, args: [{ isSignal: true, alias: "candidate", required: true }] }], intent: [{ type: i0.Input, args: [{ isSignal: true, alias: "intent", required: false }] }] } });
323
+ args: [{ selector: 'mt-start-session-dialog', standalone: true, imports: [CommonModule, Avatar, Button, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col gap-4 p-2\">\n <div class=\"flex items-start gap-3\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-12! h-12! text-lg! bg-surface-200!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <div class=\"text-base font-medium text-gray-900 truncate\">\n {{ delegation().delegator.displayName }}\n </div>\n @if (delegation().delegator.email) {\n <div class=\"text-xs text-gray-500 truncate\">\n {{ delegation().delegator.email }}\n </div>\n }\n @if (delegation().scopeSummary) {\n <div class=\"text-xs text-gray-500 mt-1\">\n {{ t(\"delegations.column.scopeSummary\") }}:\n {{ delegation().scopeSummary }}\n </div>\n }\n </div>\n </div>\n\n <p class=\"text-sm text-gray-600 leading-relaxed\">\n {{ t(bodyKey()) }}\n </p>\n\n <div class=\"flex justify-end gap-2 pt-2 border-t border-gray-200\">\n <mt-button\n variant=\"outlined\"\n color=\"secondary\"\n [label]=\"t('delegations.common.cancel')\"\n [disabled]=\"isBusy()\"\n (click)=\"cancel()\"\n ></mt-button>\n <mt-button\n color=\"primary\"\n icon=\"user.users-check\"\n [label]=\"t('delegations.action.startSession')\"\n [loading]=\"isBusy()\"\n (click)=\"confirm()\"\n ></mt-button>\n </div>\n </div>\n</ng-container>\n" }]
324
+ }], propDecorators: { delegation: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegation", required: true }] }], intent: [{ type: i0.Input, args: [{ isSignal: true, alias: "intent", required: false }] }] } });
493
325
 
494
326
  /**
495
- * Topbar surface for the delegation runtime.
327
+ * Topbar surface for the delegation runtime (doc 05, 09).
496
328
  *
497
329
  * State A — no candidates, no active session: renders nothing.
498
- * State B — candidates exist, no active session: shows a "Switch identity" affordance.
499
- * State C — active delegation session: shows the delegator name + back/switch.
500
- *
501
- * Designed to be projected into a host topbar's `[actions]` slot (alongside the
502
- * existing user popover) — it does NOT replace the user dropdown.
330
+ * State B — candidates exist, no active session: lets the user start a session.
331
+ * State C — active delegated session: shows "Acting on behalf of {delegator}" +
332
+ * "Executed by {actual user}", plus Back / Switch.
503
333
  */
504
334
  class TopbarDelegationMenu {
505
335
  /** Path to the management page, e.g. `/control-panel/delegations` or `/delegations`. */
506
336
  managePath = input('/delegations', ...(ngDevMode ? [{ debugName: "managePath" }] : /* istanbul ignore next */ []));
507
- /** Compact mode hides the inline delegator name next to the button. */
508
337
  compact = input(false, ...(ngDevMode ? [{ debugName: "compact" }] : /* istanbul ignore next */ []));
509
338
  facade = inject(DelegationSessionFacade);
510
339
  modal = inject(ModalService);
@@ -513,10 +342,8 @@ class TopbarDelegationMenu {
513
342
  active = this.facade.active;
514
343
  candidates = this.facade.candidates;
515
344
  hasCandidates = this.facade.hasCandidates;
516
- isStarting = this.facade.isStartingSession;
517
- isEnding = this.facade.isEndingSession;
518
- delegatorName = computed(() => this.active()?.delegation.delegator.displayName ?? '', ...(ngDevMode ? [{ debugName: "delegatorName" }] : /* istanbul ignore next */ []));
519
- /** State machine: which UI to render. */
345
+ onBehalfOf = this.facade.onBehalfOf;
346
+ executedBy = this.facade.executedBy;
520
347
  mode = computed(() => {
521
348
  if (this.active())
522
349
  return 'active';
@@ -527,37 +354,32 @@ class TopbarDelegationMenu {
527
354
  togglePopover(event) {
528
355
  this.popover().toggle(event);
529
356
  }
530
- startCandidate(candidate, event) {
357
+ start(row, event) {
531
358
  event.stopPropagation();
532
359
  this.popover().hide();
533
- this.openConfirmation(candidate, 'start');
360
+ this.openConfirm(row, 'start');
534
361
  }
535
- switchTo(candidate, event) {
362
+ switchTo(row, event) {
536
363
  event.stopPropagation();
537
364
  this.popover().hide();
538
- this.openConfirmation(candidate, 'switch');
365
+ this.openConfirm(row, 'switch');
539
366
  }
540
367
  endSession(event) {
541
368
  event.stopPropagation();
542
369
  this.popover().hide();
543
- this.facade.endSession().subscribe();
370
+ this.facade.endSession('Manual').subscribe();
544
371
  }
545
- openConfirmation(candidate, intent) {
372
+ openConfirm(row, intent) {
546
373
  this.modal.openModal(StartSessionDialog, 'dialog', {
547
- header: this.transloco.translate(intent === 'switch'
548
- ? 'delegations.switch-session'
549
- : 'delegations.start-session'),
374
+ header: this.transloco.translate('delegations.action.startSession'),
550
375
  styleClass: '!w-[min(96vw,28rem)] !max-w-[96vw]',
551
376
  dismissableMask: true,
552
377
  dismissible: true,
553
- inputValues: {
554
- candidate,
555
- intent,
556
- },
378
+ inputValues: { delegation: row, intent },
557
379
  });
558
380
  }
559
381
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: TopbarDelegationMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
560
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: TopbarDelegationMenu, isStandalone: true, selector: "mt-topbar-delegation-menu", inputs: { managePath: { classPropertyName: "managePath", publicName: "managePath", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "popover", first: true, predicate: ["popover"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n @if (mode() !== \"hidden\") {\r\n <div class=\"mt-delegation-trigger flex items-center gap-2\">\r\n <button\r\n type=\"button\"\r\n class=\"mt-delegation-trigger__button flex items-center gap-2 px-2 py-1 rounded-full cursor-pointer hover:bg-surface-100\"\r\n [attr.aria-label]=\"t('delegations.delegation-menu')\"\r\n (click)=\"togglePopover($event)\"\r\n >\r\n @if (mode() === \"active\") {\r\n <mt-icon\r\n icon=\"user.users-check\"\r\n styleClass=\"text-lg text-primary\"\r\n ></mt-icon>\r\n @if (!compact()) {\r\n <span class=\"mt-delegation-trigger__label text-sm font-medium\">\r\n {{ t(\"delegations.acting-on-behalf-of\") }}: {{ delegatorName() }}\r\n </span>\r\n }\r\n <mt-delegation-status-chip\r\n status=\"Active\"\r\n ></mt-delegation-status-chip>\r\n } @else {\r\n <mt-icon\r\n icon=\"user.users-plus\"\r\n styleClass=\"text-lg text-primary\"\r\n ></mt-icon>\r\n @if (!compact()) {\r\n <span class=\"mt-delegation-trigger__label text-sm font-medium\">\r\n {{ t(\"delegations.delegations-available\") }}\r\n </span>\r\n }\r\n }\r\n </button>\r\n </div>\r\n\r\n <p-popover #popover [pt]=\"{ content: { class: 'p-0!' } }\">\r\n <div class=\"flex flex-col min-w-80 max-w-96 p-0\">\r\n @if (mode() === \"active\" && active(); as session) {\r\n <!-- ============================================================ -->\r\n <!-- STATE C \u2014 active session -->\r\n <!-- ============================================================ -->\r\n <div\r\n class=\"flex items-start gap-3 border-b border-gray-200 px-4 py-4\"\r\n >\r\n <mt-avatar\r\n [image]=\"session.delegation.delegator.photo || ''\"\r\n icon=\"user.user-01\"\r\n styleClass=\"w-11! h-11! text-xl! text-gray-600! bg-surface-300!\"\r\n ></mt-avatar>\r\n <div class=\"flex flex-col min-w-0 flex-1\">\r\n <div class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.acting-on-behalf-of\") }}\r\n </div>\r\n <div class=\"text-sm font-medium text-gray-900 truncate\">\r\n {{ session.delegation.delegator.displayName }}\r\n </div>\r\n @if (session.delegation.scopeSummary) {\r\n <div\r\n class=\"text-xs text-gray-500 mt-1 truncate\"\r\n [title]=\"session.delegation.scopeSummary\"\r\n >\r\n {{ t(\"delegations.scope\") }}:\r\n {{ session.delegation.scopeSummary }}\r\n </div>\r\n }\r\n <div class=\"text-xs text-gray-400 mt-1\">\r\n {{ t(\"delegations.session-ends-at\") }}:\r\n {{ session.accessTokenExpiresAtUtc | date: \"short\" }}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"flex flex-col gap-1 px-2 py-2\">\r\n <mt-button\r\n variant=\"text\"\r\n icon=\"arrow.arrow-left\"\r\n [label]=\"t('delegations.back-to-my-session')\"\r\n [loading]=\"isEnding()\"\r\n styleClass=\"w-full justify-start\"\r\n (click)=\"endSession($event)\"\r\n ></mt-button>\r\n </div>\r\n\r\n @if (candidates().length > 1) {\r\n <div class=\"border-t border-gray-200 pt-2 px-2 pb-2\">\r\n <div class=\"text-xs text-gray-500 px-2 pb-1\">\r\n {{ t(\"delegations.switch-to-another\") }}\r\n </div>\r\n @for (cand of candidates(); track cand.id) {\r\n @if (cand.id !== session.delegation.id) {\r\n <button\r\n type=\"button\"\r\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer\"\r\n (click)=\"switchTo(cand, $event)\"\r\n >\r\n <div class=\"flex items-center gap-2 min-w-0\">\r\n <mt-avatar\r\n [image]=\"cand.delegator.photo || ''\"\r\n icon=\"user.user-01\"\r\n styleClass=\"w-7! h-7! text-sm! bg-surface-200!\"\r\n ></mt-avatar>\r\n <span class=\"text-sm text-gray-900 truncate\">\r\n {{ cand.delegator.displayName }}\r\n </span>\r\n </div>\r\n <mt-icon\r\n icon=\"arrow.switch-horizontal-01\"\r\n styleClass=\"text-base text-gray-400\"\r\n ></mt-icon>\r\n </button>\r\n }\r\n }\r\n </div>\r\n }\r\n } @else if (mode() === \"candidates\") {\r\n <!-- ============================================================ -->\r\n <!-- STATE B \u2014 candidates available, no active session -->\r\n <!-- ============================================================ -->\r\n <div class=\"border-b border-gray-200 px-4 py-3\">\r\n <div class=\"text-sm font-medium text-gray-900\">\r\n {{ t(\"delegations.you-can-act-as\") }}\r\n </div>\r\n <div class=\"text-xs text-gray-500 mt-1\">\r\n {{ t(\"delegations.choose-delegator-hint\") }}\r\n </div>\r\n </div>\r\n <div class=\"flex flex-col gap-1 px-2 py-2 max-h-72 overflow-y-auto\">\r\n @for (cand of candidates(); track cand.id) {\r\n <button\r\n type=\"button\"\r\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer text-start\"\r\n (click)=\"startCandidate(cand, $event)\"\r\n >\r\n <div class=\"flex items-center gap-2 min-w-0 flex-1\">\r\n <mt-avatar\r\n [image]=\"cand.delegator.photo || ''\"\r\n icon=\"user.user-01\"\r\n styleClass=\"w-9! h-9! text-base! bg-surface-200!\"\r\n ></mt-avatar>\r\n <div class=\"flex flex-col min-w-0\">\r\n <span class=\"text-sm font-medium text-gray-900 truncate\">\r\n {{ cand.delegator.displayName }}\r\n </span>\r\n @if (cand.scopeSummary) {\r\n <span\r\n class=\"text-xs text-gray-500 truncate\"\r\n [title]=\"cand.scopeSummary\"\r\n >\r\n {{ cand.scopeSummary }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n <mt-icon\r\n icon=\"arrow.arrow-right\"\r\n styleClass=\"text-base text-gray-400\"\r\n ></mt-icon>\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Footer (always shown when popover renders) -->\r\n <div class=\"border-t border-gray-200 px-2 py-2\">\r\n <a\r\n [routerLink]=\"managePath()\"\r\n (click)=\"popover().hide()\"\r\n class=\"flex items-center gap-2 px-3 py-2 rounded-md text-gray-700 hover:bg-surface-100 text-sm cursor-pointer\"\r\n >\r\n <mt-icon\r\n icon=\"general.settings-02\"\r\n styleClass=\"text-base\"\r\n ></mt-icon>\r\n <span>{{ t(\"delegations.manage-delegations\") }}</span>\r\n </a>\r\n </div>\r\n </div>\r\n </p-popover>\r\n }\r\n</ng-container>\r\n", styles: [":host{display:inline-flex;align-items:center}.mt-delegation-trigger__button{transition:background-color .2s ease-out}.mt-delegation-trigger__label{max-width:16rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host-context(.dark) .mt-delegation-trigger__button:hover{background-color:#94a3b826}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationStatusChip, selector: "mt-delegation-status-chip", inputs: ["status"] }, { kind: "pipe", type: i1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
382
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: TopbarDelegationMenu, isStandalone: true, selector: "mt-topbar-delegation-menu", inputs: { managePath: { classPropertyName: "managePath", publicName: "managePath", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "popover", first: true, predicate: ["popover"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t\">\n @if (mode() !== \"hidden\") {\n <div class=\"mt-delegation-trigger flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"mt-delegation-trigger__button flex items-center gap-2 px-2 py-1 rounded-full cursor-pointer hover:bg-surface-100\"\n [attr.aria-label]=\"t('delegations.title')\"\n (click)=\"togglePopover($event)\"\n >\n @if (mode() === \"active\") {\n <mt-icon\n icon=\"user.users-check\"\n styleClass=\"text-lg text-primary\"\n ></mt-icon>\n @if (!compact()) {\n <span class=\"mt-delegation-trigger__label text-sm font-medium\">\n {{\n t(\"delegations.session.banner\", {\n delegatorName: onBehalfOf()?.displayName,\n })\n }}\n </span>\n }\n <mt-delegation-status-chip\n status=\"Active\"\n ></mt-delegation-status-chip>\n } @else {\n <mt-icon\n icon=\"user.users-plus\"\n styleClass=\"text-lg text-primary\"\n ></mt-icon>\n @if (!compact()) {\n <span class=\"mt-delegation-trigger__label text-sm font-medium\">\n {{ t(\"delegations.session.availableTitle\") }}\n </span>\n }\n }\n </button>\n </div>\n\n <p-popover #popover [pt]=\"{ content: { class: 'p-0!' } }\">\n <div class=\"flex flex-col min-w-80 max-w-96 p-0\">\n @if (mode() === \"active\" && active(); as session) {\n <!-- STATE C \u2014 active session -->\n <div\n class=\"flex items-start gap-3 border-b border-gray-200 px-4 py-4\"\n >\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-11! h-11! text-xl! text-gray-600! bg-surface-300!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0 flex-1\">\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\n </div>\n <div class=\"text-sm font-medium text-gray-900 truncate\">\n {{ onBehalfOf()?.displayName }}\n </div>\n <div class=\"text-xs text-gray-500 mt-1\">\n {{\n t(\"delegations.session.executedBy\", {\n actualUserName: executedBy()?.displayName,\n })\n }}\n </div>\n @if (session.delegation.scopeSummary) {\n <div\n class=\"text-xs text-gray-500 mt-1 truncate\"\n [title]=\"session.delegation.scopeSummary\"\n >\n {{ t(\"delegations.column.scopeSummary\") }}:\n {{ session.delegation.scopeSummary }}\n </div>\n }\n </div>\n </div>\n\n <div class=\"flex flex-col gap-1 px-2 py-2\">\n <mt-button\n variant=\"text\"\n icon=\"arrow.arrow-left\"\n [label]=\"t('delegations.action.endSession')\"\n styleClass=\"w-full justify-start\"\n (click)=\"endSession($event)\"\n ></mt-button>\n </div>\n\n @if (candidates().length > 1) {\n <div class=\"border-t border-gray-200 pt-2 px-2 pb-2\">\n <div class=\"text-xs text-gray-500 px-2 pb-1\">\n {{ t(\"delegations.session.switchToAnother\") }}\n </div>\n @for (cand of candidates(); track cand.delegationId) {\n @if (cand.delegationId !== session.delegation.delegationId) {\n <button\n type=\"button\"\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer\"\n (click)=\"switchTo(cand, $event)\"\n >\n <div class=\"flex items-center gap-2 min-w-0\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-7! h-7! text-sm! bg-surface-200!\"\n ></mt-avatar>\n <span class=\"text-sm text-gray-900 truncate\">\n {{ cand.delegator.displayName }}\n </span>\n </div>\n <mt-icon\n icon=\"arrow.switch-horizontal-01\"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n </button>\n }\n }\n </div>\n }\n } @else if (mode() === \"candidates\") {\n <!-- STATE B \u2014 candidates available -->\n <div class=\"border-b border-gray-200 px-4 py-3\">\n <div class=\"text-sm font-medium text-gray-900\">\n {{ t(\"delegations.session.youCanActAs\") }}\n </div>\n <div class=\"text-xs text-gray-500 mt-1\">\n {{ t(\"delegations.session.chooseDelegatorHint\") }}\n </div>\n </div>\n <div class=\"flex flex-col gap-1 px-2 py-2 max-h-72 overflow-y-auto\">\n @for (cand of candidates(); track cand.delegationId) {\n <button\n type=\"button\"\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer text-start\"\n (click)=\"start(cand, $event)\"\n >\n <div class=\"flex items-center gap-2 min-w-0 flex-1\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-9! h-9! text-base! bg-surface-200!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <span class=\"text-sm font-medium text-gray-900 truncate\">\n {{ cand.delegator.displayName }}\n </span>\n @if (cand.scopeSummary) {\n <span\n class=\"text-xs text-gray-500 truncate\"\n [title]=\"cand.scopeSummary\"\n >\n {{ cand.scopeSummary }}\n </span>\n }\n </div>\n </div>\n <mt-icon\n icon=\"arrow.arrow-right\"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n </button>\n }\n </div>\n }\n\n <!-- Footer -->\n <div class=\"border-t border-gray-200 px-2 py-2\">\n <a\n [routerLink]=\"managePath()\"\n (click)=\"popover().hide()\"\n class=\"flex items-center gap-2 px-3 py-2 rounded-md text-gray-700 hover:bg-surface-100 text-sm cursor-pointer\"\n >\n <mt-icon\n icon=\"general.settings-02\"\n styleClass=\"text-base\"\n ></mt-icon>\n <span>{{ t(\"delegations.session.manage\") }}</span>\n </a>\n </div>\n </div>\n </p-popover>\n }\n</ng-container>\n", styles: [":host{display:inline-flex;align-items:center}.mt-delegation-trigger__button{transition:background-color .2s ease-out}.mt-delegation-trigger__label{max-width:16rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host-context(.dark) .mt-delegation-trigger__button:hover{background-color:#94a3b826}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationStatusChip, selector: "mt-delegation-status-chip", inputs: ["status"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
561
383
  }
562
384
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: TopbarDelegationMenu, decorators: [{
563
385
  type: Component,
@@ -570,7 +392,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
570
392
  RouterLink,
571
393
  TranslocoDirective,
572
394
  DelegationStatusChip,
573
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n @if (mode() !== \"hidden\") {\r\n <div class=\"mt-delegation-trigger flex items-center gap-2\">\r\n <button\r\n type=\"button\"\r\n class=\"mt-delegation-trigger__button flex items-center gap-2 px-2 py-1 rounded-full cursor-pointer hover:bg-surface-100\"\r\n [attr.aria-label]=\"t('delegations.delegation-menu')\"\r\n (click)=\"togglePopover($event)\"\r\n >\r\n @if (mode() === \"active\") {\r\n <mt-icon\r\n icon=\"user.users-check\"\r\n styleClass=\"text-lg text-primary\"\r\n ></mt-icon>\r\n @if (!compact()) {\r\n <span class=\"mt-delegation-trigger__label text-sm font-medium\">\r\n {{ t(\"delegations.acting-on-behalf-of\") }}: {{ delegatorName() }}\r\n </span>\r\n }\r\n <mt-delegation-status-chip\r\n status=\"Active\"\r\n ></mt-delegation-status-chip>\r\n } @else {\r\n <mt-icon\r\n icon=\"user.users-plus\"\r\n styleClass=\"text-lg text-primary\"\r\n ></mt-icon>\r\n @if (!compact()) {\r\n <span class=\"mt-delegation-trigger__label text-sm font-medium\">\r\n {{ t(\"delegations.delegations-available\") }}\r\n </span>\r\n }\r\n }\r\n </button>\r\n </div>\r\n\r\n <p-popover #popover [pt]=\"{ content: { class: 'p-0!' } }\">\r\n <div class=\"flex flex-col min-w-80 max-w-96 p-0\">\r\n @if (mode() === \"active\" && active(); as session) {\r\n <!-- ============================================================ -->\r\n <!-- STATE C \u2014 active session -->\r\n <!-- ============================================================ -->\r\n <div\r\n class=\"flex items-start gap-3 border-b border-gray-200 px-4 py-4\"\r\n >\r\n <mt-avatar\r\n [image]=\"session.delegation.delegator.photo || ''\"\r\n icon=\"user.user-01\"\r\n styleClass=\"w-11! h-11! text-xl! text-gray-600! bg-surface-300!\"\r\n ></mt-avatar>\r\n <div class=\"flex flex-col min-w-0 flex-1\">\r\n <div class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.acting-on-behalf-of\") }}\r\n </div>\r\n <div class=\"text-sm font-medium text-gray-900 truncate\">\r\n {{ session.delegation.delegator.displayName }}\r\n </div>\r\n @if (session.delegation.scopeSummary) {\r\n <div\r\n class=\"text-xs text-gray-500 mt-1 truncate\"\r\n [title]=\"session.delegation.scopeSummary\"\r\n >\r\n {{ t(\"delegations.scope\") }}:\r\n {{ session.delegation.scopeSummary }}\r\n </div>\r\n }\r\n <div class=\"text-xs text-gray-400 mt-1\">\r\n {{ t(\"delegations.session-ends-at\") }}:\r\n {{ session.accessTokenExpiresAtUtc | date: \"short\" }}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div class=\"flex flex-col gap-1 px-2 py-2\">\r\n <mt-button\r\n variant=\"text\"\r\n icon=\"arrow.arrow-left\"\r\n [label]=\"t('delegations.back-to-my-session')\"\r\n [loading]=\"isEnding()\"\r\n styleClass=\"w-full justify-start\"\r\n (click)=\"endSession($event)\"\r\n ></mt-button>\r\n </div>\r\n\r\n @if (candidates().length > 1) {\r\n <div class=\"border-t border-gray-200 pt-2 px-2 pb-2\">\r\n <div class=\"text-xs text-gray-500 px-2 pb-1\">\r\n {{ t(\"delegations.switch-to-another\") }}\r\n </div>\r\n @for (cand of candidates(); track cand.id) {\r\n @if (cand.id !== session.delegation.id) {\r\n <button\r\n type=\"button\"\r\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer\"\r\n (click)=\"switchTo(cand, $event)\"\r\n >\r\n <div class=\"flex items-center gap-2 min-w-0\">\r\n <mt-avatar\r\n [image]=\"cand.delegator.photo || ''\"\r\n icon=\"user.user-01\"\r\n styleClass=\"w-7! h-7! text-sm! bg-surface-200!\"\r\n ></mt-avatar>\r\n <span class=\"text-sm text-gray-900 truncate\">\r\n {{ cand.delegator.displayName }}\r\n </span>\r\n </div>\r\n <mt-icon\r\n icon=\"arrow.switch-horizontal-01\"\r\n styleClass=\"text-base text-gray-400\"\r\n ></mt-icon>\r\n </button>\r\n }\r\n }\r\n </div>\r\n }\r\n } @else if (mode() === \"candidates\") {\r\n <!-- ============================================================ -->\r\n <!-- STATE B \u2014 candidates available, no active session -->\r\n <!-- ============================================================ -->\r\n <div class=\"border-b border-gray-200 px-4 py-3\">\r\n <div class=\"text-sm font-medium text-gray-900\">\r\n {{ t(\"delegations.you-can-act-as\") }}\r\n </div>\r\n <div class=\"text-xs text-gray-500 mt-1\">\r\n {{ t(\"delegations.choose-delegator-hint\") }}\r\n </div>\r\n </div>\r\n <div class=\"flex flex-col gap-1 px-2 py-2 max-h-72 overflow-y-auto\">\r\n @for (cand of candidates(); track cand.id) {\r\n <button\r\n type=\"button\"\r\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer text-start\"\r\n (click)=\"startCandidate(cand, $event)\"\r\n >\r\n <div class=\"flex items-center gap-2 min-w-0 flex-1\">\r\n <mt-avatar\r\n [image]=\"cand.delegator.photo || ''\"\r\n icon=\"user.user-01\"\r\n styleClass=\"w-9! h-9! text-base! bg-surface-200!\"\r\n ></mt-avatar>\r\n <div class=\"flex flex-col min-w-0\">\r\n <span class=\"text-sm font-medium text-gray-900 truncate\">\r\n {{ cand.delegator.displayName }}\r\n </span>\r\n @if (cand.scopeSummary) {\r\n <span\r\n class=\"text-xs text-gray-500 truncate\"\r\n [title]=\"cand.scopeSummary\"\r\n >\r\n {{ cand.scopeSummary }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n <mt-icon\r\n icon=\"arrow.arrow-right\"\r\n styleClass=\"text-base text-gray-400\"\r\n ></mt-icon>\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n <!-- Footer (always shown when popover renders) -->\r\n <div class=\"border-t border-gray-200 px-2 py-2\">\r\n <a\r\n [routerLink]=\"managePath()\"\r\n (click)=\"popover().hide()\"\r\n class=\"flex items-center gap-2 px-3 py-2 rounded-md text-gray-700 hover:bg-surface-100 text-sm cursor-pointer\"\r\n >\r\n <mt-icon\r\n icon=\"general.settings-02\"\r\n styleClass=\"text-base\"\r\n ></mt-icon>\r\n <span>{{ t(\"delegations.manage-delegations\") }}</span>\r\n </a>\r\n </div>\r\n </div>\r\n </p-popover>\r\n }\r\n</ng-container>\r\n", styles: [":host{display:inline-flex;align-items:center}.mt-delegation-trigger__button{transition:background-color .2s ease-out}.mt-delegation-trigger__label{max-width:16rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host-context(.dark) .mt-delegation-trigger__button:hover{background-color:#94a3b826}\n"] }]
395
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n @if (mode() !== \"hidden\") {\n <div class=\"mt-delegation-trigger flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"mt-delegation-trigger__button flex items-center gap-2 px-2 py-1 rounded-full cursor-pointer hover:bg-surface-100\"\n [attr.aria-label]=\"t('delegations.title')\"\n (click)=\"togglePopover($event)\"\n >\n @if (mode() === \"active\") {\n <mt-icon\n icon=\"user.users-check\"\n styleClass=\"text-lg text-primary\"\n ></mt-icon>\n @if (!compact()) {\n <span class=\"mt-delegation-trigger__label text-sm font-medium\">\n {{\n t(\"delegations.session.banner\", {\n delegatorName: onBehalfOf()?.displayName,\n })\n }}\n </span>\n }\n <mt-delegation-status-chip\n status=\"Active\"\n ></mt-delegation-status-chip>\n } @else {\n <mt-icon\n icon=\"user.users-plus\"\n styleClass=\"text-lg text-primary\"\n ></mt-icon>\n @if (!compact()) {\n <span class=\"mt-delegation-trigger__label text-sm font-medium\">\n {{ t(\"delegations.session.availableTitle\") }}\n </span>\n }\n }\n </button>\n </div>\n\n <p-popover #popover [pt]=\"{ content: { class: 'p-0!' } }\">\n <div class=\"flex flex-col min-w-80 max-w-96 p-0\">\n @if (mode() === \"active\" && active(); as session) {\n <!-- STATE C \u2014 active session -->\n <div\n class=\"flex items-start gap-3 border-b border-gray-200 px-4 py-4\"\n >\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-11! h-11! text-xl! text-gray-600! bg-surface-300!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0 flex-1\">\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\n </div>\n <div class=\"text-sm font-medium text-gray-900 truncate\">\n {{ onBehalfOf()?.displayName }}\n </div>\n <div class=\"text-xs text-gray-500 mt-1\">\n {{\n t(\"delegations.session.executedBy\", {\n actualUserName: executedBy()?.displayName,\n })\n }}\n </div>\n @if (session.delegation.scopeSummary) {\n <div\n class=\"text-xs text-gray-500 mt-1 truncate\"\n [title]=\"session.delegation.scopeSummary\"\n >\n {{ t(\"delegations.column.scopeSummary\") }}:\n {{ session.delegation.scopeSummary }}\n </div>\n }\n </div>\n </div>\n\n <div class=\"flex flex-col gap-1 px-2 py-2\">\n <mt-button\n variant=\"text\"\n icon=\"arrow.arrow-left\"\n [label]=\"t('delegations.action.endSession')\"\n styleClass=\"w-full justify-start\"\n (click)=\"endSession($event)\"\n ></mt-button>\n </div>\n\n @if (candidates().length > 1) {\n <div class=\"border-t border-gray-200 pt-2 px-2 pb-2\">\n <div class=\"text-xs text-gray-500 px-2 pb-1\">\n {{ t(\"delegations.session.switchToAnother\") }}\n </div>\n @for (cand of candidates(); track cand.delegationId) {\n @if (cand.delegationId !== session.delegation.delegationId) {\n <button\n type=\"button\"\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer\"\n (click)=\"switchTo(cand, $event)\"\n >\n <div class=\"flex items-center gap-2 min-w-0\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-7! h-7! text-sm! bg-surface-200!\"\n ></mt-avatar>\n <span class=\"text-sm text-gray-900 truncate\">\n {{ cand.delegator.displayName }}\n </span>\n </div>\n <mt-icon\n icon=\"arrow.switch-horizontal-01\"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n </button>\n }\n }\n </div>\n }\n } @else if (mode() === \"candidates\") {\n <!-- STATE B \u2014 candidates available -->\n <div class=\"border-b border-gray-200 px-4 py-3\">\n <div class=\"text-sm font-medium text-gray-900\">\n {{ t(\"delegations.session.youCanActAs\") }}\n </div>\n <div class=\"text-xs text-gray-500 mt-1\">\n {{ t(\"delegations.session.chooseDelegatorHint\") }}\n </div>\n </div>\n <div class=\"flex flex-col gap-1 px-2 py-2 max-h-72 overflow-y-auto\">\n @for (cand of candidates(); track cand.delegationId) {\n <button\n type=\"button\"\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer text-start\"\n (click)=\"start(cand, $event)\"\n >\n <div class=\"flex items-center gap-2 min-w-0 flex-1\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-9! h-9! text-base! bg-surface-200!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <span class=\"text-sm font-medium text-gray-900 truncate\">\n {{ cand.delegator.displayName }}\n </span>\n @if (cand.scopeSummary) {\n <span\n class=\"text-xs text-gray-500 truncate\"\n [title]=\"cand.scopeSummary\"\n >\n {{ cand.scopeSummary }}\n </span>\n }\n </div>\n </div>\n <mt-icon\n icon=\"arrow.arrow-right\"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n </button>\n }\n </div>\n }\n\n <!-- Footer -->\n <div class=\"border-t border-gray-200 px-2 py-2\">\n <a\n [routerLink]=\"managePath()\"\n (click)=\"popover().hide()\"\n class=\"flex items-center gap-2 px-3 py-2 rounded-md text-gray-700 hover:bg-surface-100 text-sm cursor-pointer\"\n >\n <mt-icon\n icon=\"general.settings-02\"\n styleClass=\"text-base\"\n ></mt-icon>\n <span>{{ t(\"delegations.session.manage\") }}</span>\n </a>\n </div>\n </div>\n </p-popover>\n }\n</ng-container>\n", styles: [":host{display:inline-flex;align-items:center}.mt-delegation-trigger__button{transition:background-color .2s ease-out}.mt-delegation-trigger__label{max-width:16rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host-context(.dark) .mt-delegation-trigger__button:hover{background-color:#94a3b826}\n"] }]
574
396
  }], propDecorators: { managePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "managePath", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], popover: [{ type: i0.ViewChild, args: ['popover', { isSignal: true }] }] } });
575
397
 
576
398
  class Delegations {
@@ -579,15 +401,15 @@ class Delegations {
579
401
  this.router.navigate(['control-panel/product-settings']);
580
402
  }
581
403
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: Delegations, deps: [], target: i0.ɵɵFactoryTarget.Component });
582
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.8", type: Delegations, isStandalone: true, selector: "mt-delegations", ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <mt-page\n [title]=\"t('delegations.delegations')\"\n [avatarIcon]=\"'custom.hierarchy-structure'\"\n [contentClass]=\"'max-[1025px]:p-4 max-[640px]:p-3'\"\n [avatarStyle]=\"{\n '--p-avatar-background': 'var(--p-indigo-50)',\n '--p-avatar-color': 'var(--p-indigo-700)',\n }\"\n (backButtonClick)=\"goBack()\"\n backButton\n >\n <router-outlet />\n </mt-page>\n</ng-container>\n", styles: [""], dependencies: [{ kind: "component", type: Page, selector: "mt-page", inputs: ["backButton", "backButtonIcon", "avatarIcon", "avatarStyle", "avatarShape", "title", "tabs", "activeTab", "contentClass", "contentId"], outputs: ["backButtonClick", "tabChange"] }, { kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] });
404
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.8", type: Delegations, isStandalone: true, selector: "mt-delegations", ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <mt-page\n [title]=\"t('delegations.title')\"\n [avatarIcon]=\"'custom.hierarchy-structure'\"\n [contentClass]=\"'max-[1025px]:p-4 max-[640px]:p-3'\"\n [avatarStyle]=\"{\n '--p-avatar-background': 'var(--p-indigo-50)',\n '--p-avatar-color': 'var(--p-indigo-700)',\n }\"\n (backButtonClick)=\"goBack()\"\n backButton\n >\n <router-outlet />\n </mt-page>\n</ng-container>\n", styles: [""], dependencies: [{ kind: "component", type: Page, selector: "mt-page", inputs: ["backButton", "backButtonIcon", "avatarIcon", "avatarStyle", "avatarShape", "title", "tabs", "activeTab", "contentClass", "contentId"], outputs: ["backButtonClick", "tabChange"] }, { kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] });
583
405
  }
584
406
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: Delegations, decorators: [{
585
407
  type: Component,
586
- args: [{ selector: 'mt-delegations', imports: [Page, RouterOutlet, TranslocoDirective], template: "<ng-container *transloco=\"let t\">\n <mt-page\n [title]=\"t('delegations.delegations')\"\n [avatarIcon]=\"'custom.hierarchy-structure'\"\n [contentClass]=\"'max-[1025px]:p-4 max-[640px]:p-3'\"\n [avatarStyle]=\"{\n '--p-avatar-background': 'var(--p-indigo-50)',\n '--p-avatar-color': 'var(--p-indigo-700)',\n }\"\n (backButtonClick)=\"goBack()\"\n backButton\n >\n <router-outlet />\n </mt-page>\n</ng-container>\n" }]
408
+ args: [{ selector: 'mt-delegations', imports: [Page, RouterOutlet, TranslocoDirective], template: "<ng-container *transloco=\"let t\">\n <mt-page\n [title]=\"t('delegations.title')\"\n [avatarIcon]=\"'custom.hierarchy-structure'\"\n [contentClass]=\"'max-[1025px]:p-4 max-[640px]:p-3'\"\n [avatarStyle]=\"{\n '--p-avatar-background': 'var(--p-indigo-50)',\n '--p-avatar-color': 'var(--p-indigo-700)',\n }\"\n (backButtonClick)=\"goBack()\"\n backButton\n >\n <router-outlet />\n </mt-page>\n</ng-container>\n" }]
587
409
  }] });
588
410
 
589
411
  // ---------------------------------------------------------------------------
590
- // List / detail
412
+ // Lists / detail
591
413
  // ---------------------------------------------------------------------------
592
414
  class GetMyDelegations {
593
415
  query;
@@ -603,37 +425,81 @@ class GetAssignedDelegations {
603
425
  this.query = query;
604
426
  }
605
427
  }
606
- class GetDelegation {
428
+ class GetActiveAssignedDelegations {
429
+ query;
430
+ static type = '[Delegations] Get Active Assigned Delegations';
431
+ constructor(query = {}) {
432
+ this.query = query;
433
+ }
434
+ }
435
+ class GetDelegationDetail {
607
436
  id;
608
- includeStatusHistory;
609
- static type = '[Delegations] Get Delegation';
610
- constructor(id, includeStatusHistory = false) {
437
+ static type = '[Delegations] Get Delegation Detail';
438
+ constructor(id) {
611
439
  this.id = id;
612
- this.includeStatusHistory = includeStatusHistory;
613
440
  }
614
441
  }
615
- class ClearSelectedDelegation {
616
- static type = '[Delegations] Clear Selected Delegation';
442
+ class ClearDelegationDetail {
443
+ static type = '[Delegations] Clear Delegation Detail';
444
+ }
445
+ // ---------------------------------------------------------------------------
446
+ // Scope
447
+ // ---------------------------------------------------------------------------
448
+ class GetScopeOptions {
449
+ delegatorUserId;
450
+ static type = '[Delegations] Get Scope Options';
451
+ constructor(delegatorUserId) {
452
+ this.delegatorUserId = delegatorUserId;
453
+ }
454
+ }
455
+ class PreviewScope {
456
+ scope;
457
+ static type = '[Delegations] Preview Scope';
458
+ constructor(scope) {
459
+ this.scope = scope;
460
+ }
461
+ }
462
+ class ClearScopePreview {
463
+ static type = '[Delegations] Clear Scope Preview';
617
464
  }
618
465
  // ---------------------------------------------------------------------------
619
- // Lifecycle
466
+ // Create / edit (legacy + v2)
620
467
  // ---------------------------------------------------------------------------
621
- class CreateDelegation {
468
+ class CreateDelegationLegacy {
622
469
  request;
623
- static type = '[Delegations] Create Delegation';
470
+ static type = '[Delegations] Create Delegation (legacy)';
624
471
  constructor(request) {
625
472
  this.request = request;
626
473
  }
627
474
  }
628
- class UpdateDelegation {
475
+ class CreateDelegationV2 {
476
+ request;
477
+ static type = '[Delegations] Create Delegation (v2)';
478
+ constructor(request) {
479
+ this.request = request;
480
+ }
481
+ }
482
+ class UpdateDelegationLegacy {
483
+ id;
484
+ request;
485
+ static type = '[Delegations] Update Delegation (legacy)';
486
+ constructor(id, request) {
487
+ this.id = id;
488
+ this.request = request;
489
+ }
490
+ }
491
+ class UpdateDelegationV2 {
629
492
  id;
630
493
  request;
631
- static type = '[Delegations] Update Delegation';
494
+ static type = '[Delegations] Update Delegation (v2)';
632
495
  constructor(id, request) {
633
496
  this.id = id;
634
497
  this.request = request;
635
498
  }
636
499
  }
500
+ // ---------------------------------------------------------------------------
501
+ // Decisions
502
+ // ---------------------------------------------------------------------------
637
503
  class ApproveDelegation {
638
504
  id;
639
505
  request;
@@ -654,50 +520,32 @@ class RejectDelegation {
654
520
  }
655
521
  class CancelDelegation {
656
522
  id;
657
- request;
523
+ rowVersion;
524
+ asAdmin;
658
525
  static type = '[Delegations] Cancel Delegation';
659
- constructor(id, request) {
526
+ constructor(id, rowVersion, asAdmin = false) {
660
527
  this.id = id;
661
- this.request = request;
662
- }
663
- }
664
- // ---------------------------------------------------------------------------
665
- // Scope picker (form support)
666
- // ---------------------------------------------------------------------------
667
- class GetScopeOptions {
668
- delegatorUserId;
669
- static type = '[Delegations] Get Scope Options';
670
- /** Admin-only override; ignored for normal users. */
671
- constructor(delegatorUserId) {
672
- this.delegatorUserId = delegatorUserId;
528
+ this.rowVersion = rowVersion;
529
+ this.asAdmin = asAdmin;
673
530
  }
674
531
  }
675
- class PreviewScope {
676
- scope;
677
- static type = '[Delegations] Preview Scope';
678
- constructor(scope) {
679
- this.scope = scope;
680
- }
681
- }
682
- class ClearScopePreview {
683
- static type = '[Delegations] Clear Scope Preview';
684
- }
685
532
 
686
533
  // ============================================================================
687
534
  // NGXS state shape + action keys
688
535
  // ============================================================================
689
536
  var DelegationsActionKey;
690
537
  (function (DelegationsActionKey) {
691
- DelegationsActionKey["GetMyDelegations"] = "getMyDelegations";
692
- DelegationsActionKey["GetAssignedDelegations"] = "getAssignedDelegations";
693
- DelegationsActionKey["GetDelegation"] = "getDelegation";
694
- DelegationsActionKey["CreateDelegation"] = "createDelegation";
695
- DelegationsActionKey["UpdateDelegation"] = "updateDelegation";
696
- DelegationsActionKey["ApproveDelegation"] = "approveDelegation";
697
- DelegationsActionKey["RejectDelegation"] = "rejectDelegation";
698
- DelegationsActionKey["CancelDelegation"] = "cancelDelegation";
538
+ DelegationsActionKey["GetMy"] = "getMy";
539
+ DelegationsActionKey["GetAssigned"] = "getAssigned";
540
+ DelegationsActionKey["GetActive"] = "getActive";
541
+ DelegationsActionKey["GetDetail"] = "getDetail";
699
542
  DelegationsActionKey["GetScopeOptions"] = "getScopeOptions";
700
543
  DelegationsActionKey["PreviewScope"] = "previewScope";
544
+ DelegationsActionKey["Create"] = "create";
545
+ DelegationsActionKey["Update"] = "update";
546
+ DelegationsActionKey["Approve"] = "approve";
547
+ DelegationsActionKey["Reject"] = "reject";
548
+ DelegationsActionKey["Cancel"] = "cancel";
701
549
  })(DelegationsActionKey || (DelegationsActionKey = {}));
702
550
 
703
551
  var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
@@ -709,62 +557,44 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
709
557
  const BASE = 'identity/delegations';
710
558
  function buildListParams(query) {
711
559
  let params = new HttpParams();
712
- if (query.page != null)
713
- params = params.set('page', query.page);
714
- if (query.pageSize != null)
715
- params = params.set('pageSize', query.pageSize);
716
- if (query.q)
717
- params = params.set('q', query.q);
718
- if (query.startFrom)
719
- params = params.set('startFrom', query.startFrom);
720
- if (query.startTo)
721
- params = params.set('startTo', query.startTo);
722
- if (query.endFrom)
723
- params = params.set('endFrom', query.endFrom);
724
- if (query.endTo)
725
- params = params.set('endTo', query.endTo);
726
- if (query.activeOn)
727
- params = params.set('activeOn', query.activeOn);
728
- if (query.approvalStatus)
729
- params = params.set('approvalStatus', query.approvalStatus);
730
- if (query.moduleId != null)
731
- params = params.set('moduleId', query.moduleId);
732
- if (query.accessibilityGroupId != null)
733
- params = params.set('accessibilityGroupId', query.accessibilityGroupId);
560
+ if (query.activeOnly != null)
561
+ params = params.set('activeOnly', query.activeOnly);
562
+ if (query.status)
563
+ params = params.set('status', query.status);
564
+ if (query.search)
565
+ params = params.set('search', query.search);
566
+ if (query.fromUtc)
567
+ params = params.set('fromUtc', query.fromUtc);
568
+ if (query.toUtc)
569
+ params = params.set('toUtc', query.toUtc);
734
570
  if (query.sortBy)
735
571
  params = params.set('sortBy', query.sortBy);
736
- if (query.sortDir)
737
- params = params.set('sortDir', query.sortDir);
738
- if (query.status?.length) {
739
- for (const s of query.status) {
740
- params = params.append('status', s);
741
- }
742
- }
572
+ if (query.sortDirection)
573
+ params = params.set('sortDirection', query.sortDirection);
574
+ params = params.set('page', query.page ?? 1);
575
+ params = params.set('pageSize', query.pageSize ?? 25);
576
+ if (query.delegatorUserId)
577
+ params = params.set('delegatorUserId', query.delegatorUserId);
578
+ if (query.delegatedUserId)
579
+ params = params.set('delegatedUserId', query.delegatedUserId);
743
580
  return params;
744
581
  }
745
- function replaceInPage(page, updated) {
746
- if (!page)
747
- return page;
748
- const idx = page.items.findIndex((it) => it.id === updated.id);
749
- if (idx === -1)
750
- return page;
751
- const items = page.items.slice();
752
- items[idx] = updated;
753
- return { ...page, items };
754
- }
755
582
  let DelegationsState = class DelegationsState {
756
583
  http = inject(HttpClient);
757
584
  // ============================================================================
758
585
  // Selectors
759
586
  // ============================================================================
760
- static getMyPage(state) {
587
+ static getMy(state) {
761
588
  return state.my;
762
589
  }
763
- static getAssignedPage(state) {
590
+ static getAssigned(state) {
764
591
  return state.assigned;
765
592
  }
766
- static getSelected(state) {
767
- return state.selected;
593
+ static getActive(state) {
594
+ return state.active;
595
+ }
596
+ static getDetail(state) {
597
+ return state.detail;
768
598
  }
769
599
  static getScopeOptions(state) {
770
600
  return state.scopeOptions;
@@ -779,95 +609,57 @@ let DelegationsState = class DelegationsState {
779
609
  return state.errors;
780
610
  }
781
611
  // ============================================================================
782
- // List
612
+ // Lists
783
613
  // ============================================================================
784
- getMyDelegations(ctx, { query }) {
785
- const req$ = this.http.get(`${BASE}/my`, {
614
+ getMy(ctx, { query }) {
615
+ const req$ = this.http.get(BASE, {
786
616
  params: buildListParams(query),
787
617
  });
788
618
  return handleApiRequest({
789
619
  ctx,
790
- key: DelegationsActionKey.GetMyDelegations,
620
+ key: DelegationsActionKey.GetMy,
791
621
  request$: req$,
792
622
  onSuccess: (response) => ({ my: response.data }),
793
623
  });
794
624
  }
795
- getAssignedDelegations(ctx, { query }) {
625
+ getAssigned(ctx, { query }) {
796
626
  const req$ = this.http.get(`${BASE}/assigned`, { params: buildListParams(query) });
797
627
  return handleApiRequest({
798
628
  ctx,
799
- key: DelegationsActionKey.GetAssignedDelegations,
629
+ key: DelegationsActionKey.GetAssigned,
800
630
  request$: req$,
801
631
  onSuccess: (response) => ({ assigned: response.data }),
802
632
  });
803
633
  }
804
- getDelegation(ctx, { id, includeStatusHistory }) {
805
- let params = new HttpParams();
806
- if (includeStatusHistory) {
807
- params = params.set('include', 'statusHistory');
808
- }
809
- const req$ = this.http.get(`${BASE}/${id}`, {
810
- params,
811
- });
634
+ getActive(ctx, { query }) {
635
+ const req$ = this.http.get(`${BASE}/user/activedelegations`, { params: buildListParams(query) });
812
636
  return handleApiRequest({
813
637
  ctx,
814
- key: DelegationsActionKey.GetDelegation,
638
+ key: DelegationsActionKey.GetActive,
815
639
  request$: req$,
816
- onSuccess: (response) => ({ selected: response.data ?? null }),
640
+ onSuccess: (response) => ({ active: response.data }),
817
641
  });
818
642
  }
819
- clearSelectedDelegation(ctx) {
820
- ctx.patchState({ selected: null });
821
- }
822
- // ============================================================================
823
- // Lifecycle
824
- // ============================================================================
825
- createDelegation(ctx, { request }) {
826
- const req$ = this.http.post(BASE, request);
643
+ getDetail(ctx, { id }) {
644
+ const req$ = this.http.get(`${BASE}/${id}`);
827
645
  return handleApiRequest({
828
646
  ctx,
829
- key: DelegationsActionKey.CreateDelegation,
647
+ key: DelegationsActionKey.GetDetail,
830
648
  request$: req$,
831
- onSuccess: (response) => {
832
- const created = response.data;
833
- const state = ctx.getState();
834
- return {
835
- my: state.my
836
- ? { ...state.my, items: [created, ...state.my.items] }
837
- : state.my,
838
- };
839
- },
649
+ onSuccess: (response) => ({ detail: response.data ?? null }),
840
650
  });
841
651
  }
842
- updateDelegation(ctx, { id, request }) {
843
- const req$ = this.http.put(`${BASE}/${id}`, request);
844
- return this.applyLifecycleResult(ctx, DelegationsActionKey.UpdateDelegation, req$);
845
- }
846
- approveDelegation(ctx, { id, request }) {
847
- const req$ = this.http.post(`${BASE}/${id}/approve`, request);
848
- return this.applyLifecycleResult(ctx, DelegationsActionKey.ApproveDelegation, req$);
849
- }
850
- rejectDelegation(ctx, { id, request }) {
851
- const req$ = this.http.post(`${BASE}/${id}/reject`, request);
852
- return this.applyLifecycleResult(ctx, DelegationsActionKey.RejectDelegation, req$);
853
- }
854
- cancelDelegation(ctx, { id, request }) {
855
- const req$ = this.http.post(`${BASE}/${id}/cancel`, request);
856
- return this.applyLifecycleResult(ctx, DelegationsActionKey.CancelDelegation, req$);
652
+ clearDetail(ctx) {
653
+ ctx.patchState({ detail: null });
857
654
  }
858
655
  // ============================================================================
859
- // Scope (options + preview)
656
+ // Scope
860
657
  // ============================================================================
861
658
  getScopeOptions(ctx, { delegatorUserId }) {
862
659
  let params = new HttpParams();
863
660
  if (delegatorUserId)
864
661
  params = params.set('delegatorUserId', delegatorUserId);
865
- const url = delegatorUserId
866
- ? `identity/admin/delegations/scope/options`
867
- : `${BASE}/scope/options`;
868
- const req$ = this.http.get(url, {
869
- params,
870
- });
662
+ const req$ = this.http.get(`${BASE}/scope/options`, { params });
871
663
  return handleApiRequest({
872
664
  ctx,
873
665
  key: DelegationsActionKey.GetScopeOptions,
@@ -876,7 +668,7 @@ let DelegationsState = class DelegationsState {
876
668
  });
877
669
  }
878
670
  previewScope(ctx, { scope }) {
879
- const req$ = this.http.post(`${BASE}/scope/preview`, scope);
671
+ const req$ = this.http.post(`${BASE}/scope/preview`, { scope });
880
672
  return handleApiRequest({
881
673
  ctx,
882
674
  key: DelegationsActionKey.PreviewScope,
@@ -888,24 +680,75 @@ let DelegationsState = class DelegationsState {
888
680
  ctx.patchState({ scopePreview: null });
889
681
  }
890
682
  // ============================================================================
891
- // Helpers
683
+ // Create / edit (response is legacy DelegationDto — clients reload list/detail)
892
684
  // ============================================================================
893
- applyLifecycleResult(ctx, key, req$) {
685
+ createLegacy(ctx, { request }) {
686
+ const req$ = this.http.post(BASE, request);
894
687
  return handleApiRequest({
895
688
  ctx,
896
- key,
689
+ key: DelegationsActionKey.Create,
897
690
  request$: req$,
898
- onSuccess: (response) => {
899
- const updated = response.data;
900
- const state = ctx.getState();
901
- return {
902
- my: replaceInPage(state.my, updated),
903
- assigned: replaceInPage(state.assigned, updated),
904
- selected: state.selected && state.selected.id === updated.id
905
- ? { ...state.selected, ...updated }
906
- : state.selected,
907
- };
908
- },
691
+ onSuccess: () => ({}),
692
+ });
693
+ }
694
+ createV2(ctx, { request }) {
695
+ const req$ = this.http.post(`${BASE}/v2`, request);
696
+ return handleApiRequest({
697
+ ctx,
698
+ key: DelegationsActionKey.Create,
699
+ request$: req$,
700
+ onSuccess: () => ({}),
701
+ });
702
+ }
703
+ updateLegacy(ctx, { id, request }) {
704
+ const req$ = this.http.put(`${BASE}/${id}`, request);
705
+ return handleApiRequest({
706
+ ctx,
707
+ key: DelegationsActionKey.Update,
708
+ request$: req$,
709
+ onSuccess: () => ({}),
710
+ });
711
+ }
712
+ updateV2(ctx, { id, request }) {
713
+ const req$ = this.http.put(`${BASE}/v2/${id}`, request);
714
+ return handleApiRequest({
715
+ ctx,
716
+ key: DelegationsActionKey.Update,
717
+ request$: req$,
718
+ onSuccess: () => ({}),
719
+ });
720
+ }
721
+ // ============================================================================
722
+ // Decisions
723
+ // ============================================================================
724
+ approve(ctx, { id, request }) {
725
+ const req$ = this.http.post(`${BASE}/${id}/approve`, request);
726
+ return handleApiRequest({
727
+ ctx,
728
+ key: DelegationsActionKey.Approve,
729
+ request$: req$,
730
+ onSuccess: () => ({}),
731
+ });
732
+ }
733
+ reject(ctx, { id, request }) {
734
+ const req$ = this.http.post(`${BASE}/${id}/reject`, request);
735
+ return handleApiRequest({
736
+ ctx,
737
+ key: DelegationsActionKey.Reject,
738
+ request$: req$,
739
+ onSuccess: () => ({}),
740
+ });
741
+ }
742
+ cancel(ctx, { id, rowVersion, asAdmin }) {
743
+ const path = asAdmin ? `${BASE}/Admin/${id}` : `${BASE}/${id}`;
744
+ const req$ = this.http.delete(path, {
745
+ params: new HttpParams().set('rowVersion', rowVersion),
746
+ });
747
+ return handleApiRequest({
748
+ ctx,
749
+ key: DelegationsActionKey.Cancel,
750
+ request$: req$,
751
+ onSuccess: () => ({}),
909
752
  });
910
753
  }
911
754
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsState, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
@@ -913,31 +756,19 @@ let DelegationsState = class DelegationsState {
913
756
  };
914
757
  __decorate([
915
758
  Action(GetMyDelegations)
916
- ], DelegationsState.prototype, "getMyDelegations", null);
759
+ ], DelegationsState.prototype, "getMy", null);
917
760
  __decorate([
918
761
  Action(GetAssignedDelegations)
919
- ], DelegationsState.prototype, "getAssignedDelegations", null);
920
- __decorate([
921
- Action(GetDelegation)
922
- ], DelegationsState.prototype, "getDelegation", null);
923
- __decorate([
924
- Action(ClearSelectedDelegation)
925
- ], DelegationsState.prototype, "clearSelectedDelegation", null);
926
- __decorate([
927
- Action(CreateDelegation)
928
- ], DelegationsState.prototype, "createDelegation", null);
762
+ ], DelegationsState.prototype, "getAssigned", null);
929
763
  __decorate([
930
- Action(UpdateDelegation)
931
- ], DelegationsState.prototype, "updateDelegation", null);
764
+ Action(GetActiveAssignedDelegations)
765
+ ], DelegationsState.prototype, "getActive", null);
932
766
  __decorate([
933
- Action(ApproveDelegation)
934
- ], DelegationsState.prototype, "approveDelegation", null);
767
+ Action(GetDelegationDetail)
768
+ ], DelegationsState.prototype, "getDetail", null);
935
769
  __decorate([
936
- Action(RejectDelegation)
937
- ], DelegationsState.prototype, "rejectDelegation", null);
938
- __decorate([
939
- Action(CancelDelegation)
940
- ], DelegationsState.prototype, "cancelDelegation", null);
770
+ Action(ClearDelegationDetail)
771
+ ], DelegationsState.prototype, "clearDetail", null);
941
772
  __decorate([
942
773
  Action(GetScopeOptions)
943
774
  ], DelegationsState.prototype, "getScopeOptions", null);
@@ -947,15 +778,39 @@ __decorate([
947
778
  __decorate([
948
779
  Action(ClearScopePreview)
949
780
  ], DelegationsState.prototype, "clearScopePreview", null);
781
+ __decorate([
782
+ Action(CreateDelegationLegacy)
783
+ ], DelegationsState.prototype, "createLegacy", null);
784
+ __decorate([
785
+ Action(CreateDelegationV2)
786
+ ], DelegationsState.prototype, "createV2", null);
787
+ __decorate([
788
+ Action(UpdateDelegationLegacy)
789
+ ], DelegationsState.prototype, "updateLegacy", null);
790
+ __decorate([
791
+ Action(UpdateDelegationV2)
792
+ ], DelegationsState.prototype, "updateV2", null);
793
+ __decorate([
794
+ Action(ApproveDelegation)
795
+ ], DelegationsState.prototype, "approve", null);
796
+ __decorate([
797
+ Action(RejectDelegation)
798
+ ], DelegationsState.prototype, "reject", null);
799
+ __decorate([
800
+ Action(CancelDelegation)
801
+ ], DelegationsState.prototype, "cancel", null);
950
802
  __decorate([
951
803
  Selector()
952
- ], DelegationsState, "getMyPage", null);
804
+ ], DelegationsState, "getMy", null);
953
805
  __decorate([
954
806
  Selector()
955
- ], DelegationsState, "getAssignedPage", null);
807
+ ], DelegationsState, "getAssigned", null);
956
808
  __decorate([
957
809
  Selector()
958
- ], DelegationsState, "getSelected", null);
810
+ ], DelegationsState, "getActive", null);
811
+ __decorate([
812
+ Selector()
813
+ ], DelegationsState, "getDetail", null);
959
814
  __decorate([
960
815
  Selector()
961
816
  ], DelegationsState, "getScopeOptions", null);
@@ -974,7 +829,8 @@ DelegationsState = __decorate([
974
829
  defaults: {
975
830
  my: null,
976
831
  assigned: null,
977
- selected: null,
832
+ active: null,
833
+ detail: null,
978
834
  scopeOptions: null,
979
835
  scopePreview: null,
980
836
  loadingActive: [],
@@ -984,16 +840,17 @@ DelegationsState = __decorate([
984
840
  ], DelegationsState);
985
841
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsState, decorators: [{
986
842
  type: Injectable
987
- }], propDecorators: { getMyDelegations: [], getAssignedDelegations: [], getDelegation: [], clearSelectedDelegation: [], createDelegation: [], updateDelegation: [], approveDelegation: [], rejectDelegation: [], cancelDelegation: [], getScopeOptions: [], previewScope: [], clearScopePreview: [] } });
843
+ }], propDecorators: { getMy: [], getAssigned: [], getActive: [], getDetail: [], clearDetail: [], getScopeOptions: [], previewScope: [], clearScopePreview: [], createLegacy: [], createV2: [], updateLegacy: [], updateV2: [], approve: [], reject: [], cancel: [] } });
988
844
 
989
845
  class DelegationsFacade {
990
846
  store = inject(Store);
991
847
  // ---------------------------------------------------------------------------
992
848
  // Data slices
993
849
  // ---------------------------------------------------------------------------
994
- myPage = select(DelegationsState.getMyPage);
995
- assignedPage = select(DelegationsState.getAssignedPage);
996
- selected = select(DelegationsState.getSelected);
850
+ myPage = select(DelegationsState.getMy);
851
+ assignedPage = select(DelegationsState.getAssigned);
852
+ activePage = select(DelegationsState.getActive);
853
+ detail = select(DelegationsState.getDetail);
997
854
  scopeOptions = select(DelegationsState.getScopeOptions);
998
855
  scopePreview = select(DelegationsState.getScopePreview);
999
856
  loadingActive = select(DelegationsState.getLoadingActive);
@@ -1003,30 +860,30 @@ class DelegationsFacade {
1003
860
  // ---------------------------------------------------------------------------
1004
861
  myItems = computed(() => this.myPage()?.items ?? [], ...(ngDevMode ? [{ debugName: "myItems" }] : /* istanbul ignore next */ []));
1005
862
  assignedItems = computed(() => this.assignedPage()?.items ?? [], ...(ngDevMode ? [{ debugName: "assignedItems" }] : /* istanbul ignore next */ []));
863
+ activeItems = computed(() => this.activePage()?.items ?? [], ...(ngDevMode ? [{ debugName: "activeItems" }] : /* istanbul ignore next */ []));
1006
864
  // ---------------------------------------------------------------------------
1007
865
  // Loading
1008
866
  // ---------------------------------------------------------------------------
1009
- isLoadingMy = computed(() => this.loadingActive().includes(DelegationsActionKey.GetMyDelegations), ...(ngDevMode ? [{ debugName: "isLoadingMy" }] : /* istanbul ignore next */ []));
1010
- isLoadingAssigned = computed(() => this.loadingActive().includes(DelegationsActionKey.GetAssignedDelegations), ...(ngDevMode ? [{ debugName: "isLoadingAssigned" }] : /* istanbul ignore next */ []));
1011
- isLoadingDetail = computed(() => this.loadingActive().includes(DelegationsActionKey.GetDelegation), ...(ngDevMode ? [{ debugName: "isLoadingDetail" }] : /* istanbul ignore next */ []));
1012
- isCreating = computed(() => this.loadingActive().includes(DelegationsActionKey.CreateDelegation), ...(ngDevMode ? [{ debugName: "isCreating" }] : /* istanbul ignore next */ []));
1013
- isUpdating = computed(() => this.loadingActive().includes(DelegationsActionKey.UpdateDelegation), ...(ngDevMode ? [{ debugName: "isUpdating" }] : /* istanbul ignore next */ []));
1014
- isApproving = computed(() => this.loadingActive().includes(DelegationsActionKey.ApproveDelegation), ...(ngDevMode ? [{ debugName: "isApproving" }] : /* istanbul ignore next */ []));
1015
- isRejecting = computed(() => this.loadingActive().includes(DelegationsActionKey.RejectDelegation), ...(ngDevMode ? [{ debugName: "isRejecting" }] : /* istanbul ignore next */ []));
1016
- isCancelling = computed(() => this.loadingActive().includes(DelegationsActionKey.CancelDelegation), ...(ngDevMode ? [{ debugName: "isCancelling" }] : /* istanbul ignore next */ []));
867
+ isLoadingMy = computed(() => this.loadingActive().includes(DelegationsActionKey.GetMy), ...(ngDevMode ? [{ debugName: "isLoadingMy" }] : /* istanbul ignore next */ []));
868
+ isLoadingAssigned = computed(() => this.loadingActive().includes(DelegationsActionKey.GetAssigned), ...(ngDevMode ? [{ debugName: "isLoadingAssigned" }] : /* istanbul ignore next */ []));
869
+ isLoadingActive = computed(() => this.loadingActive().includes(DelegationsActionKey.GetActive), ...(ngDevMode ? [{ debugName: "isLoadingActive" }] : /* istanbul ignore next */ []));
870
+ isLoadingDetail = computed(() => this.loadingActive().includes(DelegationsActionKey.GetDetail), ...(ngDevMode ? [{ debugName: "isLoadingDetail" }] : /* istanbul ignore next */ []));
1017
871
  isLoadingScopeOptions = computed(() => this.loadingActive().includes(DelegationsActionKey.GetScopeOptions), ...(ngDevMode ? [{ debugName: "isLoadingScopeOptions" }] : /* istanbul ignore next */ []));
1018
872
  isPreviewingScope = computed(() => this.loadingActive().includes(DelegationsActionKey.PreviewScope), ...(ngDevMode ? [{ debugName: "isPreviewingScope" }] : /* istanbul ignore next */ []));
873
+ isSaving = computed(() => this.loadingActive().includes(DelegationsActionKey.Create) ||
874
+ this.loadingActive().includes(DelegationsActionKey.Update), ...(ngDevMode ? [{ debugName: "isSaving" }] : /* istanbul ignore next */ []));
875
+ isApproving = computed(() => this.loadingActive().includes(DelegationsActionKey.Approve), ...(ngDevMode ? [{ debugName: "isApproving" }] : /* istanbul ignore next */ []));
876
+ isRejecting = computed(() => this.loadingActive().includes(DelegationsActionKey.Reject), ...(ngDevMode ? [{ debugName: "isRejecting" }] : /* istanbul ignore next */ []));
877
+ isCancelling = computed(() => this.loadingActive().includes(DelegationsActionKey.Cancel), ...(ngDevMode ? [{ debugName: "isCancelling" }] : /* istanbul ignore next */ []));
1019
878
  // ---------------------------------------------------------------------------
1020
879
  // Errors
1021
880
  // ---------------------------------------------------------------------------
1022
- errorMy = computed(() => this.errors()[DelegationsActionKey.GetMyDelegations] ?? null, ...(ngDevMode ? [{ debugName: "errorMy" }] : /* istanbul ignore next */ []));
1023
- errorAssigned = computed(() => this.errors()[DelegationsActionKey.GetAssignedDelegations] ?? null, ...(ngDevMode ? [{ debugName: "errorAssigned" }] : /* istanbul ignore next */ []));
1024
- errorDetail = computed(() => this.errors()[DelegationsActionKey.GetDelegation] ?? null, ...(ngDevMode ? [{ debugName: "errorDetail" }] : /* istanbul ignore next */ []));
1025
- errorCreate = computed(() => this.errors()[DelegationsActionKey.CreateDelegation] ?? null, ...(ngDevMode ? [{ debugName: "errorCreate" }] : /* istanbul ignore next */ []));
1026
- errorUpdate = computed(() => this.errors()[DelegationsActionKey.UpdateDelegation] ?? null, ...(ngDevMode ? [{ debugName: "errorUpdate" }] : /* istanbul ignore next */ []));
1027
- errorApprove = computed(() => this.errors()[DelegationsActionKey.ApproveDelegation] ?? null, ...(ngDevMode ? [{ debugName: "errorApprove" }] : /* istanbul ignore next */ []));
1028
- errorReject = computed(() => this.errors()[DelegationsActionKey.RejectDelegation] ?? null, ...(ngDevMode ? [{ debugName: "errorReject" }] : /* istanbul ignore next */ []));
1029
- errorCancel = computed(() => this.errors()[DelegationsActionKey.CancelDelegation] ?? null, ...(ngDevMode ? [{ debugName: "errorCancel" }] : /* istanbul ignore next */ []));
881
+ errorMy = computed(() => this.errors()[DelegationsActionKey.GetMy] ?? null, ...(ngDevMode ? [{ debugName: "errorMy" }] : /* istanbul ignore next */ []));
882
+ errorAssigned = computed(() => this.errors()[DelegationsActionKey.GetAssigned] ?? null, ...(ngDevMode ? [{ debugName: "errorAssigned" }] : /* istanbul ignore next */ []));
883
+ errorDetail = computed(() => this.errors()[DelegationsActionKey.GetDetail] ?? null, ...(ngDevMode ? [{ debugName: "errorDetail" }] : /* istanbul ignore next */ []));
884
+ errorSave = computed(() => this.errors()[DelegationsActionKey.Create] ??
885
+ this.errors()[DelegationsActionKey.Update] ??
886
+ null, ...(ngDevMode ? [{ debugName: "errorSave" }] : /* istanbul ignore next */ []));
1030
887
  // ---------------------------------------------------------------------------
1031
888
  // Dispatchers
1032
889
  // ---------------------------------------------------------------------------
@@ -1036,26 +893,14 @@ class DelegationsFacade {
1036
893
  getAssigned(query = {}) {
1037
894
  return this.store.dispatch(new GetAssignedDelegations(query));
1038
895
  }
1039
- getOne(id, includeStatusHistory = false) {
1040
- return this.store.dispatch(new GetDelegation(id, includeStatusHistory));
1041
- }
1042
- clearSelected() {
1043
- return this.store.dispatch(new ClearSelectedDelegation());
896
+ getActive(query = {}) {
897
+ return this.store.dispatch(new GetActiveAssignedDelegations(query));
1044
898
  }
1045
- create(request) {
1046
- return this.store.dispatch(new CreateDelegation(request));
899
+ getDetail(id) {
900
+ return this.store.dispatch(new GetDelegationDetail(id));
1047
901
  }
1048
- update(id, request) {
1049
- return this.store.dispatch(new UpdateDelegation(id, request));
1050
- }
1051
- approve(id, request) {
1052
- return this.store.dispatch(new ApproveDelegation(id, request));
1053
- }
1054
- reject(id, request) {
1055
- return this.store.dispatch(new RejectDelegation(id, request));
1056
- }
1057
- cancel(id, request) {
1058
- return this.store.dispatch(new CancelDelegation(id, request));
902
+ clearDetail() {
903
+ return this.store.dispatch(new ClearDelegationDetail());
1059
904
  }
1060
905
  loadScopeOptions(delegatorUserId) {
1061
906
  return this.store.dispatch(new GetScopeOptions(delegatorUserId));
@@ -1066,6 +911,27 @@ class DelegationsFacade {
1066
911
  clearScopePreview() {
1067
912
  return this.store.dispatch(new ClearScopePreview());
1068
913
  }
914
+ createLegacy(request) {
915
+ return this.store.dispatch(new CreateDelegationLegacy(request));
916
+ }
917
+ createV2(request) {
918
+ return this.store.dispatch(new CreateDelegationV2(request));
919
+ }
920
+ updateLegacy(id, request) {
921
+ return this.store.dispatch(new UpdateDelegationLegacy(id, request));
922
+ }
923
+ updateV2(id, request) {
924
+ return this.store.dispatch(new UpdateDelegationV2(id, request));
925
+ }
926
+ approve(id, request) {
927
+ return this.store.dispatch(new ApproveDelegation(id, request));
928
+ }
929
+ reject(id, request) {
930
+ return this.store.dispatch(new RejectDelegation(id, request));
931
+ }
932
+ cancel(id, rowVersion, asAdmin = false) {
933
+ return this.store.dispatch(new CancelDelegation(id, rowVersion, asAdmin));
934
+ }
1069
935
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsFacade, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
1070
936
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsFacade, providedIn: 'root' });
1071
937
  }
@@ -1074,92 +940,67 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1074
940
  args: [{ providedIn: 'root' }]
1075
941
  }] });
1076
942
 
1077
- const EMPTY_SCOPE$1 = {
1078
- mode: 'Explicit',
1079
- readPolicy: 'ImplicitReadForSelectedActions',
1080
- accessibilityGroupIds: [],
1081
- targets: [],
1082
- };
943
+ const EMPTY_SCOPE$1 = { grants: [], metadata: {} };
1083
944
  function targetKey(t) {
1084
- return `${t.targetType}:${t.targetId ?? ''}:${t.permissionTargetId ?? ''}:${t.moduleKey}`;
945
+ return `${t.applicationKey}::${t.targetType}::${t.targetKey}`;
1085
946
  }
1086
- function requestTargetKey(t) {
1087
- return `${t.targetType}:${t.targetId ?? ''}:${t.permissionTargetId ?? ''}:${t.moduleKey}`;
947
+ function grantKey(g) {
948
+ return `${targetKey(g.target)}::${g.action.operationKey}`;
949
+ }
950
+ function normalizeGrant(g) {
951
+ return {
952
+ ...g,
953
+ accessibilities: g.accessibilities ?? [],
954
+ dataFilters: g.dataFilters ?? [],
955
+ constraints: g.constraints ?? [],
956
+ };
1088
957
  }
1089
958
  /**
1090
- * Scope picker driven entirely by Identity-provided options.
959
+ * Grants-based scope picker (doc 03). Driven entirely by `scope/options`:
960
+ * - lists grantable target/action pairs returned by the backend,
961
+ * - lets the user toggle each grant,
962
+ * - calls `scope/preview` (debounced) and surfaces summary / warnings / denied.
1091
963
  *
1092
- * - `[(scope)]` two-way binds a normalized `DelegationScopeRequest`.
1093
- * - Loads options via `DelegationsFacade.loadScopeOptions()` on init.
1094
- * - Calls `/scope/preview` (debounced) so the user sees the server-built summary
1095
- * and denied items before they save.
1096
- * - Non-delegable operations render disabled with the BE-supplied `reason`.
964
+ * `[(scope)]` two-way binds a `DelegationScopeSelection`. The component never
965
+ * invents permission metadata it only echoes grants returned by options.
1097
966
  */
1098
967
  class ScopePicker {
1099
- /** Two-way bound — the form drives initial value and reads updates back. */
1100
968
  scope = model(EMPTY_SCOPE$1, ...(ngDevMode ? [{ debugName: "scope" }] : /* istanbul ignore next */ []));
1101
969
  readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
1102
- /** Admin scope-as-another-user — passed through to the options endpoint. */
1103
970
  delegatorUserId = input(undefined, ...(ngDevMode ? [{ debugName: "delegatorUserId" }] : /* istanbul ignore next */ []));
1104
971
  facade = inject(DelegationsFacade);
1105
- transloco = inject(TranslocoService);
1106
972
  options = this.facade.scopeOptions;
1107
973
  preview = this.facade.scopePreview;
1108
974
  isLoadingOptions = this.facade.isLoadingScopeOptions;
1109
975
  isPreviewing = this.facade.isPreviewingScope;
1110
- /** Set of accordion target keys currently expanded. */
1111
- expanded = signal(new Set(), ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
1112
- readPolicies = computed(() => this.options()?.readPolicies ?? [], ...(ngDevMode ? [{ debugName: "readPolicies" }] : /* istanbul ignore next */ []));
1113
- /** Flat list of accessibility group options (across modules). */
1114
- accessibilityGroups = computed(() => {
1115
- const acc = this.options()?.accessibilities ?? [];
1116
- return acc.flatMap((a) => a.groups.map((g) => ({
1117
- appAccessibilityId: a.appAccessibilityId,
1118
- accessibilityGroupId: g.accessibilityGroupId,
1119
- moduleName: a.moduleName,
1120
- groupName: g.name,
1121
- })));
1122
- }, ...(ngDevMode ? [{ debugName: "accessibilityGroups" }] : /* istanbul ignore next */ []));
1123
- /** Targets grouped by `targetType` for the accordion UI. */
1124
- groupedTargets = computed(() => {
1125
- const targets = this.options()?.targets ?? [];
1126
- const map = new Map();
1127
- for (const t of targets) {
1128
- const list = map.get(t.targetType) ?? [];
1129
- list.push(t);
1130
- map.set(t.targetType, list);
1131
- }
1132
- return Array.from(map.entries()).map(([type, items]) => ({
1133
- type,
1134
- items,
1135
- }));
1136
- }, ...(ngDevMode ? [{ debugName: "groupedTargets" }] : /* istanbul ignore next */ []));
1137
- /** O(1) lookup: is this accessibility group selected? */
1138
- selectedAccessibilityIds = computed(() => new Set(this.scope().accessibilityGroupIds ?? []), ...(ngDevMode ? [{ debugName: "selectedAccessibilityIds" }] : /* istanbul ignore next */ []));
1139
- /** O(1) lookup: which targets are picked, and which ops per target? */
1140
- selectedTargetMap = computed(() => {
976
+ /** Grantable options grouped by target. */
977
+ groups = computed(() => {
978
+ const grants = this.options()?.grants ?? [];
1141
979
  const map = new Map();
1142
- for (const t of this.scope().targets) {
1143
- const key = requestTargetKey(t);
1144
- map.set(key, new Set(t.operations.map((op) => op.permissionCommand)));
980
+ for (const g of grants) {
981
+ const key = targetKey(g.target);
982
+ const existing = map.get(key);
983
+ if (existing) {
984
+ existing.grants.push(g);
985
+ }
986
+ else {
987
+ map.set(key, { key, target: g.target, grants: [g] });
988
+ }
1145
989
  }
1146
- return map;
1147
- }, ...(ngDevMode ? [{ debugName: "selectedTargetMap" }] : /* istanbul ignore next */ []));
1148
- // -------------------------------------------------------------------------
1149
- // Lifecycle
1150
- // -------------------------------------------------------------------------
990
+ return Array.from(map.values());
991
+ }, ...(ngDevMode ? [{ debugName: "groups" }] : /* istanbul ignore next */ []));
992
+ /** Selected grant keys for O(1) checkbox state. */
993
+ selectedKeys = computed(() => new Set(this.scope().grants.map(grantKey)), ...(ngDevMode ? [{ debugName: "selectedKeys" }] : /* istanbul ignore next */ []));
994
+ expanded = signal(new Set(), ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
1151
995
  ngOnInit() {
1152
996
  this.facade.loadScopeOptions(this.delegatorUserId());
1153
997
  }
1154
998
  constructor() {
1155
- // Debounced preview: whenever the scope changes, call /scope/preview.
1156
- // (Skips the empty initial value.)
1157
999
  let timer = null;
1158
1000
  effect(() => {
1159
1001
  const s = this.scope();
1160
- const hasContent = s.accessibilityGroupIds.length > 0 || s.targets.length > 0;
1161
1002
  untracked(() => {
1162
- if (!hasContent) {
1003
+ if (!s.grants.length) {
1163
1004
  this.facade.clearScopePreview();
1164
1005
  return;
1165
1006
  }
@@ -1169,115 +1010,38 @@ class ScopePicker {
1169
1010
  });
1170
1011
  });
1171
1012
  }
1172
- // -------------------------------------------------------------------------
1173
- // UI handlers
1174
- // -------------------------------------------------------------------------
1175
- setReadPolicy(policy) {
1176
- if (this.readonly())
1177
- return;
1178
- this.scope.update((s) => ({ ...s, readPolicy: policy }));
1179
- }
1180
- toggleAccessibility(group) {
1181
- if (this.readonly())
1182
- return;
1183
- this.scope.update((s) => {
1184
- const ids = new Set(s.accessibilityGroupIds);
1185
- if (ids.has(group.accessibilityGroupId)) {
1186
- ids.delete(group.accessibilityGroupId);
1187
- }
1188
- else {
1189
- ids.add(group.accessibilityGroupId);
1190
- }
1191
- return { ...s, accessibilityGroupIds: Array.from(ids) };
1192
- });
1193
- }
1194
- isAccessibilitySelected(group) {
1195
- return this.selectedAccessibilityIds().has(group.accessibilityGroupId);
1013
+ isExpanded(group) {
1014
+ return this.expanded().has(group.key);
1196
1015
  }
1197
- toggleAccordion(target) {
1198
- const key = targetKey(target);
1016
+ toggleAccordion(group) {
1199
1017
  const next = new Set(this.expanded());
1200
- if (next.has(key))
1201
- next.delete(key);
1018
+ if (next.has(group.key))
1019
+ next.delete(group.key);
1202
1020
  else
1203
- next.add(key);
1021
+ next.add(group.key);
1204
1022
  this.expanded.set(next);
1205
1023
  }
1206
- isExpanded(target) {
1207
- return this.expanded().has(targetKey(target));
1024
+ isSelected(grant) {
1025
+ return this.selectedKeys().has(grantKey(grant));
1208
1026
  }
1209
- toggleOperation(target, op) {
1210
- if (this.readonly() || !op.isDelegable)
1027
+ toggleGrant(grant) {
1028
+ if (this.readonly())
1211
1029
  return;
1030
+ const key = grantKey(grant);
1212
1031
  this.scope.update((s) => {
1213
- const reqKey = `${target.targetType}:${target.targetId ?? ''}:${target.permissionTargetId ?? ''}:${target.moduleKey}`;
1214
- const targets = [...s.targets];
1215
- const idx = targets.findIndex((t) => requestTargetKey(t) === reqKey);
1216
- const baseRequest = {
1217
- targetType: target.targetType,
1218
- targetId: target.targetId,
1219
- levelId: target.levelId,
1220
- levelModuleId: target.levelModuleId,
1221
- domainModuleId: target.domainModuleId,
1222
- moduleKey: target.moduleKey,
1223
- permissionModuleType: target.permissionModuleType,
1224
- permissionTargetId: target.permissionTargetId,
1225
- operations: [],
1226
- };
1227
- if (idx === -1) {
1228
- targets.push({
1229
- ...baseRequest,
1230
- operations: [
1231
- {
1232
- operationKey: op.operationKey,
1233
- permissionCommand: op.permissionCommand,
1234
- },
1235
- ],
1236
- });
1237
- }
1238
- else {
1239
- const current = targets[idx];
1240
- const opIdx = current.operations.findIndex((o) => o.permissionCommand === op.permissionCommand);
1241
- if (opIdx === -1) {
1242
- targets[idx] = {
1243
- ...current,
1244
- operations: [
1245
- ...current.operations,
1246
- {
1247
- operationKey: op.operationKey,
1248
- permissionCommand: op.permissionCommand,
1249
- },
1250
- ],
1251
- };
1252
- }
1253
- else {
1254
- const nextOps = current.operations.slice();
1255
- nextOps.splice(opIdx, 1);
1256
- if (nextOps.length === 0) {
1257
- // last op removed — drop the target entirely
1258
- targets.splice(idx, 1);
1259
- }
1260
- else {
1261
- targets[idx] = { ...current, operations: nextOps };
1262
- }
1263
- }
1264
- }
1265
- return { ...s, targets };
1032
+ const exists = s.grants.some((g) => grantKey(g) === key);
1033
+ const grants = exists
1034
+ ? s.grants.filter((g) => grantKey(g) !== key)
1035
+ : [...s.grants, normalizeGrant(grant)];
1036
+ return { ...s, grants };
1266
1037
  });
1267
1038
  }
1268
- isOperationSelected(target, op) {
1269
- const key = `${target.targetType}:${target.targetId ?? ''}:${target.permissionTargetId ?? ''}:${target.moduleKey}`;
1270
- return (this.selectedTargetMap().get(key)?.has(op.permissionCommand) ?? false);
1271
- }
1272
- selectedOpsCount(target) {
1273
- const key = `${target.targetType}:${target.targetId ?? ''}:${target.permissionTargetId ?? ''}:${target.moduleKey}`;
1274
- return this.selectedTargetMap().get(key)?.size ?? 0;
1275
- }
1276
- reasonLabel(op) {
1277
- return op.reason ?? this.transloco.translate('delegations.non-delegable-reason');
1039
+ selectedCount(group) {
1040
+ const keys = this.selectedKeys();
1041
+ return group.grants.filter((g) => keys.has(grantKey(g))).length;
1278
1042
  }
1279
1043
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ScopePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
1280
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ScopePicker, isStandalone: true, selector: "mt-scope-picker", inputs: { scope: { classPropertyName: "scope", publicName: "scope", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, delegatorUserId: { classPropertyName: "delegatorUserId", publicName: "delegatorUserId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { scope: "scopeChange" }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"mt-scope-picker flex flex-col gap-4\">\r\n <!-- Loading skeleton -->\r\n @if (isLoadingOptions() && !options()) {\r\n <p-skeleton height=\"2rem\"></p-skeleton>\r\n <p-skeleton height=\"6rem\"></p-skeleton>\r\n <p-skeleton height=\"6rem\"></p-skeleton>\r\n } @else if (options(); as opts) {\r\n <!-- ============================================================ -->\r\n <!-- Read policy -->\r\n <!-- ============================================================ -->\r\n <section class=\"flex flex-col gap-2\">\r\n <h4 class=\"text-sm font-medium text-gray-800\">\r\n {{ t('delegations.read-policy') }}\r\n </h4>\r\n <div class=\"flex flex-wrap gap-2\">\r\n @for (rp of readPolicies(); track rp.key) {\r\n <button\r\n type=\"button\"\r\n class=\"px-3 py-1.5 rounded-full text-xs border transition-colors cursor-pointer\"\r\n [class.border-primary]=\"scope().readPolicy === rp.key\"\r\n [class.bg-primary-50]=\"scope().readPolicy === rp.key\"\r\n [class.text-primary]=\"scope().readPolicy === rp.key\"\r\n [class.border-gray-300]=\"scope().readPolicy !== rp.key\"\r\n [class.text-gray-700]=\"scope().readPolicy !== rp.key\"\r\n [disabled]=\"readonly()\"\r\n (click)=\"setReadPolicy(rp.key)\">\r\n {{ t('delegations.' + (rp.key === 'ImplicitReadForSelectedActions'\r\n ? 'implicit-read'\r\n : rp.key === 'ExplicitReadOnly'\r\n ? 'explicit-read-only'\r\n : 'no-read-unless-own-permission')) }}\r\n </button>\r\n }\r\n </div>\r\n </section>\r\n\r\n <!-- ============================================================ -->\r\n <!-- Accessibilities -->\r\n <!-- ============================================================ -->\r\n @if (accessibilityGroups().length > 0) {\r\n <section class=\"flex flex-col gap-2\">\r\n <h4 class=\"text-sm font-medium text-gray-800\">\r\n {{ t('delegations.scope') }} \u2014\r\n {{ t('delegations.delegated-to') }}\r\n </h4>\r\n <div class=\"flex flex-wrap gap-2\">\r\n @for (g of accessibilityGroups(); track g.accessibilityGroupId) {\r\n <button\r\n type=\"button\"\r\n class=\"flex items-center gap-2 px-3 py-1.5 rounded-md text-xs border transition-colors cursor-pointer\"\r\n [class.border-primary]=\"isAccessibilitySelected(g)\"\r\n [class.bg-primary-50]=\"isAccessibilitySelected(g)\"\r\n [class.text-primary]=\"isAccessibilitySelected(g)\"\r\n [class.border-gray-300]=\"!isAccessibilitySelected(g)\"\r\n [class.text-gray-700]=\"!isAccessibilitySelected(g)\"\r\n [disabled]=\"readonly()\"\r\n (click)=\"toggleAccessibility(g)\">\r\n @if (isAccessibilitySelected(g)) {\r\n <mt-icon icon=\"general.check\" styleClass=\"text-sm\"></mt-icon>\r\n }\r\n <span>{{ g.moduleName }} / {{ g.groupName }}</span>\r\n </button>\r\n }\r\n </div>\r\n </section>\r\n }\r\n\r\n <!-- ============================================================ -->\r\n <!-- Targets + operations -->\r\n <!-- ============================================================ -->\r\n <section class=\"flex flex-col gap-2\">\r\n <h4 class=\"text-sm font-medium text-gray-800\">\r\n {{ t('delegations.target-type') }} /\r\n {{ t('delegations.operations') }}\r\n </h4>\r\n\r\n @if (groupedTargets().length === 0) {\r\n <p class=\"text-xs text-gray-500\">\r\n {{ t('delegations.no-targets-selected') }}\r\n </p>\r\n }\r\n\r\n @for (group of groupedTargets(); track group.type) {\r\n <div class=\"flex flex-col gap-1\">\r\n <div class=\"text-xs uppercase text-gray-500 tracking-wide px-2\">\r\n {{ group.type }}\r\n </div>\r\n @for (target of group.items; track target.displayName) {\r\n <div class=\"border border-gray-200 rounded-md overflow-hidden\">\r\n <button\r\n type=\"button\"\r\n class=\"flex items-center justify-between w-full px-3 py-2 hover:bg-surface-50 cursor-pointer\"\r\n (click)=\"toggleAccordion(target)\">\r\n <div class=\"flex items-center gap-2 min-w-0\">\r\n <mt-icon\r\n [icon]=\"\r\n isExpanded(target)\r\n ? 'arrow.chevron-down'\r\n : 'arrow.chevron-right'\r\n \"\r\n styleClass=\"text-base text-gray-400\"></mt-icon>\r\n <span class=\"text-sm text-gray-900 truncate\">\r\n {{ target.displayName }}\r\n </span>\r\n </div>\r\n @if (selectedOpsCount(target); as count) {\r\n @if (count > 0) {\r\n <span\r\n class=\"text-xs text-primary font-medium px-2 py-0.5 rounded-full bg-primary-50\">\r\n {{ count }}\r\n </span>\r\n }\r\n }\r\n </button>\r\n\r\n @if (isExpanded(target)) {\r\n <div class=\"flex flex-col gap-1 px-3 py-2 bg-surface-50/40\">\r\n @for (op of target.operations; track op.permissionCommand) {\r\n <label\r\n class=\"flex items-center gap-2 px-2 py-1.5 rounded-md cursor-pointer\"\r\n [class.opacity-60]=\"!op.isDelegable\"\r\n [class.cursor-not-allowed]=\"!op.isDelegable\"\r\n [class.hover:bg-surface-100]=\"op.isDelegable\"\r\n [pTooltip]=\"!op.isDelegable ? reasonLabel(op) : null\"\r\n tooltipPosition=\"top\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"isOperationSelected(target, op)\"\r\n [disabled]=\"!op.isDelegable || readonly()\"\r\n (change)=\"toggleOperation(target, op)\" />\r\n <span class=\"text-sm\">{{ op.displayName }}</span>\r\n @if (op.isHighRisk) {\r\n <mt-icon\r\n icon=\"alert.alert-triangle\"\r\n styleClass=\"text-sm text-amber-500\"\r\n [pTooltip]=\"t('delegations.high-risk-action')\"\r\n tooltipPosition=\"top\"></mt-icon>\r\n }\r\n @if (!op.isDelegable) {\r\n <span class=\"text-xs text-gray-400 ms-auto\">\r\n {{ reasonLabel(op) }}\r\n </span>\r\n }\r\n </label>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </section>\r\n\r\n <!-- ============================================================ -->\r\n <!-- Preview -->\r\n <!-- ============================================================ -->\r\n <section\r\n class=\"flex flex-col gap-1 px-3 py-2 rounded-md border border-dashed border-gray-300 bg-surface-50\">\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <div class=\"text-xs font-medium text-gray-700\">\r\n {{ t('delegations.preview-scope') }}\r\n </div>\r\n @if (isPreviewing()) {\r\n <p-skeleton width=\"6rem\" height=\"0.75rem\"></p-skeleton>\r\n }\r\n </div>\r\n @if (preview(); as p) {\r\n @if (p.isValid) {\r\n <div class=\"text-sm text-gray-900\">\r\n {{ p.summary || t('delegations.scope-summary') }}\r\n </div>\r\n } @else {\r\n <div class=\"text-sm text-red-700\">\r\n {{ t('delegations.scope-preview-invalid') }}\r\n </div>\r\n }\r\n @if (p.warnings.length > 0) {\r\n <ul class=\"text-xs text-amber-700 list-disc list-inside\">\r\n @for (w of p.warnings; track w.code) {\r\n <li>{{ w.message }}</li>\r\n }\r\n </ul>\r\n }\r\n @if (p.deniedItems.length > 0) {\r\n <ul class=\"text-xs text-red-700 list-disc list-inside\">\r\n @for (d of p.deniedItems; track d.permissionCommand) {\r\n <li>{{ d.message }}</li>\r\n }\r\n </ul>\r\n }\r\n } @else {\r\n <div class=\"text-xs text-gray-500\">\r\n {{ t('delegations.no-operations-selected') }}\r\n </div>\r\n }\r\n </section>\r\n }\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block}.mt-scope-picker input[type=checkbox]{accent-color:var(--p-primary-color, currentColor)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1$1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i2.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1044
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ScopePicker, isStandalone: true, selector: "mt-scope-picker", inputs: { scope: { classPropertyName: "scope", publicName: "scope", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, delegatorUserId: { classPropertyName: "delegatorUserId", publicName: "delegatorUserId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { scope: "scopeChange" }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div class=\"mt-scope-picker flex flex-col gap-4\">\n @if (isLoadingOptions() && !options()) {\n <p-skeleton height=\"2rem\"></p-skeleton>\n <p-skeleton height=\"6rem\"></p-skeleton>\n <p-skeleton height=\"6rem\"></p-skeleton>\n } @else if (options()) {\n <!-- Targets + actions -->\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-sm font-medium text-gray-800\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </h4>\n\n @if (groups().length === 0) {\n <p class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noGrantableOptions\") }}\n </p>\n }\n\n @for (group of groups(); track group.key) {\n <div class=\"border border-gray-200 rounded-md overflow-hidden\">\n <button\n type=\"button\"\n class=\"flex items-center justify-between w-full px-3 py-2 hover:bg-surface-50 cursor-pointer\"\n (click)=\"toggleAccordion(group)\"\n >\n <div class=\"flex items-center gap-2 min-w-0\">\n <mt-icon\n [icon]=\"\n isExpanded(group)\n ? 'arrow.chevron-down'\n : 'arrow.chevron-right'\n \"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n <span class=\"text-sm text-gray-900 truncate\">\n {{ group.target.displayName || group.target.targetKey }}\n </span>\n <span class=\"text-xs text-gray-400\">\n {{ group.target.targetType }}\n </span>\n </div>\n @if (selectedCount(group); as count) {\n @if (count > 0) {\n <span\n class=\"text-xs text-primary font-medium px-2 py-0.5 rounded-full bg-primary-50\"\n >\n {{ count }}\n </span>\n }\n }\n </button>\n\n @if (isExpanded(group)) {\n <div class=\"flex flex-col gap-1 px-3 py-2 bg-surface-50/40\">\n @for (grant of group.grants; track grant.action.operationKey) {\n <label\n class=\"flex items-center gap-2 px-2 py-1.5 rounded-md cursor-pointer hover:bg-surface-100\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(grant)\"\n [disabled]=\"readonly()\"\n (change)=\"toggleGrant(grant)\"\n />\n <span class=\"text-sm\">\n {{ grant.action.operationKey }}\n </span>\n @if (grant.action.isHighRisk) {\n <mt-icon\n icon=\"alert.alert-triangle\"\n styleClass=\"text-sm text-amber-500\"\n [pTooltip]=\"t('delegations.scope.highRisk')\"\n tooltipPosition=\"top\"\n ></mt-icon>\n }\n </label>\n }\n </div>\n }\n </div>\n }\n </section>\n\n <!-- Preview -->\n <section\n class=\"flex flex-col gap-1 px-3 py-2 rounded-md border border-dashed border-gray-300 bg-surface-50\"\n >\n <div class=\"flex items-center justify-between gap-2\">\n <div class=\"text-xs font-medium text-gray-700\">\n {{ t(\"delegations.scope.previewTitle\") }}\n </div>\n @if (isPreviewing()) {\n <p-skeleton width=\"6rem\" height=\"0.75rem\"></p-skeleton>\n }\n </div>\n @if (preview(); as p) {\n @if (p.isValid) {\n <div class=\"text-sm text-gray-900\">\n {{ p.summary || t(\"delegations.column.scopeSummary\") }}\n </div>\n } @else {\n <div class=\"text-sm text-red-700\">\n {{ t(\"delegations.scope.previewInvalid\") }}\n </div>\n }\n @if (p.warnings.length > 0) {\n <ul class=\"text-xs text-amber-700 list-disc list-inside\">\n @for (w of p.warnings; track w.message) {\n <li>{{ w.message }}</li>\n }\n </ul>\n }\n @if (p.deniedItems.length > 0) {\n <ul class=\"text-xs text-red-700 list-disc list-inside\">\n @for (d of p.deniedItems; track d.operationKey) {\n <li>\n {{ d.targetKey }} / {{ d.operationKey }} \u2014\n {{ d.reasonCode }}\n </li>\n }\n </ul>\n }\n } @else {\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </div>\n }\n </section>\n }\n </div>\n</ng-container>\n", styles: [":host{display:block}.mt-scope-picker input[type=checkbox]{accent-color:var(--p-primary-color, currentColor)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i2.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1281
1045
  }
1282
1046
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ScopePicker, decorators: [{
1283
1047
  type: Component,
@@ -1287,24 +1051,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1287
1051
  SkeletonModule,
1288
1052
  TooltipModule,
1289
1053
  TranslocoDirective,
1290
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"mt-scope-picker flex flex-col gap-4\">\r\n <!-- Loading skeleton -->\r\n @if (isLoadingOptions() && !options()) {\r\n <p-skeleton height=\"2rem\"></p-skeleton>\r\n <p-skeleton height=\"6rem\"></p-skeleton>\r\n <p-skeleton height=\"6rem\"></p-skeleton>\r\n } @else if (options(); as opts) {\r\n <!-- ============================================================ -->\r\n <!-- Read policy -->\r\n <!-- ============================================================ -->\r\n <section class=\"flex flex-col gap-2\">\r\n <h4 class=\"text-sm font-medium text-gray-800\">\r\n {{ t('delegations.read-policy') }}\r\n </h4>\r\n <div class=\"flex flex-wrap gap-2\">\r\n @for (rp of readPolicies(); track rp.key) {\r\n <button\r\n type=\"button\"\r\n class=\"px-3 py-1.5 rounded-full text-xs border transition-colors cursor-pointer\"\r\n [class.border-primary]=\"scope().readPolicy === rp.key\"\r\n [class.bg-primary-50]=\"scope().readPolicy === rp.key\"\r\n [class.text-primary]=\"scope().readPolicy === rp.key\"\r\n [class.border-gray-300]=\"scope().readPolicy !== rp.key\"\r\n [class.text-gray-700]=\"scope().readPolicy !== rp.key\"\r\n [disabled]=\"readonly()\"\r\n (click)=\"setReadPolicy(rp.key)\">\r\n {{ t('delegations.' + (rp.key === 'ImplicitReadForSelectedActions'\r\n ? 'implicit-read'\r\n : rp.key === 'ExplicitReadOnly'\r\n ? 'explicit-read-only'\r\n : 'no-read-unless-own-permission')) }}\r\n </button>\r\n }\r\n </div>\r\n </section>\r\n\r\n <!-- ============================================================ -->\r\n <!-- Accessibilities -->\r\n <!-- ============================================================ -->\r\n @if (accessibilityGroups().length > 0) {\r\n <section class=\"flex flex-col gap-2\">\r\n <h4 class=\"text-sm font-medium text-gray-800\">\r\n {{ t('delegations.scope') }} \u2014\r\n {{ t('delegations.delegated-to') }}\r\n </h4>\r\n <div class=\"flex flex-wrap gap-2\">\r\n @for (g of accessibilityGroups(); track g.accessibilityGroupId) {\r\n <button\r\n type=\"button\"\r\n class=\"flex items-center gap-2 px-3 py-1.5 rounded-md text-xs border transition-colors cursor-pointer\"\r\n [class.border-primary]=\"isAccessibilitySelected(g)\"\r\n [class.bg-primary-50]=\"isAccessibilitySelected(g)\"\r\n [class.text-primary]=\"isAccessibilitySelected(g)\"\r\n [class.border-gray-300]=\"!isAccessibilitySelected(g)\"\r\n [class.text-gray-700]=\"!isAccessibilitySelected(g)\"\r\n [disabled]=\"readonly()\"\r\n (click)=\"toggleAccessibility(g)\">\r\n @if (isAccessibilitySelected(g)) {\r\n <mt-icon icon=\"general.check\" styleClass=\"text-sm\"></mt-icon>\r\n }\r\n <span>{{ g.moduleName }} / {{ g.groupName }}</span>\r\n </button>\r\n }\r\n </div>\r\n </section>\r\n }\r\n\r\n <!-- ============================================================ -->\r\n <!-- Targets + operations -->\r\n <!-- ============================================================ -->\r\n <section class=\"flex flex-col gap-2\">\r\n <h4 class=\"text-sm font-medium text-gray-800\">\r\n {{ t('delegations.target-type') }} /\r\n {{ t('delegations.operations') }}\r\n </h4>\r\n\r\n @if (groupedTargets().length === 0) {\r\n <p class=\"text-xs text-gray-500\">\r\n {{ t('delegations.no-targets-selected') }}\r\n </p>\r\n }\r\n\r\n @for (group of groupedTargets(); track group.type) {\r\n <div class=\"flex flex-col gap-1\">\r\n <div class=\"text-xs uppercase text-gray-500 tracking-wide px-2\">\r\n {{ group.type }}\r\n </div>\r\n @for (target of group.items; track target.displayName) {\r\n <div class=\"border border-gray-200 rounded-md overflow-hidden\">\r\n <button\r\n type=\"button\"\r\n class=\"flex items-center justify-between w-full px-3 py-2 hover:bg-surface-50 cursor-pointer\"\r\n (click)=\"toggleAccordion(target)\">\r\n <div class=\"flex items-center gap-2 min-w-0\">\r\n <mt-icon\r\n [icon]=\"\r\n isExpanded(target)\r\n ? 'arrow.chevron-down'\r\n : 'arrow.chevron-right'\r\n \"\r\n styleClass=\"text-base text-gray-400\"></mt-icon>\r\n <span class=\"text-sm text-gray-900 truncate\">\r\n {{ target.displayName }}\r\n </span>\r\n </div>\r\n @if (selectedOpsCount(target); as count) {\r\n @if (count > 0) {\r\n <span\r\n class=\"text-xs text-primary font-medium px-2 py-0.5 rounded-full bg-primary-50\">\r\n {{ count }}\r\n </span>\r\n }\r\n }\r\n </button>\r\n\r\n @if (isExpanded(target)) {\r\n <div class=\"flex flex-col gap-1 px-3 py-2 bg-surface-50/40\">\r\n @for (op of target.operations; track op.permissionCommand) {\r\n <label\r\n class=\"flex items-center gap-2 px-2 py-1.5 rounded-md cursor-pointer\"\r\n [class.opacity-60]=\"!op.isDelegable\"\r\n [class.cursor-not-allowed]=\"!op.isDelegable\"\r\n [class.hover:bg-surface-100]=\"op.isDelegable\"\r\n [pTooltip]=\"!op.isDelegable ? reasonLabel(op) : null\"\r\n tooltipPosition=\"top\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"isOperationSelected(target, op)\"\r\n [disabled]=\"!op.isDelegable || readonly()\"\r\n (change)=\"toggleOperation(target, op)\" />\r\n <span class=\"text-sm\">{{ op.displayName }}</span>\r\n @if (op.isHighRisk) {\r\n <mt-icon\r\n icon=\"alert.alert-triangle\"\r\n styleClass=\"text-sm text-amber-500\"\r\n [pTooltip]=\"t('delegations.high-risk-action')\"\r\n tooltipPosition=\"top\"></mt-icon>\r\n }\r\n @if (!op.isDelegable) {\r\n <span class=\"text-xs text-gray-400 ms-auto\">\r\n {{ reasonLabel(op) }}\r\n </span>\r\n }\r\n </label>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </section>\r\n\r\n <!-- ============================================================ -->\r\n <!-- Preview -->\r\n <!-- ============================================================ -->\r\n <section\r\n class=\"flex flex-col gap-1 px-3 py-2 rounded-md border border-dashed border-gray-300 bg-surface-50\">\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <div class=\"text-xs font-medium text-gray-700\">\r\n {{ t('delegations.preview-scope') }}\r\n </div>\r\n @if (isPreviewing()) {\r\n <p-skeleton width=\"6rem\" height=\"0.75rem\"></p-skeleton>\r\n }\r\n </div>\r\n @if (preview(); as p) {\r\n @if (p.isValid) {\r\n <div class=\"text-sm text-gray-900\">\r\n {{ p.summary || t('delegations.scope-summary') }}\r\n </div>\r\n } @else {\r\n <div class=\"text-sm text-red-700\">\r\n {{ t('delegations.scope-preview-invalid') }}\r\n </div>\r\n }\r\n @if (p.warnings.length > 0) {\r\n <ul class=\"text-xs text-amber-700 list-disc list-inside\">\r\n @for (w of p.warnings; track w.code) {\r\n <li>{{ w.message }}</li>\r\n }\r\n </ul>\r\n }\r\n @if (p.deniedItems.length > 0) {\r\n <ul class=\"text-xs text-red-700 list-disc list-inside\">\r\n @for (d of p.deniedItems; track d.permissionCommand) {\r\n <li>{{ d.message }}</li>\r\n }\r\n </ul>\r\n }\r\n } @else {\r\n <div class=\"text-xs text-gray-500\">\r\n {{ t('delegations.no-operations-selected') }}\r\n </div>\r\n }\r\n </section>\r\n }\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block}.mt-scope-picker input[type=checkbox]{accent-color:var(--p-primary-color, currentColor)}\n"] }]
1054
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div class=\"mt-scope-picker flex flex-col gap-4\">\n @if (isLoadingOptions() && !options()) {\n <p-skeleton height=\"2rem\"></p-skeleton>\n <p-skeleton height=\"6rem\"></p-skeleton>\n <p-skeleton height=\"6rem\"></p-skeleton>\n } @else if (options()) {\n <!-- Targets + actions -->\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-sm font-medium text-gray-800\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </h4>\n\n @if (groups().length === 0) {\n <p class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noGrantableOptions\") }}\n </p>\n }\n\n @for (group of groups(); track group.key) {\n <div class=\"border border-gray-200 rounded-md overflow-hidden\">\n <button\n type=\"button\"\n class=\"flex items-center justify-between w-full px-3 py-2 hover:bg-surface-50 cursor-pointer\"\n (click)=\"toggleAccordion(group)\"\n >\n <div class=\"flex items-center gap-2 min-w-0\">\n <mt-icon\n [icon]=\"\n isExpanded(group)\n ? 'arrow.chevron-down'\n : 'arrow.chevron-right'\n \"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n <span class=\"text-sm text-gray-900 truncate\">\n {{ group.target.displayName || group.target.targetKey }}\n </span>\n <span class=\"text-xs text-gray-400\">\n {{ group.target.targetType }}\n </span>\n </div>\n @if (selectedCount(group); as count) {\n @if (count > 0) {\n <span\n class=\"text-xs text-primary font-medium px-2 py-0.5 rounded-full bg-primary-50\"\n >\n {{ count }}\n </span>\n }\n }\n </button>\n\n @if (isExpanded(group)) {\n <div class=\"flex flex-col gap-1 px-3 py-2 bg-surface-50/40\">\n @for (grant of group.grants; track grant.action.operationKey) {\n <label\n class=\"flex items-center gap-2 px-2 py-1.5 rounded-md cursor-pointer hover:bg-surface-100\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(grant)\"\n [disabled]=\"readonly()\"\n (change)=\"toggleGrant(grant)\"\n />\n <span class=\"text-sm\">\n {{ grant.action.operationKey }}\n </span>\n @if (grant.action.isHighRisk) {\n <mt-icon\n icon=\"alert.alert-triangle\"\n styleClass=\"text-sm text-amber-500\"\n [pTooltip]=\"t('delegations.scope.highRisk')\"\n tooltipPosition=\"top\"\n ></mt-icon>\n }\n </label>\n }\n </div>\n }\n </div>\n }\n </section>\n\n <!-- Preview -->\n <section\n class=\"flex flex-col gap-1 px-3 py-2 rounded-md border border-dashed border-gray-300 bg-surface-50\"\n >\n <div class=\"flex items-center justify-between gap-2\">\n <div class=\"text-xs font-medium text-gray-700\">\n {{ t(\"delegations.scope.previewTitle\") }}\n </div>\n @if (isPreviewing()) {\n <p-skeleton width=\"6rem\" height=\"0.75rem\"></p-skeleton>\n }\n </div>\n @if (preview(); as p) {\n @if (p.isValid) {\n <div class=\"text-sm text-gray-900\">\n {{ p.summary || t(\"delegations.column.scopeSummary\") }}\n </div>\n } @else {\n <div class=\"text-sm text-red-700\">\n {{ t(\"delegations.scope.previewInvalid\") }}\n </div>\n }\n @if (p.warnings.length > 0) {\n <ul class=\"text-xs text-amber-700 list-disc list-inside\">\n @for (w of p.warnings; track w.message) {\n <li>{{ w.message }}</li>\n }\n </ul>\n }\n @if (p.deniedItems.length > 0) {\n <ul class=\"text-xs text-red-700 list-disc list-inside\">\n @for (d of p.deniedItems; track d.operationKey) {\n <li>\n {{ d.targetKey }} / {{ d.operationKey }} \u2014\n {{ d.reasonCode }}\n </li>\n }\n </ul>\n }\n } @else {\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </div>\n }\n </section>\n }\n </div>\n</ng-container>\n", styles: [":host{display:block}.mt-scope-picker input[type=checkbox]{accent-color:var(--p-primary-color, currentColor)}\n"] }]
1291
1055
  }], ctorParameters: () => [], propDecorators: { scope: [{ type: i0.Input, args: [{ isSignal: true, alias: "scope", required: false }] }, { type: i0.Output, args: ["scopeChange"] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], delegatorUserId: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegatorUserId", required: false }] }] } });
1292
1056
 
1293
- const EMPTY_SCOPE = {
1294
- mode: 'Explicit',
1295
- readPolicy: 'ImplicitReadForSelectedActions',
1296
- accessibilityGroupIds: [],
1297
- targets: [],
1298
- };
1057
+ const EMPTY_SCOPE = { grants: [], metadata: {} };
1299
1058
  /**
1300
- * Delegation create/edit form, target-shape compliant.
1059
+ * Delegation create/edit form (doc 04). Uses the v2 explicit-scope endpoints so
1060
+ * the persisted `DelegationScopeSelection` matches what scope/preview validated.
1301
1061
  *
1302
- * - Delegator is server-derived from the authenticated principal not collected
1303
- * here (admin "create on behalf of" lives behind a separate flag).
1304
- * - `isActive` no longer exists; status is server-calculated.
1305
- * - Scope is collected by `<mt-scope-picker>` (loaded from `/scope/options`)
1306
- * and validated via `/scope/preview` on every change. Edit mode rehydrates
1307
- * the scope from the loaded delegation.
1062
+ * - Delegator is server-derived; only the delegated user is collected.
1063
+ * - On edit, scope grants + rowVersion come from the loaded detail.
1064
+ * - Times are sent in UTC ISO; wrappers set timeZoneId = UTC.
1308
1065
  */
1309
1066
  class DelegationForm {
1310
1067
  delegationForEdit = input(null, ...(ngDevMode ? [{ debugName: "delegationForEdit" }] : /* istanbul ignore next */ []));
@@ -1315,64 +1072,54 @@ class DelegationForm {
1315
1072
  ref = inject(ModalRef);
1316
1073
  transloco = inject(TranslocoService);
1317
1074
  facade = inject(DelegationsFacade);
1318
- // Reactive form
1319
1075
  delegationFormControl = new FormControl();
1320
1076
  formValue = toSignal(this.delegationFormControl.valueChanges);
1321
- selected = this.facade.selected;
1077
+ detail = this.facade.detail;
1322
1078
  isDetailLoading = this.facade.isLoadingDetail;
1323
- isSaving = computed(() => this.facade.isCreating() || this.facade.isUpdating(), ...(ngDevMode ? [{ debugName: "isSaving" }] : /* istanbul ignore next */ []));
1079
+ isSaving = this.facade.isSaving;
1324
1080
  context = new HttpContext().set(REQUEST_CONTEXT, { useBaseUrl: false });
1325
- isDelegatingToSelf = computed(() => {
1326
- const v = this.formValue();
1327
- if (!v)
1328
- return false;
1329
- const toId = (v.delegatedUser?.id ?? v.delegatedUser);
1330
- const meId = (this.selected()?.delegator?.id ?? '');
1331
- return !!toId && !!meId && toId === meId;
1332
- }, ...(ngDevMode ? [{ debugName: "isDelegatingToSelf" }] : /* istanbul ignore next */ []));
1333
- // The current scope draft (built outside the form for MVP; placeholder UI).
1334
1081
  scope = signal(EMPTY_SCOPE, ...(ngDevMode ? [{ debugName: "scope" }] : /* istanbul ignore next */ []));
1335
1082
  specificDaysOptions = signal([
1336
- { label: this.transloco.translate('delegations.sunday'), value: 0 },
1337
- { label: this.transloco.translate('delegations.monday'), value: 1 },
1338
- { label: this.transloco.translate('delegations.tuesday'), value: 2 },
1339
- { label: this.transloco.translate('delegations.wednesday'), value: 3 },
1340
- { label: this.transloco.translate('delegations.thursday'), value: 4 },
1341
- { label: this.transloco.translate('delegations.friday'), value: 5 },
1342
- { label: this.transloco.translate('delegations.saturday'), value: 6 },
1083
+ { label: this.transloco.translate('delegations.days.sunday'), value: 0 },
1084
+ { label: this.transloco.translate('delegations.days.monday'), value: 1 },
1085
+ { label: this.transloco.translate('delegations.days.tuesday'), value: 2 },
1086
+ { label: this.transloco.translate('delegations.days.wednesday'), value: 3 },
1087
+ { label: this.transloco.translate('delegations.days.thursday'), value: 4 },
1088
+ { label: this.transloco.translate('delegations.days.friday'), value: 5 },
1089
+ { label: this.transloco.translate('delegations.days.saturday'), value: 6 },
1343
1090
  ], ...(ngDevMode ? [{ debugName: "specificDaysOptions" }] : /* istanbul ignore next */ []));
1344
- delegationFormConfig = computed(() => ({
1091
+ formConfig = computed(() => ({
1345
1092
  sections: [
1346
1093
  {
1347
- key: 'delegationFormConfig',
1094
+ key: 'delegationForm',
1348
1095
  type: 'header',
1349
1096
  bodyClass: 'grid grid-cols-1 gap-4 items-start lg:grid-cols-12',
1350
1097
  order: 1,
1351
1098
  fields: [
1352
1099
  new UserSearchFieldConfig({
1353
- key: 'delegatedUser',
1354
- label: this.transloco.translate('delegations.delegated-to'),
1100
+ key: 'delegateTo',
1101
+ label: this.transloco.translate('delegations.column.delegatedTo'),
1355
1102
  apiUrl: 'Identity/users',
1356
1103
  context: this.context,
1357
1104
  validators: [ValidatorConfig.required()],
1358
1105
  cssClass: this.halfWidth,
1359
1106
  colSpan: 6,
1360
1107
  order: 1,
1361
- disabled: this.readonly() || !!this.delegationForEdit()?.id,
1108
+ disabled: this.readonly() || !!this.delegationForEdit(),
1362
1109
  }),
1363
1110
  new TextareaFieldConfig({
1364
1111
  key: 'description',
1365
- label: this.transloco.translate('delegations.description'),
1366
- placeholder: this.transloco.translate('delegations.description'),
1112
+ label: this.transloco.translate('delegations.form.description'),
1113
+ placeholder: this.transloco.translate('delegations.form.description'),
1367
1114
  cssClass: this.fullWidth,
1368
1115
  colSpan: 12,
1369
1116
  order: 2,
1370
1117
  disabled: this.readonly(),
1371
1118
  }),
1372
1119
  new DateFieldConfig({
1373
- key: 'startDateUtc',
1374
- label: this.transloco.translate('delegations.delegation-date-from'),
1375
- placeholder: this.transloco.translate('delegations.delegation-date-from'),
1120
+ key: 'delegateFromDateTime',
1121
+ label: this.transloco.translate('delegations.column.startDate'),
1122
+ placeholder: this.transloco.translate('delegations.column.startDate'),
1376
1123
  validators: [ValidatorConfig.required()],
1377
1124
  showTime: true,
1378
1125
  cssClass: this.halfWidth,
@@ -1381,9 +1128,9 @@ class DelegationForm {
1381
1128
  disabled: this.readonly(),
1382
1129
  }),
1383
1130
  new DateFieldConfig({
1384
- key: 'endDateUtc',
1385
- label: this.transloco.translate('delegations.delegation-date-to'),
1386
- placeholder: this.transloco.translate('delegations.delegation-date-to'),
1131
+ key: 'delegateToDateTime',
1132
+ label: this.transloco.translate('delegations.column.endDate'),
1133
+ placeholder: this.transloco.translate('delegations.column.endDate'),
1387
1134
  validators: [ValidatorConfig.required()],
1388
1135
  showTime: true,
1389
1136
  cssClass: this.halfWidth,
@@ -1392,15 +1139,15 @@ class DelegationForm {
1392
1139
  disabled: this.readonly(),
1393
1140
  }),
1394
1141
  new RadioButtonFieldConfig({
1395
- key: 'dayRulesType',
1396
- label: this.transloco.translate('delegations.delegation-days-apply'),
1142
+ key: 'delegationDaysType',
1143
+ label: this.transloco.translate('delegations.form.daysApply'),
1397
1144
  options: [
1398
1145
  {
1399
- label: this.transloco.translate('delegations.full-range'),
1146
+ label: this.transloco.translate('delegations.form.fullRange'),
1400
1147
  value: 'FullRange',
1401
1148
  },
1402
1149
  {
1403
- label: this.transloco.translate('delegations.specific-days'),
1150
+ label: this.transloco.translate('delegations.form.specificDays'),
1404
1151
  value: 'SpecificDays',
1405
1152
  },
1406
1153
  ],
@@ -1414,13 +1161,17 @@ class DelegationForm {
1414
1161
  }),
1415
1162
  new MultiSelectFieldConfig({
1416
1163
  key: 'specificDays',
1417
- label: this.transloco.translate('delegations.specific-days'),
1164
+ label: this.transloco.translate('delegations.form.specificDays'),
1418
1165
  options: this.specificDaysOptions(),
1419
1166
  optionLabel: 'label',
1420
1167
  optionValue: 'value',
1421
1168
  maxSelectedLabels: 7,
1422
1169
  relations: [
1423
- { key: 'dayRulesType', value: 'SpecificDays', action: 'show' },
1170
+ {
1171
+ key: 'delegationDaysType',
1172
+ value: 'SpecificDays',
1173
+ action: 'show',
1174
+ },
1424
1175
  ],
1425
1176
  cssClass: this.fullWidth,
1426
1177
  colSpan: 12,
@@ -1429,7 +1180,7 @@ class DelegationForm {
1429
1180
  }),
1430
1181
  new ToggleFieldConfig({
1431
1182
  key: 'requiresApproval',
1432
- label: this.transloco.translate('delegations.requires-approval'),
1183
+ label: this.transloco.translate('delegations.form.requiresApproval'),
1433
1184
  cssClass: `${this.fullWidth} mt-2`,
1434
1185
  order: 7,
1435
1186
  disabled: this.readonly(),
@@ -1437,47 +1188,29 @@ class DelegationForm {
1437
1188
  ],
1438
1189
  },
1439
1190
  ],
1440
- }), ...(ngDevMode ? [{ debugName: "delegationFormConfig" }] : /* istanbul ignore next */ []));
1191
+ }), ...(ngDevMode ? [{ debugName: "formConfig" }] : /* istanbul ignore next */ []));
1441
1192
  constructor() {
1442
1193
  effect(() => {
1443
- const id = this.delegationForEdit()?.id;
1444
- const d = this.selected();
1445
- if (id && d) {
1194
+ const editing = this.delegationForEdit();
1195
+ const d = this.detail();
1196
+ if (editing && d && d.row.delegationId === editing.delegationId) {
1446
1197
  this.delegationFormControl.patchValue({
1447
- delegatedUser: d.delegatedUser,
1448
- description: d.description,
1449
- startDateUtc: d.startDateUtc,
1450
- endDateUtc: d.endDateUtc,
1451
- dayRulesType: d.dayRules?.type ?? 'FullRange',
1452
- specificDays: d.dayRules?.specificDays ?? [],
1453
- requiresApproval: d.requiresApproval ?? true,
1198
+ delegateTo: d.row.delegatedUser,
1199
+ description: '',
1200
+ delegateFromDateTime: d.row.startsAtUtc,
1201
+ delegateToDateTime: d.row.endsAtUtc,
1202
+ delegationDaysType: d.row.dayRuleMode,
1203
+ specificDays: d.row.specificDays ?? [],
1204
+ requiresApproval: d.row.approval?.requiresApproval ?? false,
1205
+ });
1206
+ this.scope.set({
1207
+ grants: d.scope?.grants ?? [],
1208
+ metadata: d.scope?.metadata ?? {},
1454
1209
  });
1455
- // Snapshot existing scope so we round-trip it on save until the picker ships.
1456
- if (d.scope) {
1457
- this.scope.set({
1458
- mode: 'Explicit',
1459
- readPolicy: d.scope.readPolicy,
1460
- accessibilityGroupIds: d.scope.accessibilities.map((a) => a.accessibilityGroupId),
1461
- targets: d.scope.targets.map((t) => ({
1462
- targetType: t.targetType,
1463
- targetId: t.targetId,
1464
- levelId: t.levelId,
1465
- levelModuleId: t.levelModuleId,
1466
- domainModuleId: t.domainModuleId,
1467
- moduleKey: t.moduleKey,
1468
- permissionModuleType: t.permissionModuleType,
1469
- permissionTargetId: t.permissionTargetId,
1470
- operations: t.operations.map((op) => ({
1471
- operationKey: op.operationKey,
1472
- permissionCommand: op.permissionCommand,
1473
- })),
1474
- })),
1475
- });
1476
- }
1477
1210
  }
1478
- else if (!id) {
1211
+ else if (!editing) {
1479
1212
  this.delegationFormControl.reset({
1480
- dayRulesType: 'FullRange',
1213
+ delegationDaysType: 'FullRange',
1481
1214
  requiresApproval: true,
1482
1215
  });
1483
1216
  this.scope.set(EMPTY_SCOPE);
@@ -1485,51 +1218,47 @@ class DelegationForm {
1485
1218
  });
1486
1219
  }
1487
1220
  ngOnInit() {
1488
- const id = this.delegationForEdit()?.id;
1489
- if (id) {
1490
- this.facade.getOne(id, true);
1221
+ const editing = this.delegationForEdit();
1222
+ if (editing) {
1223
+ this.facade.getDetail(editing.delegationId);
1491
1224
  }
1492
1225
  }
1226
+ canSubmit = computed(() => !this.readonly() && this.scope().grants.length > 0, ...(ngDevMode ? [{ debugName: "canSubmit" }] : /* istanbul ignore next */ []));
1493
1227
  onSubmit() {
1494
1228
  if (this.readonly() || !this.delegationFormControl.valid)
1495
1229
  return;
1230
+ if (this.scope().grants.length === 0)
1231
+ return;
1496
1232
  const v = this.delegationFormControl.value;
1497
- const tz = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
1498
- const editing = this.delegationForEdit();
1499
1233
  const base = {
1234
+ delegateTo: v?.delegateTo?.userId ?? v?.delegateTo?.id ?? v?.delegateTo,
1500
1235
  description: v?.description,
1501
- startDateUtc: v?.startDateUtc,
1502
- endDateUtc: v?.endDateUtc,
1503
- timeZoneId: tz,
1504
- dayRules: {
1505
- type: (v?.dayRulesType ?? 'FullRange'),
1506
- specificDays: v?.dayRulesType === 'SpecificDays' ? (v?.specificDays ?? []) : [],
1507
- },
1236
+ delegateFromDateTime: v?.delegateFromDateTime,
1237
+ delegateToDateTime: v?.delegateToDateTime,
1238
+ delegationDaysType: (v?.delegationDaysType ?? 'FullRange'),
1239
+ specificDays: v?.delegationDaysType === 'SpecificDays' ? (v?.specificDays ?? []) : [],
1240
+ requiresApproval: !!v?.requiresApproval,
1508
1241
  scope: this.scope(),
1509
1242
  };
1510
- if (editing?.id) {
1243
+ const editing = this.delegationForEdit();
1244
+ if (editing) {
1511
1245
  const req = {
1512
1246
  ...base,
1513
1247
  rowVersion: editing.rowVersion,
1514
- requiresApproval: v?.requiresApproval,
1515
1248
  };
1516
- this.facade.update(editing.id, req).subscribe({
1249
+ this.facade.updateV2(editing.delegationId, req).subscribe({
1517
1250
  next: () => this.ref.close(true),
1518
1251
  });
1519
1252
  }
1520
1253
  else {
1521
- const req = {
1522
- ...base,
1523
- delegatedUserId: v?.delegatedUser?.id ?? v?.delegatedUser,
1524
- requiresApproval: !!v?.requiresApproval,
1525
- };
1526
- this.facade.create(req).subscribe({
1254
+ const req = base;
1255
+ this.facade.createV2(req).subscribe({
1527
1256
  next: () => this.ref.close(true),
1528
1257
  });
1529
1258
  }
1530
1259
  }
1531
1260
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
1532
- 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 } }, ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'delegations'\">\r\n <div\r\n [class]=\"\r\n 'delegation-form-content flex min-w-0 flex-col gap-3 overflow-x-hidden p-4 max-[640px]:p-3 ' +\r\n modal.contentClass\r\n \"\r\n >\r\n <mt-dynamic-form\r\n [formConfig]=\"delegationFormConfig()\"\r\n [formControl]=\"delegationFormControl\"\r\n />\r\n\r\n <div class=\"mt-2 border-t border-gray-200 pt-3 flex flex-col gap-2\">\r\n <h3 class=\"text-sm font-semibold text-gray-800\">\r\n {{ t(\"scope\") }}\r\n </h3>\r\n <mt-scope-picker\r\n [(scope)]=\"scope\"\r\n [readonly]=\"readonly()\"\r\n ></mt-scope-picker>\r\n </div>\r\n </div>\r\n\r\n <div\r\n [class]=\"\r\n 'delegation-form-footer ' +\r\n modal.footerClass +\r\n ' flex-col gap-2 sm:flex-row sm:items-center sm:justify-end'\r\n \"\r\n >\r\n <mt-button\r\n [label]=\"t('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]=\"delegationForEdit() ? t('update') : t('create')\"\r\n [loading]=\"isSaving()\"\r\n [disabled]=\"!delegationFormControl.valid || isDelegatingToSelf()\"\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", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: DynamicForm, selector: "mt-dynamic-form", inputs: ["formConfig", "forcedHiddenFieldKeys", "preserveForcedHiddenValues", "visibleSectionKeys"], outputs: ["runtimeMessagesChange"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: ScopePicker, selector: "mt-scope-picker", inputs: ["scope", "readonly", "delegatorUserId"], outputs: ["scopeChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1261
+ 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 } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div\n [class]=\"\n 'delegation-form-content flex min-w-0 flex-col gap-3 overflow-x-hidden p-4 max-[640px]:p-3 ' +\n modal.contentClass\n \"\n >\n <mt-dynamic-form\n [formConfig]=\"formConfig()\"\n [formControl]=\"delegationFormControl\"\n />\n\n <div class=\"mt-2 border-t border-gray-200 pt-3 flex flex-col gap-2\">\n <h3 class=\"text-sm font-semibold text-gray-800\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </h3>\n <mt-scope-picker\n [(scope)]=\"scope\"\n [readonly]=\"readonly()\"\n ></mt-scope-picker>\n </div>\n </div>\n\n <div\n [class]=\"\n 'delegation-form-footer ' +\n modal.footerClass +\n ' flex-col gap-2 sm:flex-row sm:items-center sm:justify-end'\n \"\n >\n <mt-button\n [label]=\"t('delegations.common.cancel')\"\n variant=\"outlined\"\n (click)=\"ref.close()\"\n styleClass=\"w-full sm:w-auto\"\n />\n @if (!readonly()) {\n <mt-button\n [label]=\"\n delegationForEdit()\n ? t('delegations.common.update')\n : t('delegations.common.create')\n \"\n [loading]=\"isSaving()\"\n [disabled]=\"!delegationFormControl.valid || !canSubmit()\"\n (click)=\"onSubmit()\"\n styleClass=\"w-full sm:w-auto\"\n />\n }\n </div>\n</ng-container>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: DynamicForm, selector: "mt-dynamic-form", inputs: ["formConfig", "forcedHiddenFieldKeys", "preserveForcedHiddenValues", "visibleSectionKeys"], outputs: ["runtimeMessagesChange"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: ScopePicker, selector: "mt-scope-picker", inputs: ["scope", "readonly", "delegatorUserId"], outputs: ["scopeChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1533
1262
  }
1534
1263
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationForm, decorators: [{
1535
1264
  type: Component,
@@ -1540,40 +1269,37 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1540
1269
  ReactiveFormsModule,
1541
1270
  ScopePicker,
1542
1271
  TranslocoDirective,
1543
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t; prefix: 'delegations'\">\r\n <div\r\n [class]=\"\r\n 'delegation-form-content flex min-w-0 flex-col gap-3 overflow-x-hidden p-4 max-[640px]:p-3 ' +\r\n modal.contentClass\r\n \"\r\n >\r\n <mt-dynamic-form\r\n [formConfig]=\"delegationFormConfig()\"\r\n [formControl]=\"delegationFormControl\"\r\n />\r\n\r\n <div class=\"mt-2 border-t border-gray-200 pt-3 flex flex-col gap-2\">\r\n <h3 class=\"text-sm font-semibold text-gray-800\">\r\n {{ t(\"scope\") }}\r\n </h3>\r\n <mt-scope-picker\r\n [(scope)]=\"scope\"\r\n [readonly]=\"readonly()\"\r\n ></mt-scope-picker>\r\n </div>\r\n </div>\r\n\r\n <div\r\n [class]=\"\r\n 'delegation-form-footer ' +\r\n modal.footerClass +\r\n ' flex-col gap-2 sm:flex-row sm:items-center sm:justify-end'\r\n \"\r\n >\r\n <mt-button\r\n [label]=\"t('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]=\"delegationForEdit() ? t('update') : t('create')\"\r\n [loading]=\"isSaving()\"\r\n [disabled]=\"!delegationFormControl.valid || isDelegatingToSelf()\"\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" }]
1272
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div\n [class]=\"\n 'delegation-form-content flex min-w-0 flex-col gap-3 overflow-x-hidden p-4 max-[640px]:p-3 ' +\n modal.contentClass\n \"\n >\n <mt-dynamic-form\n [formConfig]=\"formConfig()\"\n [formControl]=\"delegationFormControl\"\n />\n\n <div class=\"mt-2 border-t border-gray-200 pt-3 flex flex-col gap-2\">\n <h3 class=\"text-sm font-semibold text-gray-800\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </h3>\n <mt-scope-picker\n [(scope)]=\"scope\"\n [readonly]=\"readonly()\"\n ></mt-scope-picker>\n </div>\n </div>\n\n <div\n [class]=\"\n 'delegation-form-footer ' +\n modal.footerClass +\n ' flex-col gap-2 sm:flex-row sm:items-center sm:justify-end'\n \"\n >\n <mt-button\n [label]=\"t('delegations.common.cancel')\"\n variant=\"outlined\"\n (click)=\"ref.close()\"\n styleClass=\"w-full sm:w-auto\"\n />\n @if (!readonly()) {\n <mt-button\n [label]=\"\n delegationForEdit()\n ? t('delegations.common.update')\n : t('delegations.common.create')\n \"\n [loading]=\"isSaving()\"\n [disabled]=\"!delegationFormControl.valid || !canSubmit()\"\n (click)=\"onSubmit()\"\n styleClass=\"w-full sm:w-auto\"\n />\n }\n </div>\n</ng-container>\n" }]
1544
1273
  }], ctorParameters: () => [], propDecorators: { delegationForEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegationForEdit", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }] } });
1545
1274
 
1546
1275
  /**
1547
- * Read-only drawer that shows the full server DTO of a delegation, including
1548
- * its status history. Triggered from the row's "View details" action.
1276
+ * Read-only drawer that shows the full `DelegationDetail` ({ row, scope, status }).
1549
1277
  */
1550
1278
  class DelegationDetailDrawer {
1551
- /** Delegation id passed in via the modal's inputValues. */
1552
1279
  delegationId = input.required(...(ngDevMode ? [{ debugName: "delegationId" }] : /* istanbul ignore next */ []));
1553
1280
  facade = inject(DelegationsFacade);
1554
1281
  transloco = inject(TranslocoService);
1555
1282
  ref = inject(ModalRef);
1556
1283
  tabs = [
1557
- { key: 'overview', i18n: 'delegations.view-details' },
1558
- { key: 'scope', i18n: 'delegations.scope' },
1559
- { key: 'history', i18n: 'delegations.status-history' },
1284
+ { key: 'overview', i18n: 'delegations.action.view' },
1285
+ { key: 'scope', i18n: 'delegations.column.scopeSummary' },
1560
1286
  ];
1561
1287
  activeTab = signal('overview', ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
1562
1288
  isLoading = this.facade.isLoadingDetail;
1563
- detail = this.facade.selected;
1564
- statusHistory = computed(() => this.detail()?.statusHistory ?? [], ...(ngDevMode ? [{ debugName: "statusHistory" }] : /* istanbul ignore next */ []));
1289
+ detail = this.facade.detail;
1290
+ row = computed(() => this.detail()?.row ?? null, ...(ngDevMode ? [{ debugName: "row" }] : /* istanbul ignore next */ []));
1565
1291
  ngOnInit() {
1566
- this.facade.getOne(this.delegationId(), true);
1292
+ this.facade.getDetail(this.delegationId());
1567
1293
  }
1568
1294
  setTab(tab) {
1569
1295
  this.activeTab.set(tab);
1570
1296
  }
1571
1297
  approvalLabel() {
1572
- const s = this.detail()?.approval?.status ?? 'NotRequired';
1573
- return this.transloco.translate(`delegations.approval-${s.toLowerCase()}`);
1298
+ const s = this.row()?.approval?.approvalStatus ?? 'NotRequired';
1299
+ return this.transloco.translate(`delegations.approvalStatus.${s}`);
1574
1300
  }
1575
1301
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationDetailDrawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
1576
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationDetailDrawer, isStandalone: true, selector: "mt-delegation-detail-drawer", inputs: { delegationId: { classPropertyName: "delegationId", publicName: "delegationId", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col h-full\">\r\n <!-- Tabs -->\r\n <div class=\"flex border-b border-gray-200 px-4\">\r\n @for (tab of tabs; track tab.key) {\r\n <button\r\n type=\"button\"\r\n class=\"px-3 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\r\n [class.border-primary]=\"activeTab() === tab.key\"\r\n [class.text-primary]=\"activeTab() === tab.key\"\r\n [class.border-transparent]=\"activeTab() !== tab.key\"\r\n [class.text-gray-500]=\"activeTab() !== tab.key\"\r\n (click)=\"setTab(tab.key)\"\r\n >\r\n {{ t(tab.i18n) }}\r\n </button>\r\n }\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 @switch (activeTab()) {\r\n @case ('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-avatar\r\n [image]=\"d.delegator.photo || ''\"\r\n icon=\"user.user-01\"\r\n styleClass=\"w-12! h-12! text-lg!\"\r\n ></mt-avatar>\r\n <div class=\"flex flex-col min-w-0\">\r\n <div class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.delegator-name\") }}\r\n </div>\r\n <div class=\"text-base font-medium text-gray-900 truncate\">\r\n {{ d.delegator.displayName }}\r\n </div>\r\n @if (d.delegator.email) {\r\n <div class=\"text-xs text-gray-500 truncate\">\r\n {{ d.delegator.email }}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n <mt-delegation-status-chip\r\n [status]=\"d.status\"\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 <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.delegated-to\") }}\r\n </span>\r\n <span class=\"text-sm text-gray-900\">\r\n {{ d.delegatedUser.displayName }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.approval\") }}\r\n </span>\r\n <span class=\"text-sm text-gray-900\">\r\n {{ approvalLabel() }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.start-date\") }}\r\n </span>\r\n <span class=\"text-sm text-gray-900\">\r\n {{ d.startDateUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.end-date\") }}\r\n </span>\r\n <span class=\"text-sm text-gray-900\">\r\n {{ d.endDateUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.time-zone\") }}\r\n </span>\r\n <span class=\"text-sm text-gray-900\">\r\n {{ d.timeZoneId }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.days\") }}\r\n </span>\r\n <span class=\"text-sm text-gray-900\">\r\n @if (d.dayRules.type === \"FullRange\") {\r\n {{ t(\"delegations.all-days\") }}\r\n } @else if (d.dayRules.specificDayLabels?.length) {\r\n {{ d.dayRules.specificDayLabels?.join(\", \") }}\r\n } @else {\r\n {{ d.dayRules.specificDays.join(\", \") }}\r\n }\r\n </span>\r\n </div>\r\n </div>\r\n\r\n @if (d.description) {\r\n <div class=\"flex flex-col gap-1\">\r\n <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.description\") }}\r\n </span>\r\n <p class=\"text-sm text-gray-900 whitespace-pre-line\">\r\n {{ d.description }}\r\n </p>\r\n </div>\r\n }\r\n\r\n @if (d.approval.rejectionReason) {\r\n <div\r\n class=\"rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-800\"\r\n >\r\n <div class=\"font-medium\">\r\n {{ t(\"delegations.rejection-reason\") }}\r\n </div>\r\n <div>{{ d.approval.rejectionReason }}</div>\r\n </div>\r\n }\r\n\r\n @if (d.cancellation.cancellationReason) {\r\n <div\r\n class=\"rounded-md border border-gray-200 bg-surface-50 p-3 text-sm text-gray-800\"\r\n >\r\n <div class=\"font-medium\">\r\n {{ t(\"delegations.cancel-reason\") }}\r\n </div>\r\n <div>{{ d.cancellation.cancellationReason }}</div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n @case ('scope') {\r\n <div class=\"flex flex-col gap-3\">\r\n <div class=\"text-sm text-gray-900\">\r\n {{ d.scope.summary || t(\"delegations.scope-summary\") }}\r\n </div>\r\n\r\n @if (d.scope.accessibilities.length > 0) {\r\n <div class=\"flex flex-col gap-1\">\r\n <div class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.delegated-to\") }}\r\n </div>\r\n <div class=\"flex flex-wrap gap-1.5\">\r\n @for (a of d.scope.accessibilities; track a.accessibilityGroupId) {\r\n <span\r\n class=\"text-xs px-2 py-0.5 rounded-md bg-surface-100 text-gray-700\"\r\n >\r\n {{ a.moduleName }} / {{ a.groupName }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"flex flex-col gap-2\">\r\n @for (target of d.scope.targets; track target.displayName) {\r\n <div\r\n class=\"border border-gray-200 rounded-md px-3 py-2 flex flex-col gap-1\"\r\n >\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <span class=\"text-sm font-medium text-gray-900\">\r\n {{ target.displayName }}\r\n </span>\r\n <span class=\"text-xs text-gray-500\">\r\n {{ target.targetType }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-wrap gap-1.5\">\r\n @for (op of target.operations; track op.permissionCommand) {\r\n <span\r\n class=\"text-xs px-2 py-0.5 rounded-md bg-primary-50 text-primary\"\r\n >\r\n {{ op.displayName }}\r\n @if (op.isHighRisk) {\r\n <mt-icon\r\n icon=\"alert.alert-triangle\"\r\n styleClass=\"text-xs text-amber-500\"\r\n ></mt-icon>\r\n }\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (d.scope.targets.length === 0) {\r\n <p class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.no-targets-selected\") }}\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (d.scope.warnings.length > 0) {\r\n <ul class=\"text-xs text-amber-700 list-disc list-inside\">\r\n @for (w of d.scope.warnings; track w.code) {\r\n <li>{{ w.message }}</li>\r\n }\r\n </ul>\r\n }\r\n </div>\r\n }\r\n\r\n @case ('history') {\r\n <ol class=\"flex flex-col gap-2\">\r\n @for (entry of statusHistory(); track entry.id) {\r\n <li\r\n class=\"border border-gray-200 rounded-md px-3 py-2 flex flex-col gap-1\"\r\n >\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <span class=\"text-sm font-medium text-gray-900\">\r\n {{ entry.action }}\r\n </span>\r\n <span class=\"text-xs text-gray-500\">\r\n {{ entry.createdAtUtc | date: \"short\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex items-center gap-2 text-xs text-gray-600\">\r\n @if (entry.fromStatus) {\r\n <span>{{ entry.fromStatus }}</span>\r\n <mt-icon\r\n icon=\"arrow.arrow-right\"\r\n styleClass=\"text-xs text-gray-400\"\r\n ></mt-icon>\r\n }\r\n <span>{{ entry.toStatus }}</span>\r\n </div>\r\n <div class=\"text-xs text-gray-700\">\r\n {{ t(\"delegations.delegator-name\") }}:\r\n {{ entry.actor.displayName }}\r\n </div>\r\n @if (entry.reason) {\r\n <div class=\"text-xs text-gray-600\">\r\n {{ t(\"delegations.reason\") }}: {{ entry.reason }}\r\n </div>\r\n }\r\n </li>\r\n }\r\n @if (statusHistory().length === 0) {\r\n <li class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.status-history\") }} \u2014\r\n {{ t(\"delegations.no-operations-selected\") }}\r\n </li>\r\n }\r\n </ol>\r\n }\r\n }\r\n }\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div\r\n class=\"border-t border-gray-200 px-4 py-3 flex items-center justify-end\"\r\n >\r\n <mt-button\r\n [label]=\"t('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: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1$1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationStatusChip, selector: "mt-delegation-status-chip", inputs: ["status"] }, { kind: "pipe", type: i1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1302
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationDetailDrawer, isStandalone: true, selector: "mt-delegation-detail-drawer", inputs: { delegationId: { classPropertyName: "delegationId", publicName: "delegationId", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col h-full\">\n <!-- Tabs -->\n <div class=\"flex border-b border-gray-200 px-4\">\n @for (tab of tabs; track tab.key) {\n <button\n type=\"button\"\n class=\"px-3 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\n [class.border-primary]=\"activeTab() === tab.key\"\n [class.text-primary]=\"activeTab() === tab.key\"\n [class.border-transparent]=\"activeTab() !== tab.key\"\n [class.text-gray-500]=\"activeTab() !== tab.key\"\n (click)=\"setTab(tab.key)\"\n >\n {{ t(tab.i18n) }}\n </button>\n }\n </div>\n\n <!-- Body -->\n <div class=\"flex-1 overflow-y-auto p-4\">\n @if (isLoading() && !detail()) {\n <p-skeleton height=\"2rem\" class=\"mb-3\"></p-skeleton>\n <p-skeleton height=\"6rem\" class=\"mb-3\"></p-skeleton>\n <p-skeleton height=\"6rem\"></p-skeleton>\n } @else if (detail(); as d) {\n @if (activeTab() === \"overview\") {\n <div class=\"flex flex-col gap-4\">\n <div class=\"flex items-center justify-between gap-3\">\n <div class=\"flex items-center gap-3 min-w-0\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-12! h-12! text-lg!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.delegatorName\") }}\n </div>\n <div class=\"text-base font-medium text-gray-900 truncate\">\n {{ d.row.delegator.displayName }}\n </div>\n @if (d.row.delegator.email) {\n <div class=\"text-xs text-gray-500 truncate\">\n {{ d.row.delegator.email }}\n </div>\n }\n </div>\n </div>\n <mt-delegation-status-chip\n [status]=\"d.status.effectiveStatus\"\n ></mt-delegation-status-chip>\n </div>\n\n <div class=\"grid grid-cols-2 gap-4\">\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.delegatedTo\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.delegatedUser.displayName }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.approval\") }}\n </span>\n <span class=\"text-sm text-gray-900\">{{ approvalLabel() }}</span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.startDate\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.startsAtUtc | date: \"medium\" }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.endDate\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.endsAtUtc | date: \"medium\" }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.form.timeZone\") }}\n </span>\n <span class=\"text-sm text-gray-900\">{{\n d.row.timeZoneId\n }}</span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.days\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n @if (d.row.dayRuleMode === \"FullRange\") {\n {{ t(\"delegations.form.fullRange\") }}\n } @else {\n {{ d.row.specificDays.join(\", \") }}\n }\n </span>\n </div>\n </div>\n\n @if (d.row.cancellation?.cancellationReason) {\n <div\n class=\"rounded-md border border-gray-200 bg-surface-50 p-3 text-sm text-gray-800\"\n >\n <div class=\"font-medium\">\n {{ t(\"delegations.form.cancellationReason\") }}\n </div>\n <div>{{ d.row.cancellation.cancellationReason }}</div>\n </div>\n }\n </div>\n } @else {\n <!-- Scope tab -->\n <div class=\"flex flex-col gap-3\">\n <div class=\"text-sm text-gray-900\">\n {{ d.row.scopeSummary || t(\"delegations.column.scopeSummary\") }}\n </div>\n <div class=\"flex flex-col gap-2\">\n @for (grant of d.scope.grants; track $index) {\n <div\n class=\"border border-gray-200 rounded-md px-3 py-2 flex flex-col gap-1\"\n >\n <div class=\"flex items-center justify-between gap-2\">\n <span class=\"text-sm font-medium text-gray-900\">\n {{ grant.target.displayName || grant.target.targetKey }}\n </span>\n <span class=\"text-xs text-gray-500\">\n {{ grant.target.targetType }}\n </span>\n </div>\n <span\n class=\"text-xs px-2 py-0.5 rounded-md bg-primary-50 text-primary w-fit\"\n >\n {{ grant.action.operationKey }}\n </span>\n </div>\n }\n @if (d.scope.grants.length === 0) {\n <p class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </p>\n }\n </div>\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n <div\n class=\"border-t border-gray-200 px-4 py-3 flex items-center justify-end\"\n >\n <mt-button\n [label]=\"t('delegations.common.cancel')\"\n variant=\"outlined\"\n (click)=\"ref.close()\"\n ></mt-button>\n </div>\n </div>\n</ng-container>\n", styles: [":host{display:block;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationStatusChip, selector: "mt-delegation-status-chip", inputs: ["status"] }, { kind: "pipe", type: i2$1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1577
1303
  }
1578
1304
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationDetailDrawer, decorators: [{
1579
1305
  type: Component,
@@ -1581,29 +1307,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1581
1307
  CommonModule,
1582
1308
  Avatar,
1583
1309
  Button,
1584
- Icon,
1585
1310
  SkeletonModule,
1586
1311
  TranslocoDirective,
1587
1312
  DelegationStatusChip,
1588
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col h-full\">\r\n <!-- Tabs -->\r\n <div class=\"flex border-b border-gray-200 px-4\">\r\n @for (tab of tabs; track tab.key) {\r\n <button\r\n type=\"button\"\r\n class=\"px-3 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\r\n [class.border-primary]=\"activeTab() === tab.key\"\r\n [class.text-primary]=\"activeTab() === tab.key\"\r\n [class.border-transparent]=\"activeTab() !== tab.key\"\r\n [class.text-gray-500]=\"activeTab() !== tab.key\"\r\n (click)=\"setTab(tab.key)\"\r\n >\r\n {{ t(tab.i18n) }}\r\n </button>\r\n }\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 @switch (activeTab()) {\r\n @case ('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-avatar\r\n [image]=\"d.delegator.photo || ''\"\r\n icon=\"user.user-01\"\r\n styleClass=\"w-12! h-12! text-lg!\"\r\n ></mt-avatar>\r\n <div class=\"flex flex-col min-w-0\">\r\n <div class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.delegator-name\") }}\r\n </div>\r\n <div class=\"text-base font-medium text-gray-900 truncate\">\r\n {{ d.delegator.displayName }}\r\n </div>\r\n @if (d.delegator.email) {\r\n <div class=\"text-xs text-gray-500 truncate\">\r\n {{ d.delegator.email }}\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n <mt-delegation-status-chip\r\n [status]=\"d.status\"\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 <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.delegated-to\") }}\r\n </span>\r\n <span class=\"text-sm text-gray-900\">\r\n {{ d.delegatedUser.displayName }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.approval\") }}\r\n </span>\r\n <span class=\"text-sm text-gray-900\">\r\n {{ approvalLabel() }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.start-date\") }}\r\n </span>\r\n <span class=\"text-sm text-gray-900\">\r\n {{ d.startDateUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.end-date\") }}\r\n </span>\r\n <span class=\"text-sm text-gray-900\">\r\n {{ d.endDateUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.time-zone\") }}\r\n </span>\r\n <span class=\"text-sm text-gray-900\">\r\n {{ d.timeZoneId }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.days\") }}\r\n </span>\r\n <span class=\"text-sm text-gray-900\">\r\n @if (d.dayRules.type === \"FullRange\") {\r\n {{ t(\"delegations.all-days\") }}\r\n } @else if (d.dayRules.specificDayLabels?.length) {\r\n {{ d.dayRules.specificDayLabels?.join(\", \") }}\r\n } @else {\r\n {{ d.dayRules.specificDays.join(\", \") }}\r\n }\r\n </span>\r\n </div>\r\n </div>\r\n\r\n @if (d.description) {\r\n <div class=\"flex flex-col gap-1\">\r\n <span class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.description\") }}\r\n </span>\r\n <p class=\"text-sm text-gray-900 whitespace-pre-line\">\r\n {{ d.description }}\r\n </p>\r\n </div>\r\n }\r\n\r\n @if (d.approval.rejectionReason) {\r\n <div\r\n class=\"rounded-md border border-red-200 bg-red-50 p-3 text-sm text-red-800\"\r\n >\r\n <div class=\"font-medium\">\r\n {{ t(\"delegations.rejection-reason\") }}\r\n </div>\r\n <div>{{ d.approval.rejectionReason }}</div>\r\n </div>\r\n }\r\n\r\n @if (d.cancellation.cancellationReason) {\r\n <div\r\n class=\"rounded-md border border-gray-200 bg-surface-50 p-3 text-sm text-gray-800\"\r\n >\r\n <div class=\"font-medium\">\r\n {{ t(\"delegations.cancel-reason\") }}\r\n </div>\r\n <div>{{ d.cancellation.cancellationReason }}</div>\r\n </div>\r\n }\r\n </div>\r\n }\r\n\r\n @case ('scope') {\r\n <div class=\"flex flex-col gap-3\">\r\n <div class=\"text-sm text-gray-900\">\r\n {{ d.scope.summary || t(\"delegations.scope-summary\") }}\r\n </div>\r\n\r\n @if (d.scope.accessibilities.length > 0) {\r\n <div class=\"flex flex-col gap-1\">\r\n <div class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.delegated-to\") }}\r\n </div>\r\n <div class=\"flex flex-wrap gap-1.5\">\r\n @for (a of d.scope.accessibilities; track a.accessibilityGroupId) {\r\n <span\r\n class=\"text-xs px-2 py-0.5 rounded-md bg-surface-100 text-gray-700\"\r\n >\r\n {{ a.moduleName }} / {{ a.groupName }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"flex flex-col gap-2\">\r\n @for (target of d.scope.targets; track target.displayName) {\r\n <div\r\n class=\"border border-gray-200 rounded-md px-3 py-2 flex flex-col gap-1\"\r\n >\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <span class=\"text-sm font-medium text-gray-900\">\r\n {{ target.displayName }}\r\n </span>\r\n <span class=\"text-xs text-gray-500\">\r\n {{ target.targetType }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-wrap gap-1.5\">\r\n @for (op of target.operations; track op.permissionCommand) {\r\n <span\r\n class=\"text-xs px-2 py-0.5 rounded-md bg-primary-50 text-primary\"\r\n >\r\n {{ op.displayName }}\r\n @if (op.isHighRisk) {\r\n <mt-icon\r\n icon=\"alert.alert-triangle\"\r\n styleClass=\"text-xs text-amber-500\"\r\n ></mt-icon>\r\n }\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (d.scope.targets.length === 0) {\r\n <p class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.no-targets-selected\") }}\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (d.scope.warnings.length > 0) {\r\n <ul class=\"text-xs text-amber-700 list-disc list-inside\">\r\n @for (w of d.scope.warnings; track w.code) {\r\n <li>{{ w.message }}</li>\r\n }\r\n </ul>\r\n }\r\n </div>\r\n }\r\n\r\n @case ('history') {\r\n <ol class=\"flex flex-col gap-2\">\r\n @for (entry of statusHistory(); track entry.id) {\r\n <li\r\n class=\"border border-gray-200 rounded-md px-3 py-2 flex flex-col gap-1\"\r\n >\r\n <div class=\"flex items-center justify-between gap-2\">\r\n <span class=\"text-sm font-medium text-gray-900\">\r\n {{ entry.action }}\r\n </span>\r\n <span class=\"text-xs text-gray-500\">\r\n {{ entry.createdAtUtc | date: \"short\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex items-center gap-2 text-xs text-gray-600\">\r\n @if (entry.fromStatus) {\r\n <span>{{ entry.fromStatus }}</span>\r\n <mt-icon\r\n icon=\"arrow.arrow-right\"\r\n styleClass=\"text-xs text-gray-400\"\r\n ></mt-icon>\r\n }\r\n <span>{{ entry.toStatus }}</span>\r\n </div>\r\n <div class=\"text-xs text-gray-700\">\r\n {{ t(\"delegations.delegator-name\") }}:\r\n {{ entry.actor.displayName }}\r\n </div>\r\n @if (entry.reason) {\r\n <div class=\"text-xs text-gray-600\">\r\n {{ t(\"delegations.reason\") }}: {{ entry.reason }}\r\n </div>\r\n }\r\n </li>\r\n }\r\n @if (statusHistory().length === 0) {\r\n <li class=\"text-xs text-gray-500\">\r\n {{ t(\"delegations.status-history\") }} \u2014\r\n {{ t(\"delegations.no-operations-selected\") }}\r\n </li>\r\n }\r\n </ol>\r\n }\r\n }\r\n }\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div\r\n class=\"border-t border-gray-200 px-4 py-3 flex items-center justify-end\"\r\n >\r\n <mt-button\r\n [label]=\"t('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"] }]
1313
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col h-full\">\n <!-- Tabs -->\n <div class=\"flex border-b border-gray-200 px-4\">\n @for (tab of tabs; track tab.key) {\n <button\n type=\"button\"\n class=\"px-3 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\n [class.border-primary]=\"activeTab() === tab.key\"\n [class.text-primary]=\"activeTab() === tab.key\"\n [class.border-transparent]=\"activeTab() !== tab.key\"\n [class.text-gray-500]=\"activeTab() !== tab.key\"\n (click)=\"setTab(tab.key)\"\n >\n {{ t(tab.i18n) }}\n </button>\n }\n </div>\n\n <!-- Body -->\n <div class=\"flex-1 overflow-y-auto p-4\">\n @if (isLoading() && !detail()) {\n <p-skeleton height=\"2rem\" class=\"mb-3\"></p-skeleton>\n <p-skeleton height=\"6rem\" class=\"mb-3\"></p-skeleton>\n <p-skeleton height=\"6rem\"></p-skeleton>\n } @else if (detail(); as d) {\n @if (activeTab() === \"overview\") {\n <div class=\"flex flex-col gap-4\">\n <div class=\"flex items-center justify-between gap-3\">\n <div class=\"flex items-center gap-3 min-w-0\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-12! h-12! text-lg!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.delegatorName\") }}\n </div>\n <div class=\"text-base font-medium text-gray-900 truncate\">\n {{ d.row.delegator.displayName }}\n </div>\n @if (d.row.delegator.email) {\n <div class=\"text-xs text-gray-500 truncate\">\n {{ d.row.delegator.email }}\n </div>\n }\n </div>\n </div>\n <mt-delegation-status-chip\n [status]=\"d.status.effectiveStatus\"\n ></mt-delegation-status-chip>\n </div>\n\n <div class=\"grid grid-cols-2 gap-4\">\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.delegatedTo\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.delegatedUser.displayName }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.approval\") }}\n </span>\n <span class=\"text-sm text-gray-900\">{{ approvalLabel() }}</span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.startDate\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.startsAtUtc | date: \"medium\" }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.endDate\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.endsAtUtc | date: \"medium\" }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.form.timeZone\") }}\n </span>\n <span class=\"text-sm text-gray-900\">{{\n d.row.timeZoneId\n }}</span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.days\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n @if (d.row.dayRuleMode === \"FullRange\") {\n {{ t(\"delegations.form.fullRange\") }}\n } @else {\n {{ d.row.specificDays.join(\", \") }}\n }\n </span>\n </div>\n </div>\n\n @if (d.row.cancellation?.cancellationReason) {\n <div\n class=\"rounded-md border border-gray-200 bg-surface-50 p-3 text-sm text-gray-800\"\n >\n <div class=\"font-medium\">\n {{ t(\"delegations.form.cancellationReason\") }}\n </div>\n <div>{{ d.row.cancellation.cancellationReason }}</div>\n </div>\n }\n </div>\n } @else {\n <!-- Scope tab -->\n <div class=\"flex flex-col gap-3\">\n <div class=\"text-sm text-gray-900\">\n {{ d.row.scopeSummary || t(\"delegations.column.scopeSummary\") }}\n </div>\n <div class=\"flex flex-col gap-2\">\n @for (grant of d.scope.grants; track $index) {\n <div\n class=\"border border-gray-200 rounded-md px-3 py-2 flex flex-col gap-1\"\n >\n <div class=\"flex items-center justify-between gap-2\">\n <span class=\"text-sm font-medium text-gray-900\">\n {{ grant.target.displayName || grant.target.targetKey }}\n </span>\n <span class=\"text-xs text-gray-500\">\n {{ grant.target.targetType }}\n </span>\n </div>\n <span\n class=\"text-xs px-2 py-0.5 rounded-md bg-primary-50 text-primary w-fit\"\n >\n {{ grant.action.operationKey }}\n </span>\n </div>\n }\n @if (d.scope.grants.length === 0) {\n <p class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </p>\n }\n </div>\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n <div\n class=\"border-t border-gray-200 px-4 py-3 flex items-center justify-end\"\n >\n <mt-button\n [label]=\"t('delegations.common.cancel')\"\n variant=\"outlined\"\n (click)=\"ref.close()\"\n ></mt-button>\n </div>\n </div>\n</ng-container>\n", styles: [":host{display:block;height:100%}\n"] }]
1589
1314
  }], propDecorators: { delegationId: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegationId", required: true }] }] } });
1590
1315
 
1591
1316
  /**
1592
- * DelegationsPage replaces the legacy single-list CRUD view with the target
1593
- * two-list portal contract: "My Delegations" (I am delegator) and "Delegated To Me".
1594
- *
1595
- * - Rows render the BE-returned `allowedActions` only (never inferred).
1596
- * - Lifecycle actions (Approve/Reject/Cancel/Edit/StartSession) dispatch through
1597
- * the facade and the row is replaced in place from the response.
1598
- * - Scope/detail drawer and full scope-picker form are still incremental work;
1599
- * the form drawer here covers Edit/Create with the core fields.
1317
+ * Delegations portal page (doc 02, 09): two lists My Delegations (current user
1318
+ * is delegator) and Delegated To Me (current user is delegate). Rows render only
1319
+ * the BE-returned `allowedActions`; status comes from `effectiveStatus`.
1600
1320
  */
1601
1321
  class DelegationsList {
1602
1322
  facade = inject(DelegationsFacade);
1603
- session = inject(DelegationSessionFacade);
1604
1323
  modal = inject(ModalService);
1605
1324
  transloco = inject(TranslocoService);
1606
- // ---- Page header (unchanged from legacy admin breadcrumbs) ----
1607
1325
  breadcrumbItems = linkedSignal(() => [
1608
1326
  {
1609
1327
  label: '',
@@ -1615,47 +1333,42 @@ class DelegationsList {
1615
1333
  routerLink: '/control-panel/product-settings',
1616
1334
  },
1617
1335
  {
1618
- label: this.transloco.translate('delegations.delegations'),
1336
+ label: this.transloco.translate('delegations.title'),
1619
1337
  },
1620
1338
  ], ...(ngDevMode ? [{ debugName: "breadcrumbItems" }] : /* istanbul ignore next */ []));
1621
- // ---- Tabs ----
1622
1339
  activeTab = signal('my', ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
1623
1340
  tabs = signal([
1624
1341
  {
1625
1342
  value: 'my',
1626
- label: this.transloco.translate('delegations.my-delegations'),
1343
+ label: this.transloco.translate('delegations.myDelegations'),
1627
1344
  },
1628
1345
  {
1629
1346
  value: 'assigned',
1630
- label: this.transloco.translate('delegations.delegated-to-me'),
1347
+ label: this.transloco.translate('delegations.delegatedToMe'),
1631
1348
  },
1632
1349
  ], ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
1633
- // ---- Loading / data ----
1634
1350
  isLoading = computed(() => this.activeTab() === 'my'
1635
1351
  ? this.facade.isLoadingMy()
1636
1352
  : this.facade.isLoadingAssigned(), ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
1637
1353
  rows = computed(() => this.activeTab() === 'my'
1638
1354
  ? this.facade.myItems()
1639
1355
  : this.facade.assignedItems(), ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
1640
- // ---- Column templates ----
1641
1356
  statusCol = viewChild.required('statusCol');
1642
1357
  userCol = viewChild.required('userCol');
1643
1358
  scopeCol = viewChild.required('scopeCol');
1644
1359
  daysCol = viewChild.required('daysCol');
1645
- // ---- Table actions (header) ----
1646
1360
  tableActions = signal([
1647
1361
  {
1648
1362
  icon: 'general.plus',
1649
- label: this.transloco.translate('delegations.add-delegation'),
1363
+ label: this.transloco.translate('delegations.action.create'),
1650
1364
  color: 'primary',
1651
1365
  action: () => this.openForm(null),
1652
1366
  },
1653
1367
  ], ...(ngDevMode ? [{ debugName: "tableActions" }] : /* istanbul ignore next */ []));
1654
- // ---- Per-row action: read ONLY allowedActions from BE ----
1655
1368
  rowActions = signal([
1656
1369
  {
1657
1370
  icon: 'general.eye',
1658
- tooltip: this.transloco.translate('delegations.view-details'),
1371
+ tooltip: this.transloco.translate('delegations.action.view'),
1659
1372
  color: 'primary',
1660
1373
  variant: 'outlined',
1661
1374
  action: (row) => this.viewDetails(row),
@@ -1663,46 +1376,43 @@ class DelegationsList {
1663
1376
  },
1664
1377
  {
1665
1378
  icon: 'custom.pencil',
1666
- tooltip: this.transloco.translate('edit'),
1379
+ tooltip: this.transloco.translate('delegations.action.edit'),
1667
1380
  color: 'primary',
1668
1381
  action: (row) => this.openForm(row),
1669
1382
  hidden: (row) => !this.has(row, 'Edit'),
1670
1383
  },
1671
1384
  {
1672
1385
  icon: 'general.check',
1673
- tooltip: this.transloco.translate('delegations.approve'),
1386
+ tooltip: this.transloco.translate('delegations.action.approve'),
1674
1387
  color: 'primary',
1675
1388
  action: (row) => this.approve(row),
1676
1389
  hidden: (row) => !this.has(row, 'Approve'),
1677
- loading: (row) => this.busyIds().includes(`approve:${row.id}`),
1390
+ loading: (row) => this.busyIds().includes(`approve:${row.delegationId}`),
1678
1391
  confirmation: { type: 'popup' },
1679
1392
  },
1680
1393
  {
1681
1394
  icon: 'general.x-close',
1682
- tooltip: this.transloco.translate('delegations.reject'),
1395
+ tooltip: this.transloco.translate('delegations.action.reject'),
1683
1396
  color: 'danger',
1684
1397
  variant: 'outlined',
1685
1398
  action: (row) => this.reject(row),
1686
1399
  hidden: (row) => !this.has(row, 'Reject'),
1687
- loading: (row) => this.busyIds().includes(`reject:${row.id}`),
1400
+ loading: (row) => this.busyIds().includes(`reject:${row.delegationId}`),
1688
1401
  confirmation: { type: 'popup' },
1689
1402
  },
1690
1403
  {
1691
- icon: 'general.x-close',
1692
- tooltip: this.transloco.translate('delegations.cancel-delegation'),
1404
+ icon: 'general.trash-01',
1405
+ tooltip: this.transloco.translate('delegations.action.cancel'),
1693
1406
  color: 'danger',
1694
1407
  variant: 'outlined',
1695
1408
  action: (row) => this.cancel(row),
1696
- hidden: (row) => !this.has(row, 'Cancel') ||
1697
- // If row also has Approve/Reject, the cancel for a delegate is "withdraw";
1698
- // the assertion stays both can render and BE decides outcome.
1699
- false,
1700
- loading: (row) => this.busyIds().includes(`cancel:${row.id}`),
1701
- confirmation: { type: 'popup' },
1409
+ hidden: (row) => !this.has(row, 'Cancel'),
1410
+ loading: (row) => this.busyIds().includes(`cancel:${row.delegationId}`),
1411
+ confirmation: { type: 'popup', confirmationType: 'delete' },
1702
1412
  },
1703
1413
  {
1704
1414
  icon: 'arrow.arrow-right',
1705
- tooltip: this.transloco.translate('delegations.start-session'),
1415
+ tooltip: this.transloco.translate('delegations.action.startSession'),
1706
1416
  color: 'primary',
1707
1417
  action: (row) => this.startSession(row),
1708
1418
  hidden: (row) => !this.has(row, 'StartSession'),
@@ -1710,43 +1420,47 @@ class DelegationsList {
1710
1420
  ], ...(ngDevMode ? [{ debugName: "rowActions" }] : /* istanbul ignore next */ []));
1711
1421
  tableColumns = linkedSignal(() => [
1712
1422
  {
1713
- key: 'status',
1714
- label: this.transloco.translate('delegations.status'),
1423
+ key: 'effectiveStatus',
1424
+ label: this.transloco.translate('delegations.column.status'),
1715
1425
  type: 'custom',
1716
1426
  customCellTpl: this.statusCol(),
1717
1427
  },
1718
1428
  {
1719
1429
  key: this.activeTab() === 'my' ? 'delegatedUser' : 'delegator',
1720
1430
  label: this.transloco.translate(this.activeTab() === 'my'
1721
- ? 'delegations.delegated-to'
1722
- : 'delegations.delegator-name'),
1431
+ ? 'delegations.column.delegatedTo'
1432
+ : 'delegations.column.delegatorName'),
1723
1433
  type: 'custom',
1724
1434
  customCellTpl: this.userCol(),
1725
1435
  },
1726
1436
  {
1727
- key: 'scope',
1728
- label: this.transloco.translate('delegations.scope'),
1437
+ key: 'scopeSummary',
1438
+ label: this.transloco.translate('delegations.column.scopeSummary'),
1729
1439
  type: 'custom',
1730
1440
  customCellTpl: this.scopeCol(),
1731
1441
  },
1732
1442
  {
1733
- key: 'startDateUtc',
1734
- label: this.transloco.translate('delegations.start-date'),
1443
+ key: 'startsAtUtc',
1444
+ label: this.transloco.translate('delegations.column.startDate'),
1735
1445
  type: 'dateTime',
1736
1446
  },
1737
1447
  {
1738
- key: 'endDateUtc',
1739
- label: this.transloco.translate('delegations.end-date'),
1448
+ key: 'endsAtUtc',
1449
+ label: this.transloco.translate('delegations.column.endDate'),
1740
1450
  type: 'dateTime',
1741
1451
  },
1742
1452
  {
1743
- key: 'dayRules',
1744
- label: this.transloco.translate('delegations.days'),
1453
+ key: 'createdAtUtc',
1454
+ label: this.transloco.translate('delegations.column.createdDate'),
1455
+ type: 'dateTime',
1456
+ },
1457
+ {
1458
+ key: 'dayRuleMode',
1459
+ label: this.transloco.translate('delegations.column.days'),
1745
1460
  type: 'custom',
1746
1461
  customCellTpl: this.daysCol(),
1747
1462
  },
1748
1463
  ], ...(ngDevMode ? [{ debugName: "tableColumns" }] : /* istanbul ignore next */ []));
1749
- // ---- per-row "busy" tracking (no row-version conflict UX yet — toast suffices) ----
1750
1464
  busyIds = signal([], ...(ngDevMode ? [{ debugName: "busyIds" }] : /* istanbul ignore next */ []));
1751
1465
  weekdayKeys = [
1752
1466
  'sunday',
@@ -1757,9 +1471,6 @@ class DelegationsList {
1757
1471
  'friday',
1758
1472
  'saturday',
1759
1473
  ];
1760
- // ---------------------------------------------------------------------------
1761
- // Init / tab change
1762
- // ---------------------------------------------------------------------------
1763
1474
  ngOnInit() {
1764
1475
  this.loadCurrentTab();
1765
1476
  }
@@ -1767,7 +1478,7 @@ class DelegationsList {
1767
1478
  this.activeTab.set(tab);
1768
1479
  this.loadCurrentTab();
1769
1480
  }
1770
- loadCurrentTab(query = { page: 1, pageSize: 20 }) {
1481
+ loadCurrentTab(query = { page: 1, pageSize: 25 }) {
1771
1482
  if (this.activeTab() === 'my') {
1772
1483
  this.facade.getMy(query);
1773
1484
  }
@@ -1775,106 +1486,87 @@ class DelegationsList {
1775
1486
  this.facade.getAssigned(query);
1776
1487
  }
1777
1488
  }
1778
- // ---------------------------------------------------------------------------
1779
- // Helpers
1780
- // ---------------------------------------------------------------------------
1489
+ reloadCurrentTab() {
1490
+ this.loadCurrentTab();
1491
+ }
1781
1492
  has(row, action) {
1782
1493
  return row.allowedActions?.includes(action) ?? false;
1783
1494
  }
1784
1495
  formatDays(row) {
1785
- if (row.dayRules?.type === 'SpecificDays' &&
1786
- row.dayRules.specificDays?.length) {
1787
- return row.dayRules.specificDays
1496
+ if (row.dayRuleMode === 'SpecificDays' && row.specificDays?.length) {
1497
+ return row.specificDays
1788
1498
  .filter((d) => d >= 0 && d < this.weekdayKeys.length)
1789
- .map((d) => this.transloco.translate(`delegations.${this.weekdayKeys[d]}`))
1499
+ .map((d) => this.transloco.translate(`delegations.days.${this.weekdayKeys[d]}`))
1790
1500
  .join(', ');
1791
1501
  }
1792
- return this.transloco.translate('delegations.all-days');
1502
+ return this.transloco.translate('delegations.form.fullRange');
1793
1503
  }
1794
- // ---------------------------------------------------------------------------
1795
- // Lifecycle actions
1796
- // ---------------------------------------------------------------------------
1797
1504
  viewDetails(row) {
1798
1505
  this.modal.openModal(DelegationDetailDrawer, 'drawer', {
1799
- header: this.transloco.translate('delegations.view-details'),
1506
+ header: this.transloco.translate('delegations.action.view'),
1800
1507
  styleClass: '!absolute !shadow-none !w-full !max-w-full sm:!w-[42rem] xl:!w-[50rem]',
1801
1508
  position: 'end',
1802
1509
  appendTo: 'page-content',
1803
1510
  dismissableMask: true,
1804
1511
  dismissible: true,
1805
- inputValues: { delegationId: row.id },
1512
+ inputValues: { delegationId: row.delegationId },
1806
1513
  });
1807
1514
  }
1808
- openForm(row, readonly = false) {
1809
- const isDrawer = row ? 'drawer' : 'dialog';
1810
- this.modal.openModal(DelegationForm, isDrawer, {
1811
- header: this.transloco.translate(row ? 'delegations.edit-delegation' : 'delegations.create-delegation'),
1812
- styleClass: isDrawer === 'drawer'
1515
+ openForm(row) {
1516
+ const ref = this.modal.openModal(DelegationForm, row ? 'drawer' : 'dialog', {
1517
+ header: this.transloco.translate(row ? 'delegations.action.edit' : 'delegations.action.create'),
1518
+ styleClass: row
1813
1519
  ? '!absolute !shadow-none !w-full !max-w-full sm:!w-[42rem] xl:!w-[50rem]'
1814
1520
  : '!w-[min(96vw,50rem)] !max-w-[96vw] !h-[min(90vh,40rem)] !max-h-[90vh]',
1815
- position: isDrawer === 'drawer' ? 'end' : '',
1816
- appendTo: isDrawer === 'drawer' ? 'page-content' : 'body',
1521
+ position: row ? 'end' : '',
1522
+ appendTo: row ? 'page-content' : 'body',
1817
1523
  dismissableMask: true,
1818
1524
  dismissible: true,
1819
- inputValues: {
1820
- delegationForEdit: row,
1821
- readonly,
1822
- },
1525
+ inputValues: { delegationForEdit: row, readonly: false },
1526
+ });
1527
+ ref.onClose.subscribe((saved) => {
1528
+ if (saved)
1529
+ this.reloadCurrentTab();
1823
1530
  });
1824
1531
  }
1825
1532
  approve(row) {
1826
- this.mark(`approve:${row.id}`, true);
1533
+ this.mark(`approve:${row.delegationId}`, true);
1827
1534
  this.facade
1828
- .approve(row.id, { rowVersion: row.rowVersion })
1829
- .pipe(finalize(() => this.mark(`approve:${row.id}`, false)))
1830
- .subscribe();
1535
+ .approve(row.delegationId, { rowVersion: row.rowVersion })
1536
+ .pipe(finalize(() => this.mark(`approve:${row.delegationId}`, false)))
1537
+ .subscribe({ next: () => this.reloadCurrentTab() });
1831
1538
  }
1832
1539
  reject(row) {
1833
- this.mark(`reject:${row.id}`, true);
1540
+ this.mark(`reject:${row.delegationId}`, true);
1834
1541
  this.facade
1835
- .reject(row.id, {
1542
+ .reject(row.delegationId, {
1836
1543
  rowVersion: row.rowVersion,
1837
- reason: this.transloco.translate('delegations.reject'),
1544
+ reason: this.transloco.translate('delegations.action.reject'),
1838
1545
  })
1839
- .pipe(finalize(() => this.mark(`reject:${row.id}`, false)))
1840
- .subscribe();
1546
+ .pipe(finalize(() => this.mark(`reject:${row.delegationId}`, false)))
1547
+ .subscribe({ next: () => this.reloadCurrentTab() });
1841
1548
  }
1842
1549
  cancel(row) {
1843
- this.mark(`cancel:${row.id}`, true);
1550
+ this.mark(`cancel:${row.delegationId}`, true);
1844
1551
  this.facade
1845
- .cancel(row.id, {
1846
- rowVersion: row.rowVersion,
1847
- reason: this.transloco.translate('delegations.cancel-delegation'),
1848
- })
1849
- .pipe(finalize(() => this.mark(`cancel:${row.id}`, false)))
1850
- .subscribe();
1552
+ .cancel(row.delegationId, row.rowVersion)
1553
+ .pipe(finalize(() => this.mark(`cancel:${row.delegationId}`, false)))
1554
+ .subscribe({ next: () => this.reloadCurrentTab() });
1851
1555
  }
1852
1556
  startSession(row) {
1853
1557
  this.modal.openModal(StartSessionDialog, 'dialog', {
1854
- header: this.transloco.translate('delegations.start-session'),
1558
+ header: this.transloco.translate('delegations.action.startSession'),
1855
1559
  styleClass: '!w-[min(96vw,28rem)] !max-w-[96vw]',
1856
1560
  dismissableMask: true,
1857
1561
  dismissible: true,
1858
- inputValues: {
1859
- candidate: {
1860
- id: row.id,
1861
- delegator: row.delegator,
1862
- delegatedUser: row.delegatedUser,
1863
- status: row.status,
1864
- allowedActions: row.allowedActions,
1865
- startDateUtc: row.startDateUtc,
1866
- endDateUtc: row.endDateUtc,
1867
- scopeSummary: row.scope?.summary,
1868
- },
1869
- intent: 'start',
1870
- },
1562
+ inputValues: { delegation: row, intent: 'start' },
1871
1563
  });
1872
1564
  }
1873
1565
  mark(key, on) {
1874
1566
  this.busyIds.update((ids) => on ? [...ids, key] : ids.filter((id) => id !== key));
1875
1567
  }
1876
1568
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsList, deps: [], target: i0.ɵɵFactoryTarget.Component });
1877
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationsList, isStandalone: true, selector: "mt-delegations-list", viewQueries: [{ propertyName: "statusCol", first: true, predicate: ["statusCol"], descendants: true, isSignal: true }, { propertyName: "userCol", first: true, predicate: ["userCol"], descendants: true, isSignal: true }, { propertyName: "scopeCol", first: true, predicate: ["scopeCol"], descendants: true, isSignal: true }, { propertyName: "daysCol", first: true, predicate: ["daysCol"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col h-full gap-2 p-4\">\r\n <mt-breadcrumb [items]=\"breadcrumbItems()\"></mt-breadcrumb>\r\n\r\n <div class=\"flex items-center gap-2 border-b border-gray-200\">\r\n @for (tab of tabs(); track tab.value) {\r\n <button\r\n type=\"button\"\r\n class=\"px-4 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\r\n [class.border-primary]=\"activeTab() === tab.value\"\r\n [class.text-primary]=\"activeTab() === tab.value\"\r\n [class.border-transparent]=\"activeTab() !== tab.value\"\r\n [class.text-gray-500]=\"activeTab() !== tab.value\"\r\n (click)=\"switchTab(tab.value)\"\r\n >\r\n {{ tab.label }}\r\n </button>\r\n }\r\n </div>\r\n\r\n <mt-table\r\n [data]=\"rows()\"\r\n [columns]=\"tableColumns()\"\r\n [actions]=\"tableActions()\"\r\n [rowActions]=\"rowActions()\"\r\n [loading]=\"isLoading()\"\r\n >\r\n <ng-template #statusCol let-row>\r\n <mt-delegation-status-chip\r\n [status]=\"row.status\"\r\n ></mt-delegation-status-chip>\r\n </ng-template>\r\n\r\n <ng-template #userCol let-row>\r\n @let user = activeTab() === \"my\" ? row.delegatedUser : row.delegator;\r\n <div class=\"flex items-center gap-2\">\r\n <mt-avatar\r\n [image]=\"user?.photo || ''\"\r\n icon=\"user.user-01\"\r\n styleClass=\"w-8! h-8!\"\r\n ></mt-avatar>\r\n <div class=\"flex flex-col min-w-0\">\r\n <span class=\"text-sm text-gray-900 truncate\">\r\n {{ user?.displayName }}\r\n </span>\r\n @if (user?.email) {\r\n <span class=\"text-xs text-gray-500 truncate\">\r\n {{ user.email }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n </ng-template>\r\n\r\n <ng-template #scopeCol let-row>\r\n <span class=\"text-sm text-gray-700\" [title]=\"row.scope?.summary\">\r\n {{ row.scope?.summary || \"\u2014\" }}\r\n </span>\r\n </ng-template>\r\n\r\n <ng-template #daysCol let-row>\r\n <span class=\"text-sm text-gray-700\">{{ formatDays(row) }}</span>\r\n </ng-template>\r\n </mt-table>\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block;min-width:0}:host ::ng-deep mt-table .p-datatable-header>div,:host ::ng-deep mt-table .p-datatable-header>div>div{gap:.75rem;min-width:0;flex-wrap:wrap}:host ::ng-deep mt-table .p-datatable-header mt-tabs{display:block;max-width:100%;overflow-x:auto;padding-bottom:.25rem}:host ::ng-deep mt-table .p-datatable-header mt-text-field{display:block;flex:1 1 16rem;min-width:min(100%,16rem)}:host ::ng-deep mt-table .p-datatable-header .p-inputtext,:host ::ng-deep mt-table .p-datatable-header .p-inputwrapper,:host ::ng-deep mt-table .p-datatable-header .p-selectbutton{width:100%;max-width:100%}@media(max-width:1024px){:host ::ng-deep mt-table .p-datatable-header>div{flex-direction:column;align-items:stretch}:host ::ng-deep mt-table .p-datatable-header>div>div{width:100%;justify-content:flex-start}:host ::ng-deep mt-table .p-datatable-header mt-text-field{flex-basis:100%;min-width:0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { kind: "component", type: Breadcrumb, selector: "mt-breadcrumb", inputs: ["items", "styleClass"], outputs: ["onItemClick"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "filterMode", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "exportable", "printable", "groupable", "cellClickFilter", "freezeActions", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationStatusChip, selector: "mt-delegation-status-chip", inputs: ["status"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1569
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationsList, isStandalone: true, selector: "mt-delegations-list", viewQueries: [{ propertyName: "statusCol", first: true, predicate: ["statusCol"], descendants: true, isSignal: true }, { propertyName: "userCol", first: true, predicate: ["userCol"], descendants: true, isSignal: true }, { propertyName: "scopeCol", first: true, predicate: ["scopeCol"], descendants: true, isSignal: true }, { propertyName: "daysCol", first: true, predicate: ["daysCol"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col h-full gap-2 p-4\">\n <mt-breadcrumb [items]=\"breadcrumbItems()\"></mt-breadcrumb>\n\n <div class=\"flex items-center gap-2 border-b border-gray-200\">\n @for (tab of tabs(); track tab.value) {\n <button\n type=\"button\"\n class=\"px-4 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\n [class.border-primary]=\"activeTab() === tab.value\"\n [class.text-primary]=\"activeTab() === tab.value\"\n [class.border-transparent]=\"activeTab() !== tab.value\"\n [class.text-gray-500]=\"activeTab() !== tab.value\"\n (click)=\"switchTab(tab.value)\"\n >\n {{ tab.label }}\n </button>\n }\n </div>\n\n <mt-table\n [data]=\"rows()\"\n [columns]=\"tableColumns()\"\n [actions]=\"tableActions()\"\n [rowActions]=\"rowActions()\"\n [loading]=\"isLoading()\"\n >\n <ng-template #statusCol let-row>\n <mt-delegation-status-chip\n [status]=\"row.effectiveStatus\"\n ></mt-delegation-status-chip>\n </ng-template>\n\n <ng-template #userCol let-row>\n @let party = activeTab() === \"my\" ? row.delegatedUser : row.delegator;\n <div class=\"flex items-center gap-2\">\n <mt-avatar icon=\"user.user-01\" styleClass=\"w-8! h-8!\"></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <span class=\"text-sm text-gray-900 truncate\">\n {{ party?.displayName }}\n </span>\n @if (party?.email) {\n <span class=\"text-xs text-gray-500 truncate\">\n {{ party.email }}\n </span>\n }\n </div>\n </div>\n </ng-template>\n\n <ng-template #scopeCol let-row>\n <span class=\"text-sm text-gray-700\" [title]=\"row.scopeSummary\">\n {{ row.scopeSummary || \"\u2014\" }}\n </span>\n </ng-template>\n\n <ng-template #daysCol let-row>\n <span class=\"text-sm text-gray-700\">{{ formatDays(row) }}</span>\n </ng-template>\n </mt-table>\n </div>\n</ng-container>\n", styles: [":host{display:block;min-width:0}:host ::ng-deep mt-table .p-datatable-header>div,:host ::ng-deep mt-table .p-datatable-header>div>div{gap:.75rem;min-width:0;flex-wrap:wrap}:host ::ng-deep mt-table .p-datatable-header mt-tabs{display:block;max-width:100%;overflow-x:auto;padding-bottom:.25rem}:host ::ng-deep mt-table .p-datatable-header mt-text-field{display:block;flex:1 1 16rem;min-width:min(100%,16rem)}:host ::ng-deep mt-table .p-datatable-header .p-inputtext,:host ::ng-deep mt-table .p-datatable-header .p-inputwrapper,:host ::ng-deep mt-table .p-datatable-header .p-selectbutton{width:100%;max-width:100%}@media(max-width:1024px){:host ::ng-deep mt-table .p-datatable-header>div{flex-direction:column;align-items:stretch}:host ::ng-deep mt-table .p-datatable-header>div>div{width:100%;justify-content:flex-start}:host ::ng-deep mt-table .p-datatable-header mt-text-field{flex-basis:100%;min-width:0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { kind: "component", type: Breadcrumb, selector: "mt-breadcrumb", inputs: ["items", "styleClass"], outputs: ["onItemClick"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "filterMode", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "exportable", "printable", "groupable", "cellClickFilter", "freezeActions", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationStatusChip, selector: "mt-delegation-status-chip", inputs: ["status"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1878
1570
  }
1879
1571
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsList, decorators: [{
1880
1572
  type: Component,
@@ -1885,33 +1577,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
1885
1577
  Table,
1886
1578
  TranslocoDirective,
1887
1579
  DelegationStatusChip,
1888
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col h-full gap-2 p-4\">\r\n <mt-breadcrumb [items]=\"breadcrumbItems()\"></mt-breadcrumb>\r\n\r\n <div class=\"flex items-center gap-2 border-b border-gray-200\">\r\n @for (tab of tabs(); track tab.value) {\r\n <button\r\n type=\"button\"\r\n class=\"px-4 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\r\n [class.border-primary]=\"activeTab() === tab.value\"\r\n [class.text-primary]=\"activeTab() === tab.value\"\r\n [class.border-transparent]=\"activeTab() !== tab.value\"\r\n [class.text-gray-500]=\"activeTab() !== tab.value\"\r\n (click)=\"switchTab(tab.value)\"\r\n >\r\n {{ tab.label }}\r\n </button>\r\n }\r\n </div>\r\n\r\n <mt-table\r\n [data]=\"rows()\"\r\n [columns]=\"tableColumns()\"\r\n [actions]=\"tableActions()\"\r\n [rowActions]=\"rowActions()\"\r\n [loading]=\"isLoading()\"\r\n >\r\n <ng-template #statusCol let-row>\r\n <mt-delegation-status-chip\r\n [status]=\"row.status\"\r\n ></mt-delegation-status-chip>\r\n </ng-template>\r\n\r\n <ng-template #userCol let-row>\r\n @let user = activeTab() === \"my\" ? row.delegatedUser : row.delegator;\r\n <div class=\"flex items-center gap-2\">\r\n <mt-avatar\r\n [image]=\"user?.photo || ''\"\r\n icon=\"user.user-01\"\r\n styleClass=\"w-8! h-8!\"\r\n ></mt-avatar>\r\n <div class=\"flex flex-col min-w-0\">\r\n <span class=\"text-sm text-gray-900 truncate\">\r\n {{ user?.displayName }}\r\n </span>\r\n @if (user?.email) {\r\n <span class=\"text-xs text-gray-500 truncate\">\r\n {{ user.email }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n </ng-template>\r\n\r\n <ng-template #scopeCol let-row>\r\n <span class=\"text-sm text-gray-700\" [title]=\"row.scope?.summary\">\r\n {{ row.scope?.summary || \"\u2014\" }}\r\n </span>\r\n </ng-template>\r\n\r\n <ng-template #daysCol let-row>\r\n <span class=\"text-sm text-gray-700\">{{ formatDays(row) }}</span>\r\n </ng-template>\r\n </mt-table>\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block;min-width:0}:host ::ng-deep mt-table .p-datatable-header>div,:host ::ng-deep mt-table .p-datatable-header>div>div{gap:.75rem;min-width:0;flex-wrap:wrap}:host ::ng-deep mt-table .p-datatable-header mt-tabs{display:block;max-width:100%;overflow-x:auto;padding-bottom:.25rem}:host ::ng-deep mt-table .p-datatable-header mt-text-field{display:block;flex:1 1 16rem;min-width:min(100%,16rem)}:host ::ng-deep mt-table .p-datatable-header .p-inputtext,:host ::ng-deep mt-table .p-datatable-header .p-inputwrapper,:host ::ng-deep mt-table .p-datatable-header .p-selectbutton{width:100%;max-width:100%}@media(max-width:1024px){:host ::ng-deep mt-table .p-datatable-header>div{flex-direction:column;align-items:stretch}:host ::ng-deep mt-table .p-datatable-header>div>div{width:100%;justify-content:flex-start}:host ::ng-deep mt-table .p-datatable-header mt-text-field{flex-basis:100%;min-width:0}}\n"] }]
1580
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col h-full gap-2 p-4\">\n <mt-breadcrumb [items]=\"breadcrumbItems()\"></mt-breadcrumb>\n\n <div class=\"flex items-center gap-2 border-b border-gray-200\">\n @for (tab of tabs(); track tab.value) {\n <button\n type=\"button\"\n class=\"px-4 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\n [class.border-primary]=\"activeTab() === tab.value\"\n [class.text-primary]=\"activeTab() === tab.value\"\n [class.border-transparent]=\"activeTab() !== tab.value\"\n [class.text-gray-500]=\"activeTab() !== tab.value\"\n (click)=\"switchTab(tab.value)\"\n >\n {{ tab.label }}\n </button>\n }\n </div>\n\n <mt-table\n [data]=\"rows()\"\n [columns]=\"tableColumns()\"\n [actions]=\"tableActions()\"\n [rowActions]=\"rowActions()\"\n [loading]=\"isLoading()\"\n >\n <ng-template #statusCol let-row>\n <mt-delegation-status-chip\n [status]=\"row.effectiveStatus\"\n ></mt-delegation-status-chip>\n </ng-template>\n\n <ng-template #userCol let-row>\n @let party = activeTab() === \"my\" ? row.delegatedUser : row.delegator;\n <div class=\"flex items-center gap-2\">\n <mt-avatar icon=\"user.user-01\" styleClass=\"w-8! h-8!\"></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <span class=\"text-sm text-gray-900 truncate\">\n {{ party?.displayName }}\n </span>\n @if (party?.email) {\n <span class=\"text-xs text-gray-500 truncate\">\n {{ party.email }}\n </span>\n }\n </div>\n </div>\n </ng-template>\n\n <ng-template #scopeCol let-row>\n <span class=\"text-sm text-gray-700\" [title]=\"row.scopeSummary\">\n {{ row.scopeSummary || \"\u2014\" }}\n </span>\n </ng-template>\n\n <ng-template #daysCol let-row>\n <span class=\"text-sm text-gray-700\">{{ formatDays(row) }}</span>\n </ng-template>\n </mt-table>\n </div>\n</ng-container>\n", styles: [":host{display:block;min-width:0}:host ::ng-deep mt-table .p-datatable-header>div,:host ::ng-deep mt-table .p-datatable-header>div>div{gap:.75rem;min-width:0;flex-wrap:wrap}:host ::ng-deep mt-table .p-datatable-header mt-tabs{display:block;max-width:100%;overflow-x:auto;padding-bottom:.25rem}:host ::ng-deep mt-table .p-datatable-header mt-text-field{display:block;flex:1 1 16rem;min-width:min(100%,16rem)}:host ::ng-deep mt-table .p-datatable-header .p-inputtext,:host ::ng-deep mt-table .p-datatable-header .p-inputwrapper,:host ::ng-deep mt-table .p-datatable-header .p-selectbutton{width:100%;max-width:100%}@media(max-width:1024px){:host ::ng-deep mt-table .p-datatable-header>div{flex-direction:column;align-items:stretch}:host ::ng-deep mt-table .p-datatable-header>div>div{width:100%;justify-content:flex-start}:host ::ng-deep mt-table .p-datatable-header mt-text-field{flex-basis:100%;min-width:0}}\n"] }]
1889
1581
  }], propDecorators: { statusCol: [{ type: i0.ViewChild, args: ['statusCol', { isSignal: true }] }], userCol: [{ type: i0.ViewChild, args: ['userCol', { isSignal: true }] }], scopeCol: [{ type: i0.ViewChild, args: ['scopeCol', { isSignal: true }] }], daysCol: [{ type: i0.ViewChild, args: ['daysCol', { isSignal: true }] }] } });
1890
1582
 
1891
1583
  /**
1892
- * Pattern for endpoints that should NEVER receive the `app-delegation` header.
1893
- * Token issuance, refresh, end-session and session-read calls use only `Authorization`
1894
- * (architecture doc §9.1).
1584
+ * Endpoints that must NEVER carry the `app-delegation` header.
1585
+ * Starting a delegated session uses only the normal `Authorization` token
1586
+ * (doc 05). All other `identity/delegations` management calls are normal
1587
+ * (non-delegated) actions too, so the whole namespace is excluded.
1895
1588
  */
1896
- const EXCLUDE = /\bidentity\/delegations\/(?:[^/]+\/)?(?:token|session)(?:\/|$)/i;
1589
+ const EXCLUDE = /\bidentity\/delegations\b/i;
1897
1590
  /**
1898
- * Adds `app-delegation: Bearer <token>` to outgoing requests when a delegation
1899
- * session is active. Token-management endpoints are excluded.
1900
- *
1901
- * Register AFTER the gateway-auth interceptor (so `Authorization` is set first)
1902
- * and BEFORE the message interceptor (so 403s still flow through toast handling).
1591
+ * Adds `app-delegation: Bearer <token>` to outgoing requests while a delegated
1592
+ * session is active. Register AFTER the gateway-auth interceptor (so the normal
1593
+ * `Authorization` header is set first) and BEFORE the message interceptor.
1903
1594
  */
1904
1595
  const appDelegationInterceptor = (req, next) => {
1905
1596
  if (EXCLUDE.test(req.url)) {
1906
1597
  return next(req);
1907
1598
  }
1908
- const token = inject(DelegationSessionFacade).accessToken();
1599
+ const token = inject(DelegationSessionFacade).token();
1909
1600
  if (!token) {
1910
1601
  return next(req);
1911
1602
  }
1912
- return next(req.clone({
1913
- setHeaders: { 'app-delegation': `Bearer ${token}` },
1914
- }));
1603
+ return next(req.clone({ setHeaders: { 'app-delegation': `Bearer ${token}` } }));
1915
1604
  };
1916
1605
 
1917
1606
  // store/index.ts
@@ -1922,5 +1611,5 @@ const appDelegationInterceptor = (req, next) => {
1922
1611
  * Generated bundle index. Do not edit.
1923
1612
  */
1924
1613
 
1925
- export { ApproveDelegation, BootstrapDelegationSession, CancelDelegation, ClearScopePreview, ClearSelectedDelegation, CreateDelegation, DELEGATION_SESSION_STORAGE_KEY, DelegationDetailDrawer, DelegationForm, DelegationSessionActionKey, DelegationSessionFacade, DelegationSessionInvalidated, DelegationSessionState, DelegationStatusChip, Delegations, DelegationsActionKey, DelegationsFacade, DelegationsList, DelegationsState, EndDelegationSession, GetAssignedDelegations, GetDelegation, GetMyDelegations, GetScopeOptions, PreviewScope, RefreshDelegationSession, RejectDelegation, ScopePicker, SetDelegationCandidates, StartDelegationSession, StartSessionDialog, SwitchDelegationSession, TopbarDelegationMenu, UpdateDelegation, VerifyDelegationSession, appDelegationInterceptor, pickCandidates, toActiveSession };
1614
+ export { ApproveDelegation, CancelDelegation, ClearDelegationDetail, ClearScopePreview, CreateDelegationLegacy, CreateDelegationV2, DelegationDetailDrawer, DelegationForm, DelegationSessionActionKey, DelegationSessionFacade, DelegationSessionState, DelegationStatusChip, Delegations, DelegationsActionKey, DelegationsFacade, DelegationsList, DelegationsState, EndDelegationSession, GetActiveAssignedDelegations, GetAssignedDelegations, GetDelegationDetail, GetMyDelegations, GetScopeOptions, LoadDelegationCandidates, PreviewScope, RejectDelegation, ScopePicker, StartDelegationSession, StartSessionDialog, SwitchDelegationSession, TopbarDelegationMenu, UpdateDelegationLegacy, UpdateDelegationV2, appDelegationInterceptor };
1926
1615
  //# sourceMappingURL=masterteam-delegations.mjs.map