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