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