@masterteam/delegations 0.0.12 → 0.0.14

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,399 @@
1
+ import * as i2$1 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, 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$1 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 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.status.active',
33
+ styleClass: 'mt-status-chip mt-status-chip--active',
34
+ },
35
+ Scheduled: {
36
+ i18nKey: 'delegations.status.scheduled',
37
+ styleClass: 'mt-status-chip mt-status-chip--scheduled',
38
+ },
39
+ PendingApproval: {
40
+ i18nKey: 'delegations.status.pendingApproval',
41
+ styleClass: 'mt-status-chip mt-status-chip--pending',
42
+ },
43
+ InactiveToday: {
44
+ i18nKey: 'delegations.status.inactiveToday',
45
+ styleClass: 'mt-status-chip mt-status-chip--scheduled',
46
+ },
47
+ Expired: {
48
+ i18nKey: 'delegations.status.expired',
49
+ styleClass: 'mt-status-chip mt-status-chip--expired',
50
+ },
51
+ Rejected: {
52
+ i18nKey: 'delegations.status.rejected',
53
+ styleClass: 'mt-status-chip mt-status-chip--rejected',
54
+ },
55
+ Cancelled: {
56
+ i18nKey: 'delegations.status.cancelled',
57
+ styleClass: 'mt-status-chip mt-status-chip--cancelled',
58
+ },
59
+ };
60
+ class DelegationStatusChip {
61
+ status = input.required(...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
62
+ visual = computed(() => STATUS_VISUAL[this.status()] ?? STATUS_VISUAL.Scheduled, ...(ngDevMode ? [{ debugName: "visual" }] : /* istanbul ignore next */ []));
63
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationStatusChip, deps: [], target: i0.ɵɵFactoryTarget.Component });
64
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.8", type: DelegationStatusChip, isStandalone: true, selector: "mt-delegation-status-chip", inputs: { status: { classPropertyName: "status", publicName: "status", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
65
+ <ng-container *transloco="let t">
66
+ @let v = visual();
67
+ <mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
68
+ </ng-container>
69
+ `, isInline: true, styles: [":host{display:inline-flex}:host ::ng-deep .mt-status-chip{font-weight:500;font-size:.75rem;border-radius:9999px;padding-inline:.625rem;padding-block:.125rem}:host ::ng-deep .mt-status-chip--active{background-color:#dcfce7;color:#166534}:host ::ng-deep .mt-status-chip--scheduled{background-color:#dbeafe;color:#1e40af}:host ::ng-deep .mt-status-chip--pending{background-color:#fef9c3;color:#854d0e}:host ::ng-deep .mt-status-chip--expired{background-color:#f3f4f6;color:#4b5563}:host ::ng-deep .mt-status-chip--rejected{background-color:#fee2e2;color:#991b1b}:host ::ng-deep .mt-status-chip--cancelled{background-color:#e5e7eb;color:#374151}:host-context(.dark) ::ng-deep .mt-status-chip--active{background-color:#22c55e33;color:#86efac}:host-context(.dark) ::ng-deep .mt-status-chip--scheduled{background-color:#3b82f633;color:#93c5fd}:host-context(.dark) ::ng-deep .mt-status-chip--pending{background-color:#eab30833;color:#fde047}:host-context(.dark) ::ng-deep .mt-status-chip--expired{background-color:#94a3b833;color:#cbd5e1}:host-context(.dark) ::ng-deep .mt-status-chip--rejected{background-color:#ef444433;color:#fca5a5}:host-context(.dark) ::ng-deep .mt-status-chip--cancelled{background-color:#94a3b826;color:#cbd5e1}\n"], dependencies: [{ kind: "component", type: Chip, selector: "mt-chip", inputs: ["label", "icon", "image", "removable", "removeIcon", "styleClass"], outputs: ["onRemove", "onImageError"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] });
70
+ }
71
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationStatusChip, decorators: [{
72
+ type: Component,
73
+ args: [{ selector: 'mt-delegation-status-chip', standalone: true, imports: [Chip, TranslocoDirective], template: `
74
+ <ng-container *transloco="let t">
75
+ @let v = visual();
76
+ <mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
77
+ </ng-container>
78
+ `, styles: [":host{display:inline-flex}:host ::ng-deep .mt-status-chip{font-weight:500;font-size:.75rem;border-radius:9999px;padding-inline:.625rem;padding-block:.125rem}:host ::ng-deep .mt-status-chip--active{background-color:#dcfce7;color:#166534}:host ::ng-deep .mt-status-chip--scheduled{background-color:#dbeafe;color:#1e40af}:host ::ng-deep .mt-status-chip--pending{background-color:#fef9c3;color:#854d0e}:host ::ng-deep .mt-status-chip--expired{background-color:#f3f4f6;color:#4b5563}:host ::ng-deep .mt-status-chip--rejected{background-color:#fee2e2;color:#991b1b}:host ::ng-deep .mt-status-chip--cancelled{background-color:#e5e7eb;color:#374151}:host-context(.dark) ::ng-deep .mt-status-chip--active{background-color:#22c55e33;color:#86efac}:host-context(.dark) ::ng-deep .mt-status-chip--scheduled{background-color:#3b82f633;color:#93c5fd}:host-context(.dark) ::ng-deep .mt-status-chip--pending{background-color:#eab30833;color:#fde047}:host-context(.dark) ::ng-deep .mt-status-chip--expired{background-color:#94a3b833;color:#cbd5e1}:host-context(.dark) ::ng-deep .mt-status-chip--rejected{background-color:#ef444433;color:#fca5a5}:host-context(.dark) ::ng-deep .mt-status-chip--cancelled{background-color:#94a3b826;color:#cbd5e1}\n"] }]
79
+ }], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: true }] }] } });
80
+
81
+ /** Fetch the active delegations the current user may start (user/activedelegations). */
82
+ class LoadDelegationCandidates {
83
+ static type = '[DelegationSession] Load Candidates';
84
+ }
85
+ /** Start a delegated session for the given assignment row. */
86
+ class StartDelegationSession {
87
+ delegation;
88
+ static type = '[DelegationSession] Start';
89
+ constructor(delegation) {
90
+ this.delegation = delegation;
91
+ }
92
+ }
93
+ /** End the current delegated session. Client-local only (doc 05). */
94
+ class EndDelegationSession {
95
+ reason;
96
+ static type = '[DelegationSession] End';
97
+ constructor(reason = 'Manual') {
98
+ this.reason = reason;
99
+ }
100
+ }
101
+ /** Switch directly from the current session to another delegation. */
102
+ class SwitchDelegationSession {
103
+ delegation;
104
+ static type = '[DelegationSession] Switch';
105
+ constructor(delegation) {
106
+ this.delegation = delegation;
107
+ }
108
+ }
109
+
110
+ var DelegationSessionActionKey;
111
+ (function (DelegationSessionActionKey) {
112
+ DelegationSessionActionKey["LoadCandidates"] = "loadCandidates";
113
+ DelegationSessionActionKey["StartSession"] = "startSession";
114
+ })(DelegationSessionActionKey || (DelegationSessionActionKey = {}));
115
+
116
+ var __decorate$1 = (this && this.__decorate) || function (decorators, target, key, desc) {
117
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
118
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
119
+ 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;
120
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
121
+ };
122
+ const BASE$1 = 'identity/delegations';
123
+ // Action shells matching gateway-auth type strings — loose coupling, matched by
124
+ // `type` only so this package does not depend on @masterteam/gateway-auth.
125
+ class GatewayLoginSuccessShell {
126
+ static type = '[Auth] Login Success';
127
+ }
128
+ class GatewayLogoutShell {
129
+ static type = '[Auth] Logout';
130
+ }
131
+ function canStart(row) {
132
+ return (row.effectiveStatus === 'Active' &&
133
+ row.allowedActions?.includes('StartSession'));
134
+ }
135
+ let DelegationSessionState = class DelegationSessionState {
136
+ http = inject(HttpClient);
137
+ actions$ = inject(Actions);
138
+ store = inject(Store);
139
+ constructor() {
140
+ this.actions$
141
+ .pipe(ofActionSuccessful(GatewayLoginSuccessShell))
142
+ .subscribe(() => this.store.dispatch(new LoadDelegationCandidates()));
143
+ this.actions$
144
+ .pipe(ofActionDispatched(GatewayLogoutShell))
145
+ .subscribe(() => this.store.dispatch(new EndDelegationSession('Logout')));
146
+ }
147
+ // ---------------------------------------------------------------------------
148
+ // Selectors
149
+ // ---------------------------------------------------------------------------
150
+ static getActive(state) {
151
+ return state.active;
152
+ }
153
+ static getCandidates(state) {
154
+ return state.candidates;
155
+ }
156
+ static isDelegated(state) {
157
+ return !!state.active;
158
+ }
159
+ static getLoadingActive(state) {
160
+ return state.loadingActive;
161
+ }
162
+ static getErrors(state) {
163
+ return state.errors;
164
+ }
165
+ // ---------------------------------------------------------------------------
166
+ // Actions
167
+ // ---------------------------------------------------------------------------
168
+ loadCandidates(ctx) {
169
+ const req$ = this.http.get(`${BASE$1}/user/activedelegations`);
170
+ return handleApiRequest({
171
+ ctx,
172
+ key: DelegationSessionActionKey.LoadCandidates,
173
+ request$: req$,
174
+ onSuccess: (response) => ({
175
+ candidates: (response.data?.items ?? []).filter(canStart),
176
+ }),
177
+ });
178
+ }
179
+ start(ctx, { delegation }) {
180
+ const req$ = this.http.post(`${BASE$1}/delegationToken/${delegation.delegationId}`, {});
181
+ return handleApiRequest({
182
+ ctx,
183
+ key: DelegationSessionActionKey.StartSession,
184
+ request$: req$,
185
+ onSuccess: (response) => ({
186
+ active: { token: response.data, delegation },
187
+ }),
188
+ });
189
+ }
190
+ end(ctx) {
191
+ // Client-local only — there is no server end-session endpoint (doc 05).
192
+ ctx.patchState({ active: null });
193
+ return EMPTY;
194
+ }
195
+ switch(ctx, { delegation }) {
196
+ return this.store
197
+ .dispatch(new EndDelegationSession('Manual'))
198
+ .pipe(switchMap(() => this.store.dispatch(new StartDelegationSession(delegation))));
199
+ }
200
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionState, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
201
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionState });
202
+ };
203
+ __decorate$1([
204
+ Action(LoadDelegationCandidates)
205
+ ], DelegationSessionState.prototype, "loadCandidates", null);
206
+ __decorate$1([
207
+ Action(StartDelegationSession)
208
+ ], DelegationSessionState.prototype, "start", null);
209
+ __decorate$1([
210
+ Action(EndDelegationSession)
211
+ ], DelegationSessionState.prototype, "end", null);
212
+ __decorate$1([
213
+ Action(SwitchDelegationSession)
214
+ ], DelegationSessionState.prototype, "switch", null);
215
+ __decorate$1([
216
+ Selector()
217
+ ], DelegationSessionState, "getActive", null);
218
+ __decorate$1([
219
+ Selector()
220
+ ], DelegationSessionState, "getCandidates", null);
221
+ __decorate$1([
222
+ Selector()
223
+ ], DelegationSessionState, "isDelegated", null);
224
+ __decorate$1([
225
+ Selector()
226
+ ], DelegationSessionState, "getLoadingActive", null);
227
+ __decorate$1([
228
+ Selector()
229
+ ], DelegationSessionState, "getErrors", null);
230
+ DelegationSessionState = __decorate$1([
231
+ State({
232
+ name: 'delegationSession',
233
+ defaults: {
234
+ active: null,
235
+ candidates: [],
236
+ loadingActive: [],
237
+ errors: {},
238
+ },
239
+ })
240
+ ], DelegationSessionState);
241
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionState, decorators: [{
242
+ type: Injectable
243
+ }], ctorParameters: () => [], propDecorators: { loadCandidates: [], start: [], end: [], switch: [] } });
244
+
245
+ class DelegationSessionFacade {
246
+ store = inject(Store);
247
+ // ---------------------------------------------------------------------------
248
+ // Data slices
249
+ // ---------------------------------------------------------------------------
250
+ active = select(DelegationSessionState.getActive);
251
+ candidates = select(DelegationSessionState.getCandidates);
252
+ isDelegated = select(DelegationSessionState.isDelegated);
253
+ loadingActive = select(DelegationSessionState.getLoadingActive);
254
+ // ---------------------------------------------------------------------------
255
+ // Derived (interceptor + topbar)
256
+ // ---------------------------------------------------------------------------
257
+ /** Raw delegation token for the `app-delegation` header. */
258
+ token = computed(() => this.active()?.token ?? null, ...(ngDevMode ? [{ debugName: "token" }] : /* istanbul ignore next */ []));
259
+ /** On-behalf-of (delegator). */
260
+ onBehalfOf = computed(() => this.active()?.delegation.delegator ?? null, ...(ngDevMode ? [{ debugName: "onBehalfOf" }] : /* istanbul ignore next */ []));
261
+ /** Executed-by (actual logged-in / delegated user). */
262
+ executedBy = computed(() => this.active()?.delegation.delegatedUser ?? null, ...(ngDevMode ? [{ debugName: "executedBy" }] : /* istanbul ignore next */ []));
263
+ hasCandidates = computed(() => this.candidates().length > 0, ...(ngDevMode ? [{ debugName: "hasCandidates" }] : /* istanbul ignore next */ []));
264
+ isStarting = computed(() => this.loadingActive().includes(DelegationSessionActionKey.StartSession), ...(ngDevMode ? [{ debugName: "isStarting" }] : /* istanbul ignore next */ []));
265
+ isLoadingCandidates = computed(() => this.loadingActive().includes(DelegationSessionActionKey.LoadCandidates), ...(ngDevMode ? [{ debugName: "isLoadingCandidates" }] : /* istanbul ignore next */ []));
266
+ // ---------------------------------------------------------------------------
267
+ // Dispatchers
268
+ // ---------------------------------------------------------------------------
269
+ loadCandidates() {
270
+ return this.store.dispatch(new LoadDelegationCandidates());
271
+ }
272
+ startSession(delegation) {
273
+ return this.store.dispatch(new StartDelegationSession(delegation));
274
+ }
275
+ switchSession(delegation) {
276
+ return this.store.dispatch(new SwitchDelegationSession(delegation));
277
+ }
278
+ endSession(reason = 'Manual') {
279
+ return this.store.dispatch(new EndDelegationSession(reason));
280
+ }
281
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionFacade, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
282
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionFacade, providedIn: 'root' });
283
+ }
284
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionFacade, decorators: [{
285
+ type: Injectable,
286
+ args: [{ providedIn: 'root' }]
287
+ }] });
288
+
289
+ /**
290
+ * Confirmation dialog before starting (or switching to) a delegated session.
291
+ * Calls the facade, which POSTs to `delegationToken/{id}` and stores the raw
292
+ * token in memory only.
293
+ */
294
+ class StartSessionDialog {
295
+ delegation = input.required(...(ngDevMode ? [{ debugName: "delegation" }] : /* istanbul ignore next */ []));
296
+ intent = input('start', ...(ngDevMode ? [{ debugName: "intent" }] : /* istanbul ignore next */ []));
297
+ ref = inject(ModalRef);
298
+ facade = inject(DelegationSessionFacade);
299
+ isBusy = this.facade.isStarting;
300
+ bodyKey = computed(() => this.intent() === 'switch'
301
+ ? 'delegations.session.switchConfirmBody'
302
+ : 'delegations.session.startConfirmBody', ...(ngDevMode ? [{ debugName: "bodyKey" }] : /* istanbul ignore next */ []));
303
+ confirm() {
304
+ const row = this.delegation();
305
+ const op$ = this.intent() === 'switch'
306
+ ? this.facade.switchSession(row)
307
+ : this.facade.startSession(row);
308
+ op$.subscribe({
309
+ next: () => this.ref.close(true),
310
+ error: () => {
311
+ /* error surfaced via shared toast pipeline; keep dialog open */
312
+ },
313
+ });
314
+ }
315
+ cancel() {
316
+ this.ref.close(false);
317
+ }
318
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: StartSessionDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
319
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: StartSessionDialog, isStandalone: true, selector: "mt-start-session-dialog", inputs: { delegation: { classPropertyName: "delegation", publicName: "delegation", isSignal: true, isRequired: true, transformFunction: null }, intent: { classPropertyName: "intent", publicName: "intent", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col gap-4 p-2\">\n <div class=\"flex items-start gap-3\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-12! h-12! text-lg! bg-surface-200!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <div class=\"text-base font-medium text-gray-900 truncate\">\n {{ delegation().delegator.displayName }}\n </div>\n @if (delegation().delegator.email) {\n <div class=\"text-xs text-gray-500 truncate\">\n {{ delegation().delegator.email }}\n </div>\n }\n @if (delegation().scopeSummary) {\n <div class=\"text-xs text-gray-500 mt-1\">\n {{ t(\"delegations.column.scopeSummary\") }}:\n {{ delegation().scopeSummary }}\n </div>\n }\n </div>\n </div>\n\n <p class=\"text-sm text-gray-600 leading-relaxed\">\n {{ t(bodyKey()) }}\n </p>\n\n <div class=\"flex justify-end gap-2 pt-2 border-t border-gray-200\">\n <mt-button\n variant=\"outlined\"\n color=\"secondary\"\n [label]=\"t('delegations.common.cancel')\"\n [disabled]=\"isBusy()\"\n (click)=\"cancel()\"\n ></mt-button>\n <mt-button\n color=\"primary\"\n icon=\"user.users-check\"\n [label]=\"t('delegations.action.startSession')\"\n [loading]=\"isBusy()\"\n (click)=\"confirm()\"\n ></mt-button>\n </div>\n </div>\n</ng-container>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
320
+ }
321
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: StartSessionDialog, decorators: [{
322
+ type: Component,
323
+ args: [{ selector: 'mt-start-session-dialog', standalone: true, imports: [CommonModule, Avatar, Button, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col gap-4 p-2\">\n <div class=\"flex items-start gap-3\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-12! h-12! text-lg! bg-surface-200!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <div class=\"text-base font-medium text-gray-900 truncate\">\n {{ delegation().delegator.displayName }}\n </div>\n @if (delegation().delegator.email) {\n <div class=\"text-xs text-gray-500 truncate\">\n {{ delegation().delegator.email }}\n </div>\n }\n @if (delegation().scopeSummary) {\n <div class=\"text-xs text-gray-500 mt-1\">\n {{ t(\"delegations.column.scopeSummary\") }}:\n {{ delegation().scopeSummary }}\n </div>\n }\n </div>\n </div>\n\n <p class=\"text-sm text-gray-600 leading-relaxed\">\n {{ t(bodyKey()) }}\n </p>\n\n <div class=\"flex justify-end gap-2 pt-2 border-t border-gray-200\">\n <mt-button\n variant=\"outlined\"\n color=\"secondary\"\n [label]=\"t('delegations.common.cancel')\"\n [disabled]=\"isBusy()\"\n (click)=\"cancel()\"\n ></mt-button>\n <mt-button\n color=\"primary\"\n icon=\"user.users-check\"\n [label]=\"t('delegations.action.startSession')\"\n [loading]=\"isBusy()\"\n (click)=\"confirm()\"\n ></mt-button>\n </div>\n </div>\n</ng-container>\n" }]
324
+ }], propDecorators: { delegation: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegation", required: true }] }], intent: [{ type: i0.Input, args: [{ isSignal: true, alias: "intent", required: false }] }] } });
325
+
326
+ /**
327
+ * Topbar surface for the delegation runtime (doc 05, 09).
328
+ *
329
+ * State A — no candidates, no active session: renders nothing.
330
+ * State B — candidates exist, no active session: lets the user start a session.
331
+ * State C — active delegated session: shows "Acting on behalf of {delegator}" +
332
+ * "Executed by {actual user}", plus Back / Switch.
333
+ */
334
+ class TopbarDelegationMenu {
335
+ /** Path to the management page, e.g. `/control-panel/delegations` or `/delegations`. */
336
+ managePath = input('/delegations', ...(ngDevMode ? [{ debugName: "managePath" }] : /* istanbul ignore next */ []));
337
+ compact = input(false, ...(ngDevMode ? [{ debugName: "compact" }] : /* istanbul ignore next */ []));
338
+ facade = inject(DelegationSessionFacade);
339
+ modal = inject(ModalService);
340
+ transloco = inject(TranslocoService);
341
+ popover = viewChild.required('popover');
342
+ active = this.facade.active;
343
+ candidates = this.facade.candidates;
344
+ hasCandidates = this.facade.hasCandidates;
345
+ onBehalfOf = this.facade.onBehalfOf;
346
+ executedBy = this.facade.executedBy;
347
+ mode = computed(() => {
348
+ if (this.active())
349
+ return 'active';
350
+ if (this.hasCandidates())
351
+ return 'candidates';
352
+ return 'hidden';
353
+ }, ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
354
+ togglePopover(event) {
355
+ this.popover().toggle(event);
356
+ }
357
+ start(row, event) {
358
+ event.stopPropagation();
359
+ this.popover().hide();
360
+ this.openConfirm(row, 'start');
361
+ }
362
+ switchTo(row, event) {
363
+ event.stopPropagation();
364
+ this.popover().hide();
365
+ this.openConfirm(row, 'switch');
366
+ }
367
+ endSession(event) {
368
+ event.stopPropagation();
369
+ this.popover().hide();
370
+ this.facade.endSession('Manual').subscribe();
371
+ }
372
+ openConfirm(row, intent) {
373
+ this.modal.openModal(StartSessionDialog, 'dialog', {
374
+ header: this.transloco.translate('delegations.action.startSession'),
375
+ styleClass: '!w-[min(96vw,28rem)] !max-w-[96vw]',
376
+ dismissableMask: true,
377
+ dismissible: true,
378
+ inputValues: { delegation: row, intent },
379
+ });
380
+ }
381
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: TopbarDelegationMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
382
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: TopbarDelegationMenu, isStandalone: true, selector: "mt-topbar-delegation-menu", inputs: { managePath: { classPropertyName: "managePath", publicName: "managePath", isSignal: true, isRequired: false, transformFunction: null }, compact: { classPropertyName: "compact", publicName: "compact", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "popover", first: true, predicate: ["popover"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t\">\n @if (mode() !== \"hidden\") {\n <div class=\"mt-delegation-trigger flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"mt-delegation-trigger__button flex items-center gap-2 px-2 py-1 rounded-full cursor-pointer hover:bg-surface-100\"\n [attr.aria-label]=\"t('delegations.title')\"\n (click)=\"togglePopover($event)\"\n >\n @if (mode() === \"active\") {\n <mt-icon\n icon=\"user.users-check\"\n styleClass=\"text-lg text-primary\"\n ></mt-icon>\n @if (!compact()) {\n <span class=\"mt-delegation-trigger__label text-sm font-medium\">\n {{\n t(\"delegations.session.banner\", {\n delegatorName: onBehalfOf()?.displayName,\n })\n }}\n </span>\n }\n <mt-delegation-status-chip\n status=\"Active\"\n ></mt-delegation-status-chip>\n } @else {\n <mt-icon\n icon=\"user.users-plus\"\n styleClass=\"text-lg text-primary\"\n ></mt-icon>\n @if (!compact()) {\n <span class=\"mt-delegation-trigger__label text-sm font-medium\">\n {{ t(\"delegations.session.availableTitle\") }}\n </span>\n }\n }\n </button>\n </div>\n\n <p-popover #popover [pt]=\"{ content: { class: 'p-0!' } }\">\n <div class=\"flex flex-col min-w-80 max-w-96 p-0\">\n @if (mode() === \"active\" && active(); as session) {\n <!-- STATE C \u2014 active session -->\n <div\n class=\"flex items-start gap-3 border-b border-gray-200 px-4 py-4\"\n >\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-11! h-11! text-xl! text-gray-600! bg-surface-300!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0 flex-1\">\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\n </div>\n <div class=\"text-sm font-medium text-gray-900 truncate\">\n {{ onBehalfOf()?.displayName }}\n </div>\n <div class=\"text-xs text-gray-500 mt-1\">\n {{\n t(\"delegations.session.executedBy\", {\n actualUserName: executedBy()?.displayName,\n })\n }}\n </div>\n @if (session.delegation.scopeSummary) {\n <div\n class=\"text-xs text-gray-500 mt-1 truncate\"\n [title]=\"session.delegation.scopeSummary\"\n >\n {{ t(\"delegations.column.scopeSummary\") }}:\n {{ session.delegation.scopeSummary }}\n </div>\n }\n </div>\n </div>\n\n <div class=\"flex flex-col gap-1 px-2 py-2\">\n <mt-button\n variant=\"text\"\n icon=\"arrow.arrow-left\"\n [label]=\"t('delegations.action.endSession')\"\n styleClass=\"w-full justify-start\"\n (click)=\"endSession($event)\"\n ></mt-button>\n </div>\n\n @if (candidates().length > 1) {\n <div class=\"border-t border-gray-200 pt-2 px-2 pb-2\">\n <div class=\"text-xs text-gray-500 px-2 pb-1\">\n {{ t(\"delegations.session.switchToAnother\") }}\n </div>\n @for (cand of candidates(); track cand.delegationId) {\n @if (cand.delegationId !== session.delegation.delegationId) {\n <button\n type=\"button\"\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer\"\n (click)=\"switchTo(cand, $event)\"\n >\n <div class=\"flex items-center gap-2 min-w-0\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-7! h-7! text-sm! bg-surface-200!\"\n ></mt-avatar>\n <span class=\"text-sm text-gray-900 truncate\">\n {{ cand.delegator.displayName }}\n </span>\n </div>\n <mt-icon\n icon=\"arrow.switch-horizontal-01\"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n </button>\n }\n }\n </div>\n }\n } @else if (mode() === \"candidates\") {\n <!-- STATE B \u2014 candidates available -->\n <div class=\"border-b border-gray-200 px-4 py-3\">\n <div class=\"text-sm font-medium text-gray-900\">\n {{ t(\"delegations.session.youCanActAs\") }}\n </div>\n <div class=\"text-xs text-gray-500 mt-1\">\n {{ t(\"delegations.session.chooseDelegatorHint\") }}\n </div>\n </div>\n <div class=\"flex flex-col gap-1 px-2 py-2 max-h-72 overflow-y-auto\">\n @for (cand of candidates(); track cand.delegationId) {\n <button\n type=\"button\"\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer text-start\"\n (click)=\"start(cand, $event)\"\n >\n <div class=\"flex items-center gap-2 min-w-0 flex-1\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-9! h-9! text-base! bg-surface-200!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <span class=\"text-sm font-medium text-gray-900 truncate\">\n {{ cand.delegator.displayName }}\n </span>\n @if (cand.scopeSummary) {\n <span\n class=\"text-xs text-gray-500 truncate\"\n [title]=\"cand.scopeSummary\"\n >\n {{ cand.scopeSummary }}\n </span>\n }\n </div>\n </div>\n <mt-icon\n icon=\"arrow.arrow-right\"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n </button>\n }\n </div>\n }\n\n <!-- Footer -->\n <div class=\"border-t border-gray-200 px-2 py-2\">\n <a\n [routerLink]=\"managePath()\"\n (click)=\"popover().hide()\"\n class=\"flex items-center gap-2 px-3 py-2 rounded-md text-gray-700 hover:bg-surface-100 text-sm cursor-pointer\"\n >\n <mt-icon\n icon=\"general.settings-02\"\n styleClass=\"text-base\"\n ></mt-icon>\n <span>{{ t(\"delegations.session.manage\") }}</span>\n </a>\n </div>\n </div>\n </p-popover>\n }\n</ng-container>\n", styles: [":host{display:inline-flex;align-items:center}.mt-delegation-trigger__button{transition:background-color .2s ease-out}.mt-delegation-trigger__label{max-width:16rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host-context(.dark) .mt-delegation-trigger__button:hover{background-color:#94a3b826}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: Popover, selector: "p-popover", inputs: ["ariaLabel", "ariaLabelledBy", "dismissable", "style", "styleClass", "appendTo", "autoZIndex", "ariaCloseLabel", "baseZIndex", "focusOnShow", "showTransitionOptions", "hideTransitionOptions", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationStatusChip, selector: "mt-delegation-status-chip", inputs: ["status"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
383
+ }
384
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: TopbarDelegationMenu, decorators: [{
385
+ type: Component,
386
+ args: [{ selector: 'mt-topbar-delegation-menu', standalone: true, imports: [
387
+ CommonModule,
388
+ Avatar,
389
+ Button,
390
+ Icon,
391
+ Popover,
392
+ RouterLink,
393
+ TranslocoDirective,
394
+ DelegationStatusChip,
395
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n @if (mode() !== \"hidden\") {\n <div class=\"mt-delegation-trigger flex items-center gap-2\">\n <button\n type=\"button\"\n class=\"mt-delegation-trigger__button flex items-center gap-2 px-2 py-1 rounded-full cursor-pointer hover:bg-surface-100\"\n [attr.aria-label]=\"t('delegations.title')\"\n (click)=\"togglePopover($event)\"\n >\n @if (mode() === \"active\") {\n <mt-icon\n icon=\"user.users-check\"\n styleClass=\"text-lg text-primary\"\n ></mt-icon>\n @if (!compact()) {\n <span class=\"mt-delegation-trigger__label text-sm font-medium\">\n {{\n t(\"delegations.session.banner\", {\n delegatorName: onBehalfOf()?.displayName,\n })\n }}\n </span>\n }\n <mt-delegation-status-chip\n status=\"Active\"\n ></mt-delegation-status-chip>\n } @else {\n <mt-icon\n icon=\"user.users-plus\"\n styleClass=\"text-lg text-primary\"\n ></mt-icon>\n @if (!compact()) {\n <span class=\"mt-delegation-trigger__label text-sm font-medium\">\n {{ t(\"delegations.session.availableTitle\") }}\n </span>\n }\n }\n </button>\n </div>\n\n <p-popover #popover [pt]=\"{ content: { class: 'p-0!' } }\">\n <div class=\"flex flex-col min-w-80 max-w-96 p-0\">\n @if (mode() === \"active\" && active(); as session) {\n <!-- STATE C \u2014 active session -->\n <div\n class=\"flex items-start gap-3 border-b border-gray-200 px-4 py-4\"\n >\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-11! h-11! text-xl! text-gray-600! bg-surface-300!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0 flex-1\">\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\n </div>\n <div class=\"text-sm font-medium text-gray-900 truncate\">\n {{ onBehalfOf()?.displayName }}\n </div>\n <div class=\"text-xs text-gray-500 mt-1\">\n {{\n t(\"delegations.session.executedBy\", {\n actualUserName: executedBy()?.displayName,\n })\n }}\n </div>\n @if (session.delegation.scopeSummary) {\n <div\n class=\"text-xs text-gray-500 mt-1 truncate\"\n [title]=\"session.delegation.scopeSummary\"\n >\n {{ t(\"delegations.column.scopeSummary\") }}:\n {{ session.delegation.scopeSummary }}\n </div>\n }\n </div>\n </div>\n\n <div class=\"flex flex-col gap-1 px-2 py-2\">\n <mt-button\n variant=\"text\"\n icon=\"arrow.arrow-left\"\n [label]=\"t('delegations.action.endSession')\"\n styleClass=\"w-full justify-start\"\n (click)=\"endSession($event)\"\n ></mt-button>\n </div>\n\n @if (candidates().length > 1) {\n <div class=\"border-t border-gray-200 pt-2 px-2 pb-2\">\n <div class=\"text-xs text-gray-500 px-2 pb-1\">\n {{ t(\"delegations.session.switchToAnother\") }}\n </div>\n @for (cand of candidates(); track cand.delegationId) {\n @if (cand.delegationId !== session.delegation.delegationId) {\n <button\n type=\"button\"\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer\"\n (click)=\"switchTo(cand, $event)\"\n >\n <div class=\"flex items-center gap-2 min-w-0\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-7! h-7! text-sm! bg-surface-200!\"\n ></mt-avatar>\n <span class=\"text-sm text-gray-900 truncate\">\n {{ cand.delegator.displayName }}\n </span>\n </div>\n <mt-icon\n icon=\"arrow.switch-horizontal-01\"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n </button>\n }\n }\n </div>\n }\n } @else if (mode() === \"candidates\") {\n <!-- STATE B \u2014 candidates available -->\n <div class=\"border-b border-gray-200 px-4 py-3\">\n <div class=\"text-sm font-medium text-gray-900\">\n {{ t(\"delegations.session.youCanActAs\") }}\n </div>\n <div class=\"text-xs text-gray-500 mt-1\">\n {{ t(\"delegations.session.chooseDelegatorHint\") }}\n </div>\n </div>\n <div class=\"flex flex-col gap-1 px-2 py-2 max-h-72 overflow-y-auto\">\n @for (cand of candidates(); track cand.delegationId) {\n <button\n type=\"button\"\n class=\"flex items-center justify-between gap-2 w-full px-2 py-2 rounded-md hover:bg-surface-100 cursor-pointer text-start\"\n (click)=\"start(cand, $event)\"\n >\n <div class=\"flex items-center gap-2 min-w-0 flex-1\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-9! h-9! text-base! bg-surface-200!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <span class=\"text-sm font-medium text-gray-900 truncate\">\n {{ cand.delegator.displayName }}\n </span>\n @if (cand.scopeSummary) {\n <span\n class=\"text-xs text-gray-500 truncate\"\n [title]=\"cand.scopeSummary\"\n >\n {{ cand.scopeSummary }}\n </span>\n }\n </div>\n </div>\n <mt-icon\n icon=\"arrow.arrow-right\"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n </button>\n }\n </div>\n }\n\n <!-- Footer -->\n <div class=\"border-t border-gray-200 px-2 py-2\">\n <a\n [routerLink]=\"managePath()\"\n (click)=\"popover().hide()\"\n class=\"flex items-center gap-2 px-3 py-2 rounded-md text-gray-700 hover:bg-surface-100 text-sm cursor-pointer\"\n >\n <mt-icon\n icon=\"general.settings-02\"\n styleClass=\"text-base\"\n ></mt-icon>\n <span>{{ t(\"delegations.session.manage\") }}</span>\n </a>\n </div>\n </div>\n </p-popover>\n }\n</ng-container>\n", styles: [":host{display:inline-flex;align-items:center}.mt-delegation-trigger__button{transition:background-color .2s ease-out}.mt-delegation-trigger__label{max-width:16rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}:host-context(.dark) .mt-delegation-trigger__button:hover{background-color:#94a3b826}\n"] }]
396
+ }], propDecorators: { managePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "managePath", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], popover: [{ type: i0.ViewChild, args: ['popover', { isSignal: true }] }] } });
24
397
 
25
398
  class Delegations {
26
399
  router = inject(Router);
@@ -28,79 +401,207 @@ class Delegations {
28
401
  this.router.navigate(['control-panel/product-settings']);
29
402
  }
30
403
  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"] }] });
404
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.8", type: Delegations, isStandalone: true, selector: "mt-delegations", ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <mt-page\n [title]=\"t('delegations.title')\"\n [avatarIcon]=\"'custom.hierarchy-structure'\"\n [contentClass]=\"'max-[1025px]:p-4 max-[640px]:p-3'\"\n [avatarStyle]=\"{\n '--p-avatar-background': 'var(--p-indigo-50)',\n '--p-avatar-color': 'var(--p-indigo-700)',\n }\"\n (backButtonClick)=\"goBack()\"\n backButton\n >\n <router-outlet />\n </mt-page>\n</ng-container>\n", styles: [""], dependencies: [{ kind: "component", type: Page, selector: "mt-page", inputs: ["backButton", "backButtonIcon", "avatarIcon", "avatarStyle", "avatarShape", "title", "tabs", "activeTab", "contentClass", "contentId"], outputs: ["backButtonClick", "tabChange"] }, { kind: "directive", type: RouterOutlet, selector: "router-outlet", inputs: ["name", "routerOutletData"], outputs: ["activate", "deactivate", "attach", "detach"], exportAs: ["outlet"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] });
32
405
  }
33
406
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: Delegations, decorators: [{
34
407
  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" }]
408
+ args: [{ selector: 'mt-delegations', imports: [Page, RouterOutlet, TranslocoDirective], template: "<ng-container *transloco=\"let t\">\n <mt-page\n [title]=\"t('delegations.title')\"\n [avatarIcon]=\"'custom.hierarchy-structure'\"\n [contentClass]=\"'max-[1025px]:p-4 max-[640px]:p-3'\"\n [avatarStyle]=\"{\n '--p-avatar-background': 'var(--p-indigo-50)',\n '--p-avatar-color': 'var(--p-indigo-700)',\n }\"\n (backButtonClick)=\"goBack()\"\n backButton\n >\n <router-outlet />\n </mt-page>\n</ng-container>\n" }]
36
409
  }] });
37
410
 
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';
411
+ // ---------------------------------------------------------------------------
412
+ // Lists / detail
413
+ // ---------------------------------------------------------------------------
414
+ class GetMyDelegations {
415
+ query;
416
+ static type = '[Delegations] Get My Delegations';
417
+ constructor(query = {}) {
418
+ this.query = query;
419
+ }
49
420
  }
50
- class AddDelegation {
51
- delegation;
52
- static type = '[Delegations] Add Delegation';
53
- constructor(delegation) {
54
- this.delegation = delegation;
421
+ class GetAssignedDelegations {
422
+ query;
423
+ static type = '[Delegations] Get Assigned Delegations';
424
+ constructor(query = {}) {
425
+ this.query = query;
55
426
  }
56
427
  }
57
- class UpdateDelegation {
428
+ class GetActiveAssignedDelegations {
429
+ query;
430
+ static type = '[Delegations] Get Active Assigned Delegations';
431
+ constructor(query = {}) {
432
+ this.query = query;
433
+ }
434
+ }
435
+ class GetDelegationDetail {
58
436
  id;
59
- delegation;
60
- static type = '[Delegations] Update Delegation';
61
- constructor(id, delegation) {
437
+ static type = '[Delegations] Get Delegation Detail';
438
+ constructor(id) {
62
439
  this.id = id;
63
- this.delegation = delegation;
64
440
  }
65
441
  }
66
- class DeleteDelegation {
442
+ class ClearDelegationDetail {
443
+ static type = '[Delegations] Clear Delegation Detail';
444
+ }
445
+ // ---------------------------------------------------------------------------
446
+ // Scope
447
+ // ---------------------------------------------------------------------------
448
+ class GetScopeOptions {
449
+ delegatorUserId;
450
+ static type = '[Delegations] Get Scope Options';
451
+ constructor(delegatorUserId) {
452
+ this.delegatorUserId = delegatorUserId;
453
+ }
454
+ }
455
+ class PreviewScope {
456
+ scope;
457
+ static type = '[Delegations] Preview Scope';
458
+ constructor(scope) {
459
+ this.scope = scope;
460
+ }
461
+ }
462
+ class ClearScopePreview {
463
+ static type = '[Delegations] Clear Scope Preview';
464
+ }
465
+ // ---------------------------------------------------------------------------
466
+ // Create / edit (legacy + v2)
467
+ // ---------------------------------------------------------------------------
468
+ class CreateDelegationLegacy {
469
+ request;
470
+ static type = '[Delegations] Create Delegation (legacy)';
471
+ constructor(request) {
472
+ this.request = request;
473
+ }
474
+ }
475
+ class CreateDelegationV2 {
476
+ request;
477
+ static type = '[Delegations] Create Delegation (v2)';
478
+ constructor(request) {
479
+ this.request = request;
480
+ }
481
+ }
482
+ class UpdateDelegationLegacy {
67
483
  id;
68
- static type = '[Delegations] Delete Delegation';
69
- constructor(id) {
484
+ request;
485
+ static type = '[Delegations] Update Delegation (legacy)';
486
+ constructor(id, request) {
70
487
  this.id = id;
488
+ this.request = request;
71
489
  }
72
490
  }
73
- class GetDelegation {
491
+ class UpdateDelegationV2 {
74
492
  id;
75
- static type = '[Delegations] Get Delegation';
76
- constructor(id) {
493
+ request;
494
+ static type = '[Delegations] Update Delegation (v2)';
495
+ constructor(id, request) {
77
496
  this.id = id;
497
+ this.request = request;
78
498
  }
79
499
  }
80
- class ClearSelectedDelegation {
81
- static type = '[Delegations] Clear Selected Delegation';
500
+ // ---------------------------------------------------------------------------
501
+ // Decisions
502
+ // ---------------------------------------------------------------------------
503
+ class ApproveDelegation {
504
+ id;
505
+ request;
506
+ static type = '[Delegations] Approve Delegation';
507
+ constructor(id, request) {
508
+ this.id = id;
509
+ this.request = request;
510
+ }
511
+ }
512
+ class RejectDelegation {
513
+ id;
514
+ request;
515
+ static type = '[Delegations] Reject Delegation';
516
+ constructor(id, request) {
517
+ this.id = id;
518
+ this.request = request;
519
+ }
520
+ }
521
+ class CancelDelegation {
522
+ id;
523
+ rowVersion;
524
+ asAdmin;
525
+ static type = '[Delegations] Cancel Delegation';
526
+ constructor(id, rowVersion, asAdmin = false) {
527
+ this.id = id;
528
+ this.rowVersion = rowVersion;
529
+ this.asAdmin = asAdmin;
530
+ }
82
531
  }
83
532
 
533
+ // ============================================================================
534
+ // NGXS state shape + action keys
535
+ // ============================================================================
536
+ var DelegationsActionKey;
537
+ (function (DelegationsActionKey) {
538
+ DelegationsActionKey["GetMy"] = "getMy";
539
+ DelegationsActionKey["GetAssigned"] = "getAssigned";
540
+ DelegationsActionKey["GetActive"] = "getActive";
541
+ DelegationsActionKey["GetDetail"] = "getDetail";
542
+ DelegationsActionKey["GetScopeOptions"] = "getScopeOptions";
543
+ DelegationsActionKey["PreviewScope"] = "previewScope";
544
+ DelegationsActionKey["Create"] = "create";
545
+ DelegationsActionKey["Update"] = "update";
546
+ DelegationsActionKey["Approve"] = "approve";
547
+ DelegationsActionKey["Reject"] = "reject";
548
+ DelegationsActionKey["Cancel"] = "cancel";
549
+ })(DelegationsActionKey || (DelegationsActionKey = {}));
550
+
84
551
  var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
85
552
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
86
553
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
87
554
  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
555
  return c > 3 && r && Object.defineProperty(target, key, r), r;
89
556
  };
90
- let DelegationsState = class DelegationsState extends CrudStateBase {
557
+ const BASE = 'identity/delegations';
558
+ function buildListParams(query) {
559
+ let params = new HttpParams();
560
+ if (query.activeOnly != null)
561
+ params = params.set('activeOnly', query.activeOnly);
562
+ if (query.status)
563
+ params = params.set('status', query.status);
564
+ if (query.search)
565
+ params = params.set('search', query.search);
566
+ if (query.fromUtc)
567
+ params = params.set('fromUtc', query.fromUtc);
568
+ if (query.toUtc)
569
+ params = params.set('toUtc', query.toUtc);
570
+ if (query.sortBy)
571
+ params = params.set('sortBy', query.sortBy);
572
+ if (query.sortDirection)
573
+ params = params.set('sortDirection', query.sortDirection);
574
+ params = params.set('page', query.page ?? 1);
575
+ params = params.set('pageSize', query.pageSize ?? 25);
576
+ if (query.delegatorUserId)
577
+ params = params.set('delegatorUserId', query.delegatorUserId);
578
+ if (query.delegatedUserId)
579
+ params = params.set('delegatedUserId', query.delegatedUserId);
580
+ return params;
581
+ }
582
+ let DelegationsState = class DelegationsState {
91
583
  http = inject(HttpClient);
92
584
  // ============================================================================
93
- // Selectors - Individual data selectors for fine-grained reactivity
585
+ // Selectors
94
586
  // ============================================================================
95
- static getAllDelegations(state) {
96
- return state.allDelegations;
587
+ static getMy(state) {
588
+ return state.my;
97
589
  }
98
- static getSelectedDelegation(state) {
99
- return state.selectedDelegation;
590
+ static getAssigned(state) {
591
+ return state.assigned;
592
+ }
593
+ static getActive(state) {
594
+ return state.active;
595
+ }
596
+ static getDetail(state) {
597
+ return state.detail;
598
+ }
599
+ static getScopeOptions(state) {
600
+ return state.scopeOptions;
601
+ }
602
+ static getScopePreview(state) {
603
+ return state.scopePreview;
100
604
  }
101
- // ============================================================================
102
- // Loading/Error Slice Selectors - REQUIRED for optimal performance
103
- // ============================================================================
104
605
  static getLoadingActive(state) {
105
606
  return state.loadingActive;
106
607
  }
@@ -108,88 +609,214 @@ let DelegationsState = class DelegationsState extends CrudStateBase {
108
609
  return state.errors;
109
610
  }
110
611
  // ============================================================================
111
- // CRUD Actions
612
+ // Lists
112
613
  // ============================================================================
113
- getDelegations(ctx) {
114
- const req$ = this.http.get('identity/delegations/all');
614
+ getMy(ctx, { query }) {
615
+ const req$ = this.http.get(BASE, {
616
+ params: buildListParams(query),
617
+ });
115
618
  return handleApiRequest({
116
619
  ctx,
117
- key: DelegationsActionKey.GetDelegations,
620
+ key: DelegationsActionKey.GetMy,
118
621
  request$: req$,
119
- onSuccess: (response) => ({
120
- allDelegations: response.data ?? [],
121
- }),
622
+ onSuccess: (response) => ({ my: response.data }),
122
623
  });
123
624
  }
124
- getDelegation(ctx, { id }) {
125
- const req$ = this.http.get(`identity/delegations/${id}`);
625
+ getAssigned(ctx, { query }) {
626
+ const req$ = this.http.get(`${BASE}/assigned`, { params: buildListParams(query) });
126
627
  return handleApiRequest({
127
628
  ctx,
128
- key: DelegationsActionKey.GetDelegationForm,
629
+ key: DelegationsActionKey.GetAssigned,
129
630
  request$: req$,
130
- onSuccess: (response) => ({
131
- selectedDelegation: response.data ?? null,
132
- }),
631
+ onSuccess: (response) => ({ assigned: response.data }),
632
+ });
633
+ }
634
+ getActive(ctx, { query }) {
635
+ const req$ = this.http.get(`${BASE}/user/activedelegations`, { params: buildListParams(query) });
636
+ return handleApiRequest({
637
+ ctx,
638
+ key: DelegationsActionKey.GetActive,
639
+ request$: req$,
640
+ onSuccess: (response) => ({ active: response.data }),
641
+ });
642
+ }
643
+ getDetail(ctx, { id }) {
644
+ const req$ = this.http.get(`${BASE}/${id}`);
645
+ return handleApiRequest({
646
+ ctx,
647
+ key: DelegationsActionKey.GetDetail,
648
+ request$: req$,
649
+ onSuccess: (response) => ({ detail: response.data ?? null }),
650
+ });
651
+ }
652
+ clearDetail(ctx) {
653
+ ctx.patchState({ detail: null });
654
+ }
655
+ // ============================================================================
656
+ // Scope
657
+ // ============================================================================
658
+ getScopeOptions(ctx, { delegatorUserId }) {
659
+ let params = new HttpParams();
660
+ if (delegatorUserId)
661
+ params = params.set('delegatorUserId', delegatorUserId);
662
+ const req$ = this.http.get(`${BASE}/scope/options`, { params });
663
+ return handleApiRequest({
664
+ ctx,
665
+ key: DelegationsActionKey.GetScopeOptions,
666
+ request$: req$,
667
+ onSuccess: (response) => ({ scopeOptions: response.data ?? null }),
668
+ });
669
+ }
670
+ previewScope(ctx, { scope }) {
671
+ const req$ = this.http.post(`${BASE}/scope/preview`, { scope });
672
+ return handleApiRequest({
673
+ ctx,
674
+ key: DelegationsActionKey.PreviewScope,
675
+ request$: req$,
676
+ onSuccess: (response) => ({ scopePreview: response.data ?? null }),
677
+ });
678
+ }
679
+ clearScopePreview(ctx) {
680
+ ctx.patchState({ scopePreview: null });
681
+ }
682
+ // ============================================================================
683
+ // Create / edit (response is legacy DelegationDto — clients reload list/detail)
684
+ // ============================================================================
685
+ createLegacy(ctx, { request }) {
686
+ const req$ = this.http.post(BASE, request);
687
+ return handleApiRequest({
688
+ ctx,
689
+ key: DelegationsActionKey.Create,
690
+ request$: req$,
691
+ onSuccess: () => ({}),
692
+ });
693
+ }
694
+ createV2(ctx, { request }) {
695
+ const req$ = this.http.post(`${BASE}/v2`, request);
696
+ return handleApiRequest({
697
+ ctx,
698
+ key: DelegationsActionKey.Create,
699
+ request$: req$,
700
+ onSuccess: () => ({}),
701
+ });
702
+ }
703
+ updateLegacy(ctx, { id, request }) {
704
+ const req$ = this.http.put(`${BASE}/${id}`, request);
705
+ return handleApiRequest({
706
+ ctx,
707
+ key: DelegationsActionKey.Update,
708
+ request$: req$,
709
+ onSuccess: () => ({}),
133
710
  });
134
711
  }
135
- addDelegation(ctx, { delegation }) {
136
- const req$ = this.http.post('identity/delegations', delegation);
137
- return this.create(ctx, {
138
- key: DelegationsActionKey.AddDelegation,
712
+ updateV2(ctx, { id, request }) {
713
+ const req$ = this.http.put(`${BASE}/v2/${id}`, request);
714
+ return handleApiRequest({
715
+ ctx,
716
+ key: DelegationsActionKey.Update,
139
717
  request$: req$,
140
- stateProperty: (state) => state.allDelegations,
718
+ onSuccess: () => ({}),
141
719
  });
142
720
  }
143
- updateDelegation(ctx, { id, delegation }) {
144
- const req$ = this.http.put(`identity/delegations/${id}`, delegation);
145
- return this.update(ctx, {
146
- key: DelegationsActionKey.UpdateDelegation,
721
+ // ============================================================================
722
+ // Decisions
723
+ // ============================================================================
724
+ approve(ctx, { id, request }) {
725
+ const req$ = this.http.post(`${BASE}/${id}/approve`, request);
726
+ return handleApiRequest({
727
+ ctx,
728
+ key: DelegationsActionKey.Approve,
147
729
  request$: req$,
148
- uniqueKey: 'id',
149
- id,
150
- stateProperty: (state) => state.allDelegations,
730
+ onSuccess: () => ({}),
151
731
  });
152
732
  }
153
- deleteDelegation(ctx, { id }) {
154
- const req$ = this.http.delete(`identity/delegations/${id}`);
155
- return this.delete(ctx, {
156
- key: DelegationsActionKey.DeleteDelegation,
733
+ reject(ctx, { id, request }) {
734
+ const req$ = this.http.post(`${BASE}/${id}/reject`, request);
735
+ return handleApiRequest({
736
+ ctx,
737
+ key: DelegationsActionKey.Reject,
157
738
  request$: req$,
158
- uniqueKey: 'id',
159
- id,
160
- stateProperty: (state) => state.allDelegations,
739
+ onSuccess: () => ({}),
161
740
  });
162
741
  }
163
- clearSelectedDelegation(ctx) {
164
- ctx.patchState({ selectedDelegation: null });
742
+ cancel(ctx, { id, rowVersion, asAdmin }) {
743
+ const path = asAdmin ? `${BASE}/Admin/${id}` : `${BASE}/${id}`;
744
+ const req$ = this.http.delete(path, {
745
+ params: new HttpParams().set('rowVersion', rowVersion),
746
+ });
747
+ return handleApiRequest({
748
+ ctx,
749
+ key: DelegationsActionKey.Cancel,
750
+ request$: req$,
751
+ onSuccess: () => ({}),
752
+ });
165
753
  }
166
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsState, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
754
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsState, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
167
755
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsState });
168
756
  };
169
757
  __decorate([
170
- Action(GetDelegations)
171
- ], DelegationsState.prototype, "getDelegations", null);
758
+ Action(GetMyDelegations)
759
+ ], DelegationsState.prototype, "getMy", null);
760
+ __decorate([
761
+ Action(GetAssignedDelegations)
762
+ ], DelegationsState.prototype, "getAssigned", null);
172
763
  __decorate([
173
- Action(GetDelegation)
174
- ], DelegationsState.prototype, "getDelegation", null);
764
+ Action(GetActiveAssignedDelegations)
765
+ ], DelegationsState.prototype, "getActive", null);
175
766
  __decorate([
176
- Action(AddDelegation)
177
- ], DelegationsState.prototype, "addDelegation", null);
767
+ Action(GetDelegationDetail)
768
+ ], DelegationsState.prototype, "getDetail", null);
178
769
  __decorate([
179
- Action(UpdateDelegation)
180
- ], DelegationsState.prototype, "updateDelegation", null);
770
+ Action(ClearDelegationDetail)
771
+ ], DelegationsState.prototype, "clearDetail", null);
181
772
  __decorate([
182
- Action(DeleteDelegation)
183
- ], DelegationsState.prototype, "deleteDelegation", null);
773
+ Action(GetScopeOptions)
774
+ ], DelegationsState.prototype, "getScopeOptions", null);
184
775
  __decorate([
185
- Action(ClearSelectedDelegation)
186
- ], DelegationsState.prototype, "clearSelectedDelegation", null);
776
+ Action(PreviewScope)
777
+ ], DelegationsState.prototype, "previewScope", null);
778
+ __decorate([
779
+ Action(ClearScopePreview)
780
+ ], DelegationsState.prototype, "clearScopePreview", null);
781
+ __decorate([
782
+ Action(CreateDelegationLegacy)
783
+ ], DelegationsState.prototype, "createLegacy", null);
784
+ __decorate([
785
+ Action(CreateDelegationV2)
786
+ ], DelegationsState.prototype, "createV2", null);
787
+ __decorate([
788
+ Action(UpdateDelegationLegacy)
789
+ ], DelegationsState.prototype, "updateLegacy", null);
790
+ __decorate([
791
+ Action(UpdateDelegationV2)
792
+ ], DelegationsState.prototype, "updateV2", null);
793
+ __decorate([
794
+ Action(ApproveDelegation)
795
+ ], DelegationsState.prototype, "approve", null);
796
+ __decorate([
797
+ Action(RejectDelegation)
798
+ ], DelegationsState.prototype, "reject", null);
799
+ __decorate([
800
+ Action(CancelDelegation)
801
+ ], DelegationsState.prototype, "cancel", null);
802
+ __decorate([
803
+ Selector()
804
+ ], DelegationsState, "getMy", null);
805
+ __decorate([
806
+ Selector()
807
+ ], DelegationsState, "getAssigned", null);
808
+ __decorate([
809
+ Selector()
810
+ ], DelegationsState, "getActive", null);
187
811
  __decorate([
188
812
  Selector()
189
- ], DelegationsState, "getAllDelegations", null);
813
+ ], DelegationsState, "getDetail", null);
190
814
  __decorate([
191
815
  Selector()
192
- ], DelegationsState, "getSelectedDelegation", null);
816
+ ], DelegationsState, "getScopeOptions", null);
817
+ __decorate([
818
+ Selector()
819
+ ], DelegationsState, "getScopePreview", null);
193
820
  __decorate([
194
821
  Selector()
195
822
  ], DelegationsState, "getLoadingActive", null);
@@ -200,8 +827,12 @@ DelegationsState = __decorate([
200
827
  State({
201
828
  name: 'delegations',
202
829
  defaults: {
203
- allDelegations: [],
204
- selectedDelegation: null,
830
+ my: null,
831
+ assigned: null,
832
+ active: null,
833
+ detail: null,
834
+ scopeOptions: null,
835
+ scopePreview: null,
205
836
  loadingActive: [],
206
837
  errors: {},
207
838
  },
@@ -209,61 +840,97 @@ DelegationsState = __decorate([
209
840
  ], DelegationsState);
210
841
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsState, decorators: [{
211
842
  type: Injectable
212
- }], propDecorators: { getDelegations: [], getDelegation: [], addDelegation: [], updateDelegation: [], deleteDelegation: [], clearSelectedDelegation: [] } });
843
+ }], propDecorators: { getMy: [], getAssigned: [], getActive: [], getDetail: [], clearDetail: [], getScopeOptions: [], previewScope: [], clearScopePreview: [], createLegacy: [], createV2: [], updateLegacy: [], updateV2: [], approve: [], reject: [], cancel: [] } });
213
844
 
214
845
  class DelegationsFacade {
215
846
  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
- // ============================================================================
847
+ // ---------------------------------------------------------------------------
848
+ // Data slices
849
+ // ---------------------------------------------------------------------------
850
+ myPage = select(DelegationsState.getMy);
851
+ assignedPage = select(DelegationsState.getAssigned);
852
+ activePage = select(DelegationsState.getActive);
853
+ detail = select(DelegationsState.getDetail);
854
+ scopeOptions = select(DelegationsState.getScopeOptions);
855
+ scopePreview = select(DelegationsState.getScopePreview);
224
856
  loadingActive = select(DelegationsState.getLoadingActive);
225
857
  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());
858
+ // ---------------------------------------------------------------------------
859
+ // Derived
860
+ // ---------------------------------------------------------------------------
861
+ myItems = computed(() => this.myPage()?.items ?? [], ...(ngDevMode ? [{ debugName: "myItems" }] : /* istanbul ignore next */ []));
862
+ assignedItems = computed(() => this.assignedPage()?.items ?? [], ...(ngDevMode ? [{ debugName: "assignedItems" }] : /* istanbul ignore next */ []));
863
+ activeItems = computed(() => this.activePage()?.items ?? [], ...(ngDevMode ? [{ debugName: "activeItems" }] : /* istanbul ignore next */ []));
864
+ // ---------------------------------------------------------------------------
865
+ // Loading
866
+ // ---------------------------------------------------------------------------
867
+ isLoadingMy = computed(() => this.loadingActive().includes(DelegationsActionKey.GetMy), ...(ngDevMode ? [{ debugName: "isLoadingMy" }] : /* istanbul ignore next */ []));
868
+ isLoadingAssigned = computed(() => this.loadingActive().includes(DelegationsActionKey.GetAssigned), ...(ngDevMode ? [{ debugName: "isLoadingAssigned" }] : /* istanbul ignore next */ []));
869
+ isLoadingActive = computed(() => this.loadingActive().includes(DelegationsActionKey.GetActive), ...(ngDevMode ? [{ debugName: "isLoadingActive" }] : /* istanbul ignore next */ []));
870
+ isLoadingDetail = computed(() => this.loadingActive().includes(DelegationsActionKey.GetDetail), ...(ngDevMode ? [{ debugName: "isLoadingDetail" }] : /* istanbul ignore next */ []));
871
+ isLoadingScopeOptions = computed(() => this.loadingActive().includes(DelegationsActionKey.GetScopeOptions), ...(ngDevMode ? [{ debugName: "isLoadingScopeOptions" }] : /* istanbul ignore next */ []));
872
+ isPreviewingScope = computed(() => this.loadingActive().includes(DelegationsActionKey.PreviewScope), ...(ngDevMode ? [{ debugName: "isPreviewingScope" }] : /* istanbul ignore next */ []));
873
+ isSaving = computed(() => this.loadingActive().includes(DelegationsActionKey.Create) ||
874
+ this.loadingActive().includes(DelegationsActionKey.Update), ...(ngDevMode ? [{ debugName: "isSaving" }] : /* istanbul ignore next */ []));
875
+ isApproving = computed(() => this.loadingActive().includes(DelegationsActionKey.Approve), ...(ngDevMode ? [{ debugName: "isApproving" }] : /* istanbul ignore next */ []));
876
+ isRejecting = computed(() => this.loadingActive().includes(DelegationsActionKey.Reject), ...(ngDevMode ? [{ debugName: "isRejecting" }] : /* istanbul ignore next */ []));
877
+ isCancelling = computed(() => this.loadingActive().includes(DelegationsActionKey.Cancel), ...(ngDevMode ? [{ debugName: "isCancelling" }] : /* istanbul ignore next */ []));
878
+ // ---------------------------------------------------------------------------
879
+ // Errors
880
+ // ---------------------------------------------------------------------------
881
+ errorMy = computed(() => this.errors()[DelegationsActionKey.GetMy] ?? null, ...(ngDevMode ? [{ debugName: "errorMy" }] : /* istanbul ignore next */ []));
882
+ errorAssigned = computed(() => this.errors()[DelegationsActionKey.GetAssigned] ?? null, ...(ngDevMode ? [{ debugName: "errorAssigned" }] : /* istanbul ignore next */ []));
883
+ errorDetail = computed(() => this.errors()[DelegationsActionKey.GetDetail] ?? null, ...(ngDevMode ? [{ debugName: "errorDetail" }] : /* istanbul ignore next */ []));
884
+ errorSave = computed(() => this.errors()[DelegationsActionKey.Create] ??
885
+ this.errors()[DelegationsActionKey.Update] ??
886
+ null, ...(ngDevMode ? [{ debugName: "errorSave" }] : /* istanbul ignore next */ []));
887
+ // ---------------------------------------------------------------------------
888
+ // Dispatchers
889
+ // ---------------------------------------------------------------------------
890
+ getMy(query = {}) {
891
+ return this.store.dispatch(new GetMyDelegations(query));
252
892
  }
253
- addDelegation(delegation) {
254
- return this.store.dispatch(new AddDelegation(delegation));
893
+ getAssigned(query = {}) {
894
+ return this.store.dispatch(new GetAssignedDelegations(query));
255
895
  }
256
- updateDelegation(id, delegation) {
257
- return this.store.dispatch(new UpdateDelegation(id, delegation));
896
+ getActive(query = {}) {
897
+ return this.store.dispatch(new GetActiveAssignedDelegations(query));
258
898
  }
259
- deleteDelegation(id) {
260
- return this.store.dispatch(new DeleteDelegation(id));
899
+ getDetail(id) {
900
+ return this.store.dispatch(new GetDelegationDetail(id));
261
901
  }
262
- loadDelegation(id) {
263
- return this.store.dispatch(new GetDelegation(id));
902
+ clearDetail() {
903
+ return this.store.dispatch(new ClearDelegationDetail());
264
904
  }
265
- clearSelectedDelegation() {
266
- return this.store.dispatch(new ClearSelectedDelegation());
905
+ loadScopeOptions(delegatorUserId) {
906
+ return this.store.dispatch(new GetScopeOptions(delegatorUserId));
907
+ }
908
+ previewScope(scope) {
909
+ return this.store.dispatch(new PreviewScope(scope));
910
+ }
911
+ clearScopePreview() {
912
+ return this.store.dispatch(new ClearScopePreview());
913
+ }
914
+ createLegacy(request) {
915
+ return this.store.dispatch(new CreateDelegationLegacy(request));
916
+ }
917
+ createV2(request) {
918
+ return this.store.dispatch(new CreateDelegationV2(request));
919
+ }
920
+ updateLegacy(id, request) {
921
+ return this.store.dispatch(new UpdateDelegationLegacy(id, request));
922
+ }
923
+ updateV2(id, request) {
924
+ return this.store.dispatch(new UpdateDelegationV2(id, request));
925
+ }
926
+ approve(id, request) {
927
+ return this.store.dispatch(new ApproveDelegation(id, request));
928
+ }
929
+ reject(id, request) {
930
+ return this.store.dispatch(new RejectDelegation(id, request));
931
+ }
932
+ cancel(id, rowVersion, asAdmin = false) {
933
+ return this.store.dispatch(new CancelDelegation(id, rowVersion, asAdmin));
267
934
  }
268
935
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsFacade, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
269
936
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsFacade, providedIn: 'root' });
@@ -273,140 +940,228 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
273
940
  args: [{ providedIn: 'root' }]
274
941
  }] });
275
942
 
943
+ const EMPTY_SCOPE$1 = { grants: [], metadata: {} };
944
+ function targetKey(t) {
945
+ return `${t.applicationKey}::${t.targetType}::${t.targetKey}`;
946
+ }
947
+ function grantKey(g) {
948
+ return `${targetKey(g.target)}::${g.action.operationKey}`;
949
+ }
950
+ function normalizeGrant(g) {
951
+ return {
952
+ ...g,
953
+ accessibilities: g.accessibilities ?? [],
954
+ dataFilters: g.dataFilters ?? [],
955
+ constraints: g.constraints ?? [],
956
+ };
957
+ }
958
+ /**
959
+ * Grants-based scope picker (doc 03). Driven entirely by `scope/options`:
960
+ * - lists grantable target/action pairs returned by the backend,
961
+ * - lets the user toggle each grant,
962
+ * - calls `scope/preview` (debounced) and surfaces summary / warnings / denied.
963
+ *
964
+ * `[(scope)]` two-way binds a `DelegationScopeSelection`. The component never
965
+ * invents permission metadata — it only echoes grants returned by options.
966
+ */
967
+ class ScopePicker {
968
+ scope = model(EMPTY_SCOPE$1, ...(ngDevMode ? [{ debugName: "scope" }] : /* istanbul ignore next */ []));
969
+ readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
970
+ delegatorUserId = input(undefined, ...(ngDevMode ? [{ debugName: "delegatorUserId" }] : /* istanbul ignore next */ []));
971
+ facade = inject(DelegationsFacade);
972
+ options = this.facade.scopeOptions;
973
+ preview = this.facade.scopePreview;
974
+ isLoadingOptions = this.facade.isLoadingScopeOptions;
975
+ isPreviewing = this.facade.isPreviewingScope;
976
+ /** Grantable options grouped by target. */
977
+ groups = computed(() => {
978
+ const grants = this.options()?.grants ?? [];
979
+ const map = new Map();
980
+ for (const g of grants) {
981
+ const key = targetKey(g.target);
982
+ const existing = map.get(key);
983
+ if (existing) {
984
+ existing.grants.push(g);
985
+ }
986
+ else {
987
+ map.set(key, { key, target: g.target, grants: [g] });
988
+ }
989
+ }
990
+ return Array.from(map.values());
991
+ }, ...(ngDevMode ? [{ debugName: "groups" }] : /* istanbul ignore next */ []));
992
+ /** Selected grant keys for O(1) checkbox state. */
993
+ selectedKeys = computed(() => new Set(this.scope().grants.map(grantKey)), ...(ngDevMode ? [{ debugName: "selectedKeys" }] : /* istanbul ignore next */ []));
994
+ expanded = signal(new Set(), ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
995
+ ngOnInit() {
996
+ this.facade.loadScopeOptions(this.delegatorUserId());
997
+ }
998
+ constructor() {
999
+ let timer = null;
1000
+ effect(() => {
1001
+ const s = this.scope();
1002
+ untracked(() => {
1003
+ if (!s.grants.length) {
1004
+ this.facade.clearScopePreview();
1005
+ return;
1006
+ }
1007
+ if (timer)
1008
+ clearTimeout(timer);
1009
+ timer = setTimeout(() => this.facade.previewScope(s), 300);
1010
+ });
1011
+ });
1012
+ }
1013
+ isExpanded(group) {
1014
+ return this.expanded().has(group.key);
1015
+ }
1016
+ toggleAccordion(group) {
1017
+ const next = new Set(this.expanded());
1018
+ if (next.has(group.key))
1019
+ next.delete(group.key);
1020
+ else
1021
+ next.add(group.key);
1022
+ this.expanded.set(next);
1023
+ }
1024
+ isSelected(grant) {
1025
+ return this.selectedKeys().has(grantKey(grant));
1026
+ }
1027
+ toggleGrant(grant) {
1028
+ if (this.readonly())
1029
+ return;
1030
+ const key = grantKey(grant);
1031
+ this.scope.update((s) => {
1032
+ const exists = s.grants.some((g) => grantKey(g) === key);
1033
+ const grants = exists
1034
+ ? s.grants.filter((g) => grantKey(g) !== key)
1035
+ : [...s.grants, normalizeGrant(grant)];
1036
+ return { ...s, grants };
1037
+ });
1038
+ }
1039
+ selectedCount(group) {
1040
+ const keys = this.selectedKeys();
1041
+ return group.grants.filter((g) => keys.has(grantKey(g))).length;
1042
+ }
1043
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ScopePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
1044
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: ScopePicker, isStandalone: true, selector: "mt-scope-picker", inputs: { scope: { classPropertyName: "scope", publicName: "scope", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null }, delegatorUserId: { classPropertyName: "delegatorUserId", publicName: "delegatorUserId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { scope: "scopeChange" }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div class=\"mt-scope-picker flex flex-col gap-4\">\n @if (isLoadingOptions() && !options()) {\n <p-skeleton height=\"2rem\"></p-skeleton>\n <p-skeleton height=\"6rem\"></p-skeleton>\n <p-skeleton height=\"6rem\"></p-skeleton>\n } @else if (options()) {\n <!-- Targets + actions -->\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-sm font-medium text-gray-800\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </h4>\n\n @if (groups().length === 0) {\n <p class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noGrantableOptions\") }}\n </p>\n }\n\n @for (group of groups(); track group.key) {\n <div class=\"border border-gray-200 rounded-md overflow-hidden\">\n <button\n type=\"button\"\n class=\"flex items-center justify-between w-full px-3 py-2 hover:bg-surface-50 cursor-pointer\"\n (click)=\"toggleAccordion(group)\"\n >\n <div class=\"flex items-center gap-2 min-w-0\">\n <mt-icon\n [icon]=\"\n isExpanded(group)\n ? 'arrow.chevron-down'\n : 'arrow.chevron-right'\n \"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n <span class=\"text-sm text-gray-900 truncate\">\n {{ group.target.displayName || group.target.targetKey }}\n </span>\n <span class=\"text-xs text-gray-400\">\n {{ group.target.targetType }}\n </span>\n </div>\n @if (selectedCount(group); as count) {\n @if (count > 0) {\n <span\n class=\"text-xs text-primary font-medium px-2 py-0.5 rounded-full bg-primary-50\"\n >\n {{ count }}\n </span>\n }\n }\n </button>\n\n @if (isExpanded(group)) {\n <div class=\"flex flex-col gap-1 px-3 py-2 bg-surface-50/40\">\n @for (grant of group.grants; track grant.action.operationKey) {\n <label\n class=\"flex items-center gap-2 px-2 py-1.5 rounded-md cursor-pointer hover:bg-surface-100\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(grant)\"\n [disabled]=\"readonly()\"\n (change)=\"toggleGrant(grant)\"\n />\n <span class=\"text-sm\">\n {{ grant.action.operationKey }}\n </span>\n @if (grant.action.isHighRisk) {\n <mt-icon\n icon=\"alert.alert-triangle\"\n styleClass=\"text-sm text-amber-500\"\n [pTooltip]=\"t('delegations.scope.highRisk')\"\n tooltipPosition=\"top\"\n ></mt-icon>\n }\n </label>\n }\n </div>\n }\n </div>\n }\n </section>\n\n <!-- Preview -->\n <section\n class=\"flex flex-col gap-1 px-3 py-2 rounded-md border border-dashed border-gray-300 bg-surface-50\"\n >\n <div class=\"flex items-center justify-between gap-2\">\n <div class=\"text-xs font-medium text-gray-700\">\n {{ t(\"delegations.scope.previewTitle\") }}\n </div>\n @if (isPreviewing()) {\n <p-skeleton width=\"6rem\" height=\"0.75rem\"></p-skeleton>\n }\n </div>\n @if (preview(); as p) {\n @if (p.isValid) {\n <div class=\"text-sm text-gray-900\">\n {{ p.summary || t(\"delegations.column.scopeSummary\") }}\n </div>\n } @else {\n <div class=\"text-sm text-red-700\">\n {{ t(\"delegations.scope.previewInvalid\") }}\n </div>\n }\n @if (p.warnings.length > 0) {\n <ul class=\"text-xs text-amber-700 list-disc list-inside\">\n @for (w of p.warnings; track w.message) {\n <li>{{ w.message }}</li>\n }\n </ul>\n }\n @if (p.deniedItems.length > 0) {\n <ul class=\"text-xs text-red-700 list-disc list-inside\">\n @for (d of p.deniedItems; track d.operationKey) {\n <li>\n {{ d.targetKey }} / {{ d.operationKey }} \u2014\n {{ d.reasonCode }}\n </li>\n }\n </ul>\n }\n } @else {\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </div>\n }\n </section>\n }\n </div>\n</ng-container>\n", styles: [":host{display:block}.mt-scope-picker input[type=checkbox]{accent-color:var(--p-primary-color, currentColor)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i2.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1045
+ }
1046
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ScopePicker, decorators: [{
1047
+ type: Component,
1048
+ args: [{ selector: 'mt-scope-picker', standalone: true, imports: [
1049
+ CommonModule,
1050
+ Icon,
1051
+ SkeletonModule,
1052
+ TooltipModule,
1053
+ TranslocoDirective,
1054
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div class=\"mt-scope-picker flex flex-col gap-4\">\n @if (isLoadingOptions() && !options()) {\n <p-skeleton height=\"2rem\"></p-skeleton>\n <p-skeleton height=\"6rem\"></p-skeleton>\n <p-skeleton height=\"6rem\"></p-skeleton>\n } @else if (options()) {\n <!-- Targets + actions -->\n <section class=\"flex flex-col gap-2\">\n <h4 class=\"text-sm font-medium text-gray-800\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </h4>\n\n @if (groups().length === 0) {\n <p class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noGrantableOptions\") }}\n </p>\n }\n\n @for (group of groups(); track group.key) {\n <div class=\"border border-gray-200 rounded-md overflow-hidden\">\n <button\n type=\"button\"\n class=\"flex items-center justify-between w-full px-3 py-2 hover:bg-surface-50 cursor-pointer\"\n (click)=\"toggleAccordion(group)\"\n >\n <div class=\"flex items-center gap-2 min-w-0\">\n <mt-icon\n [icon]=\"\n isExpanded(group)\n ? 'arrow.chevron-down'\n : 'arrow.chevron-right'\n \"\n styleClass=\"text-base text-gray-400\"\n ></mt-icon>\n <span class=\"text-sm text-gray-900 truncate\">\n {{ group.target.displayName || group.target.targetKey }}\n </span>\n <span class=\"text-xs text-gray-400\">\n {{ group.target.targetType }}\n </span>\n </div>\n @if (selectedCount(group); as count) {\n @if (count > 0) {\n <span\n class=\"text-xs text-primary font-medium px-2 py-0.5 rounded-full bg-primary-50\"\n >\n {{ count }}\n </span>\n }\n }\n </button>\n\n @if (isExpanded(group)) {\n <div class=\"flex flex-col gap-1 px-3 py-2 bg-surface-50/40\">\n @for (grant of group.grants; track grant.action.operationKey) {\n <label\n class=\"flex items-center gap-2 px-2 py-1.5 rounded-md cursor-pointer hover:bg-surface-100\"\n >\n <input\n type=\"checkbox\"\n [checked]=\"isSelected(grant)\"\n [disabled]=\"readonly()\"\n (change)=\"toggleGrant(grant)\"\n />\n <span class=\"text-sm\">\n {{ grant.action.operationKey }}\n </span>\n @if (grant.action.isHighRisk) {\n <mt-icon\n icon=\"alert.alert-triangle\"\n styleClass=\"text-sm text-amber-500\"\n [pTooltip]=\"t('delegations.scope.highRisk')\"\n tooltipPosition=\"top\"\n ></mt-icon>\n }\n </label>\n }\n </div>\n }\n </div>\n }\n </section>\n\n <!-- Preview -->\n <section\n class=\"flex flex-col gap-1 px-3 py-2 rounded-md border border-dashed border-gray-300 bg-surface-50\"\n >\n <div class=\"flex items-center justify-between gap-2\">\n <div class=\"text-xs font-medium text-gray-700\">\n {{ t(\"delegations.scope.previewTitle\") }}\n </div>\n @if (isPreviewing()) {\n <p-skeleton width=\"6rem\" height=\"0.75rem\"></p-skeleton>\n }\n </div>\n @if (preview(); as p) {\n @if (p.isValid) {\n <div class=\"text-sm text-gray-900\">\n {{ p.summary || t(\"delegations.column.scopeSummary\") }}\n </div>\n } @else {\n <div class=\"text-sm text-red-700\">\n {{ t(\"delegations.scope.previewInvalid\") }}\n </div>\n }\n @if (p.warnings.length > 0) {\n <ul class=\"text-xs text-amber-700 list-disc list-inside\">\n @for (w of p.warnings; track w.message) {\n <li>{{ w.message }}</li>\n }\n </ul>\n }\n @if (p.deniedItems.length > 0) {\n <ul class=\"text-xs text-red-700 list-disc list-inside\">\n @for (d of p.deniedItems; track d.operationKey) {\n <li>\n {{ d.targetKey }} / {{ d.operationKey }} \u2014\n {{ d.reasonCode }}\n </li>\n }\n </ul>\n }\n } @else {\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </div>\n }\n </section>\n }\n </div>\n</ng-container>\n", styles: [":host{display:block}.mt-scope-picker input[type=checkbox]{accent-color:var(--p-primary-color, currentColor)}\n"] }]
1055
+ }], ctorParameters: () => [], propDecorators: { scope: [{ type: i0.Input, args: [{ isSignal: true, alias: "scope", required: false }] }, { type: i0.Output, args: ["scopeChange"] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }], delegatorUserId: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegatorUserId", required: false }] }] } });
1056
+
1057
+ const EMPTY_SCOPE = { grants: [], metadata: {} };
1058
+ /**
1059
+ * Delegation create/edit form (doc 04). Uses the v2 explicit-scope endpoints so
1060
+ * the persisted `DelegationScopeSelection` matches what scope/preview validated.
1061
+ *
1062
+ * - Delegator is server-derived; only the delegated user is collected.
1063
+ * - On edit, scope grants + rowVersion come from the loaded detail.
1064
+ * - Times are sent in UTC ISO; wrappers set timeZoneId = UTC.
1065
+ */
276
1066
  class DelegationForm {
277
- halfWidthFieldClass = 'flex flex-col gap-1 lg:col-span-6';
278
- fullWidthFieldClass = 'flex flex-col gap-1 lg:col-span-12';
1067
+ delegationForEdit = input(null, ...(ngDevMode ? [{ debugName: "delegationForEdit" }] : /* istanbul ignore next */ []));
1068
+ readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
1069
+ halfWidth = 'flex flex-col gap-1 lg:col-span-6';
1070
+ fullWidth = 'flex flex-col gap-1 lg:col-span-12';
279
1071
  modal = inject(ModalService);
280
1072
  ref = inject(ModalRef);
281
- delegationForEdit = input(null, ...(ngDevMode ? [{ debugName: "delegationForEdit" }] : /* istanbul ignore next */ []));
282
- translocoService = inject(TranslocoService);
1073
+ transloco = inject(TranslocoService);
283
1074
  facade = inject(DelegationsFacade);
284
- selectedDelegation = this.facade.selectedDelegation;
285
1075
  delegationFormControl = new FormControl();
286
1076
  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
- });
293
- 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;
298
- }, ...(ngDevMode ? [{ debugName: "isDelegatingToSelf" }] : /* istanbul ignore next */ []));
1077
+ detail = this.facade.detail;
1078
+ isDetailLoading = this.facade.isLoadingDetail;
1079
+ isSaving = this.facade.isSaving;
1080
+ context = new HttpContext().set(REQUEST_CONTEXT, { useBaseUrl: false });
1081
+ scope = signal(EMPTY_SCOPE, ...(ngDevMode ? [{ debugName: "scope" }] : /* istanbul ignore next */ []));
299
1082
  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
- },
1083
+ { label: this.transloco.translate('delegations.days.sunday'), value: 0 },
1084
+ { label: this.transloco.translate('delegations.days.monday'), value: 1 },
1085
+ { label: this.transloco.translate('delegations.days.tuesday'), value: 2 },
1086
+ { label: this.transloco.translate('delegations.days.wednesday'), value: 3 },
1087
+ { label: this.transloco.translate('delegations.days.thursday'), value: 4 },
1088
+ { label: this.transloco.translate('delegations.days.friday'), value: 5 },
1089
+ { label: this.transloco.translate('delegations.days.saturday'), value: 6 },
328
1090
  ], ...(ngDevMode ? [{ debugName: "specificDaysOptions" }] : /* istanbul ignore next */ []));
329
- delegationFormConfig = computed(() => ({
1091
+ formConfig = computed(() => ({
330
1092
  sections: [
331
1093
  {
332
- key: 'delegationFormConfig',
1094
+ key: 'delegationForm',
333
1095
  type: 'header',
334
1096
  bodyClass: 'grid grid-cols-1 gap-4 items-start lg:grid-cols-12',
335
1097
  order: 1,
336
1098
  fields: [
337
- 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
1099
  new UserSearchFieldConfig({
349
1100
  key: 'delegateTo',
350
- label: this.translocoService.translate('delegations.delegated-to'),
1101
+ label: this.transloco.translate('delegations.column.delegatedTo'),
351
1102
  apiUrl: 'Identity/users',
352
1103
  context: this.context,
353
1104
  validators: [ValidatorConfig.required()],
354
- order: 2,
355
- cssClass: this.halfWidthFieldClass,
1105
+ cssClass: this.halfWidth,
356
1106
  colSpan: 6,
357
- disabled: !!this.delegationForEdit()?.id,
1107
+ order: 1,
1108
+ disabled: this.readonly() || !!this.delegationForEdit(),
358
1109
  }),
359
1110
  new TextareaFieldConfig({
360
1111
  key: 'description',
361
- label: this.translocoService.translate('delegations.description'),
362
- placeholder: this.translocoService.translate('delegations.description'),
363
- cssClass: this.fullWidthFieldClass,
1112
+ label: this.transloco.translate('delegations.form.description'),
1113
+ placeholder: this.transloco.translate('delegations.form.description'),
1114
+ cssClass: this.fullWidth,
364
1115
  colSpan: 12,
365
- order: 3,
1116
+ order: 2,
1117
+ disabled: this.readonly(),
366
1118
  }),
367
1119
  new DateFieldConfig({
368
1120
  key: 'delegateFromDateTime',
369
- label: this.translocoService.translate('delegations.delegation-date-from'),
370
- placeholder: this.translocoService.translate('delegations.delegation-date-from'),
1121
+ label: this.transloco.translate('delegations.column.startDate'),
1122
+ placeholder: this.transloco.translate('delegations.column.startDate'),
371
1123
  validators: [ValidatorConfig.required()],
372
1124
  showTime: true,
373
- cssClass: this.halfWidthFieldClass,
1125
+ cssClass: this.halfWidth,
374
1126
  colSpan: 6,
375
- order: 4,
1127
+ order: 3,
1128
+ disabled: this.readonly(),
376
1129
  }),
377
1130
  new DateFieldConfig({
378
1131
  key: 'delegateToDateTime',
379
- label: this.translocoService.translate('delegations.delegation-date-to'),
380
- placeholder: this.translocoService.translate('delegations.delegation-date-to'),
1132
+ label: this.transloco.translate('delegations.column.endDate'),
1133
+ placeholder: this.transloco.translate('delegations.column.endDate'),
381
1134
  validators: [ValidatorConfig.required()],
382
1135
  showTime: true,
383
- cssClass: this.halfWidthFieldClass,
1136
+ cssClass: this.halfWidth,
384
1137
  colSpan: 6,
385
- order: 5,
1138
+ order: 4,
1139
+ disabled: this.readonly(),
386
1140
  }),
387
1141
  new RadioButtonFieldConfig({
388
1142
  key: 'delegationDaysType',
389
- label: this.translocoService.translate('delegations.delegation-days-apply'),
1143
+ label: this.transloco.translate('delegations.form.daysApply'),
390
1144
  options: [
391
1145
  {
392
- label: this.translocoService.translate('delegations.full-range'),
1146
+ label: this.transloco.translate('delegations.form.fullRange'),
393
1147
  value: 'FullRange',
394
1148
  },
395
1149
  {
396
- label: this.translocoService.translate('delegations.specific-days'),
1150
+ label: this.transloco.translate('delegations.form.specificDays'),
397
1151
  value: 'SpecificDays',
398
1152
  },
399
1153
  ],
400
1154
  optionLabel: 'label',
401
1155
  optionValue: 'value',
402
1156
  validators: [ValidatorConfig.required()],
403
- cssClass: this.fullWidthFieldClass,
1157
+ cssClass: this.fullWidth,
404
1158
  colSpan: 12,
405
- order: 6,
1159
+ order: 5,
1160
+ disabled: this.readonly(),
406
1161
  }),
407
1162
  new MultiSelectFieldConfig({
408
1163
  key: 'specificDays',
409
- label: this.translocoService.translate('delegations.specific-days'),
1164
+ label: this.transloco.translate('delegations.form.specificDays'),
410
1165
  options: this.specificDaysOptions(),
411
1166
  optionLabel: 'label',
412
1167
  optionValue: 'value',
@@ -418,109 +1173,162 @@ class DelegationForm {
418
1173
  action: 'show',
419
1174
  },
420
1175
  ],
421
- cssClass: this.fullWidthFieldClass,
1176
+ cssClass: this.fullWidth,
422
1177
  colSpan: 12,
423
- order: 7,
1178
+ order: 6,
1179
+ disabled: this.readonly(),
424
1180
  }),
425
1181
  new ToggleFieldConfig({
426
- key: 'isActive',
427
- label: this.translocoService.translate('delegations.active-delegation'),
428
- cssClass: `${this.fullWidthFieldClass} mt-5`,
429
- order: 8,
1182
+ key: 'requiresApproval',
1183
+ label: this.transloco.translate('delegations.form.requiresApproval'),
1184
+ cssClass: `${this.fullWidth} mt-2`,
1185
+ order: 7,
1186
+ disabled: this.readonly(),
430
1187
  }),
431
1188
  ],
432
1189
  },
433
1190
  ],
434
- }), ...(ngDevMode ? [{ debugName: "delegationFormConfig" }] : /* istanbul ignore next */ []));
1191
+ }), ...(ngDevMode ? [{ debugName: "formConfig" }] : /* istanbul ignore next */ []));
435
1192
  constructor() {
436
1193
  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);
1194
+ const editing = this.delegationForEdit();
1195
+ const d = this.detail();
1196
+ if (editing && d && d.row.delegationId === editing.delegationId) {
1197
+ this.delegationFormControl.patchValue({
1198
+ delegateTo: d.row.delegatedUser,
1199
+ description: '',
1200
+ delegateFromDateTime: d.row.startsAtUtc,
1201
+ delegateToDateTime: d.row.endsAtUtc,
1202
+ delegationDaysType: d.row.dayRuleMode,
1203
+ specificDays: d.row.specificDays ?? [],
1204
+ requiresApproval: d.row.approval?.requiresApproval ?? false,
1205
+ });
1206
+ this.scope.set({
1207
+ grants: d.scope?.grants ?? [],
1208
+ metadata: d.scope?.metadata ?? {},
1209
+ });
451
1210
  }
452
- else {
1211
+ else if (!editing) {
453
1212
  this.delegationFormControl.reset({
454
1213
  delegationDaysType: 'FullRange',
455
- isActive: true,
1214
+ requiresApproval: true,
456
1215
  });
1216
+ this.scope.set(EMPTY_SCOPE);
457
1217
  }
458
1218
  });
459
1219
  }
460
1220
  ngOnInit() {
461
- const delegationId = this.delegationForEdit()?.id;
462
- if (delegationId) {
463
- this.facade.loadDelegation(delegationId);
1221
+ const editing = this.delegationForEdit();
1222
+ if (editing) {
1223
+ this.facade.getDetail(editing.delegationId);
464
1224
  }
465
1225
  }
1226
+ canSubmit = computed(() => !this.readonly() && this.scope().grants.length > 0, ...(ngDevMode ? [{ debugName: "canSubmit" }] : /* istanbul ignore next */ []));
466
1227
  onSubmit() {
467
- if (!this.delegationFormControl.valid) {
1228
+ if (this.readonly() || !this.delegationFormControl.valid)
468
1229
  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,
1230
+ if (this.scope().grants.length === 0)
1231
+ return;
1232
+ const v = this.delegationFormControl.value;
1233
+ const base = {
1234
+ delegateTo: v?.delegateTo?.userId ?? v?.delegateTo?.id ?? v?.delegateTo,
1235
+ description: v?.description,
1236
+ delegateFromDateTime: v?.delegateFromDateTime,
1237
+ delegateToDateTime: v?.delegateToDateTime,
1238
+ delegationDaysType: (v?.delegationDaysType ?? 'FullRange'),
1239
+ specificDays: v?.delegationDaysType === 'SpecificDays' ? (v?.specificDays ?? []) : [],
1240
+ requiresApproval: !!v?.requiresApproval,
1241
+ scope: this.scope(),
482
1242
  };
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: () => { },
1243
+ const editing = this.delegationForEdit();
1244
+ if (editing) {
1245
+ const req = {
1246
+ ...base,
1247
+ rowVersion: editing.rowVersion,
1248
+ };
1249
+ this.facade.updateV2(editing.delegationId, req).subscribe({
1250
+ next: () => this.ref.close(true),
490
1251
  });
491
1252
  }
492
1253
  else {
493
- this.facade.addDelegation(payload).subscribe({
494
- next: () => {
495
- this.ref.close(true);
496
- },
497
- error: () => { },
1254
+ const req = base;
1255
+ this.facade.createV2(req).subscribe({
1256
+ next: () => this.ref.close(true),
498
1257
  });
499
1258
  }
500
1259
  }
501
1260
  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"] }] });
1261
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationForm, isStandalone: true, selector: "mt-delegation-form", inputs: { delegationForEdit: { classPropertyName: "delegationForEdit", publicName: "delegationForEdit", isSignal: true, isRequired: false, transformFunction: null }, readonly: { classPropertyName: "readonly", publicName: "readonly", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div\n [class]=\"\n 'delegation-form-content flex min-w-0 flex-col gap-3 overflow-x-hidden p-4 max-[640px]:p-3 ' +\n modal.contentClass\n \"\n >\n <mt-dynamic-form\n [formConfig]=\"formConfig()\"\n [formControl]=\"delegationFormControl\"\n />\n\n <div class=\"mt-2 border-t border-gray-200 pt-3 flex flex-col gap-2\">\n <h3 class=\"text-sm font-semibold text-gray-800\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </h3>\n <mt-scope-picker\n [(scope)]=\"scope\"\n [readonly]=\"readonly()\"\n ></mt-scope-picker>\n </div>\n </div>\n\n <div\n [class]=\"\n 'delegation-form-footer ' +\n modal.footerClass +\n ' flex-col gap-2 sm:flex-row sm:items-center sm:justify-end'\n \"\n >\n <mt-button\n [label]=\"t('delegations.common.cancel')\"\n variant=\"outlined\"\n (click)=\"ref.close()\"\n styleClass=\"w-full sm:w-auto\"\n />\n @if (!readonly()) {\n <mt-button\n [label]=\"\n delegationForEdit()\n ? t('delegations.common.update')\n : t('delegations.common.create')\n \"\n [loading]=\"isSaving()\"\n [disabled]=\"!delegationFormControl.valid || !canSubmit()\"\n (click)=\"onSubmit()\"\n styleClass=\"w-full sm:w-auto\"\n />\n }\n </div>\n</ng-container>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: DynamicForm, selector: "mt-dynamic-form", inputs: ["formConfig", "forcedHiddenFieldKeys", "preserveForcedHiddenValues", "visibleSectionKeys"], outputs: ["runtimeMessagesChange"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: ScopePicker, selector: "mt-scope-picker", inputs: ["scope", "readonly", "delegatorUserId"], outputs: ["scopeChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
503
1262
  }
504
1263
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationForm, decorators: [{
505
1264
  type: Component,
506
1265
  args: [{ selector: 'mt-delegation-form', standalone: true, imports: [
507
1266
  CommonModule,
508
1267
  Button,
509
- TranslocoDirective,
510
- SkeletonModule,
511
1268
  DynamicForm,
512
1269
  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 }] }] } });
1270
+ ScopePicker,
1271
+ TranslocoDirective,
1272
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div\n [class]=\"\n 'delegation-form-content flex min-w-0 flex-col gap-3 overflow-x-hidden p-4 max-[640px]:p-3 ' +\n modal.contentClass\n \"\n >\n <mt-dynamic-form\n [formConfig]=\"formConfig()\"\n [formControl]=\"delegationFormControl\"\n />\n\n <div class=\"mt-2 border-t border-gray-200 pt-3 flex flex-col gap-2\">\n <h3 class=\"text-sm font-semibold text-gray-800\">\n {{ t(\"delegations.scope.permissionsTitle\") }}\n </h3>\n <mt-scope-picker\n [(scope)]=\"scope\"\n [readonly]=\"readonly()\"\n ></mt-scope-picker>\n </div>\n </div>\n\n <div\n [class]=\"\n 'delegation-form-footer ' +\n modal.footerClass +\n ' flex-col gap-2 sm:flex-row sm:items-center sm:justify-end'\n \"\n >\n <mt-button\n [label]=\"t('delegations.common.cancel')\"\n variant=\"outlined\"\n (click)=\"ref.close()\"\n styleClass=\"w-full sm:w-auto\"\n />\n @if (!readonly()) {\n <mt-button\n [label]=\"\n delegationForEdit()\n ? t('delegations.common.update')\n : t('delegations.common.create')\n \"\n [loading]=\"isSaving()\"\n [disabled]=\"!delegationFormControl.valid || !canSubmit()\"\n (click)=\"onSubmit()\"\n styleClass=\"w-full sm:w-auto\"\n />\n }\n </div>\n</ng-container>\n" }]
1273
+ }], ctorParameters: () => [], propDecorators: { delegationForEdit: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegationForEdit", required: false }] }], readonly: [{ type: i0.Input, args: [{ isSignal: true, alias: "readonly", required: false }] }] } });
515
1274
 
1275
+ /**
1276
+ * Read-only drawer that shows the full `DelegationDetail` ({ row, scope, status }).
1277
+ */
1278
+ class DelegationDetailDrawer {
1279
+ delegationId = input.required(...(ngDevMode ? [{ debugName: "delegationId" }] : /* istanbul ignore next */ []));
1280
+ facade = inject(DelegationsFacade);
1281
+ transloco = inject(TranslocoService);
1282
+ ref = inject(ModalRef);
1283
+ tabs = [
1284
+ { key: 'overview', i18n: 'delegations.action.view' },
1285
+ { key: 'scope', i18n: 'delegations.column.scopeSummary' },
1286
+ ];
1287
+ activeTab = signal('overview', ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
1288
+ isLoading = this.facade.isLoadingDetail;
1289
+ detail = this.facade.detail;
1290
+ row = computed(() => this.detail()?.row ?? null, ...(ngDevMode ? [{ debugName: "row" }] : /* istanbul ignore next */ []));
1291
+ ngOnInit() {
1292
+ this.facade.getDetail(this.delegationId());
1293
+ }
1294
+ setTab(tab) {
1295
+ this.activeTab.set(tab);
1296
+ }
1297
+ approvalLabel() {
1298
+ const s = this.row()?.approval?.approvalStatus ?? 'NotRequired';
1299
+ return this.transloco.translate(`delegations.approvalStatus.${s}`);
1300
+ }
1301
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationDetailDrawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
1302
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationDetailDrawer, isStandalone: true, selector: "mt-delegation-detail-drawer", inputs: { delegationId: { classPropertyName: "delegationId", publicName: "delegationId", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col h-full\">\n <!-- Tabs -->\n <div class=\"flex border-b border-gray-200 px-4\">\n @for (tab of tabs; track tab.key) {\n <button\n type=\"button\"\n class=\"px-3 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\n [class.border-primary]=\"activeTab() === tab.key\"\n [class.text-primary]=\"activeTab() === tab.key\"\n [class.border-transparent]=\"activeTab() !== tab.key\"\n [class.text-gray-500]=\"activeTab() !== tab.key\"\n (click)=\"setTab(tab.key)\"\n >\n {{ t(tab.i18n) }}\n </button>\n }\n </div>\n\n <!-- Body -->\n <div class=\"flex-1 overflow-y-auto p-4\">\n @if (isLoading() && !detail()) {\n <p-skeleton height=\"2rem\" class=\"mb-3\"></p-skeleton>\n <p-skeleton height=\"6rem\" class=\"mb-3\"></p-skeleton>\n <p-skeleton height=\"6rem\"></p-skeleton>\n } @else if (detail(); as d) {\n @if (activeTab() === \"overview\") {\n <div class=\"flex flex-col gap-4\">\n <div class=\"flex items-center justify-between gap-3\">\n <div class=\"flex items-center gap-3 min-w-0\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-12! h-12! text-lg!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.delegatorName\") }}\n </div>\n <div class=\"text-base font-medium text-gray-900 truncate\">\n {{ d.row.delegator.displayName }}\n </div>\n @if (d.row.delegator.email) {\n <div class=\"text-xs text-gray-500 truncate\">\n {{ d.row.delegator.email }}\n </div>\n }\n </div>\n </div>\n <mt-delegation-status-chip\n [status]=\"d.status.effectiveStatus\"\n ></mt-delegation-status-chip>\n </div>\n\n <div class=\"grid grid-cols-2 gap-4\">\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.delegatedTo\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.delegatedUser.displayName }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.approval\") }}\n </span>\n <span class=\"text-sm text-gray-900\">{{ approvalLabel() }}</span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.startDate\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.startsAtUtc | date: \"medium\" }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.endDate\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.endsAtUtc | date: \"medium\" }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.form.timeZone\") }}\n </span>\n <span class=\"text-sm text-gray-900\">{{\n d.row.timeZoneId\n }}</span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.days\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n @if (d.row.dayRuleMode === \"FullRange\") {\n {{ t(\"delegations.form.fullRange\") }}\n } @else {\n {{ d.row.specificDays.join(\", \") }}\n }\n </span>\n </div>\n </div>\n\n @if (d.row.cancellation?.cancellationReason) {\n <div\n class=\"rounded-md border border-gray-200 bg-surface-50 p-3 text-sm text-gray-800\"\n >\n <div class=\"font-medium\">\n {{ t(\"delegations.form.cancellationReason\") }}\n </div>\n <div>{{ d.row.cancellation.cancellationReason }}</div>\n </div>\n }\n </div>\n } @else {\n <!-- Scope tab -->\n <div class=\"flex flex-col gap-3\">\n <div class=\"text-sm text-gray-900\">\n {{ d.row.scopeSummary || t(\"delegations.column.scopeSummary\") }}\n </div>\n <div class=\"flex flex-col gap-2\">\n @for (grant of d.scope.grants; track $index) {\n <div\n class=\"border border-gray-200 rounded-md px-3 py-2 flex flex-col gap-1\"\n >\n <div class=\"flex items-center justify-between gap-2\">\n <span class=\"text-sm font-medium text-gray-900\">\n {{ grant.target.displayName || grant.target.targetKey }}\n </span>\n <span class=\"text-xs text-gray-500\">\n {{ grant.target.targetType }}\n </span>\n </div>\n <span\n class=\"text-xs px-2 py-0.5 rounded-md bg-primary-50 text-primary w-fit\"\n >\n {{ grant.action.operationKey }}\n </span>\n </div>\n }\n @if (d.scope.grants.length === 0) {\n <p class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </p>\n }\n </div>\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n <div\n class=\"border-t border-gray-200 px-4 py-3 flex items-center justify-end\"\n >\n <mt-button\n [label]=\"t('delegations.common.cancel')\"\n variant=\"outlined\"\n (click)=\"ref.close()\"\n ></mt-button>\n </div>\n </div>\n</ng-container>\n", styles: [":host{display:block;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { kind: "component", type: Button, selector: "mt-button", inputs: ["icon", "label", "tooltip", "class", "type", "styleClass", "severity", "badge", "variant", "badgeSeverity", "size", "iconPos", "autofocus", "fluid", "raised", "rounded", "text", "plain", "outlined", "link", "disabled", "loading", "pInputs"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i1.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationStatusChip, selector: "mt-delegation-status-chip", inputs: ["status"] }, { kind: "pipe", type: i2$1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1303
+ }
1304
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationDetailDrawer, decorators: [{
1305
+ type: Component,
1306
+ args: [{ selector: 'mt-delegation-detail-drawer', standalone: true, imports: [
1307
+ CommonModule,
1308
+ Avatar,
1309
+ Button,
1310
+ SkeletonModule,
1311
+ TranslocoDirective,
1312
+ DelegationStatusChip,
1313
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col h-full\">\n <!-- Tabs -->\n <div class=\"flex border-b border-gray-200 px-4\">\n @for (tab of tabs; track tab.key) {\n <button\n type=\"button\"\n class=\"px-3 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\n [class.border-primary]=\"activeTab() === tab.key\"\n [class.text-primary]=\"activeTab() === tab.key\"\n [class.border-transparent]=\"activeTab() !== tab.key\"\n [class.text-gray-500]=\"activeTab() !== tab.key\"\n (click)=\"setTab(tab.key)\"\n >\n {{ t(tab.i18n) }}\n </button>\n }\n </div>\n\n <!-- Body -->\n <div class=\"flex-1 overflow-y-auto p-4\">\n @if (isLoading() && !detail()) {\n <p-skeleton height=\"2rem\" class=\"mb-3\"></p-skeleton>\n <p-skeleton height=\"6rem\" class=\"mb-3\"></p-skeleton>\n <p-skeleton height=\"6rem\"></p-skeleton>\n } @else if (detail(); as d) {\n @if (activeTab() === \"overview\") {\n <div class=\"flex flex-col gap-4\">\n <div class=\"flex items-center justify-between gap-3\">\n <div class=\"flex items-center gap-3 min-w-0\">\n <mt-avatar\n icon=\"user.user-01\"\n styleClass=\"w-12! h-12! text-lg!\"\n ></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <div class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.delegatorName\") }}\n </div>\n <div class=\"text-base font-medium text-gray-900 truncate\">\n {{ d.row.delegator.displayName }}\n </div>\n @if (d.row.delegator.email) {\n <div class=\"text-xs text-gray-500 truncate\">\n {{ d.row.delegator.email }}\n </div>\n }\n </div>\n </div>\n <mt-delegation-status-chip\n [status]=\"d.status.effectiveStatus\"\n ></mt-delegation-status-chip>\n </div>\n\n <div class=\"grid grid-cols-2 gap-4\">\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.delegatedTo\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.delegatedUser.displayName }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.approval\") }}\n </span>\n <span class=\"text-sm text-gray-900\">{{ approvalLabel() }}</span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.startDate\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.startsAtUtc | date: \"medium\" }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.endDate\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n {{ d.row.endsAtUtc | date: \"medium\" }}\n </span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.form.timeZone\") }}\n </span>\n <span class=\"text-sm text-gray-900\">{{\n d.row.timeZoneId\n }}</span>\n </div>\n <div class=\"flex flex-col\">\n <span class=\"text-xs text-gray-500\">\n {{ t(\"delegations.column.days\") }}\n </span>\n <span class=\"text-sm text-gray-900\">\n @if (d.row.dayRuleMode === \"FullRange\") {\n {{ t(\"delegations.form.fullRange\") }}\n } @else {\n {{ d.row.specificDays.join(\", \") }}\n }\n </span>\n </div>\n </div>\n\n @if (d.row.cancellation?.cancellationReason) {\n <div\n class=\"rounded-md border border-gray-200 bg-surface-50 p-3 text-sm text-gray-800\"\n >\n <div class=\"font-medium\">\n {{ t(\"delegations.form.cancellationReason\") }}\n </div>\n <div>{{ d.row.cancellation.cancellationReason }}</div>\n </div>\n }\n </div>\n } @else {\n <!-- Scope tab -->\n <div class=\"flex flex-col gap-3\">\n <div class=\"text-sm text-gray-900\">\n {{ d.row.scopeSummary || t(\"delegations.column.scopeSummary\") }}\n </div>\n <div class=\"flex flex-col gap-2\">\n @for (grant of d.scope.grants; track $index) {\n <div\n class=\"border border-gray-200 rounded-md px-3 py-2 flex flex-col gap-1\"\n >\n <div class=\"flex items-center justify-between gap-2\">\n <span class=\"text-sm font-medium text-gray-900\">\n {{ grant.target.displayName || grant.target.targetKey }}\n </span>\n <span class=\"text-xs text-gray-500\">\n {{ grant.target.targetType }}\n </span>\n </div>\n <span\n class=\"text-xs px-2 py-0.5 rounded-md bg-primary-50 text-primary w-fit\"\n >\n {{ grant.action.operationKey }}\n </span>\n </div>\n }\n @if (d.scope.grants.length === 0) {\n <p class=\"text-xs text-gray-500\">\n {{ t(\"delegations.scope.noSelection\") }}\n </p>\n }\n </div>\n </div>\n }\n }\n </div>\n\n <!-- Footer -->\n <div\n class=\"border-t border-gray-200 px-4 py-3 flex items-center justify-end\"\n >\n <mt-button\n [label]=\"t('delegations.common.cancel')\"\n variant=\"outlined\"\n (click)=\"ref.close()\"\n ></mt-button>\n </div>\n </div>\n</ng-container>\n", styles: [":host{display:block;height:100%}\n"] }]
1314
+ }], propDecorators: { delegationId: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegationId", required: true }] }] } });
1315
+
1316
+ /**
1317
+ * Delegations portal page (doc 02, 09): two lists — My Delegations (current user
1318
+ * is delegator) and Delegated To Me (current user is delegate). Rows render only
1319
+ * the BE-returned `allowedActions`; status comes from `effectiveStatus`.
1320
+ */
516
1321
  class DelegationsList {
517
- statusCol = viewChild.required('statusCol');
518
- userCol = viewChild.required('userCol');
519
- userCol2 = viewChild.required('userCol2');
520
- daysCol = viewChild.required('daysCol');
1322
+ /**
1323
+ * Admin/control-panel breadcrumb. Hidden on the client self-service surface
1324
+ * (its links point at /control-panel/*). Bound from route data via
1325
+ * `withComponentInputBinding()` — admin leaves it default; client passes
1326
+ * `data: { showBreadcrumb: false }`.
1327
+ */
1328
+ showBreadcrumb = input(true, ...(ngDevMode ? [{ debugName: "showBreadcrumb" }] : /* istanbul ignore next */ []));
521
1329
  facade = inject(DelegationsFacade);
522
1330
  modal = inject(ModalService);
523
- translocoService = inject(TranslocoService);
1331
+ transloco = inject(TranslocoService);
524
1332
  breadcrumbItems = linkedSignal(() => [
525
1333
  {
526
1334
  label: '',
@@ -528,147 +1336,139 @@ class DelegationsList {
528
1336
  routerLink: '/control-panel/workspaces',
529
1337
  },
530
1338
  {
531
- label: this.translocoService.translate('product-settings.product-settings'),
1339
+ label: this.transloco.translate('product-settings.product-settings'),
532
1340
  routerLink: '/control-panel/product-settings',
533
1341
  },
534
1342
  {
535
- label: this.translocoService.translate('delegations.delegations'),
1343
+ label: this.transloco.translate('delegations.title'),
536
1344
  },
537
1345
  ], ...(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 */ []));
1346
+ activeTab = signal('my', ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
553
1347
  tabs = signal([
554
1348
  {
555
- label: this.translocoService.translate('delegations.all'),
556
- value: 'all',
1349
+ value: 'my',
1350
+ label: this.transloco.translate('delegations.myDelegations'),
557
1351
  },
558
1352
  {
559
- label: this.translocoService.translate('delegations.active'),
560
- value: 'active',
561
- },
562
- {
563
- label: this.translocoService.translate('delegations.inactive'),
564
- value: 'inactive',
1353
+ value: 'assigned',
1354
+ label: this.transloco.translate('delegations.delegatedToMe'),
565
1355
  },
566
1356
  ], ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
567
- activeTab = signal('all', ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
1357
+ isLoading = computed(() => this.activeTab() === 'my'
1358
+ ? this.facade.isLoadingMy()
1359
+ : this.facade.isLoadingAssigned(), ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
1360
+ rows = computed(() => this.activeTab() === 'my'
1361
+ ? this.facade.myItems()
1362
+ : this.facade.assignedItems(), ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
1363
+ statusCol = viewChild.required('statusCol');
1364
+ userCol = viewChild.required('userCol');
1365
+ scopeCol = viewChild.required('scopeCol');
1366
+ daysCol = viewChild.required('daysCol');
568
1367
  tableActions = signal([
569
1368
  {
570
1369
  icon: 'general.plus',
571
- label: this.translocoService.translate('delegations.add-delegation'),
1370
+ label: this.transloco.translate('delegations.action.create'),
572
1371
  color: 'primary',
573
- action: () => {
574
- this.addDelegationDialog();
575
- },
1372
+ action: () => this.openForm(null),
576
1373
  },
577
1374
  ], ...(ngDevMode ? [{ debugName: "tableActions" }] : /* istanbul ignore next */ []));
578
- deletingRowIds = signal([], ...(ngDevMode ? [{ debugName: "deletingRowIds" }] : /* istanbul ignore next */ []));
579
1375
  rowActions = signal([
1376
+ {
1377
+ icon: 'general.eye',
1378
+ tooltip: this.transloco.translate('delegations.action.view'),
1379
+ color: 'primary',
1380
+ variant: 'outlined',
1381
+ action: (row) => this.viewDetails(row),
1382
+ hidden: (row) => !this.has(row, 'View'),
1383
+ },
580
1384
  {
581
1385
  icon: 'custom.pencil',
582
- tooltip: this.translocoService.translate('edit'),
1386
+ tooltip: this.transloco.translate('delegations.action.edit'),
583
1387
  color: 'primary',
584
- action: (row) => {
585
- this.facade.clearSelectedDelegation();
586
- this.addDelegationDialog(row);
587
- },
1388
+ action: (row) => this.openForm(row),
1389
+ hidden: (row) => !this.has(row, 'Edit'),
1390
+ },
1391
+ {
1392
+ icon: 'general.check',
1393
+ tooltip: this.transloco.translate('delegations.action.approve'),
1394
+ color: 'primary',
1395
+ action: (row) => this.approve(row),
1396
+ hidden: (row) => !this.has(row, 'Approve'),
1397
+ loading: (row) => this.busyIds().includes(`approve:${row.delegationId}`),
1398
+ confirmation: { type: 'popup' },
1399
+ },
1400
+ {
1401
+ icon: 'general.x-close',
1402
+ tooltip: this.transloco.translate('delegations.action.reject'),
1403
+ color: 'danger',
1404
+ variant: 'outlined',
1405
+ action: (row) => this.reject(row),
1406
+ hidden: (row) => !this.has(row, 'Reject'),
1407
+ loading: (row) => this.busyIds().includes(`reject:${row.delegationId}`),
1408
+ confirmation: { type: 'popup' },
588
1409
  },
589
1410
  {
590
1411
  icon: 'general.trash-01',
591
- tooltip: this.translocoService.translate('delete'),
1412
+ tooltip: this.transloco.translate('delegations.action.cancel'),
592
1413
  color: 'danger',
593
1414
  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),
1415
+ action: (row) => this.cancel(row),
1416
+ hidden: (row) => !this.has(row, 'Cancel'),
1417
+ loading: (row) => this.busyIds().includes(`cancel:${row.delegationId}`),
1418
+ confirmation: { type: 'popup', confirmationType: 'delete' },
1419
+ },
1420
+ {
1421
+ icon: 'arrow.arrow-right',
1422
+ tooltip: this.transloco.translate('delegations.action.startSession'),
1423
+ color: 'primary',
1424
+ action: (row) => this.startSession(row),
1425
+ hidden: (row) => !this.has(row, 'StartSession'),
608
1426
  },
609
1427
  ], ...(ngDevMode ? [{ debugName: "rowActions" }] : /* istanbul ignore next */ []));
610
1428
  tableColumns = linkedSignal(() => [
611
1429
  {
612
- key: 'isActive',
613
- label: this.translocoService.translate('delegations.status'),
1430
+ key: 'effectiveStatus',
1431
+ label: this.transloco.translate('delegations.column.status'),
614
1432
  type: 'custom',
615
1433
  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
1434
  },
631
1435
  {
632
- key: 'delegateFrom.displayName',
633
- label: this.translocoService.translate('delegations.originally-assigned-to'),
1436
+ key: this.activeTab() === 'my' ? 'delegatedUser' : 'delegator',
1437
+ label: this.transloco.translate(this.activeTab() === 'my'
1438
+ ? 'delegations.column.delegatedTo'
1439
+ : 'delegations.column.delegatorName'),
634
1440
  type: 'custom',
635
1441
  customCellTpl: this.userCol(),
636
1442
  },
637
1443
  {
638
- key: 'delegateTo.displayName',
639
- label: this.translocoService.translate('delegations.delegated-to'),
1444
+ key: 'scopeSummary',
1445
+ label: this.transloco.translate('delegations.column.scopeSummary'),
640
1446
  type: 'custom',
641
- customCellTpl: this.userCol2(),
1447
+ customCellTpl: this.scopeCol(),
642
1448
  },
643
1449
  {
644
- key: 'delegateFromDateTime',
645
- label: this.translocoService.translate('delegations.start-date'),
1450
+ key: 'startsAtUtc',
1451
+ label: this.transloco.translate('delegations.column.startDate'),
646
1452
  type: 'dateTime',
647
- filterConfig: {
648
- type: 'date',
649
- label: this.translocoService.translate('delegations.start-date'),
650
- },
651
1453
  },
652
1454
  {
653
- key: 'delegateToDateTime',
654
- label: this.translocoService.translate('delegations.end-date'),
1455
+ key: 'endsAtUtc',
1456
+ label: this.transloco.translate('delegations.column.endDate'),
655
1457
  type: 'dateTime',
656
- filterConfig: {
657
- type: 'date',
658
- label: this.translocoService.translate('delegations.end-date'),
659
- },
660
1458
  },
661
1459
  {
662
- key: 'days',
663
- label: this.translocoService.translate('delegations.days'),
1460
+ key: 'createdAtUtc',
1461
+ label: this.transloco.translate('delegations.column.createdDate'),
1462
+ type: 'dateTime',
1463
+ },
1464
+ {
1465
+ key: 'dayRuleMode',
1466
+ label: this.transloco.translate('delegations.column.days'),
664
1467
  type: 'custom',
665
1468
  customCellTpl: this.daysCol(),
666
1469
  },
667
1470
  ], ...(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;
1471
+ busyIds = signal([], ...(ngDevMode ? [{ debugName: "busyIds" }] : /* istanbul ignore next */ []));
672
1472
  weekdayKeys = [
673
1473
  'sunday',
674
1474
  'monday',
@@ -679,58 +1479,144 @@ class DelegationsList {
679
1479
  'saturday',
680
1480
  ];
681
1481
  ngOnInit() {
682
- this.facade.getDelegations();
1482
+ this.loadCurrentTab();
1483
+ }
1484
+ switchTab(tab) {
1485
+ this.activeTab.set(tab);
1486
+ this.loadCurrentTab();
683
1487
  }
684
- formatDelegationDays(row) {
685
- if (row?.delegationDaysType === 'SpecificDays' &&
686
- row?.specificDays?.length) {
1488
+ loadCurrentTab(query = { page: 1, pageSize: 25 }) {
1489
+ if (this.activeTab() === 'my') {
1490
+ this.facade.getMy(query);
1491
+ }
1492
+ else {
1493
+ this.facade.getAssigned(query);
1494
+ }
1495
+ }
1496
+ reloadCurrentTab() {
1497
+ this.loadCurrentTab();
1498
+ }
1499
+ has(row, action) {
1500
+ return row.allowedActions?.includes(action) ?? false;
1501
+ }
1502
+ formatDays(row) {
1503
+ if (row.dayRuleMode === 'SpecificDays' && row.specificDays?.length) {
687
1504
  return row.specificDays
688
- .filter((day) => day >= 0 && day < this.weekdayKeys.length)
689
- .map((day) => this.translocoService.translate(`delegations.${this.weekdayKeys[day]}`))
1505
+ .filter((d) => d >= 0 && d < this.weekdayKeys.length)
1506
+ .map((d) => this.transloco.translate(`delegations.days.${this.weekdayKeys[d]}`))
690
1507
  .join(', ');
691
1508
  }
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',
1509
+ return this.transloco.translate('delegations.form.fullRange');
1510
+ }
1511
+ viewDetails(row) {
1512
+ this.modal.openModal(DelegationDetailDrawer, 'drawer', {
1513
+ header: this.transloco.translate('delegations.action.view'),
1514
+ styleClass: '!absolute !shadow-none !w-full !max-w-full sm:!w-[42rem] xl:!w-[50rem]',
1515
+ position: 'end',
1516
+ appendTo: 'page-content',
706
1517
  dismissableMask: true,
707
1518
  dismissible: true,
708
- inputValues: {
709
- delegationForEdit: delegation,
710
- },
1519
+ inputValues: { delegationId: row.delegationId },
1520
+ });
1521
+ }
1522
+ openForm(row) {
1523
+ const ref = this.modal.openModal(DelegationForm, row ? 'drawer' : 'dialog', {
1524
+ header: this.transloco.translate(row ? 'delegations.action.edit' : 'delegations.action.create'),
1525
+ styleClass: row
1526
+ ? '!absolute !shadow-none !w-full !max-w-full sm:!w-[42rem] xl:!w-[50rem]'
1527
+ : '!w-[min(96vw,50rem)] !max-w-[96vw] !h-[min(90vh,40rem)] !max-h-[90vh]',
1528
+ position: row ? 'end' : '',
1529
+ appendTo: row ? 'page-content' : 'body',
1530
+ dismissableMask: true,
1531
+ dismissible: true,
1532
+ inputValues: { delegationForEdit: row, readonly: false },
1533
+ });
1534
+ ref.onClose.subscribe((saved) => {
1535
+ if (saved)
1536
+ this.reloadCurrentTab();
1537
+ });
1538
+ }
1539
+ approve(row) {
1540
+ this.mark(`approve:${row.delegationId}`, true);
1541
+ this.facade
1542
+ .approve(row.delegationId, { rowVersion: row.rowVersion })
1543
+ .pipe(finalize(() => this.mark(`approve:${row.delegationId}`, false)))
1544
+ .subscribe({ next: () => this.reloadCurrentTab() });
1545
+ }
1546
+ reject(row) {
1547
+ this.mark(`reject:${row.delegationId}`, true);
1548
+ this.facade
1549
+ .reject(row.delegationId, {
1550
+ rowVersion: row.rowVersion,
1551
+ reason: this.transloco.translate('delegations.action.reject'),
1552
+ })
1553
+ .pipe(finalize(() => this.mark(`reject:${row.delegationId}`, false)))
1554
+ .subscribe({ next: () => this.reloadCurrentTab() });
1555
+ }
1556
+ cancel(row) {
1557
+ this.mark(`cancel:${row.delegationId}`, true);
1558
+ this.facade
1559
+ .cancel(row.delegationId, row.rowVersion)
1560
+ .pipe(finalize(() => this.mark(`cancel:${row.delegationId}`, false)))
1561
+ .subscribe({ next: () => this.reloadCurrentTab() });
1562
+ }
1563
+ startSession(row) {
1564
+ this.modal.openModal(StartSessionDialog, 'dialog', {
1565
+ header: this.transloco.translate('delegations.action.startSession'),
1566
+ styleClass: '!w-[min(96vw,28rem)] !max-w-[96vw]',
1567
+ dismissableMask: true,
1568
+ dismissible: true,
1569
+ inputValues: { delegation: row, intent: 'start' },
711
1570
  });
712
1571
  }
1572
+ mark(key, on) {
1573
+ this.busyIds.update((ids) => on ? [...ids, key] : ids.filter((id) => id !== key));
1574
+ }
713
1575
  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"] }] });
1576
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationsList, isStandalone: true, selector: "mt-delegations-list", inputs: { showBreadcrumb: { classPropertyName: "showBreadcrumb", publicName: "showBreadcrumb", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "statusCol", first: true, predicate: ["statusCol"], descendants: true, isSignal: true }, { propertyName: "userCol", first: true, predicate: ["userCol"], descendants: true, isSignal: true }, { propertyName: "scopeCol", first: true, predicate: ["scopeCol"], descendants: true, isSignal: true }, { propertyName: "daysCol", first: true, predicate: ["daysCol"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col h-full gap-2 p-4\">\n @if (showBreadcrumb()) {\n <mt-breadcrumb [items]=\"breadcrumbItems()\"></mt-breadcrumb>\n }\n\n <div class=\"flex items-center gap-2 border-b border-gray-200\">\n @for (tab of tabs(); track tab.value) {\n <button\n type=\"button\"\n class=\"px-4 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\n [class.border-primary]=\"activeTab() === tab.value\"\n [class.text-primary]=\"activeTab() === tab.value\"\n [class.border-transparent]=\"activeTab() !== tab.value\"\n [class.text-gray-500]=\"activeTab() !== tab.value\"\n (click)=\"switchTab(tab.value)\"\n >\n {{ tab.label }}\n </button>\n }\n </div>\n\n <mt-table\n [data]=\"rows()\"\n [columns]=\"tableColumns()\"\n [actions]=\"tableActions()\"\n [rowActions]=\"rowActions()\"\n [loading]=\"isLoading()\"\n >\n <ng-template #statusCol let-row>\n <mt-delegation-status-chip\n [status]=\"row.effectiveStatus\"\n ></mt-delegation-status-chip>\n </ng-template>\n\n <ng-template #userCol let-row>\n @let party = activeTab() === \"my\" ? row.delegatedUser : row.delegator;\n <div class=\"flex items-center gap-2\">\n <mt-avatar icon=\"user.user-01\" styleClass=\"w-8! h-8!\"></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <span class=\"text-sm text-gray-900 truncate\">\n {{ party?.displayName }}\n </span>\n @if (party?.email) {\n <span class=\"text-xs text-gray-500 truncate\">\n {{ party.email }}\n </span>\n }\n </div>\n </div>\n </ng-template>\n\n <ng-template #scopeCol let-row>\n <span class=\"text-sm text-gray-700\" [title]=\"row.scopeSummary\">\n {{ row.scopeSummary || \"\u2014\" }}\n </span>\n </ng-template>\n\n <ng-template #daysCol let-row>\n <span class=\"text-sm text-gray-700\">{{ formatDays(row) }}</span>\n </ng-template>\n </mt-table>\n </div>\n</ng-container>\n", styles: [":host{display:block;min-width:0}:host ::ng-deep mt-table .p-datatable-header>div,:host ::ng-deep mt-table .p-datatable-header>div>div{gap:.75rem;min-width:0;flex-wrap:wrap}:host ::ng-deep mt-table .p-datatable-header mt-tabs{display:block;max-width:100%;overflow-x:auto;padding-bottom:.25rem}:host ::ng-deep mt-table .p-datatable-header mt-text-field{display:block;flex:1 1 16rem;min-width:min(100%,16rem)}:host ::ng-deep mt-table .p-datatable-header .p-inputtext,:host ::ng-deep mt-table .p-datatable-header .p-inputwrapper,:host ::ng-deep mt-table .p-datatable-header .p-selectbutton{width:100%;max-width:100%}@media(max-width:1024px){:host ::ng-deep mt-table .p-datatable-header>div{flex-direction:column;align-items:stretch}:host ::ng-deep mt-table .p-datatable-header>div>div{width:100%;justify-content:flex-start}:host ::ng-deep mt-table .p-datatable-header mt-text-field{flex-basis:100%;min-width:0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { kind: "component", type: Breadcrumb, selector: "mt-breadcrumb", inputs: ["items", "styleClass"], outputs: ["onItemClick"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "filterMode", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "exportable", "printable", "groupable", "cellClickFilter", "freezeActions", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationStatusChip, selector: "mt-delegation-status-chip", inputs: ["status"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
715
1577
  }
716
1578
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsList, decorators: [{
717
1579
  type: Component,
718
1580
  args: [{ selector: 'mt-delegations-list', standalone: true, imports: [
719
1581
  CommonModule,
720
- SkeletonModule,
721
- Table,
722
1582
  Avatar,
723
- TranslocoDirective,
724
1583
  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 }] }] } });
1584
+ Table,
1585
+ TranslocoDirective,
1586
+ DelegationStatusChip,
1587
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col h-full gap-2 p-4\">\n @if (showBreadcrumb()) {\n <mt-breadcrumb [items]=\"breadcrumbItems()\"></mt-breadcrumb>\n }\n\n <div class=\"flex items-center gap-2 border-b border-gray-200\">\n @for (tab of tabs(); track tab.value) {\n <button\n type=\"button\"\n class=\"px-4 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\n [class.border-primary]=\"activeTab() === tab.value\"\n [class.text-primary]=\"activeTab() === tab.value\"\n [class.border-transparent]=\"activeTab() !== tab.value\"\n [class.text-gray-500]=\"activeTab() !== tab.value\"\n (click)=\"switchTab(tab.value)\"\n >\n {{ tab.label }}\n </button>\n }\n </div>\n\n <mt-table\n [data]=\"rows()\"\n [columns]=\"tableColumns()\"\n [actions]=\"tableActions()\"\n [rowActions]=\"rowActions()\"\n [loading]=\"isLoading()\"\n >\n <ng-template #statusCol let-row>\n <mt-delegation-status-chip\n [status]=\"row.effectiveStatus\"\n ></mt-delegation-status-chip>\n </ng-template>\n\n <ng-template #userCol let-row>\n @let party = activeTab() === \"my\" ? row.delegatedUser : row.delegator;\n <div class=\"flex items-center gap-2\">\n <mt-avatar icon=\"user.user-01\" styleClass=\"w-8! h-8!\"></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <span class=\"text-sm text-gray-900 truncate\">\n {{ party?.displayName }}\n </span>\n @if (party?.email) {\n <span class=\"text-xs text-gray-500 truncate\">\n {{ party.email }}\n </span>\n }\n </div>\n </div>\n </ng-template>\n\n <ng-template #scopeCol let-row>\n <span class=\"text-sm text-gray-700\" [title]=\"row.scopeSummary\">\n {{ row.scopeSummary || \"\u2014\" }}\n </span>\n </ng-template>\n\n <ng-template #daysCol let-row>\n <span class=\"text-sm text-gray-700\">{{ formatDays(row) }}</span>\n </ng-template>\n </mt-table>\n </div>\n</ng-container>\n", styles: [":host{display:block;min-width:0}:host ::ng-deep mt-table .p-datatable-header>div,:host ::ng-deep mt-table .p-datatable-header>div>div{gap:.75rem;min-width:0;flex-wrap:wrap}:host ::ng-deep mt-table .p-datatable-header mt-tabs{display:block;max-width:100%;overflow-x:auto;padding-bottom:.25rem}:host ::ng-deep mt-table .p-datatable-header mt-text-field{display:block;flex:1 1 16rem;min-width:min(100%,16rem)}:host ::ng-deep mt-table .p-datatable-header .p-inputtext,:host ::ng-deep mt-table .p-datatable-header .p-inputwrapper,:host ::ng-deep mt-table .p-datatable-header .p-selectbutton{width:100%;max-width:100%}@media(max-width:1024px){:host ::ng-deep mt-table .p-datatable-header>div{flex-direction:column;align-items:stretch}:host ::ng-deep mt-table .p-datatable-header>div>div{width:100%;justify-content:flex-start}:host ::ng-deep mt-table .p-datatable-header mt-text-field{flex-basis:100%;min-width:0}}\n"] }]
1588
+ }], propDecorators: { showBreadcrumb: [{ type: i0.Input, args: [{ isSignal: true, alias: "showBreadcrumb", required: false }] }], statusCol: [{ type: i0.ViewChild, args: ['statusCol', { isSignal: true }] }], userCol: [{ type: i0.ViewChild, args: ['userCol', { isSignal: true }] }], scopeCol: [{ type: i0.ViewChild, args: ['scopeCol', { isSignal: true }] }], daysCol: [{ type: i0.ViewChild, args: ['daysCol', { isSignal: true }] }] } });
1589
+
1590
+ /**
1591
+ * Endpoints that must NEVER carry the `app-delegation` header.
1592
+ * Starting a delegated session uses only the normal `Authorization` token
1593
+ * (doc 05). All other `identity/delegations` management calls are normal
1594
+ * (non-delegated) actions too, so the whole namespace is excluded.
1595
+ */
1596
+ const EXCLUDE = /\bidentity\/delegations\b/i;
1597
+ /**
1598
+ * Adds `app-delegation: Bearer <token>` to outgoing requests while a delegated
1599
+ * session is active. Register AFTER the gateway-auth interceptor (so the normal
1600
+ * `Authorization` header is set first) and BEFORE the message interceptor.
1601
+ */
1602
+ const appDelegationInterceptor = (req, next) => {
1603
+ if (EXCLUDE.test(req.url)) {
1604
+ return next(req);
1605
+ }
1606
+ const token = inject(DelegationSessionFacade).token();
1607
+ if (!token) {
1608
+ return next(req);
1609
+ }
1610
+ return next(req.clone({ setHeaders: { 'app-delegation': `Bearer ${token}` } }));
1611
+ };
728
1612
 
729
1613
  // store/index.ts
730
1614
 
1615
+ // Runtime UI surfaces — projected into host topbars + opened via ModalService.
1616
+
731
1617
  /**
732
1618
  * Generated bundle index. Do not edit.
733
1619
  */
734
1620
 
735
- export { AddDelegation, ClearSelectedDelegation, DelegationForm, Delegations, DelegationsActionKey, DelegationsFacade, DelegationsList, DelegationsState, DeleteDelegation, GetDelegation, GetDelegations, UpdateDelegation };
1621
+ export { ApproveDelegation, CancelDelegation, ClearDelegationDetail, ClearScopePreview, CreateDelegationLegacy, CreateDelegationV2, DelegationDetailDrawer, DelegationForm, DelegationSessionActionKey, DelegationSessionFacade, DelegationSessionState, DelegationStatusChip, Delegations, DelegationsActionKey, DelegationsFacade, DelegationsList, DelegationsState, EndDelegationSession, GetActiveAssignedDelegations, GetAssignedDelegations, GetDelegationDetail, GetMyDelegations, GetScopeOptions, LoadDelegationCandidates, PreviewScope, RejectDelegation, ScopePicker, StartDelegationSession, StartSessionDialog, SwitchDelegationSession, TopbarDelegationMenu, UpdateDelegationLegacy, UpdateDelegationV2, appDelegationInterceptor };
736
1622
  //# sourceMappingURL=masterteam-delegations.mjs.map