@masterteam/delegations 0.0.12 → 0.1.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,26 +1,577 @@
1
+ import * as i1 from '@angular/common';
2
+ import { CommonModule } from '@angular/common';
1
3
  import * as i0 from '@angular/core';
2
- import { inject, Component, Injectable, computed, input, signal, effect, viewChild, linkedSignal } from '@angular/core';
3
- import { Router, RouterOutlet } from '@angular/router';
4
+ import { input, computed, Component, inject, Injectable, ChangeDetectionStrategy, viewChild, model, signal, effect, untracked, linkedSignal } from '@angular/core';
5
+ import { RouterLink, Router, RouterOutlet } from '@angular/router';
4
6
  import { TranslocoDirective, TranslocoService } from '@jsverse/transloco';
5
- import { Page } from '@masterteam/components/page';
6
- import { CommonModule } from '@angular/common';
7
- import { Table } from '@masterteam/components/table';
8
- import * as i1 from 'primeng/skeleton';
9
- import { SkeletonModule } from 'primeng/skeleton';
10
- import { Action, Selector, State, Store, select } from '@ngxs/store';
11
- import { HttpClient, HttpContext } from '@angular/common/http';
12
- import { CrudStateBase, handleApiRequest, REQUEST_CONTEXT, UserSearchFieldConfig, TextareaFieldConfig, DateFieldConfig, RadioButtonFieldConfig, MultiSelectFieldConfig, ToggleFieldConfig, ValidatorConfig } from '@masterteam/components';
13
7
  import { Avatar } from '@masterteam/components/avatar';
8
+ import { Button } from '@masterteam/components/button';
14
9
  import { ModalService } from '@masterteam/components/modal';
15
- import * as i2 from '@angular/forms';
10
+ import { Icon } from '@masterteam/icons';
11
+ import { Popover } from 'primeng/popover';
12
+ import { Chip } from '@masterteam/components/chip';
13
+ import { Actions, Store, ofActionSuccessful, ofActionDispatched, Action, Selector, State, select } from '@ngxs/store';
14
+ import { HttpClient, HttpParams, HttpContext } from '@angular/common/http';
15
+ import { handleApiRequest, REQUEST_CONTEXT, UserSearchFieldConfig, TextareaFieldConfig, DateFieldConfig, RadioButtonFieldConfig, MultiSelectFieldConfig, ToggleFieldConfig, ValidatorConfig } from '@masterteam/components';
16
+ import { EMPTY, catchError, switchMap, finalize } from 'rxjs';
17
+ import { ModalRef } from '@masterteam/components/dialog';
18
+ import { Page } from '@masterteam/components/page';
19
+ import { Breadcrumb } from '@masterteam/components/breadcrumb';
20
+ import { Table } from '@masterteam/components/table';
21
+ import * as i1$2 from '@angular/forms';
16
22
  import { FormControl, ReactiveFormsModule } from '@angular/forms';
17
23
  import { toSignal } from '@angular/core/rxjs-interop';
18
24
  import { DynamicForm } from '@masterteam/forms/dynamic-form';
