@masterteam/delegations 0.0.11 → 0.0.13

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,58 +401,152 @@ 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;
426
+ }
427
+ }
428
+ class GetActiveAssignedDelegations {
429
+ query;
430
+ static type = '[Delegations] Get Active Assigned Delegations';
431
+ constructor(query = {}) {
432
+ this.query = query;
55
433
  }
56
434
  }
57
- class UpdateDelegation {
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
+ }
82
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
+ }
531
+ }
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 = {}));
83
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;
@@ -87,20 +554,54 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
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 }),
133
668
  });
134
669
  }
135
- addDelegation(ctx, { delegation }) {
136
- const req$ = this.http.post('identity/delegations', delegation);
137
- return this.create(ctx, {
138
- key: DelegationsActionKey.AddDelegation,
670
+ previewScope(ctx, { scope }) {
671
+ const req$ = this.http.post(`${BASE}/scope/preview`, { scope });
672
+ return handleApiRequest({
673
+ ctx,
674
+ key: DelegationsActionKey.PreviewScope,
139
675
  request$: req$,
140
- stateProperty: (state) => state.allDelegations,
676
+ onSuccess: (response) => ({ scopePreview: response.data ?? null }),
141
677
  });
142
678
  }
143
- updateDelegation(ctx, { id, delegation }) {
144
- const req$ = this.http.put(`identity/delegations/${id}`, delegation);
145
- return this.update(ctx, {
146
- key: DelegationsActionKey.UpdateDelegation,
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,
147
690
  request$: req$,
148
- uniqueKey: 'id',
149
- id,
150
- stateProperty: (state) => state.allDelegations,
691
+ onSuccess: () => ({}),
151
692
  });
152
693
  }
153
- deleteDelegation(ctx, { id }) {
154
- const req$ = this.http.delete(`identity/delegations/${id}`);
155
- return this.delete(ctx, {
156
- key: DelegationsActionKey.DeleteDelegation,
694
+ createV2(ctx, { request }) {
695
+ const req$ = this.http.post(`${BASE}/v2`, request);
696
+ return handleApiRequest({
697
+ ctx,
698
+ key: DelegationsActionKey.Create,
157
699
  request$: req$,
158
- uniqueKey: 'id',
159
- id,
160
- stateProperty: (state) => state.allDelegations,
700
+ onSuccess: () => ({}),
161
701
  });
162
702
  }
163
- clearSelectedDelegation(ctx) {
164
- ctx.patchState({ selectedDelegation: null });
703
+ updateLegacy(ctx, { id, request }) {
704
+ const req$ = this.http.put(`${BASE}/${id}`, request);
705
+ return handleApiRequest({
706
+ ctx,
707
+ key: DelegationsActionKey.Update,
708
+ request$: req$,
709
+ onSuccess: () => ({}),
710
+ });
711
+ }
712
+ updateV2(ctx, { id, request }) {
713
+ const req$ = this.http.put(`${BASE}/v2/${id}`, request);
714
+ return handleApiRequest({
715
+ ctx,
716
+ key: DelegationsActionKey.Update,
717
+ request$: req$,
718
+ onSuccess: () => ({}),
719
+ });
165
720
  }
166
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsState, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
721
+ // ============================================================================
722
+ // Decisions
723
+ // ============================================================================
724
+ approve(ctx, { id, request }) {
725
+ const req$ = this.http.post(`${BASE}/${id}/approve`, request);
726
+ return handleApiRequest({
727
+ ctx,
728
+ key: DelegationsActionKey.Approve,
729
+ request$: req$,
730
+ onSuccess: () => ({}),
731
+ });
732
+ }
733
+ reject(ctx, { id, request }) {
734
+ const req$ = this.http.post(`${BASE}/${id}/reject`, request);
735
+ return handleApiRequest({
736
+ ctx,
737
+ key: DelegationsActionKey.Reject,
738
+ request$: req$,
739
+ onSuccess: () => ({}),
740
+ });
741
+ }
742
+ cancel(ctx, { id, rowVersion, asAdmin }) {
743
+ const path = asAdmin ? `${BASE}/Admin/${id}` : `${BASE}/${id}`;
744
+ const req$ = this.http.delete(path, {
745
+ params: new HttpParams().set('rowVersion', rowVersion),
746
+ });
747
+ return handleApiRequest({
748
+ ctx,
749
+ key: DelegationsActionKey.Cancel,
750
+ request$: req$,
751
+ onSuccess: () => ({}),
752
+ });
753
+ }
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);
763
+ __decorate([
764
+ Action(GetActiveAssignedDelegations)
765
+ ], DelegationsState.prototype, "getActive", null);
172
766
  __decorate([
173
- Action(GetDelegation)
174
- ], DelegationsState.prototype, "getDelegation", null);
767
+ Action(GetDelegationDetail)
768
+ ], DelegationsState.prototype, "getDetail", null);
175
769
  __decorate([
176
- Action(AddDelegation)
177
- ], DelegationsState.prototype, "addDelegation", null);
770
+ Action(ClearDelegationDetail)
771
+ ], DelegationsState.prototype, "clearDetail", null);
178
772
  __decorate([
179
- Action(UpdateDelegation)
180
- ], DelegationsState.prototype, "updateDelegation", null);
773
+ Action(GetScopeOptions)
774
+ ], DelegationsState.prototype, "getScopeOptions", null);
181
775
  __decorate([
182
- Action(DeleteDelegation)
183
- ], DelegationsState.prototype, "deleteDelegation", null);
776
+ Action(PreviewScope)
777
+ ], DelegationsState.prototype, "previewScope", null);
184
778
  __decorate([
185
- Action(ClearSelectedDelegation)
186
- ], DelegationsState.prototype, "clearSelectedDelegation", null);
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);
187
802
  __decorate([
188
803
  Selector()
189
- ], DelegationsState, "getAllDelegations", null);
804
+ ], DelegationsState, "getMy", null);
190
805
  __decorate([
191
806
  Selector()
192
- ], DelegationsState, "getSelectedDelegation", null);
807
+ ], DelegationsState, "getAssigned", null);
808
+ __decorate([
809
+ Selector()
810
+ ], DelegationsState, "getActive", null);
811
+ __decorate([
812
+ Selector()
813
+ ], DelegationsState, "getDetail", null);
814
+ __decorate([
815
+ Selector()
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));
892
+ }
893
+ getAssigned(query = {}) {
894
+ return this.store.dispatch(new GetAssignedDelegations(query));
252
895
  }
253
- addDelegation(delegation) {
254
- return this.store.dispatch(new AddDelegation(delegation));
896
+ getActive(query = {}) {
897
+ return this.store.dispatch(new GetActiveAssignedDelegations(query));
255
898
  }
256
- updateDelegation(id, delegation) {
257
- return this.store.dispatch(new UpdateDelegation(id, delegation));
899
+ getDetail(id) {
900
+ return this.store.dispatch(new GetDelegationDetail(id));
258
901
  }
259
- deleteDelegation(id) {
260
- return this.store.dispatch(new DeleteDelegation(id));
902
+ clearDetail() {
903
+ return this.store.dispatch(new ClearDelegationDetail());
261
904
  }
262
- loadDelegation(id) {
263
- return this.store.dispatch(new GetDelegation(id));
905
+ loadScopeOptions(delegatorUserId) {
906
+ return this.store.dispatch(new GetScopeOptions(delegatorUserId));
264
907
  }
265
- clearSelectedDelegation() {
266
- return this.store.dispatch(new ClearSelectedDelegation());
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,155 @@ 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');
521
1322
  facade = inject(DelegationsFacade);
522
1323
  modal = inject(ModalService);
523
- translocoService = inject(TranslocoService);
1324
+ transloco = inject(TranslocoService);
524
1325
  breadcrumbItems = linkedSignal(() => [
525
1326
  {
526
1327
  label: '',
@@ -528,190 +1329,287 @@ class DelegationsList {
528
1329
  routerLink: '/control-panel/workspaces',
529
1330
  },
530
1331
  {
531
- label: this.translocoService.translate('product-settings.product-settings'),
1332
+ label: this.transloco.translate('product-settings.product-settings'),
532
1333
  routerLink: '/control-panel/product-settings',
533
1334
  },
534
1335
  {
535
- label: this.translocoService.translate('delegations.delegations'),
1336
+ label: this.transloco.translate('delegations.title'),
536
1337
  },
537
1338
  ], ...(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 */ []));
1339
+ activeTab = signal('my', ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
553
1340
  tabs = signal([
554
1341
  {
555
- label: this.translocoService.translate('delegations.all'),
556
- value: 'all',
557
- },
558
- {
559
- label: this.translocoService.translate('delegations.active'),
560
- value: 'active',
1342
+ value: 'my',
1343
+ label: this.transloco.translate('delegations.myDelegations'),
561
1344
  },
562
1345
  {
563
- label: this.translocoService.translate('delegations.inactive'),
564
- value: 'inactive',
1346
+ value: 'assigned',
1347
+ label: this.transloco.translate('delegations.delegatedToMe'),
565
1348
  },
566
1349
  ], ...(ngDevMode ? [{ debugName: "tabs" }] : /* istanbul ignore next */ []));
567
- activeTab = signal('all', ...(ngDevMode ? [{ debugName: "activeTab" }] : /* istanbul ignore next */ []));
1350
+ isLoading = computed(() => this.activeTab() === 'my'
1351
+ ? this.facade.isLoadingMy()
1352
+ : this.facade.isLoadingAssigned(), ...(ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []));
1353
+ rows = computed(() => this.activeTab() === 'my'
1354
+ ? this.facade.myItems()
1355
+ : this.facade.assignedItems(), ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
1356
+ statusCol = viewChild.required('statusCol');
1357
+ userCol = viewChild.required('userCol');
1358
+ scopeCol = viewChild.required('scopeCol');
1359
+ daysCol = viewChild.required('daysCol');
568
1360
  tableActions = signal([
569
1361
  {
570
1362
  icon: 'general.plus',
571
- label: this.translocoService.translate('delegations.add-delegation'),
1363
+ label: this.transloco.translate('delegations.action.create'),
572
1364
  color: 'primary',
573
- action: () => {
574
- this.addDelegationDialog();
575
- },
1365
+ action: () => this.openForm(null),
576
1366
  },
577
1367
  ], ...(ngDevMode ? [{ debugName: "tableActions" }] : /* istanbul ignore next */ []));
578
- deletingRowIds = signal([], ...(ngDevMode ? [{ debugName: "deletingRowIds" }] : /* istanbul ignore next */ []));
579
1368
  rowActions = signal([
1369
+ {
1370
+ icon: 'general.eye',
1371
+ tooltip: this.transloco.translate('delegations.action.view'),
1372
+ color: 'primary',
1373
+ variant: 'outlined',
1374
+ action: (row) => this.viewDetails(row),
1375
+ hidden: (row) => !this.has(row, 'View'),
1376
+ },
580
1377
  {
581
1378
  icon: 'custom.pencil',
582
- tooltip: this.translocoService.translate('edit'),
1379
+ tooltip: this.transloco.translate('delegations.action.edit'),
583
1380
  color: 'primary',
584
- action: (row) => {
585
- this.facade.clearSelectedDelegation();
586
- this.addDelegationDialog(row);
587
- },
1381
+ action: (row) => this.openForm(row),
1382
+ hidden: (row) => !this.has(row, 'Edit'),
1383
+ },
1384
+ {
1385
+ icon: 'general.check',
1386
+ tooltip: this.transloco.translate('delegations.action.approve'),
1387
+ color: 'primary',
1388
+ action: (row) => this.approve(row),
1389
+ hidden: (row) => !this.has(row, 'Approve'),
1390
+ loading: (row) => this.busyIds().includes(`approve:${row.delegationId}`),
1391
+ confirmation: { type: 'popup' },
1392
+ },
1393
+ {
1394
+ icon: 'general.x-close',
1395
+ tooltip: this.transloco.translate('delegations.action.reject'),
1396
+ color: 'danger',
1397
+ variant: 'outlined',
1398
+ action: (row) => this.reject(row),
1399
+ hidden: (row) => !this.has(row, 'Reject'),
1400
+ loading: (row) => this.busyIds().includes(`reject:${row.delegationId}`),
1401
+ confirmation: { type: 'popup' },
588
1402
  },
589
1403
  {
590
1404
  icon: 'general.trash-01',
591
- tooltip: this.translocoService.translate('delete'),
1405
+ tooltip: this.transloco.translate('delegations.action.cancel'),
592
1406
  color: 'danger',
593
1407
  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),
1408
+ action: (row) => this.cancel(row),
1409
+ hidden: (row) => !this.has(row, 'Cancel'),
1410
+ loading: (row) => this.busyIds().includes(`cancel:${row.delegationId}`),
1411
+ confirmation: { type: 'popup', confirmationType: 'delete' },
1412
+ },
1413
+ {
1414
+ icon: 'arrow.arrow-right',
1415
+ tooltip: this.transloco.translate('delegations.action.startSession'),
1416
+ color: 'primary',
1417
+ action: (row) => this.startSession(row),
1418
+ hidden: (row) => !this.has(row, 'StartSession'),
608
1419
  },
609
1420
  ], ...(ngDevMode ? [{ debugName: "rowActions" }] : /* istanbul ignore next */ []));
610
1421
  tableColumns = linkedSignal(() => [
611
1422
  {
612
- key: 'isActive',
613
- label: this.translocoService.translate('delegations.status'),
1423
+ key: 'effectiveStatus',
1424
+ label: this.transloco.translate('delegations.column.status'),
614
1425
  type: 'custom',
615
1426
  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
1427
  },
631
1428
  {
632
- key: 'delegateFrom.displayName',
633
- label: this.translocoService.translate('delegations.originally-assigned-to'),
1429
+ key: this.activeTab() === 'my' ? 'delegatedUser' : 'delegator',
1430
+ label: this.transloco.translate(this.activeTab() === 'my'
1431
+ ? 'delegations.column.delegatedTo'
1432
+ : 'delegations.column.delegatorName'),
634
1433
  type: 'custom',
635
1434
  customCellTpl: this.userCol(),
636
1435
  },
637
1436
  {
638
- key: 'delegateTo.displayName',
639
- label: this.translocoService.translate('delegations.delegated-to'),
1437
+ key: 'scopeSummary',
1438
+ label: this.transloco.translate('delegations.column.scopeSummary'),
640
1439
  type: 'custom',
641
- customCellTpl: this.userCol2(),
1440
+ customCellTpl: this.scopeCol(),
642
1441
  },
643
1442
  {
644
- key: 'delegateFromDateTime',
645
- label: this.translocoService.translate('delegations.start-date'),
1443
+ key: 'startsAtUtc',
1444
+ label: this.transloco.translate('delegations.column.startDate'),
646
1445
  type: 'dateTime',
647
- filterConfig: {
648
- type: 'date',
649
- label: this.translocoService.translate('delegations.start-date'),
650
- },
651
1446
  },
652
1447
  {
653
- key: 'delegateToDateTime',
654
- label: this.translocoService.translate('delegations.end-date'),
1448
+ key: 'endsAtUtc',
1449
+ label: this.transloco.translate('delegations.column.endDate'),
655
1450
  type: 'dateTime',
656
- filterConfig: {
657
- type: 'date',
658
- label: this.translocoService.translate('delegations.end-date'),
659
- },
660
1451
  },
661
1452
  {
662
- key: 'days',
663
- label: this.translocoService.translate('delegations.days'),
1453
+ key: 'createdAtUtc',
1454
+ label: this.transloco.translate('delegations.column.createdDate'),
1455
+ type: 'dateTime',
1456
+ },
1457
+ {
1458
+ key: 'dayRuleMode',
1459
+ label: this.transloco.translate('delegations.column.days'),
664
1460
  type: 'custom',
665
1461
  customCellTpl: this.daysCol(),
666
1462
  },
667
1463
  ], ...(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;
1464
+ busyIds = signal([], ...(ngDevMode ? [{ debugName: "busyIds" }] : /* istanbul ignore next */ []));
1465
+ weekdayKeys = [
1466
+ 'sunday',
1467
+ 'monday',
1468
+ 'tuesday',
1469
+ 'wednesday',
1470
+ 'thursday',
1471
+ 'friday',
1472
+ 'saturday',
1473
+ ];
672
1474
  ngOnInit() {
673
- this.facade.getDelegations();
674
- }
675
- addDelegationDialog(delegation = null) {
676
- const modalType = delegation ? 'drawer' : 'dialog';
677
- const styleClass = modalType === 'drawer'
678
- ? '!absolute !shadow-none !w-full !max-w-full sm:!w-[42rem] xl:!w-[50rem]'
679
- : '!w-[min(96vw,50rem)] !max-w-[96vw]';
680
- this.modal.openModal(DelegationForm, modalType, {
681
- header: delegation
682
- ? this.translocoService.translate('delegations.edit-delegation')
683
- : this.translocoService.translate('delegations.add-delegation'),
684
- styleClass,
685
- position: modalType === 'drawer' ? 'end' : '',
686
- appendTo: modalType === 'drawer' ? 'page-content' : 'body',
1475
+ this.loadCurrentTab();
1476
+ }
1477
+ switchTab(tab) {
1478
+ this.activeTab.set(tab);
1479
+ this.loadCurrentTab();
1480
+ }
1481
+ loadCurrentTab(query = { page: 1, pageSize: 25 }) {
1482
+ if (this.activeTab() === 'my') {
1483
+ this.facade.getMy(query);
1484
+ }
1485
+ else {
1486
+ this.facade.getAssigned(query);
1487
+ }
1488
+ }
1489
+ reloadCurrentTab() {
1490
+ this.loadCurrentTab();
1491
+ }
1492
+ has(row, action) {
1493
+ return row.allowedActions?.includes(action) ?? false;
1494
+ }
1495
+ formatDays(row) {
1496
+ if (row.dayRuleMode === 'SpecificDays' && row.specificDays?.length) {
1497
+ return row.specificDays
1498
+ .filter((d) => d >= 0 && d < this.weekdayKeys.length)
1499
+ .map((d) => this.transloco.translate(`delegations.days.${this.weekdayKeys[d]}`))
1500
+ .join(', ');
1501
+ }
1502
+ return this.transloco.translate('delegations.form.fullRange');
1503
+ }
1504
+ viewDetails(row) {
1505
+ this.modal.openModal(DelegationDetailDrawer, 'drawer', {
1506
+ header: this.transloco.translate('delegations.action.view'),
1507
+ styleClass: '!absolute !shadow-none !w-full !max-w-full sm:!w-[42rem] xl:!w-[50rem]',
1508
+ position: 'end',
1509
+ appendTo: 'page-content',
687
1510
  dismissableMask: true,
688
1511
  dismissible: true,
689
- inputValues: {
690
- delegationForEdit: delegation,
691
- },
1512
+ inputValues: { delegationId: row.delegationId },
1513
+ });
1514
+ }
1515
+ openForm(row) {
1516
+ const ref = this.modal.openModal(DelegationForm, row ? 'drawer' : 'dialog', {
1517
+ header: this.transloco.translate(row ? 'delegations.action.edit' : 'delegations.action.create'),
1518
+ styleClass: row
1519
+ ? '!absolute !shadow-none !w-full !max-w-full sm:!w-[42rem] xl:!w-[50rem]'
1520
+ : '!w-[min(96vw,50rem)] !max-w-[96vw] !h-[min(90vh,40rem)] !max-h-[90vh]',
1521
+ position: row ? 'end' : '',
1522
+ appendTo: row ? 'page-content' : 'body',
1523
+ dismissableMask: true,
1524
+ dismissible: true,
1525
+ inputValues: { delegationForEdit: row, readonly: false },
1526
+ });
1527
+ ref.onClose.subscribe((saved) => {
1528
+ if (saved)
1529
+ this.reloadCurrentTab();
1530
+ });
1531
+ }
1532
+ approve(row) {
1533
+ this.mark(`approve:${row.delegationId}`, true);
1534
+ this.facade
1535
+ .approve(row.delegationId, { rowVersion: row.rowVersion })
1536
+ .pipe(finalize(() => this.mark(`approve:${row.delegationId}`, false)))
1537
+ .subscribe({ next: () => this.reloadCurrentTab() });
1538
+ }
1539
+ reject(row) {
1540
+ this.mark(`reject:${row.delegationId}`, true);
1541
+ this.facade
1542
+ .reject(row.delegationId, {
1543
+ rowVersion: row.rowVersion,
1544
+ reason: this.transloco.translate('delegations.action.reject'),
1545
+ })
1546
+ .pipe(finalize(() => this.mark(`reject:${row.delegationId}`, false)))
1547
+ .subscribe({ next: () => this.reloadCurrentTab() });
1548
+ }
1549
+ cancel(row) {
1550
+ this.mark(`cancel:${row.delegationId}`, true);
1551
+ this.facade
1552
+ .cancel(row.delegationId, row.rowVersion)
1553
+ .pipe(finalize(() => this.mark(`cancel:${row.delegationId}`, false)))
1554
+ .subscribe({ next: () => this.reloadCurrentTab() });
1555
+ }
1556
+ startSession(row) {
1557
+ this.modal.openModal(StartSessionDialog, 'dialog', {
1558
+ header: this.transloco.translate('delegations.action.startSession'),
1559
+ styleClass: '!w-[min(96vw,28rem)] !max-w-[96vw]',
1560
+ dismissableMask: true,
1561
+ dismissible: true,
1562
+ inputValues: { delegation: row, intent: 'start' },
692
1563
  });
693
1564
  }
1565
+ mark(key, on) {
1566
+ this.busyIds.update((ids) => on ? [...ids, key] : ids.filter((id) => id !== key));
1567
+ }
694
1568
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsList, deps: [], target: i0.ɵɵFactoryTarget.Component });
695
- 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>{{ row.dayCounts }} {{ t(\"days\") }}</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"] }] });
1569
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationsList, isStandalone: true, selector: "mt-delegations-list", viewQueries: [{ propertyName: "statusCol", first: true, predicate: ["statusCol"], descendants: true, isSignal: true }, { propertyName: "userCol", first: true, predicate: ["userCol"], descendants: true, isSignal: true }, { propertyName: "scopeCol", first: true, predicate: ["scopeCol"], descendants: true, isSignal: true }, { propertyName: "daysCol", first: true, predicate: ["daysCol"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col h-full gap-2 p-4\">\n <mt-breadcrumb [items]=\"breadcrumbItems()\"></mt-breadcrumb>\n\n <div class=\"flex items-center gap-2 border-b border-gray-200\">\n @for (tab of tabs(); track tab.value) {\n <button\n type=\"button\"\n class=\"px-4 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\n [class.border-primary]=\"activeTab() === tab.value\"\n [class.text-primary]=\"activeTab() === tab.value\"\n [class.border-transparent]=\"activeTab() !== tab.value\"\n [class.text-gray-500]=\"activeTab() !== tab.value\"\n (click)=\"switchTab(tab.value)\"\n >\n {{ tab.label }}\n </button>\n }\n </div>\n\n <mt-table\n [data]=\"rows()\"\n [columns]=\"tableColumns()\"\n [actions]=\"tableActions()\"\n [rowActions]=\"rowActions()\"\n [loading]=\"isLoading()\"\n >\n <ng-template #statusCol let-row>\n <mt-delegation-status-chip\n [status]=\"row.effectiveStatus\"\n ></mt-delegation-status-chip>\n </ng-template>\n\n <ng-template #userCol let-row>\n @let party = activeTab() === \"my\" ? row.delegatedUser : row.delegator;\n <div class=\"flex items-center gap-2\">\n <mt-avatar icon=\"user.user-01\" styleClass=\"w-8! h-8!\"></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <span class=\"text-sm text-gray-900 truncate\">\n {{ party?.displayName }}\n </span>\n @if (party?.email) {\n <span class=\"text-xs text-gray-500 truncate\">\n {{ party.email }}\n </span>\n }\n </div>\n </div>\n </ng-template>\n\n <ng-template #scopeCol let-row>\n <span class=\"text-sm text-gray-700\" [title]=\"row.scopeSummary\">\n {{ row.scopeSummary || \"\u2014\" }}\n </span>\n </ng-template>\n\n <ng-template #daysCol let-row>\n <span class=\"text-sm text-gray-700\">{{ formatDays(row) }}</span>\n </ng-template>\n </mt-table>\n </div>\n</ng-container>\n", styles: [":host{display:block;min-width:0}:host ::ng-deep mt-table .p-datatable-header>div,:host ::ng-deep mt-table .p-datatable-header>div>div{gap:.75rem;min-width:0;flex-wrap:wrap}:host ::ng-deep mt-table .p-datatable-header mt-tabs{display:block;max-width:100%;overflow-x:auto;padding-bottom:.25rem}:host ::ng-deep mt-table .p-datatable-header mt-text-field{display:block;flex:1 1 16rem;min-width:min(100%,16rem)}:host ::ng-deep mt-table .p-datatable-header .p-inputtext,:host ::ng-deep mt-table .p-datatable-header .p-inputwrapper,:host ::ng-deep mt-table .p-datatable-header .p-selectbutton{width:100%;max-width:100%}@media(max-width:1024px){:host ::ng-deep mt-table .p-datatable-header>div{flex-direction:column;align-items:stretch}:host ::ng-deep mt-table .p-datatable-header>div>div{width:100%;justify-content:flex-start}:host ::ng-deep mt-table .p-datatable-header mt-text-field{flex-basis:100%;min-width:0}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: Avatar, selector: "mt-avatar", inputs: ["label", "icon", "image", "styleClass", "size", "shape", "badge", "badgeSize", "badgeSeverity"], outputs: ["onImageError"] }, { kind: "component", type: Breadcrumb, selector: "mt-breadcrumb", inputs: ["items", "styleClass"], outputs: ["onItemClick"] }, { kind: "component", type: Table, selector: "mt-table", inputs: ["filters", "data", "columns", "rowActions", "size", "showGridlines", "stripedRows", "selectableRows", "clickableRows", "generalSearch", "lazyLocalSearch", "showFilters", "filterMode", "loading", "updating", "lazy", "lazyLocalSort", "lazyTotalRecords", "reorderableColumns", "reorderableRows", "dataKey", "storageKey", "storageMode", "exportable", "printable", "groupable", "cellClickFilter", "freezeActions", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationStatusChip, selector: "mt-delegation-status-chip", inputs: ["status"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
696
1570
  }
697
1571
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsList, decorators: [{
698
1572
  type: Component,
699
1573
  args: [{ selector: 'mt-delegations-list', standalone: true, imports: [
700
1574
  CommonModule,
701
- SkeletonModule,
702
- Table,
703
1575
  Avatar,
704
- TranslocoDirective,
705
1576
  Breadcrumb,
706
- Icon,
707
- ], 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>{{ row.dayCounts }} {{ t(\"days\") }}</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"] }]
708
- }], 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 }] }] } });
1577
+ Table,
1578
+ TranslocoDirective,
1579
+ DelegationStatusChip,
1580
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div class=\"flex flex-col h-full gap-2 p-4\">\n <mt-breadcrumb [items]=\"breadcrumbItems()\"></mt-breadcrumb>\n\n <div class=\"flex items-center gap-2 border-b border-gray-200\">\n @for (tab of tabs(); track tab.value) {\n <button\n type=\"button\"\n class=\"px-4 py-2 -mb-px border-b-2 text-sm font-medium transition-colors\"\n [class.border-primary]=\"activeTab() === tab.value\"\n [class.text-primary]=\"activeTab() === tab.value\"\n [class.border-transparent]=\"activeTab() !== tab.value\"\n [class.text-gray-500]=\"activeTab() !== tab.value\"\n (click)=\"switchTab(tab.value)\"\n >\n {{ tab.label }}\n </button>\n }\n </div>\n\n <mt-table\n [data]=\"rows()\"\n [columns]=\"tableColumns()\"\n [actions]=\"tableActions()\"\n [rowActions]=\"rowActions()\"\n [loading]=\"isLoading()\"\n >\n <ng-template #statusCol let-row>\n <mt-delegation-status-chip\n [status]=\"row.effectiveStatus\"\n ></mt-delegation-status-chip>\n </ng-template>\n\n <ng-template #userCol let-row>\n @let party = activeTab() === \"my\" ? row.delegatedUser : row.delegator;\n <div class=\"flex items-center gap-2\">\n <mt-avatar icon=\"user.user-01\" styleClass=\"w-8! h-8!\"></mt-avatar>\n <div class=\"flex flex-col min-w-0\">\n <span class=\"text-sm text-gray-900 truncate\">\n {{ party?.displayName }}\n </span>\n @if (party?.email) {\n <span class=\"text-xs text-gray-500 truncate\">\n {{ party.email }}\n </span>\n }\n </div>\n </div>\n </ng-template>\n\n <ng-template #scopeCol let-row>\n <span class=\"text-sm text-gray-700\" [title]=\"row.scopeSummary\">\n {{ row.scopeSummary || \"\u2014\" }}\n </span>\n </ng-template>\n\n <ng-template #daysCol let-row>\n <span class=\"text-sm text-gray-700\">{{ formatDays(row) }}</span>\n </ng-template>\n </mt-table>\n </div>\n</ng-container>\n", styles: [":host{display:block;min-width:0}:host ::ng-deep mt-table .p-datatable-header>div,:host ::ng-deep mt-table .p-datatable-header>div>div{gap:.75rem;min-width:0;flex-wrap:wrap}:host ::ng-deep mt-table .p-datatable-header mt-tabs{display:block;max-width:100%;overflow-x:auto;padding-bottom:.25rem}:host ::ng-deep mt-table .p-datatable-header mt-text-field{display:block;flex:1 1 16rem;min-width:min(100%,16rem)}:host ::ng-deep mt-table .p-datatable-header .p-inputtext,:host ::ng-deep mt-table .p-datatable-header .p-inputwrapper,:host ::ng-deep mt-table .p-datatable-header .p-selectbutton{width:100%;max-width:100%}@media(max-width:1024px){:host ::ng-deep mt-table .p-datatable-header>div{flex-direction:column;align-items:stretch}:host ::ng-deep mt-table .p-datatable-header>div>div{width:100%;justify-content:flex-start}:host ::ng-deep mt-table .p-datatable-header mt-text-field{flex-basis:100%;min-width:0}}\n"] }]
1581
+ }], propDecorators: { statusCol: [{ type: i0.ViewChild, args: ['statusCol', { isSignal: true }] }], userCol: [{ type: i0.ViewChild, args: ['userCol', { isSignal: true }] }], scopeCol: [{ type: i0.ViewChild, args: ['scopeCol', { isSignal: true }] }], daysCol: [{ type: i0.ViewChild, args: ['daysCol', { isSignal: true }] }] } });
1582
+
1583
+ /**
1584
+ * Endpoints that must NEVER carry the `app-delegation` header.
1585
+ * Starting a delegated session uses only the normal `Authorization` token
1586
+ * (doc 05). All other `identity/delegations` management calls are normal
1587
+ * (non-delegated) actions too, so the whole namespace is excluded.
1588
+ */
1589
+ const EXCLUDE = /\bidentity\/delegations\b/i;
1590
+ /**
1591
+ * Adds `app-delegation: Bearer <token>` to outgoing requests while a delegated
1592
+ * session is active. Register AFTER the gateway-auth interceptor (so the normal
1593
+ * `Authorization` header is set first) and BEFORE the message interceptor.
1594
+ */
1595
+ const appDelegationInterceptor = (req, next) => {
1596
+ if (EXCLUDE.test(req.url)) {
1597
+ return next(req);
1598
+ }
1599
+ const token = inject(DelegationSessionFacade).token();
1600
+ if (!token) {
1601
+ return next(req);
1602
+ }
1603
+ return next(req.clone({ setHeaders: { 'app-delegation': `Bearer ${token}` } }));
1604
+ };
709
1605
 