19
- import { Button } from '@masterteam/components/button';
20
- import { ModalRef, DialogService } from '@masterteam/components/dialog';
21
- import { finalize } from 'rxjs';
22
- import { Breadcrumb } from '@masterteam/components/breadcrumb';
23
- import { Icon } from '@masterteam/icons';
25
+ import * as i1$1 from 'primeng/skeleton';
26
+ import { SkeletonModule } from 'primeng/skeleton';
27
+ import * as i2 from 'primeng/tooltip';
28
+ import { TooltipModule } from 'primeng/tooltip';
29
+
30
+ const STATUS_VISUAL = {
31
+ Active: {
32
+ i18nKey: 'delegations.active',
33
+ styleClass: 'mt-status-chip mt-status-chip--active',
34
+ },
35
+ Scheduled: {
36
+ i18nKey: 'delegations.scheduled',
37
+ styleClass: 'mt-status-chip mt-status-chip--scheduled',
38
+ },
39
+ PendingApproval: {
40
+ i18nKey: 'delegations.pending-approval',
41
+ styleClass: 'mt-status-chip mt-status-chip--pending',
42
+ },
43
+ Expired: {
44
+ i18nKey: 'delegations.expired',
45
+ styleClass: 'mt-status-chip mt-status-chip--expired',
46
+ },
47
+ Rejected: {
48
+ i18nKey: 'delegations.rejected',
49
+ styleClass: 'mt-status-chip mt-status-chip--rejected',
50
+ },
51
+ Cancelled: {
52
+ i18nKey: 'delegations.cancelled',
53
+ styleClass: 'mt-status-chip mt-status-chip--cancelled',
54
+ },
55
+ };
56
+ class DelegationStatusChip {
57
+ status = input.required(...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
58
+ visual = computed(() => STATUS_VISUAL[this.status()], ...(ngDevMode ? [{ debugName: "visual" }] : /* istanbul ignore next */ []));
59
+ 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>
65
+ `, 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
+ }
67
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationStatusChip, decorators: [{
68
+ 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>
74
+ `, 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
+ }], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: true }] }] } });
76
+
77
+ /** Reads sessionStorage + login candidates, restores any tab-local active session. */
78
+ class BootstrapDelegationSession {
79
+ static type = '[DelegationSession] Bootstrap';
80
+ }
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. */
90
+ class StartDelegationSession {
91
+ delegationId;
92
+ static type = '[DelegationSession] Start';
93
+ constructor(delegationId) {
94
+ this.delegationId = delegationId;
95
+ }
96
+ }
97
+ /** End the current delegated session (server + local). */
98
+ class EndDelegationSession {
99
+ options;
100
+ static type = '[DelegationSession] End';
101
+ constructor(options = {}) {
102
+ this.options = options;
103
+ }
104
+ }
105
+ /** Atomically end current + start another. */
106
+ class SwitchDelegationSession {
107
+ delegationId;
108
+ 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;
127
+ }
128
+ }
129
+
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
+ var DelegationSessionActionKey;
141
+ (function (DelegationSessionActionKey) {
142
+ DelegationSessionActionKey["Bootstrap"] = "bootstrap";
143
+ DelegationSessionActionKey["StartSession"] = "startSession";
144
+ DelegationSessionActionKey["EndSession"] = "endSession";
145
+ DelegationSessionActionKey["RefreshSession"] = "refreshSession";
146
+ DelegationSessionActionKey["GetSession"] = "getSession";
147
+ })(DelegationSessionActionKey || (DelegationSessionActionKey = {}));
148
+
149
+ var __decorate$1 = (this && this.__decorate) || function (decorators, target, key, desc) {
150
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
151
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
152
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
153
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
154
+ };
155
+ const BASE$1 = 'identity/delegations';
156
+ // ---------------------------------------------------------------------------
157
+ // Action shells for loose coupling with gateway-auth (matched by `type` string).
158
+ // ---------------------------------------------------------------------------
159
+ class GatewayLoginSuccessShell {
160
+ static type = '[Auth] Login Success';
161
+ }
162
+ class GatewayLogoutShell {
163
+ static type = '[Auth] Logout';
164
+ }
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'));
204
+ }
205
+ let DelegationSessionState = class DelegationSessionState {
206
+ http = inject(HttpClient);
207
+ actions$ = inject(Actions);
208
+ store = inject(Store);
209
+ 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
+ this.actions$
215
+ .pipe(ofActionSuccessful(GatewayLoginSuccessShell))
216
+ .subscribe(() => {
217
+ this.store.dispatch(new BootstrapDelegationSession());
218
+ });
219
+ this.actions$
220
+ .pipe(ofActionDispatched(GatewayLogoutShell))
221
+ .subscribe(() => {
222
+ this.store.dispatch(new EndDelegationSession({ silent: true }));
223
+ });
224
+ }
225
+ // ---------------------------------------------------------------------------
226
+ // Selectors
227
+ // ---------------------------------------------------------------------------
228
+ static getActive(state) {
229
+ return state.active;
230
+ }
231
+ static getCandidates(state) {
232
+ return state.candidates;
233
+ }
234
+ static isDelegated(state) {
235
+ return !!state.active;
236
+ }
237
+ static getLoadingActive(state) {
238
+ return state.loadingActive;
239
+ }
240
+ static getErrors(state) {
241
+ return state.errors;
242
+ }
243
+ // ---------------------------------------------------------------------------
244
+ // Actions
245
+ // ---------------------------------------------------------------------------
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`);
265
+ return handleApiRequest({
266
+ ctx,
267
+ key: DelegationSessionActionKey.GetSession,
268
+ 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
+ },
288
+ });
289
+ }
290
+ start(ctx, { delegationId }) {
291
+ const req$ = this.http.post(`${BASE$1}/${delegationId}/token`, {});
292
+ return handleApiRequest({
293
+ ctx,
294
+ key: DelegationSessionActionKey.StartSession,
295
+ request$: req$,
296
+ onSuccess: (response) => {
297
+ const active = toActiveSession(response.data);
298
+ writeStored(active);
299
+ return { active };
300
+ },
301
+ });
302
+ }
303
+ end(ctx, { options }) {
304
+ const state = ctx.getState();
305
+ // Always clear local state immediately.
306
+ writeStored(null);
307
+ 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));
315
+ }
316
+ switch(ctx, { delegationId }) {
317
+ 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;
343
+ }
344
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionState, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
345
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionState });
346
+ };
347
+ __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);
356
+ __decorate$1([
357
+ Action(StartDelegationSession)
358
+ ], DelegationSessionState.prototype, "start", null);
359
+ __decorate$1([
360
+ Action(EndDelegationSession)
361
+ ], DelegationSessionState.prototype, "end", null);
362
+ __decorate$1([
363
+ Action(SwitchDelegationSession)
364
+ ], 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
+ __decorate$1([
372
+ Selector()
373
+ ], DelegationSessionState, "getActive", null);
374
+ __decorate$1([
375
+ Selector()
376
+ ], DelegationSessionState, "getCandidates", null);
377
+ __decorate$1([
378
+ Selector()
379
+ ], DelegationSessionState, "isDelegated", null);
380
+ __decorate$1([
381
+ Selector()
382
+ ], DelegationSessionState, "getLoadingActive", null);
383
+ __decorate$1([
384
+ Selector()
385
+ ], DelegationSessionState, "getErrors", null);
386
+ DelegationSessionState = __decorate$1([
387
+ State({
388
+ name: 'delegationSession',
389
+ defaults: {
390
+ active: null,
391
+ candidates: [],
392
+ loadingActive: [],
393
+ errors: {},
394
+ },
395
+ })
396
+ ], DelegationSessionState);
397
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionState, decorators: [{
398
+ type: Injectable
399
+ }], ctorParameters: () => [], propDecorators: { bootstrap: [], setCandidates: [], verify: [], start: [], end: [], switch: [], refresh: [], invalidated: [] } });
400
+
401
+ class DelegationSessionFacade {
402
+ store = inject(Store);
403
+ // ---------------------------------------------------------------------------
404
+ // Data slices
405
+ // ---------------------------------------------------------------------------
406
+ active = select(DelegationSessionState.getActive);
407
+ candidates = select(DelegationSessionState.getCandidates);
408
+ isDelegated = select(DelegationSessionState.isDelegated);
409
+ loadingActive = select(DelegationSessionState.getLoadingActive);
410
+ // ---------------------------------------------------------------------------
411
+ // Derived (used by interceptor + topbar menu)
412
+ // ---------------------------------------------------------------------------
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 */ []));
416
+ 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 */ []));
420
+ // ---------------------------------------------------------------------------
421
+ // Dispatchers
422
+ // ---------------------------------------------------------------------------
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));
431
+ }
432
+ endSession(opts = {}) {
433
+ return this.store.dispatch(new EndDelegationSession(opts));
434
+ }
435
+ switchSession(delegationId) {
436
+ return this.store.dispatch(new SwitchDelegationSession(delegationId));
437
+ }
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));
446
+ }
447
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionFacade, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
448
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionFacade, providedIn: 'root' });
449
+ }
450
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionFacade, decorators: [{
451
+ type: Injectable,
452
+ args: [{ providedIn: 'root' }]
453
+ }] });
454
+
455
+ /**
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.
458
+ */
459
+ class StartSessionDialog {
460
+ candidate = input.required(...(ngDevMode ? [{ debugName: "candidate" }] : /* istanbul ignore next */ []));
461
+ intent = input('start', ...(ngDevMode ? [{ debugName: "intent" }] : /* istanbul ignore next */ []));
462
+ ref = inject(ModalRef);
463
+ 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 */ []));
471
+ confirm() {
472
+ const id = this.candidate().id;
473
+ const op$ = this.intent() === 'switch'
474
+ ? this.facade.switchSession(id)
475
+ : this.facade.startSession(id);
476
+ op$.subscribe({
477
+ next: () => this.ref.close(true),
478
+ error: () => {
479
+ /* facade publishes the error to the toast pipeline; just stay open */
480
+ },
481
+ });
482
+ }
483
+ cancel() {
484
+ this.ref.close(false);
485
+ }
486
+ 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 });
488
+ }
489
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: StartSessionDialog, decorators: [{
490
+ 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 }] }] } });
493
+
494
+ /**
495
+ * Topbar surface for the delegation runtime.
496
+ *
497
+ * 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.
503
+ */
504
+ class TopbarDelegationMenu {
505
+ /** Path to the management page, e.g. `/control-panel/delegations` or `/delegations`. */
506
+ managePath = input('/delegations', ...(ngDevMode ? [{ debugName: "managePath" }] : /* istanbul ignore next */ []));
507
+ /** Compact mode hides the inline delegator name next to the button. */
508
+ compact = input(false, ...(ngDevMode ? [{ debugName: "compact" }] : /* istanbul ignore next */ []));
509
+ facade = inject(DelegationSessionFacade);
510
+ modal = inject(ModalService);
511
+ transloco = inject(TranslocoService);
512
+ popover = viewChild.required('popover');
513
+ active = this.facade.active;
514
+ candidates = this.facade.candidates;
515
+ 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. */
520
+ mode = computed(() => {
521
+ if (this.active())
522
+ return 'active';
523
+ if (this.hasCandidates())
524
+ return 'candidates';
525
+ return 'hidden';
526
+ }, ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
527
+ togglePopover(event) {
528
+ this.popover().toggle(event);
529
+ }
530
+ startCandidate(candidate, event) {
531
+ event.stopPropagation();
532
+ this.popover().hide();
533
+ this.openConfirmation(candidate, 'start');
534
+ }
535
+ switchTo(candidate, event) {
536
+ event.stopPropagation();
537
+ this.popover().hide();
538
+ this.openConfirmation(candidate, 'switch');
539
+ }
540
+ endSession(event) {
541
+ event.stopPropagation();
542
+ this.popover().hide();
543
+ this.facade.endSession().subscribe();
544
+ }
545
+ openConfirmation(candidate, intent) {
546
+ this.modal.openModal(StartSessionDialog, 'dialog', {
547
+ header: this.transloco.translate(intent === 'switch'
548
+ ? 'delegations.switch-session'
549
+ : 'delegations.start-session'),
550
+ styleClass: '!w-[min(96vw,28rem)] !max-w-[96vw]',
551
+ dismissableMask: true,
552
+ dismissible: true,
553
+ inputValues: {
554
+ candidate,
555
+ intent,
556
+ },
557
+ });
558
+ }
559
+ 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 });
561
+ }
562
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: TopbarDelegationMenu, decorators: [{
563
+ type: Component,
564
+ args: [{ selector: 'mt-topbar-delegation-menu', standalone: true, imports: [
565
+ CommonModule,
566
+ Avatar,
567
+ Button,
568
+ Icon,
569
+ Popover,
570
+ RouterLink,
571
+ TranslocoDirective,
572
+ 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"] }]
574
+ }], 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 }] }] } });
24
575
 
25
576
  class Delegations {
26
577
  router = inject(Router);
@@ -28,79 +579,199 @@ class Delegations {
28
579
  this.router.navigate(['control-panel/product-settings']);
29
580
  }
30
581
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: Delegations, deps: [], target: i0.ɵɵFactoryTarget.Component });
31
- 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\">\r\n <mt-page\r\n [title]=\"t('delegations.delegations')\"\r\n [avatarIcon]=\"'custom.hierarchy-structure'\"\r\n [contentClass]=\"'max-[1025px]:p-4 max-[640px]:p-3'\"\r\n [avatarStyle]=\"{\r\n '--p-avatar-background': 'var(--p-indigo-50)',\r\n '--p-avatar-color': 'var(--p-indigo-700)',\r\n }\"\r\n (backButtonClick)=\"goBack()\"\r\n backButton\r\n >\r\n <router-outlet />\r\n </mt-page>\r\n</ng-container>\r\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"] }] });
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"] }] });
32
583
  }
33
584
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: Delegations, decorators: [{
34
585
  type: Component,
35
- args: [{ selector: 'mt-delegations', imports: [Page, RouterOutlet, TranslocoDirective], template: "<ng-container *transloco=\"let t\">\r\n <mt-page\r\n [title]=\"t('delegations.delegations')\"\r\n [avatarIcon]=\"'custom.hierarchy-structure'\"\r\n [contentClass]=\"'max-[1025px]:p-4 max-[640px]:p-3'\"\r\n [avatarStyle]=\"{\r\n '--p-avatar-background': 'var(--p-indigo-50)',\r\n '--p-avatar-color': 'var(--p-indigo-700)',\r\n }\"\r\n (backButtonClick)=\"goBack()\"\r\n backButton\r\n >\r\n <router-outlet />\r\n </mt-page>\r\n</ng-container>\r\n" }]
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" }]
36
587
  }] });
37
588
 
38
- var DelegationsActionKey;
39
- (function (DelegationsActionKey) {
40
- DelegationsActionKey["GetDelegations"] = "getDelegations";
41
- DelegationsActionKey["AddDelegation"] = "addDelegation";
42
- DelegationsActionKey["UpdateDelegation"] = "updateDelegation";
43
- DelegationsActionKey["DeleteDelegation"] = "deleteDelegation";
44
- DelegationsActionKey["GetDelegationForm"] = "getDelegationForm";
45
- })(DelegationsActionKey || (DelegationsActionKey = {}));
46
-
47
- class GetDelegations {
48
- static type = '[Delegations] Get Delegations';
589
+ // ---------------------------------------------------------------------------
590
+ // List / detail
591
+ // ---------------------------------------------------------------------------
592
+ class GetMyDelegations {
593
+ query;
594
+ static type = '[Delegations] Get My Delegations';
595
+ constructor(query = {}) {
596
+ this.query = query;
597
+ }
598
+ }
599
+ class GetAssignedDelegations {
600
+ query;
601
+ static type = '[Delegations] Get Assigned Delegations';
602
+ constructor(query = {}) {
603
+ this.query = query;
604
+ }
605
+ }
606
+ class GetDelegation {
607
+ id;
608
+ includeStatusHistory;
609
+ static type = '[Delegations] Get Delegation';
610
+ constructor(id, includeStatusHistory = false) {
611
+ this.id = id;
612
+ this.includeStatusHistory = includeStatusHistory;
613
+ }
49
614
  }
50
- class AddDelegation {
51
- delegation;
52
- static type = '[Delegations] Add Delegation';
53
- constructor(delegation) {
54
- this.delegation = delegation;
615
+ class ClearSelectedDelegation {
616
+ static type = '[Delegations] Clear Selected Delegation';
617
+ }
618
+ // ---------------------------------------------------------------------------
619
+ // Lifecycle
620
+ // ---------------------------------------------------------------------------
621
+ class CreateDelegation {
622
+ request;
623
+ static type = '[Delegations] Create Delegation';
624
+ constructor(request) {
625
+ this.request = request;
55
626
  }
56
627
  }
57
628
  class UpdateDelegation {
58
629
  id;
59
- delegation;
630
+ request;
60
631
  static type = '[Delegations] Update Delegation';
61
- constructor(id, delegation) {
632
+ constructor(id, request) {
62
633
  this.id = id;
63
- this.delegation = delegation;
634
+ this.request = request;
64
635
  }
65
636
  }
66
- class DeleteDelegation {
637
+ class ApproveDelegation {
67
638
  id;
68
- static type = '[Delegations] Delete Delegation';
69
- constructor(id) {
639
+ request;
640
+ static type = '[Delegations] Approve Delegation';
641
+ constructor(id, request) {
70
642
  this.id = id;
643
+ this.request = request;
71
644
  }
72
645
  }
73
- class GetDelegation {
646
+ class RejectDelegation {
74
647
  id;
75
- static type = '[Delegations] Get Delegation';
76
- constructor(id) {
648
+ request;
649
+ static type = '[Delegations] Reject Delegation';
650
+ constructor(id, request) {
77
651
  this.id = id;
652
+ this.request = request;
78
653
  }
79
654
  }
80
- class ClearSelectedDelegation {
81
- static type = '[Delegations] Clear Selected Delegation';
655
+ class CancelDelegation {
656
+ id;
657
+ request;
658
+ static type = '[Delegations] Cancel Delegation';
659
+ constructor(id, request) {
660
+ 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;
673
+ }
674
+ }
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';
82
684
  }
83
685
 
686
+ // ============================================================================
687
+ // NGXS state shape + action keys
688
+ // ============================================================================
689
+ var DelegationsActionKey;
690
+ (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";
699
+ DelegationsActionKey["GetScopeOptions"] = "getScopeOptions";
700
+ DelegationsActionKey["PreviewScope"] = "previewScope";
701
+ })(DelegationsActionKey || (DelegationsActionKey = {}));
702
+
84
703
  var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
85
704
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
86
705
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
87
706
  else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
88
707
  return c > 3 && r && Object.defineProperty(target, key, r), r;
89
708
  };
90
- let DelegationsState = class DelegationsState extends CrudStateBase {
709
+ const BASE = 'identity/delegations';
710
+ function buildListParams(query) {
711
+ 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);
734
+ if (query.sortBy)
735
+ 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
+ }
743
+ return params;
744
+ }
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
+ let DelegationsState = class DelegationsState {
91
756
  http = inject(HttpClient);
92
757
  // ============================================================================
93
- // Selectors - Individual data selectors for fine-grained reactivity
758
+ // Selectors
94
759
  // ============================================================================
95
- static getAllDelegations(state) {
96
- return state.allDelegations;
760
+ static getMyPage(state) {
761
+ return state.my;
97
762
  }
98
- static getSelectedDelegation(state) {
99
- return state.selectedDelegation;
763
+ static getAssignedPage(state) {
764
+ return state.assigned;
765
+ }
766
+ static getSelected(state) {
767
+ return state.selected;
768
+ }
769
+ static getScopeOptions(state) {
770
+ return state.scopeOptions;
771
+ }
772
+ static getScopePreview(state) {
773
+ return state.scopePreview;
100
774
  }
101
- // ============================================================================
102
- // Loading/Error Slice Selectors - REQUIRED for optimal performance
103
- // ============================================================================
104
775
  static getLoadingActive(state) {
105
776
  return state.loadingActive;
106
777
  }
@@ -108,88 +779,189 @@ let DelegationsState = class DelegationsState extends CrudStateBase {
108
779
  return state.errors;
109
780
  }
110
781
  // ============================================================================
111
- // CRUD Actions
782
+ // List
112
783
  // ============================================================================
113
- getDelegations(ctx) {
114
- const req$ = this.http.get('identity/delegations/all');
784
+ getMyDelegations(ctx, { query }) {
785
+ const req$ = this.http.get(`${BASE}/my`, {
786
+ params: buildListParams(query),
787
+ });
115
788
  return handleApiRequest({
116
789
  ctx,
117
- key: DelegationsActionKey.GetDelegations,
790
+ key: DelegationsActionKey.GetMyDelegations,
118
791
  request$: req$,
119
- onSuccess: (response) => ({
120
- allDelegations: response.data ?? [],
121
- }),
792
+ onSuccess: (response) => ({ my: response.data }),
122
793
  });
123
794
  }
124
- getDelegation(ctx, { id }) {
125
- const req$ = this.http.get(`identity/delegations/${id}`);
795
+ getAssignedDelegations(ctx, { query }) {
796
+ const req$ = this.http.get(`${BASE}/assigned`, { params: buildListParams(query) });
126
797
  return handleApiRequest({
127
798
  ctx,
128
- key: DelegationsActionKey.GetDelegationForm,
799
+ key: DelegationsActionKey.GetAssignedDelegations,
129
800
  request$: req$,
130
- onSuccess: (response) => ({
131
- selectedDelegation: response.data ?? null,
132
- }),
801
+ onSuccess: (response) => ({ assigned: response.data }),
133
802
  });
134
803
  }
135
- addDelegation(ctx, { delegation }) {
136
- const req$ = this.http.post('identity/delegations', delegation);
137
- return this.create(ctx, {
138
- key: DelegationsActionKey.AddDelegation,
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
+ });
812
+ return handleApiRequest({
813
+ ctx,
814
+ key: DelegationsActionKey.GetDelegation,
815
+ request$: req$,
816
+ onSuccess: (response) => ({ selected: response.data ?? null }),
817
+ });
818
+ }
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);
827
+ return handleApiRequest({
828
+ ctx,
829
+ key: DelegationsActionKey.CreateDelegation,
139
830
  request$: req$,
140
- stateProperty: (state) => state.allDelegations,
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
+ },
141
840
  });
142
841
  }
143
- updateDelegation(ctx, { id, delegation }) {
144
- const req$ = this.http.put(`identity/delegations/${id}`, delegation);
145
- return this.update(ctx, {
146
- key: DelegationsActionKey.UpdateDelegation,
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$);
857
+ }
858
+ // ============================================================================
859
+ // Scope (options + preview)
860
+ // ============================================================================
861
+ getScopeOptions(ctx, { delegatorUserId }) {
862
+ let params = new HttpParams();
863
+ if (delegatorUserId)
864
+ 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
+ });
871
+ return handleApiRequest({
872
+ ctx,
873
+ key: DelegationsActionKey.GetScopeOptions,
147
874
  request$: req$,
148
- uniqueKey: 'id',
149
- id,
150
- stateProperty: (state) => state.allDelegations,
875
+ onSuccess: (response) => ({ scopeOptions: response.data ?? null }),
151
876
  });
152
877
  }
153
- deleteDelegation(ctx, { id }) {
154
- const req$ = this.http.delete(`identity/delegations/${id}`);
155
- return this.delete(ctx, {
156
- key: DelegationsActionKey.DeleteDelegation,
878
+ previewScope(ctx, { scope }) {
879
+ const req$ = this.http.post(`${BASE}/scope/preview`, scope);
880
+ return handleApiRequest({
881
+ ctx,
882
+ key: DelegationsActionKey.PreviewScope,
157
883
  request$: req$,
158
- uniqueKey: 'id',
159
- id,
160
- stateProperty: (state) => state.allDelegations,
884
+ onSuccess: (response) => ({ scopePreview: response.data ?? null }),
161
885
  });
162
886
  }
163
- clearSelectedDelegation(ctx) {
164
- ctx.patchState({ selectedDelegation: null });
887
+ clearScopePreview(ctx) {
888
+ ctx.patchState({ scopePreview: null });
889
+ }
890
+ // ============================================================================
891
+ // Helpers
892
+ // ============================================================================
893
+ applyLifecycleResult(ctx, key, req$) {
894
+ return handleApiRequest({
895
+ ctx,
896
+ key,
897
+ 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
+ },
909
+ });
165
910
  }
166
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsState, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
911
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsState, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
167
912
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsState });
168
913
  };
169
914
  __decorate([
170
- Action(GetDelegations)
171
- ], DelegationsState.prototype, "getDelegations", null);
915
+ Action(GetMyDelegations)
916
+ ], DelegationsState.prototype, "getMyDelegations", null);
917
+ __decorate([
918
+ Action(GetAssignedDelegations)
919
+ ], DelegationsState.prototype, "getAssignedDelegations", null);
172
920
  __decorate([
173
921
  Action(GetDelegation)
174
922
  ], DelegationsState.prototype, "getDelegation", null);
175
923
  __decorate([
176
- Action(AddDelegation)
177
- ], DelegationsState.prototype, "addDelegation", null);
924
+ Action(ClearSelectedDelegation)
925
+ ], DelegationsState.prototype, "clearSelectedDelegation", null);
926
+ __decorate([
927
+ Action(CreateDelegation)
928
+ ], DelegationsState.prototype, "createDelegation", null);
178
929
  __decorate([
179
930
  Action(UpdateDelegation)
180
931
  ], DelegationsState.prototype, "updateDelegation", null);
181
932
  __decorate([
182
- Action(DeleteDelegation)
183
- ], DelegationsState.prototype, "deleteDelegation", null);
933
+ Action(ApproveDelegation)
934
+ ], DelegationsState.prototype, "approveDelegation", null);
184
935
  __decorate([
185
- Action(ClearSelectedDelegation)
186
- ], DelegationsState.prototype, "clearSelectedDelegation", null);
936
+ Action(RejectDelegation)
937
+ ], DelegationsState.prototype, "rejectDelegation", null);
938
+ __decorate([
939
+ Action(CancelDelegation)
940
+ ], DelegationsState.prototype, "cancelDelegation", null);
941
+ __decorate([
942
+ Action(GetScopeOptions)
943
+ ], DelegationsState.prototype, "getScopeOptions", null);
944
+ __decorate([
945
+ Action(PreviewScope)
946
+ ], DelegationsState.prototype, "previewScope", null);
947
+ __decorate([
948
+ Action(ClearScopePreview)
949
+ ], DelegationsState.prototype, "clearScopePreview", null);
187
950
  __decorate([
188
951
  Selector()
189
- ], DelegationsState, "getAllDelegations", null);
952
+ ], DelegationsState, "getMyPage", null);
190
953
  __decorate([
191
954
  Selector()
192
- ], DelegationsState, "getSelectedDelegation", null);
955
+ ], DelegationsState, "getAssignedPage", null);
956
+ __decorate([
957
+ Selector()
958
+ ], DelegationsState, "getSelected", null);
959
+ __decorate([
960
+ Selector()
961
+ ], DelegationsState, "getScopeOptions", null);
962
+ __decorate([
963
+ Selector()
964
+ ], DelegationsState, "getScopePreview", null);
193
965
  __decorate([
194
966
  Selector()
195
967
  ], DelegationsState, "getLoadingActive", null);
@@ -200,8 +972,11 @@ DelegationsState = __decorate([
200
972
  State({
201
973
  name: 'delegations',
202
974
  defaults: {
203
- allDelegations: [],
204
- selectedDelegation: null,
975
+ my: null,
976
+ assigned: null,
977
+ selected: null,
978
+ scopeOptions: null,
979
+ scopePreview: null,
205
980
  loadingActive: [],
206
981
  errors: {},
207
982
  },
@@ -209,61 +984,87 @@ DelegationsState = __decorate([
209
984
  ], DelegationsState);
210
985
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsState, decorators: [{
211
986
  type: Injectable
212
- }], propDecorators: { getDelegations: [], getDelegation: [], addDelegation: [], updateDelegation: [], deleteDelegation: [], clearSelectedDelegation: [] } });
987
+ }], propDecorators: { getMyDelegations: [], getAssignedDelegations: [], getDelegation: [], clearSelectedDelegation: [], createDelegation: [], updateDelegation: [], approveDelegation: [], rejectDelegation: [], cancelDelegation: [], getScopeOptions: [], previewScope: [], clearScopePreview: [] } });
213
988
 
214
989
  class DelegationsFacade {
215
990
  store = inject(Store);
216
- // ============================================================================
217
- // Data Selectors - Memoized by NGXS (fine-grained reactivity)
218
- // ============================================================================
219
- allDelegations = select(DelegationsState.getAllDelegations);
220
- selectedDelegation = select(DelegationsState.getSelectedDelegation);
221
- // ============================================================================
222
- // Loading/Error Slices - Memoized by NGXS
223
- // ============================================================================
991
+ // ---------------------------------------------------------------------------
992
+ // Data slices
993
+ // ---------------------------------------------------------------------------
994
+ myPage = select(DelegationsState.getMyPage);
995
+ assignedPage = select(DelegationsState.getAssignedPage);
996
+ selected = select(DelegationsState.getSelected);
997
+ scopeOptions = select(DelegationsState.getScopeOptions);
998
+ scopePreview = select(DelegationsState.getScopePreview);
224
999
  loadingActive = select(DelegationsState.getLoadingActive);
225
1000
  errors = select(DelegationsState.getErrors);
226
- // ============================================================================
227
- // Loading Signals - Computed from slice (minimal reactivity)
228
- // ============================================================================
229
- isLoadingDelegations = computed(() => this.loadingActive().includes(DelegationsActionKey.GetDelegations), ...(ngDevMode ? [{ debugName: "isLoadingDelegations" }] : /* istanbul ignore next */ []));
230
- isLoadingDelegation = computed(() => this.loadingActive().includes(DelegationsActionKey.GetDelegationForm), ...(ngDevMode ? [{ debugName: "isLoadingDelegation" }] : /* istanbul ignore next */ []));
231
- isAddingDelegation = computed(() => this.loadingActive().includes(DelegationsActionKey.AddDelegation), ...(ngDevMode ? [{ debugName: "isAddingDelegation" }] : /* istanbul ignore next */ []));
232
- isUpdatingDelegation = computed(() => this.loadingActive().includes(DelegationsActionKey.UpdateDelegation), ...(ngDevMode ? [{ debugName: "isUpdatingDelegation" }] : /* istanbul ignore next */ []));
233
- isDeletingDelegation = computed(() => this.loadingActive().includes(DelegationsActionKey.DeleteDelegation), ...(ngDevMode ? [{ debugName: "isDeletingDelegation" }] : /* istanbul ignore next */ []));
234
- // ============================================================================
235
- // Error Signals - Computed from slice (minimal reactivity)
236
- // ============================================================================
237
- delegationsError = computed(() => this.errors()[DelegationsActionKey.GetDelegations] ?? null, ...(ngDevMode ? [{ debugName: "delegationsError" }] : /* istanbul ignore next */ []));
238
- delegationError = computed(() => this.errors()[DelegationsActionKey.GetDelegationForm] ?? null, ...(ngDevMode ? [{ debugName: "delegationError" }] : /* istanbul ignore next */ []));
239
- addDelegationError = computed(() => this.errors()[DelegationsActionKey.AddDelegation] ?? null, ...(ngDevMode ? [{ debugName: "addDelegationError" }] : /* istanbul ignore next */ []));
240
- updateDelegationError = computed(() => this.errors()[DelegationsActionKey.UpdateDelegation] ?? null, ...(ngDevMode ? [{ debugName: "updateDelegationError" }] : /* istanbul ignore next */ []));
241
- deleteDelegationError = computed(() => this.errors()[DelegationsActionKey.DeleteDelegation] ?? null, ...(ngDevMode ? [{ debugName: "deleteDelegationError" }] : /* istanbul ignore next */ []));
242
- // ============================================================================
243
- // Derived Data - Computed from data selectors
244
- // ============================================================================
245
- activeDelegations = computed(() => this.allDelegations().filter((d) => d.isActive === true), ...(ngDevMode ? [{ debugName: "activeDelegations" }] : /* istanbul ignore next */ []));
246
- inactiveDelegations = computed(() => this.allDelegations().filter((d) => d.isActive === false), ...(ngDevMode ? [{ debugName: "inactiveDelegations" }] : /* istanbul ignore next */ []));
247
- // ============================================================================
248
- // Action Dispatchers
249
- // ============================================================================
250
- getDelegations() {
251
- return this.store.dispatch(new GetDelegations());
1001
+ // ---------------------------------------------------------------------------
1002
+ // Derived
1003
+ // ---------------------------------------------------------------------------
1004
+ myItems = computed(() => this.myPage()?.items ?? [], ...(ngDevMode ? [{ debugName: "myItems" }] : /* istanbul ignore next */ []));
1005
+ assignedItems = computed(() => this.assignedPage()?.items ?? [], ...(ngDevMode ? [{ debugName: "assignedItems" }] : /* istanbul ignore next */ []));
1006
+ // ---------------------------------------------------------------------------
1007
+ // Loading
1008
+ // ---------------------------------------------------------------------------
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 */ []));
1017
+ isLoadingScopeOptions = computed(() => this.loadingActive().includes(DelegationsActionKey.GetScopeOptions), ...(ngDevMode ? [{ debugName: "isLoadingScopeOptions" }] : /* istanbul ignore next */ []));
1018
+ isPreviewingScope = computed(() => this.loadingActive().includes(DelegationsActionKey.PreviewScope), ...(ngDevMode ? [{ debugName: "isPreviewingScope" }] : /* istanbul ignore next */ []));
1019
+ // ---------------------------------------------------------------------------
1020
+ // Errors
1021
+ // ---------------------------------------------------------------------------
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 */ []));
1030
+ // ---------------------------------------------------------------------------
1031
+ // Dispatchers
1032
+ // ---------------------------------------------------------------------------
1033
+ getMy(query = {}) {
1034
+ return this.store.dispatch(new GetMyDelegations(query));
252
1035
  }
253
- addDelegation(delegation) {
254
- return this.store.dispatch(new AddDelegation(delegation));
1036
+ getAssigned(query = {}) {
1037
+ return this.store.dispatch(new GetAssignedDelegations(query));
255
1038
  }
256
- updateDelegation(id, delegation) {
257
- return this.store.dispatch(new UpdateDelegation(id, delegation));
1039
+ getOne(id, includeStatusHistory = false) {
1040
+ return this.store.dispatch(new GetDelegation(id, includeStatusHistory));
258
1041
  }
259
- deleteDelegation(id) {
260
- return this.store.dispatch(new DeleteDelegation(id));
1042
+ clearSelected() {
1043
+ return this.store.dispatch(new ClearSelectedDelegation());
261
1044
  }
262
- loadDelegation(id) {
263
- return this.store.dispatch(new GetDelegation(id));
1045
+ create(request) {
1046
+ return this.store.dispatch(new CreateDelegation(request));
264
1047
  }
265
- clearSelectedDelegation() {
266
- return this.store.dispatch(new ClearSelectedDelegation());
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));
1059
+ }
1060
+ loadScopeOptions(delegatorUserId) {
1061
+ return this.store.dispatch(new GetScopeOptions(delegatorUserId));
1062
+ }
1063
+ previewScope(scope) {
1064
+ return this.store.dispatch(new PreviewScope(scope));
1065
+ }
1066
+ clearScopePreview() {
1067
+ return this.store.dispatch(new ClearScopePreview());
267
1068
  }
268
1069
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsFacade, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
269
1070
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsFacade, providedIn: 'root' });
@@ -273,58 +1074,272 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
273
1074
  args: [{ providedIn: 'root' }]
274
1075
  }] });
275
1076
 
1077
+ const EMPTY_SCOPE$1 = {
1078
+ mode: 'Explicit',
1079
+ readPolicy: 'ImplicitReadForSelectedActions',
1080
+ accessibilityGroupIds: [],
1081
+ targets: [],
1082
+ };
1083
+ function targetKey(t) {
1084
+ return `${t.targetType}:${t.targetId ?? ''}:${t.permissionTargetId ?? ''}:${t.moduleKey}`;
1085
+ }
1086
+ function requestTargetKey(t) {
1087
+ return `${t.targetType}:${t.targetId ?? ''}:${t.permissionTargetId ?? ''}:${t.moduleKey}`;
1088
+ }
1089
+ /**
1090
+ * Scope picker driven entirely by Identity-provided options.
1091
+ *
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`.
1097
+ */
1098
+ class ScopePicker {
1099
+ /** Two-way bound — the form drives initial value and reads updates back. */
1100
+ scope = model(EMPTY_SCOPE$1, ...(ngDevMode ? [{ debugName: "scope" }] : /* istanbul ignore next */ []));
1101
+ readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
1102
+ /** Admin scope-as-another-user — passed through to the options endpoint. */
1103
+ delegatorUserId = input(undefined, ...(ngDevMode ? [{ debugName: "delegatorUserId" }] : /* istanbul ignore next */ []));
1104
+ facade = inject(DelegationsFacade);
1105
+ transloco = inject(TranslocoService);
1106
+ options = this.facade.scopeOptions;
1107
+ preview = this.facade.scopePreview;
1108
+ isLoadingOptions = this.facade.isLoadingScopeOptions;
1109
+ 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(() => {
1141
+ 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)));
1145
+ }
1146
+ return map;
1147
+ }, ...(ngDevMode ? [{ debugName: "selectedTargetMap" }] : /* istanbul ignore next */ []));
1148
+ // -------------------------------------------------------------------------
1149
+ // Lifecycle
1150
+ // -------------------------------------------------------------------------
1151
+ ngOnInit() {
1152
+ this.facade.loadScopeOptions(this.delegatorUserId());
1153
+ }
1154
+ constructor() {
1155
+ // Debounced preview: whenever the scope changes, call /scope/preview.
1156
+ // (Skips the empty initial value.)
1157
+ let timer = null;
1158
+ effect(() => {
1159
+ const s = this.scope();
1160
+ const hasContent = s.accessibilityGroupIds.length > 0 || s.targets.length > 0;
1161
+ untracked(() => {
1162
+ if (!hasContent) {
1163
+ this.facade.clearScopePreview();
1164
+ return;
1165
+ }
1166
+ if (timer)
1167
+ clearTimeout(timer);
1168
+ timer = setTimeout(() => this.facade.previewScope(s), 300);
1169
+ });
1170
+ });
1171
+ }
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);
1196
+ }
1197
+ toggleAccordion(target) {
1198
+ const key = targetKey(target);
1199
+ const next = new Set(this.expanded());
1200
+ if (next.has(key))
1201
+ next.delete(key);
1202
+ else
1203
+ next.add(key);
1204
+ this.expanded.set(next);
1205
+ }
1206
+ isExpanded(target) {
1207
+ return this.expanded().has(targetKey(target));
1208
+ }
1209
+ toggleOperation(target, op) {
1210
+ if (this.readonly() || !op.isDelegable)
1211
+ return;
1212
+ 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 };
1266
+ });
1267
+ }
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');
1278
+ }
1279
+ 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 });
1281
+ }
1282
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ScopePicker, decorators: [{
1283
+ type: Component,
1284
+ args: [{ selector: 'mt-scope-picker', standalone: true, imports: [
1285
+ CommonModule,
1286
+ Icon,
1287
+ SkeletonModule,
1288
+ TooltipModule,
1289
+ 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"] }]
1291
+ }], 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
+
1293
+ const EMPTY_SCOPE = {
1294
+ mode: 'Explicit',
1295
+ readPolicy: 'ImplicitReadForSelectedActions',
1296
+ accessibilityGroupIds: [],
1297
+ targets: [],
1298
+ };
1299
+ /**
1300
+ * Delegation create/edit form, target-shape compliant.
1301
+ *
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.
1308
+ */
276
1309
  class DelegationForm {
277
- halfWidthFieldClass = 'flex flex-col gap-1 lg:col-span-6';
278
- fullWidthFieldClass = 'flex flex-col gap-1 lg:col-span-12';
1310
+ delegationForEdit = input(null, ...(ngDevMode ? [{ debugName: "delegationForEdit" }] : /* istanbul ignore next */ []));
1311
+ readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
1312
+ halfWidth = 'flex flex-col gap-1 lg:col-span-6';
1313
+ fullWidth = 'flex flex-col gap-1 lg:col-span-12';
279
1314
  modal = inject(ModalService);
280
1315
  ref = inject(ModalRef);
281
- delegationForEdit = input(null, ...(ngDevMode ? [{ debugName: "delegationForEdit" }] : /* istanbul ignore next */ []));
282
- translocoService = inject(TranslocoService);
1316
+ transloco = inject(TranslocoService);
283
1317
  facade = inject(DelegationsFacade);
284
- selectedDelegation = this.facade.selectedDelegation;
1318
+ // Reactive form
285
1319
  delegationFormControl = new FormControl();
286
1320
  formValue = toSignal(this.delegationFormControl.valueChanges);
287
- getDelegationFormLoading = this.facade.isLoadingDelegation;
288
- isAddingDelegation = this.facade.isAddingDelegation;
289
- isUpdatingDelegation = this.facade.isUpdatingDelegation;
290
- context = new HttpContext().set(REQUEST_CONTEXT, {
291
- useBaseUrl: false,
292
- });
1321
+ selected = this.facade.selected;
1322
+ isDetailLoading = this.facade.isLoadingDetail;
1323
+ isSaving = computed(() => this.facade.isCreating() || this.facade.isUpdating(), ...(ngDevMode ? [{ debugName: "isSaving" }] : /* istanbul ignore next */ []));
1324
+ context = new HttpContext().set(REQUEST_CONTEXT, { useBaseUrl: false });
293
1325
  isDelegatingToSelf = computed(() => {
294
- const formValue = this.formValue();
295
- const fromId = formValue.delegateFrom?.id || formValue.delegateFrom;
296
- const toId = formValue.delegateTo?.id || formValue.delegateTo;
297
- return fromId === toId;
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;
298
1332
  }, ...(ngDevMode ? [{ debugName: "isDelegatingToSelf" }] : /* istanbul ignore next */ []));
1333
+ // The current scope draft (built outside the form for MVP; placeholder UI).
1334
+ scope = signal(EMPTY_SCOPE, ...(ngDevMode ? [{ debugName: "scope" }] : /* istanbul ignore next */ []));
299
1335
  specificDaysOptions = signal([
300
- {
301
- label: this.translocoService.translate('delegations.sunday'),
302
- value: 0,
303
- },
304
- {
305
- label: this.translocoService.translate('delegations.monday'),
306
- value: 1,
307
- },
308
- {
309
- label: this.translocoService.translate('delegations.tuesday'),
310
- value: 2,
311
- },
312
- {
313
- label: this.translocoService.translate('delegations.wednesday'),
314
- value: 3,
315
- },
316
- {
317
- label: this.translocoService.translate('delegations.thursday'),
318
- value: 4,
319
- },
320
- {
321
- label: this.translocoService.translate('delegations.friday'),
322
- value: 5,
323
- },
324
- {
325
- label: this.translocoService.translate('delegations.saturday'),
326
- value: 6,
327
- },
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 },
328
1343
  ], ...(ngDevMode ? [{ debugName: "specificDaysOptions" }] : /* istanbul ignore next */ []));
329
1344
  delegationFormConfig = computed(() => ({
330
1345
  sections: [
@@ -335,98 +1350,89 @@ class DelegationForm {
335
1350
  order: 1,
336
1351
  fields: [
337
1352
  new UserSearchFieldConfig({
338
- key: 'delegateFrom',
339
- label: this.translocoService.translate('delegations.originally-assigned-to'),
340
- apiUrl: 'Identity/users',
341
- context: this.context,
342
- validators: [ValidatorConfig.required()],
343
- order: 1,
344
- cssClass: this.halfWidthFieldClass,
345
- colSpan: 6,
346
- disabled: !!this.delegationForEdit()?.id,
347
- }),
348
- new UserSearchFieldConfig({
349
- key: 'delegateTo',
350
- label: this.translocoService.translate('delegations.delegated-to'),
1353
+ key: 'delegatedUser',
1354
+ label: this.transloco.translate('delegations.delegated-to'),
351
1355
  apiUrl: 'Identity/users',
352
1356
  context: this.context,
353
1357
  validators: [ValidatorConfig.required()],
354
- order: 2,
355
- cssClass: this.halfWidthFieldClass,
1358
+ cssClass: this.halfWidth,
356
1359
  colSpan: 6,
357
- disabled: !!this.delegationForEdit()?.id,
1360
+ order: 1,
1361
+ disabled: this.readonly() || !!this.delegationForEdit()?.id,
358
1362
  }),
359
1363
  new TextareaFieldConfig({
360
1364
  key: 'description',
361
- label: this.translocoService.translate('delegations.description'),
362
- placeholder: this.translocoService.translate('delegations.description'),
363
- cssClass: this.fullWidthFieldClass,
1365
+ label: this.transloco.translate('delegations.description'),
1366
+ placeholder: this.transloco.translate('delegations.description'),
1367
+ cssClass: this.fullWidth,
364
1368
  colSpan: 12,
365
- order: 3,
1369
+ order: 2,
1370
+ disabled: this.readonly(),
366
1371
  }),
367
1372
  new DateFieldConfig({
368
- key: 'delegateFromDateTime',
369
- label: this.translocoService.translate('delegations.delegation-date-from'),
370
- placeholder: this.translocoService.translate('delegations.delegation-date-from'),
1373
+ key: 'startDateUtc',
1374
+ label: this.transloco.translate('delegations.delegation-date-from'),
1375
+ placeholder: this.transloco.translate('delegations.delegation-date-from'),
371
1376
  validators: [ValidatorConfig.required()],
372
1377
  showTime: true,
373
- cssClass: this.halfWidthFieldClass,
1378
+ cssClass: this.halfWidth,
374
1379
  colSpan: 6,
375
- order: 4,
1380
+ order: 3,
1381
+ disabled: this.readonly(),
376
1382
  }),
377
1383
  new DateFieldConfig({
378
- key: 'delegateToDateTime',
379
- label: this.translocoService.translate('delegations.delegation-date-to'),
380
- placeholder: this.translocoService.translate('delegations.delegation-date-to'),
1384
+ key: 'endDateUtc',
1385
+ label: this.transloco.translate('delegations.delegation-date-to'),
1386
+ placeholder: this.transloco.translate('delegations.delegation-date-to'),
381
1387
  validators: [ValidatorConfig.required()],
382
1388
  showTime: true,
383
- cssClass: this.halfWidthFieldClass,
1389
+ cssClass: this.halfWidth,
384
1390
  colSpan: 6,
385
- order: 5,
1391
+ order: 4,
1392
+ disabled: this.readonly(),
386
1393
  }),
387
1394
  new RadioButtonFieldConfig({
388
- key: 'delegationDaysType',
389
- label: this.translocoService.translate('delegations.delegation-days-apply'),
1395
+ key: 'dayRulesType',
1396
+ label: this.transloco.translate('delegations.delegation-days-apply'),
390
1397
  options: [
391
1398
  {
392
- label: this.translocoService.translate('delegations.full-range'),
1399
+ label: this.transloco.translate('delegations.full-range'),
393
1400
  value: 'FullRange',
394
1401
  },
395
1402
  {
396
- label: this.translocoService.translate('delegations.specific-days'),
1403
+ label: this.transloco.translate('delegations.specific-days'),
397
1404
  value: 'SpecificDays',
398
1405
  },
399
1406
  ],
400
1407
  optionLabel: 'label',
401
1408
  optionValue: 'value',
402
1409
  validators: [ValidatorConfig.required()],
403
- cssClass: this.fullWidthFieldClass,
1410
+ cssClass: this.fullWidth,
404
1411
  colSpan: 12,
405
- order: 6,
1412
+ order: 5,
1413
+ disabled: this.readonly(),
406
1414
  }),
407
1415
  new MultiSelectFieldConfig({
408
1416
  key: 'specificDays',
409
- label: this.translocoService.translate('delegations.specific-days'),
1417
+ label: this.transloco.translate('delegations.specific-days'),
410
1418
  options: this.specificDaysOptions(),
411
1419
  optionLabel: 'label',
412
1420
  optionValue: 'value',
413
1421
  maxSelectedLabels: 7,
414
1422
  relations: [
415
- {
416
- key: 'delegationDaysType',
417
- value: 'SpecificDays',
418
- action: 'show',
419
- },
1423
+ { key: 'dayRulesType', value: 'SpecificDays', action: 'show' },
420
1424
  ],
421
- cssClass: this.fullWidthFieldClass,
1425
+ cssClass: this.fullWidth,
422
1426
  colSpan: 12,
423
- order: 7,
1427
+ order: 6,
1428
+ disabled: this.readonly(),
424
1429
  }),
425
1430
  new ToggleFieldConfig({
426
- key: 'isActive',
427
- label: this.translocoService.translate('delegations.active-delegation'),
428
- cssClass: `${this.fullWidthFieldClass} mt-5`,
429
- order: 8,
1431
+ key: 'requiresApproval',
1432
+ label: this.transloco.translate('delegations.requires-approval'),
1433
+ cssClass: `${this.fullWidth} mt-2`,
1434
+ order: 7,
1435
+ disabled: this.readonly(),
430
1436
  }),
431
1437
  ],
432
1438
  },
@@ -434,93 +1440,170 @@ class DelegationForm {
434
1440
  }), ...(ngDevMode ? [{ debugName: "delegationFormConfig" }] : /* istanbul ignore next */ []));
435
1441
  constructor() {
436
1442
  effect(() => {
437
- const delegationId = this.delegationForEdit()?.id;
438
- if (this.selectedDelegation() && delegationId) {
439
- const delegation = this.selectedDelegation();
440
- const data = {
441
- delegateFrom: delegation?.delegateFrom,
442
- delegateTo: delegation?.delegateTo,
443
- description: delegation?.description,
444
- delegateFromDateTime: delegation?.delegateFromDateTime?.actualValue,
445
- delegateToDateTime: delegation?.delegateToDateTime?.actualValue,
446
- delegationDaysType: delegation?.delegationDaysType || 'FullRange',
447
- specificDays: delegation?.specificDays || [],
448
- isActive: delegation?.isActive ?? false,
449
- };
450
- this.delegationFormControl.patchValue(data);
1443
+ const id = this.delegationForEdit()?.id;
1444
+ const d = this.selected();
1445
+ if (id && d) {
1446
+ 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,
1454
+ });
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
+ }
451
1477
  }
452
- else {
1478
+ else if (!id) {
453
1479
  this.delegationFormControl.reset({
454
- delegationDaysType: 'FullRange',
455
- isActive: true,
1480
+ dayRulesType: 'FullRange',
1481
+ requiresApproval: true,
456
1482
  });
1483
+ this.scope.set(EMPTY_SCOPE);
457
1484
  }
458
1485
  });
459
1486
  }
460
1487
  ngOnInit() {
461
- const delegationId = this.delegationForEdit()?.id;
462
- if (delegationId) {
463
- this.facade.loadDelegation(delegationId);
1488
+ const id = this.delegationForEdit()?.id;
1489
+ if (id) {
1490
+ this.facade.getOne(id, true);
464
1491
  }
465
1492
  }
466
1493
  onSubmit() {
467
- if (!this.delegationFormControl.valid) {
1494
+ if (this.readonly() || !this.delegationFormControl.valid)
468
1495
  return;
469
- }
470
- const formValue = this.delegationFormControl.value;
471
- const payload = {
472
- delegateFrom: formValue?.delegateFrom?.id || formValue?.delegateFrom,
473
- delegateTo: formValue?.delegateTo?.id || formValue?.delegateTo,
474
- description: formValue?.description,
475
- delegateFromDateTime: formValue?.delegateFromDateTime,
476
- delegateToDateTime: formValue?.delegateToDateTime,
477
- delegationDaysType: formValue?.delegationDaysType || 'FullRange',
478
- specificDays: formValue?.delegationDaysType === 'SpecificDays'
479
- ? formValue?.specificDays || []
480
- : undefined,
481
- isActive: formValue?.isActive ?? false,
1496
+ const v = this.delegationFormControl.value;
1497
+ const tz = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
1498
+ const editing = this.delegationForEdit();
1499
+ const base = {
1500
+ 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
+ },
1508
+ scope: this.scope(),
482
1509
  };
483
- const delegationId = this.delegationForEdit()?.id;
484
- if (delegationId) {
485
- this.facade.updateDelegation(delegationId, payload).subscribe({
486
- next: () => {
487
- this.ref.close(true);
488
- },
489
- error: () => { },
1510
+ if (editing?.id) {
1511
+ const req = {
1512
+ ...base,
1513
+ rowVersion: editing.rowVersion,
1514
+ requiresApproval: v?.requiresApproval,
1515
+ };
1516
+ this.facade.update(editing.id, req).subscribe({
1517
+ next: () => this.ref.close(true),
490
1518
  });
491
1519
  }
492
1520
  else {
493
- this.facade.addDelegation(payload).subscribe({
494
- next: () => {
495
- this.ref.close(true);
496
- },
497
- error: () => { },
1521
+ const req = {
1522
+ ...base,
1523
+ delegatedUserId: v?.delegatedUser?.id ?? v?.delegatedUser,
1524
+ requiresApproval: !!v?.requiresApproval,
1525
+ };
1526
+ this.facade.create(req).subscribe({
1527
+ next: () => this.ref.close(true),
498
1528
  });
499
1529
  }
500
1530
  }
501
1531
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
502
- 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 } }, providers: [DialogService], 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 @if (getDelegationFormLoading()) {\r\n <p-skeleton class=\"my-4 mt-7\" height=\"3rem\" />\r\n <p-skeleton class=\"my-4\" height=\"3rem\" />\r\n <p-skeleton class=\"my-4\" height=\"6rem\" />\r\n <p-skeleton class=\"my-4\" height=\"3rem\" />\r\n } @else {\r\n <mt-dynamic-form\r\n [formConfig]=\"delegationFormConfig()\"\r\n [formControl]=\"delegationFormControl\"\r\n />\r\n }\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 <mt-button\r\n [label]=\"delegationForEdit() ? t('update') : t('create')\"\r\n [loading]=\"isAddingDelegation() || isUpdatingDelegation()\"\r\n [disabled]=\"!delegationFormControl.valid || isDelegatingToSelf()\"\r\n (click)=\"onSubmit()\"\r\n styleClass=\"w-full sm:w-auto\"\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: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: DynamicForm, selector: "mt-dynamic-form", inputs: ["formConfig", "forcedHiddenFieldKeys", "preserveForcedHiddenValues", "visibleSectionKeys"], outputs: ["runtimeMessagesChange"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }] });
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 });
503
1533
  }
504
1534
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationForm, decorators: [{
505
1535
  type: Component,
506
1536
  args: [{ selector: 'mt-delegation-form', standalone: true, imports: [
507
1537
  CommonModule,
508
1538
  Button,
509
- TranslocoDirective,
510
- SkeletonModule,
511
1539
  DynamicForm,
512
1540
  ReactiveFormsModule,
513
- ], providers: [DialogService], 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 @if (getDelegationFormLoading()) {\r\n <p-skeleton class=\"my-4 mt-7\" height=\"3rem\" />\r\n <p-skeleton class=\"my-4\" height=\"3rem\" />\r\n <p-skeleton class=\"my-4\" height=\"6rem\" />\r\n <p-skeleton class=\"my-4\" height=\"3rem\" />\r\n } @else {\r\n <mt-dynamic-form\r\n [formConfig]=\"delegationFormConfig()\"\r\n [formControl]=\"delegationFormControl\"\r\n />\r\n }\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 <mt-button\r\n [label]=\"delegationForEdit() ? t('update') : t('create')\"\r\n [loading]=\"isAddingDelegation() || isUpdatingDelegation()\"\r\n [disabled]=\"!delegationFormControl.valid || isDelegatingToSelf()\"\r\n (click)=\"onSubmit()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n />\r\n </div>\r\n</ng-container>\r\n" }]
514
- }], ctorParameters: () => [], propDecorators: { delegationForEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegationForEdit", required: false }] }] } });
1541
+ ScopePicker,
1542
+ 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" }]
1544
+ }], ctorParameters: () => [], propDecorators: { delegationForEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegationForEdit", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }] } });
515
1545
 
1546
+ /**
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.
1549
+ */
1550
+ class DelegationDetailDrawer {
1551
+ /** Delegation id passed in via the modal's inputValues. */
1552
+ delegationId = input.required(...(ngDevMode ? [{ debugName: "delegationId" }] : /* istanbul ignore next */ []));
1553
+ facade = inject(DelegationsFacade);
1554
+ transloco = inject(TranslocoService);
1555
+ ref = inject(ModalRef);
1556
+ tabs = [
1557
+ { key: 'overview', i18n: 'delegations.view-details' },
1558
+ { key: 'scope', i18n: 'delegations.scope' },
1559
+ { key: 'history', i18n: 'delegations.status-history' },
1560
+ ];
1561
+ activeTab = signal('overview', ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
1562
+ isLoading = this.facade.isLoadingDetail;
1563
+ detail = this.facade.selected;
1564
+ statusHistory = computed(() => this.detail()?.statusHistory ?? [], ...(ngDevMode ? [{ debugName: "statusHistory" }] : /* istanbul ignore next */ []));
1565
+ ngOnInit() {
1566
+ this.facade.getOne(this.delegationId(), true);
1567
+ }
1568
+ setTab(tab) {
1569
+ this.activeTab.set(tab);
1570
+ }
1571
+ approvalLabel() {
1572
+ const s = this.detail()?.approval?.status ?? 'NotRequired';
1573
+ return this.transloco.translate(`delegations.approval-${s.toLowerCase()}`);
1574
+ }
1575
+ 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 });
1577
+ }
1578
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationDetailDrawer, decorators: [{
1579
+ type: Component,
1580
+ args: [{ selector: 'mt-delegation-detail-drawer', standalone: true, imports: [
1581
+ CommonModule,
1582
+ Avatar,
1583
+ Button,
1584
+ Icon,
1585
+ SkeletonModule,
1586
+ TranslocoDirective,
1587
+ 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"] }]
1589
+ }], propDecorators: { delegationId: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegationId", required: true }] }] } });
1590
+
1591
+ /**
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.
1600
+ */
516
1601
  class DelegationsList {
517
- statusCol = viewChild.required('statusCol');
518
- userCol = viewChild.required('userCol');
519
- userCol2 = viewChild.required('userCol2');
520
- daysCol = viewChild.required('daysCol');
521
1602
  facade = inject(DelegationsFacade);
1603
+ session = inject(DelegationSessionFacade);
522
1604
  modal = inject(ModalService);
523
- translocoService = inject(TranslocoService);
1605
+ transloco = inject(TranslocoService);
1606
+ // ---- Page header (unchanged from legacy admin breadcrumbs) ----
524
1607
  breadcrumbItems = linkedSignal(() => [
525
1608
  {
526
1609
  label: '',
@@ -528,147 +1611,143 @@ class DelegationsList {
528
1611
  routerLink: '/control-panel/workspaces',
529
1612
  },
530
1613
  {
531
- label: this.translocoService.translate('product-settings.product-settings'),
1614
+ label: this.transloco.translate('product-settings.product-settings'),
532
1615
  routerLink: '/control-panel/product-settings',
533
1616
  },
534
1617
  {
535
- label: this.translocoService.translate('delegations.delegations'),
1618
+ label: this.transloco.translate('delegations.delegations'),
536
1619
  },
537
1620
  ], ...(ngDevMode ? [{ debugName: "breadcrumbItems" }] : /* istanbul ignore next */ []));
538
- delegations = computed(() => {
539
- const tab = this.activeTab();
540
- const all = this.allDelegations();
541
- const activeDelegations = this.activeDelegations();
542
- const inactiveDelegations = this.inactiveDelegations();
543
- switch (tab) {
544
- case 'active':
545
- return activeDelegations;
546
- case 'inactive':
547
- return inactiveDelegations;
548
- case 'all':
549
- default:
550
- return all;
551
- }
552
- }, ...(ngDevMode ? [{ debugName: "delegations" }] : /* istanbul ignore next */ []));
1621
+ // ---- Tabs ----
1622
+ activeTab = signal('my', ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
553
1623
  tabs = signal([
554
1624
  {
555
- label: this.translocoService.translate('delegations.all'),
556
- value: 'all',
557
- },
558
- {
559
- label: this.translocoService.translate('delegations.active'),
560
- value: 'active',
1625
+ value: 'my',
1626
+ label: this.transloco.translate('delegations.my-delegations'),
561
1627
  },
562
1628
  {
563
- label: this.translocoService.translate('delegations.inactive'),
564
- value: 'inactive',
1629
+ value: 'assigned',
1630
+ label: this.transloco.translate('delegations.delegated-to-me'),
565
1631
  },
566
1632
  ], ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
567
- activeTab = signal('all', ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
1633
+ // ---- Loading / data ----
1634
+ isLoading = computed(() => this.activeTab() === 'my'
1635
+ ? this.facade.isLoadingMy()
1636
+ : this.facade.isLoadingAssigned(), ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
1637
+ rows = computed(() => this.activeTab() === 'my'
1638
+ ? this.facade.myItems()
1639
+ : this.facade.assignedItems(), ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
1640
+ // ---- Column templates ----
1641
+ statusCol = viewChild.required('statusCol');
1642
+ userCol = viewChild.required('userCol');
1643
+ scopeCol = viewChild.required('scopeCol');
1644
+ daysCol = viewChild.required('daysCol');
1645
+ // ---- Table actions (header) ----
568
1646
  tableActions = signal([
569
1647
  {
570
1648
  icon: 'general.plus',
571
- label: this.translocoService.translate('delegations.add-delegation'),
1649
+ label: this.transloco.translate('delegations.add-delegation'),
572
1650
  color: 'primary',
573
- action: () => {
574
- this.addDelegationDialog();
575
- },
1651
+ action: () => this.openForm(null),
576
1652
  },
577
1653
  ], ...(ngDevMode ? [{ debugName: "tableActions" }] : /* istanbul ignore next */ []));
578
- deletingRowIds = signal([], ...(ngDevMode ? [{ debugName: "deletingRowIds" }] : /* istanbul ignore next */ []));
1654
+ // ---- Per-row action: read ONLY allowedActions from BE ----
579
1655
  rowActions = signal([
1656
+ {
1657
+ icon: 'general.eye',
1658
+ tooltip: this.transloco.translate('delegations.view-details'),
1659
+ color: 'primary',
1660
+ variant: 'outlined',
1661
+ action: (row) => this.viewDetails(row),
1662
+ hidden: (row) => !this.has(row, 'View'),
1663
+ },
580
1664
  {
581
1665
  icon: 'custom.pencil',
582
- tooltip: this.translocoService.translate('edit'),
1666
+ tooltip: this.transloco.translate('edit'),
583
1667
  color: 'primary',
584
- action: (row) => {
585
- this.facade.clearSelectedDelegation();
586
- this.addDelegationDialog(row);
587
- },
1668
+ action: (row) => this.openForm(row),
1669
+ hidden: (row) => !this.has(row, 'Edit'),
1670
+ },
1671
+ {
1672
+ icon: 'general.check',
1673
+ tooltip: this.transloco.translate('delegations.approve'),
1674
+ color: 'primary',
1675
+ action: (row) => this.approve(row),
1676
+ hidden: (row) => !this.has(row, 'Approve'),
1677
+ loading: (row) => this.busyIds().includes(`approve:${row.id}`),
1678
+ confirmation: { type: 'popup' },
588
1679
  },
589
1680
  {
590
- icon: 'general.trash-01',
591
- tooltip: this.translocoService.translate('delete'),
1681
+ icon: 'general.x-close',
1682
+ tooltip: this.transloco.translate('delegations.reject'),
592
1683
  color: 'danger',
593
1684
  variant: 'outlined',
594
- action: (row) => {
595
- this.deletingRowIds.update((ids) => [...ids, row.id]);
596
- this.facade
597
- .deleteDelegation(row.id)
598
- .pipe(finalize(() => {
599
- this.deletingRowIds.update((ids) => ids.filter((id) => id !== row.id));
600
- }))
601
- .subscribe();
602
- },
603
- confirmation: {
604
- type: 'popup',
605
- confirmationType: 'delete',
606
- },
607
- loading: (row) => this.deletingRowIds().includes(row.id),
1685
+ action: (row) => this.reject(row),
1686
+ hidden: (row) => !this.has(row, 'Reject'),
1687
+ loading: (row) => this.busyIds().includes(`reject:${row.id}`),
1688
+ confirmation: { type: 'popup' },
1689
+ },
1690
+ {
1691
+ icon: 'general.x-close',
1692
+ tooltip: this.transloco.translate('delegations.cancel-delegation'),
1693
+ color: 'danger',
1694
+ variant: 'outlined',
1695
+ 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' },
1702
+ },
1703
+ {
1704
+ icon: 'arrow.arrow-right',
1705
+ tooltip: this.transloco.translate('delegations.start-session'),
1706
+ color: 'primary',
1707
+ action: (row) => this.startSession(row),
1708
+ hidden: (row) => !this.has(row, 'StartSession'),
608
1709
  },
609
1710
  ], ...(ngDevMode ? [{ debugName: "rowActions" }] : /* istanbul ignore next */ []));
610
1711
  tableColumns = linkedSignal(() => [
611
1712
  {
612
- key: 'isActive',
613
- label: this.translocoService.translate('delegations.status'),
1713
+ key: 'status',
1714
+ label: this.transloco.translate('delegations.status'),
614
1715
  type: 'custom',
615
1716
  customCellTpl: this.statusCol(),
616
- filterConfig: {
617
- type: 'select',
618
- label: this.translocoService.translate('delegations.status'),
619
- options: [
620
- {
621
- label: this.translocoService.translate('delegations.active'),
622
- value: true,
623
- },
624
- {
625
- label: this.translocoService.translate('delegations.inactive'),
626
- value: false,
627
- },
628
- ],
629
- },
630
1717
  },
631
1718
  {
632
- key: 'delegateFrom.displayName',
633
- label: this.translocoService.translate('delegations.originally-assigned-to'),
1719
+ key: this.activeTab() === 'my' ? 'delegatedUser' : 'delegator',
1720
+ label: this.transloco.translate(this.activeTab() === 'my'
1721
+ ? 'delegations.delegated-to'
1722
+ : 'delegations.delegator-name'),
634
1723
  type: 'custom',
635
1724
  customCellTpl: this.userCol(),
636
1725
  },
637
1726
  {
638
- key: 'delegateTo.displayName',
639
- label: this.translocoService.translate('delegations.delegated-to'),
1727
+ key: 'scope',
1728
+ label: this.transloco.translate('delegations.scope'),
640
1729
  type: 'custom',
641
- customCellTpl: this.userCol2(),
1730
+ customCellTpl: this.scopeCol(),
642
1731
  },
643
1732
  {
644
- key: 'delegateFromDateTime',
645
- label: this.translocoService.translate('delegations.start-date'),
1733
+ key: 'startDateUtc',
1734
+ label: this.transloco.translate('delegations.start-date'),
646
1735
  type: 'dateTime',
647
- filterConfig: {
648
- type: 'date',
649
- label: this.translocoService.translate('delegations.start-date'),
650
- },
651
1736
  },
652
1737
  {
653
- key: 'delegateToDateTime',
654
- label: this.translocoService.translate('delegations.end-date'),
1738
+ key: 'endDateUtc',
1739
+ label: this.transloco.translate('delegations.end-date'),
655
1740
  type: 'dateTime',
656
- filterConfig: {
657
- type: 'date',
658
- label: this.translocoService.translate('delegations.end-date'),
659
- },
660
1741
  },
661
1742
  {
662
- key: 'days',
663
- label: this.translocoService.translate('delegations.days'),
1743
+ key: 'dayRules',
1744
+ label: this.transloco.translate('delegations.days'),
664
1745
  type: 'custom',
665
1746
  customCellTpl: this.daysCol(),
666
1747
  },
667
1748
  ], ...(ngDevMode ? [{ debugName: "tableColumns" }] : /* istanbul ignore next */ []));
668
- loading = this.facade.isLoadingDelegations;
669
- allDelegations = this.facade.allDelegations;
670
- activeDelegations = this.facade.activeDelegations;
671
- inactiveDelegations = this.facade.inactiveDelegations;
1749
+ // ---- per-row "busy" tracking (no row-version conflict UX yet — toast suffices) ----
1750
+ busyIds = signal([], ...(ngDevMode ? [{ debugName: "busyIds" }] : /* istanbul ignore next */ []));
672
1751
  weekdayKeys = [
673
1752
  'sunday',
674
1753
  'monday',
@@ -678,59 +1757,170 @@ class DelegationsList {
678
1757
  'friday',
679
1758
  'saturday',
680
1759
  ];
1760
+ // ---------------------------------------------------------------------------
1761
+ // Init / tab change
1762
+ // ---------------------------------------------------------------------------
681
1763
  ngOnInit() {
682
- this.facade.getDelegations();
683
- }
684
- formatDelegationDays(row) {
685
- if (row?.delegationDaysType === 'SpecificDays' &&
686
- row?.specificDays?.length) {
687
- return row.specificDays
688
- .filter((day) => day >= 0 && day < this.weekdayKeys.length)
689
- .map((day) => this.translocoService.translate(`delegations.${this.weekdayKeys[day]}`))
1764
+ this.loadCurrentTab();
1765
+ }
1766
+ switchTab(tab) {
1767
+ this.activeTab.set(tab);
1768
+ this.loadCurrentTab();
1769
+ }
1770
+ loadCurrentTab(query = { page: 1, pageSize: 20 }) {
1771
+ if (this.activeTab() === 'my') {
1772
+ this.facade.getMy(query);
1773
+ }
1774
+ else {
1775
+ this.facade.getAssigned(query);
1776
+ }
1777
+ }
1778
+ // ---------------------------------------------------------------------------
1779
+ // Helpers
1780
+ // ---------------------------------------------------------------------------
1781
+ has(row, action) {
1782
+ return row.allowedActions?.includes(action) ?? false;
1783
+ }
1784
+ formatDays(row) {
1785
+ if (row.dayRules?.type === 'SpecificDays' &&
1786
+ row.dayRules.specificDays?.length) {
1787
+ return row.dayRules.specificDays
1788
+ .filter((d) => d >= 0 && d < this.weekdayKeys.length)
1789
+ .map((d) => this.transloco.translate(`delegations.${this.weekdayKeys[d]}`))
690
1790
  .join(', ');
691
1791
  }
692
- return this.translocoService.translate('delegations.all-days');
693
- }
694
- addDelegationDialog(delegation = null) {
695
- const modalType = delegation ? 'drawer' : 'dialog';
696
- const styleClass = modalType === 'drawer'
697
- ? '!absolute !shadow-none !w-full !max-w-full sm:!w-[42rem] xl:!w-[50rem]'
698
- : '!w-[min(96vw,50rem)] !max-w-[96vw] !h-[min(90vh,40rem)] !max-h-[90vh]';
699
- this.modal.openModal(DelegationForm, modalType, {
700
- header: delegation
701
- ? this.translocoService.translate('delegations.edit-delegation')
702
- : this.translocoService.translate('delegations.add-delegation'),
703
- styleClass,
704
- position: modalType === 'drawer' ? 'end' : '',
705
- appendTo: modalType === 'drawer' ? 'page-content' : 'body',
1792
+ return this.transloco.translate('delegations.all-days');
1793
+ }
1794
+ // ---------------------------------------------------------------------------
1795
+ // Lifecycle actions
1796
+ // ---------------------------------------------------------------------------
1797
+ viewDetails(row) {
1798
+ this.modal.openModal(DelegationDetailDrawer, 'drawer', {
1799
+ header: this.transloco.translate('delegations.view-details'),
1800
+ styleClass: '!absolute !shadow-none !w-full !max-w-full sm:!w-[42rem] xl:!w-[50rem]',
1801
+ position: 'end',
1802
+ appendTo: 'page-content',
1803
+ dismissableMask: true,
1804
+ dismissible: true,
1805
+ inputValues: { delegationId: row.id },
1806
+ });
1807
+ }
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'
1813
+ ? '!absolute !shadow-none !w-full !max-w-full sm:!w-[42rem] xl:!w-[50rem]'
1814
+ : '!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',
1817
+ dismissableMask: true,
1818
+ dismissible: true,
1819
+ inputValues: {
1820
+ delegationForEdit: row,
1821
+ readonly,
1822
+ },
1823
+ });
1824
+ }
1825
+ approve(row) {
1826
+ this.mark(`approve:${row.id}`, true);
1827
+ this.facade
1828
+ .approve(row.id, { rowVersion: row.rowVersion })
1829
+ .pipe(finalize(() => this.mark(`approve:${row.id}`, false)))
1830
+ .subscribe();
1831
+ }
1832
+ reject(row) {
1833
+ this.mark(`reject:${row.id}`, true);
1834
+ this.facade
1835
+ .reject(row.id, {
1836
+ rowVersion: row.rowVersion,
1837
+ reason: this.transloco.translate('delegations.reject'),
1838
+ })
1839
+ .pipe(finalize(() => this.mark(`reject:${row.id}`, false)))
1840
+ .subscribe();
1841
+ }
1842
+ cancel(row) {
1843
+ this.mark(`cancel:${row.id}`, true);
1844
+ 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();
1851
+ }
1852
+ startSession(row) {
1853
+ this.modal.openModal(StartSessionDialog, 'dialog', {
1854
+ header: this.transloco.translate('delegations.start-session'),
1855
+ styleClass: '!w-[min(96vw,28rem)] !max-w-[96vw]',
706
1856
  dismissableMask: true,
707
1857
  dismissible: true,
708
1858
  inputValues: {
709
- delegationForEdit: delegation,
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',
710
1870
  },
711
1871
  });
712
1872
  }
1873
+ mark(key, on) {
1874
+ this.busyIds.update((ids) => on ? [...ids, key] : ids.filter((id) => id !== key));
1875
+ }
713
1876
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsList, deps: [], target: i0.ɵɵFactoryTarget.Component });
714
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.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: "userCol2", first: true, predicate: ["userCol2"], descendants: true, isSignal: true }, { propertyName: "daysCol", first: true, predicate: ["daysCol"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t; prefix: 'delegations'\">\r\n <div class=\"min-w-0 space-y-4\">\r\n <div class=\"flex min-w-0 items-center justify-between gap-4\">\r\n <div class=\"min-w-0 space-y-1\">\r\n <h1 class=\"text-xl font-semibold text-slate-900 tracking-tight\">\r\n {{ t(\"delegations\") }}\r\n </h1>\r\n <mt-breadcrumb\r\n [items]=\"breadcrumbItems()\"\r\n [styleClass]=\"'flex justify-start mx-1'\"\r\n ></mt-breadcrumb>\r\n </div>\r\n </div>\r\n <ng-template #statusCol let-row>\r\n <span\r\n class=\"flex items-center gap-2 text-md\"\r\n [class]=\"row.isActive ? 'text-green-700 ' : 'text-red-700 '\"\r\n >\r\n <mt-icon\r\n class=\"text-2xl\"\r\n [icon]=\"row.isActive ? 'media.play-circle' : 'media.pause-circle'\"\r\n ></mt-icon>\r\n {{ row.isActive ? t(\"active\") : t(\"inactive\") }}\r\n </span>\r\n </ng-template>\r\n\r\n <ng-template #userCol let-row>\r\n <div class=\"flex min-w-0 items-center gap-2\">\r\n <mt-avatar class=\"shrink-0\" [icon]=\"'custom.user-pp'\"></mt-avatar>\r\n <span class=\"truncate\">{{ row.delegateFrom?.displayName }}</span>\r\n </div>\r\n </ng-template>\r\n <ng-template #userCol2 let-row>\r\n <div class=\"flex min-w-0 items-center gap-2\">\r\n <mt-avatar class=\"shrink-0\" [icon]=\"'custom.user-pp'\"></mt-avatar>\r\n <span class=\"truncate\">{{ row.delegateTo?.displayName }}</span>\r\n </div>\r\n </ng-template>\r\n\r\n <ng-template #daysCol let-row>\r\n <div class=\"truncate\">{{ formatDelegationDays(row) }}</div>\r\n </ng-template>\r\n\r\n <mt-table\r\n [tabs]=\"tabs()\"\r\n [(activeTab)]=\"activeTab\"\r\n [data]=\"delegations()\"\r\n [columns]=\"tableColumns()\"\r\n [actions]=\"tableActions()\"\r\n [rowActions]=\"rowActions()\"\r\n [generalSearch]=\"true\"\r\n [showFilters]=\"true\"\r\n [loading]=\"loading()\"\r\n storageKey=\"delegations-list-table\"\r\n >\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: "ngmodule", type: SkeletonModule }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "filterMode", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "exportable", "printable", "groupable", "cellClickFilter", "freezeActions", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange"] }, { kind: "component", type: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: Breadcrumb, selector: "mt-breadcrumb", inputs: ["items", "styleClass"], outputs: ["onItemClick"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }] });
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 });
715
1878
  }
716
1879
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsList, decorators: [{
717
1880
  type: Component,
718
1881
  args: [{ selector: 'mt-delegations-list', standalone: true, imports: [
719
1882
  CommonModule,
720
- SkeletonModule,
721
- Table,
722
1883
  Avatar,
723
- TranslocoDirective,
724
1884
  Breadcrumb,
725
- Icon,
726
- ], template: "<ng-container *transloco=\"let t; prefix: 'delegations'\">\r\n <div class=\"min-w-0 space-y-4\">\r\n <div class=\"flex min-w-0 items-center justify-between gap-4\">\r\n <div class=\"min-w-0 space-y-1\">\r\n <h1 class=\"text-xl font-semibold text-slate-900 tracking-tight\">\r\n {{ t(\"delegations\") }}\r\n </h1>\r\n <mt-breadcrumb\r\n [items]=\"breadcrumbItems()\"\r\n [styleClass]=\"'flex justify-start mx-1'\"\r\n ></mt-breadcrumb>\r\n </div>\r\n </div>\r\n <ng-template #statusCol let-row>\r\n <span\r\n class=\"flex items-center gap-2 text-md\"\r\n [class]=\"row.isActive ? 'text-green-700 ' : 'text-red-700 '\"\r\n >\r\n <mt-icon\r\n class=\"text-2xl\"\r\n [icon]=\"row.isActive ? 'media.play-circle' : 'media.pause-circle'\"\r\n ></mt-icon>\r\n {{ row.isActive ? t(\"active\") : t(\"inactive\") }}\r\n </span>\r\n </ng-template>\r\n\r\n <ng-template #userCol let-row>\r\n <div class=\"flex min-w-0 items-center gap-2\">\r\n <mt-avatar class=\"shrink-0\" [icon]=\"'custom.user-pp'\"></mt-avatar>\r\n <span class=\"truncate\">{{ row.delegateFrom?.displayName }}</span>\r\n </div>\r\n </ng-template>\r\n <ng-template #userCol2 let-row>\r\n <div class=\"flex min-w-0 items-center gap-2\">\r\n <mt-avatar class=\"shrink-0\" [icon]=\"'custom.user-pp'\"></mt-avatar>\r\n <span class=\"truncate\">{{ row.delegateTo?.displayName }}</span>\r\n </div>\r\n </ng-template>\r\n\r\n <ng-template #daysCol let-row>\r\n <div class=\"truncate\">{{ formatDelegationDays(row) }}</div>\r\n </ng-template>\r\n\r\n <mt-table\r\n [tabs]=\"tabs()\"\r\n [(activeTab)]=\"activeTab\"\r\n [data]=\"delegations()\"\r\n [columns]=\"tableColumns()\"\r\n [actions]=\"tableActions()\"\r\n [rowActions]=\"rowActions()\"\r\n [generalSearch]=\"true\"\r\n [showFilters]=\"true\"\r\n [loading]=\"loading()\"\r\n storageKey=\"delegations-list-table\"\r\n >\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"] }]
727
- }], propDecorators: { statusCol: [{ type: i0.ViewChild, args: ['statusCol', { isSignal: true }] }], userCol: [{ type: i0.ViewChild, args: ['userCol', { isSignal: true }] }], userCol2: [{ type: i0.ViewChild, args: ['userCol2', { isSignal: true }] }], daysCol: [{ type: i0.ViewChild, args: ['daysCol', { isSignal: true }] }] } });
1885
+ Table,
1886
+ TranslocoDirective,
1887
+ 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"] }]
1889
+ }], 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
+
1891
+ /**
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).
1895
+ */
1896
+ const EXCLUDE = /\bidentity\/delegations\/(?:[^/]+\/)?(?:token|session)(?:\/|$)/i;
1897
+ /**
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).
1903
+ */
1904
+ const appDelegationInterceptor = (req, next) => {
1905
+ if (EXCLUDE.test(req.url)) {
1906
+ return next(req);
1907
+ }
1908
+ const token = inject(DelegationSessionFacade).accessToken();
1909
+ if (!token) {
1910
+ return next(req);
1911
+ }
1912
+ return next(req.clone({
1913
+ setHeaders: { 'app-delegation': `Bearer ${token}` },
1914
+ }));
1915
+ };
728
1916
 
729
1917
  // store/index.ts
730
1918
 
1919
+ // Runtime UI surfaces — projected into host topbars + opened via ModalService.
1920
+
731
1921
  /**
732
1922
  * Generated bundle index. Do not edit.
733
1923
  */
734
1924
 
735
- export { AddDelegation, ClearSelectedDelegation, DelegationForm, Delegations, DelegationsActionKey, DelegationsFacade, DelegationsList, DelegationsState, DeleteDelegation, GetDelegation, GetDelegations, UpdateDelegation };
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 };
736
1926
  //# sourceMappingURL=masterteam-delegations.mjs.map