710
1606
  // store/index.ts
711
1607
 
1608
+ // Runtime UI surfaces — projected into host topbars + opened via ModalService.
1609
+
712
1610
  /**
713
1611
  * Generated bundle index. Do not edit.
714
1612
  */
715
1613
 
716
- export { AddDelegation, ClearSelectedDelegation, DelegationForm, Delegations, DelegationsActionKey, DelegationsFacade, DelegationsList, DelegationsState, DeleteDelegation, GetDelegation, GetDelegations, UpdateDelegation };
1614
+ export { ApproveDelegation, CancelDelegation, ClearDelegationDetail, ClearScopePreview, CreateDelegationLegacy, CreateDelegationV2, DelegationDetailDrawer, DelegationForm, DelegationSessionActionKey, DelegationSessionFacade, DelegationSessionState, DelegationStatusChip, Delegations, DelegationsActionKey, DelegationsFacade, DelegationsList, DelegationsState, EndDelegationSession, GetActiveAssignedDelegations, GetAssignedDelegations, GetDelegationDetail, GetMyDelegations, GetScopeOptions, LoadDelegationCandidates, PreviewScope, RejectDelegation, ScopePicker, StartDelegationSession, StartSessionDialog, SwitchDelegationSession, TopbarDelegationMenu, UpdateDelegationLegacy, UpdateDelegationV2, appDelegationInterceptor };
717
1615
  //# sourceMappingURL=masterteam-delegations.mjs.map