@masterteam/delegations 0.0.54 → 0.0.56
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 +175 -167
- package/assets/i18n/en.json +175 -167
- package/fesm2022/masterteam-delegations.mjs +467 -166
- package/fesm2022/masterteam-delegations.mjs.map +1 -1
- package/package.json +3 -3
- package/types/masterteam-delegations.d.ts +152 -25
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import * as i1 from '@angular/common';
|
|
2
2
|
import { DOCUMENT, CommonModule, Location } from '@angular/common';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { InjectionToken, makeEnvironmentProviders, inject, Injectable, signal, computed, input, ChangeDetectionStrategy, Component, output, viewChild, effect, model,
|
|
4
|
+
import { InjectionToken, makeEnvironmentProviders, inject, Injectable, signal, computed, input, ChangeDetectionStrategy, Component, output, DestroyRef, viewChild, effect, model, untracked, booleanAttribute, numberAttribute, linkedSignal } from '@angular/core';
|
|
5
|
+
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
|
|
5
6
|
import { TranslocoService, TranslocoDirective } from '@jsverse/transloco';
|
|
7
|
+
import { Actions, Store, ofActionSuccessful, ofActionDispatched, Action, Selector, State, select } from '@ngxs/store';
|
|
6
8
|
import { Avatar } from '@masterteam/components/avatar';
|
|
7
9
|
import { ModalService } from '@masterteam/components/modal';
|
|
10
|
+
import { ToastService } from '@masterteam/components/toast';
|
|
8
11
|
import { Icon } from '@masterteam/icons';
|
|
9
12
|
import { Popover } from 'primeng/popover';
|
|
10
|
-
import {
|
|
11
|
-
import { switchMap, from, EMPTY, finalize, shareReplay, filter, catchError, throwError } from 'rxjs';
|
|
13
|
+
import { EMPTY, switchMap, from, finalize, shareReplay, filter, catchError, throwError } from 'rxjs';
|
|
12
14
|
import { HttpContextToken, HttpContext, HttpClient, HttpParams, HttpHeaders, HttpErrorResponse } from '@angular/common/http';
|
|
13
15
|
import { handleApiRequest, REQUEST_CONTEXT, UserSearchFieldConfig, TextareaFieldConfig, DateFieldConfig, RadioButtonFieldConfig, MultiSelectFieldConfig, ToggleFieldConfig, ValidatorConfig } from '@masterteam/components';
|
|
14
16
|
import { Button } from '@masterteam/components/button';
|
|
15
17
|
import { ModalRef } from '@masterteam/components/dialog';
|
|
16
18
|
import { EntityPreview } from '@masterteam/components/entities';
|
|
17
|
-
import { ToastService } from '@masterteam/components/toast';
|
|
18
19
|
import { RouterLink, ActivatedRoute, Router, NavigationEnd, RouterOutlet } from '@angular/router';
|
|
19
20
|
import { Chip } from '@masterteam/components/chip';
|
|
20
|
-
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
|
|
21
21
|
import { Page } from '@masterteam/components/page';
|
|
22
22
|
import { Breadcrumb } from '@masterteam/components/breadcrumb';
|
|
23
23
|
import { Card } from '@masterteam/components/card';
|
|
@@ -32,6 +32,54 @@ import { TextField } from '@masterteam/components/text-field';
|
|
|
32
32
|
import * as i4 from 'primeng/skeleton';
|
|
33
33
|
import { SkeletonModule } from 'primeng/skeleton';
|
|
34
34
|
|
|
35
|
+
/** Fetch the active delegations the current user may start (user/activedelegations). */
|
|
36
|
+
class LoadDelegationCandidates {
|
|
37
|
+
page;
|
|
38
|
+
pageSize;
|
|
39
|
+
append;
|
|
40
|
+
static type = '[DelegationSession] Load Candidates';
|
|
41
|
+
constructor(page = 1, pageSize = 25, append = false) {
|
|
42
|
+
this.page = page;
|
|
43
|
+
this.pageSize = pageSize;
|
|
44
|
+
this.append = append;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Start a delegated session for the given assignment row. */
|
|
48
|
+
class StartDelegationSession {
|
|
49
|
+
delegation;
|
|
50
|
+
static type = '[DelegationSession] Start';
|
|
51
|
+
constructor(delegation) {
|
|
52
|
+
this.delegation = delegation;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Re-mint the session the user was in before they reloaded the page. Dispatched
|
|
57
|
+
* by the state itself from the persisted delegation id — never a token.
|
|
58
|
+
*/
|
|
59
|
+
class ResumeDelegationSession {
|
|
60
|
+
delegation;
|
|
61
|
+
static type = '[DelegationSession] Resume';
|
|
62
|
+
constructor(delegation) {
|
|
63
|
+
this.delegation = delegation;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** End the current delegated session. Client-local only (doc 05). */
|
|
67
|
+
class EndDelegationSession {
|
|
68
|
+
reason;
|
|
69
|
+
static type = '[DelegationSession] End';
|
|
70
|
+
constructor(reason = 'Manual') {
|
|
71
|
+
this.reason = reason;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Switch directly from the current session to another delegation. */
|
|
75
|
+
class SwitchDelegationSession {
|
|
76
|
+
delegation;
|
|
77
|
+
static type = '[DelegationSession] Switch';
|
|
78
|
+
constructor(delegation) {
|
|
79
|
+
this.delegation = delegation;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
35
83
|
const DEFAULT_CONFIG = {
|
|
36
84
|
resolveApplicationApiBaseUrl: () => '',
|
|
37
85
|
resolvePromptNamespace: () => ({
|
|
@@ -58,7 +106,7 @@ function withDelegatedRuntime(context = new HttpContext()) {
|
|
|
58
106
|
return context.set(DELEGATED_RUNTIME_REQUEST, true);
|
|
59
107
|
}
|
|
60
108
|
|
|
61
|
-
const STORAGE_KEY = 'mt.delegation.prompt-receipts.v1';
|
|
109
|
+
const STORAGE_KEY$1 = 'mt.delegation.prompt-receipts.v1';
|
|
62
110
|
const MAX_RECEIPTS = 100;
|
|
63
111
|
class DelegationPromptReceiptService {
|
|
64
112
|
document = inject(DOCUMENT);
|
|
@@ -103,7 +151,7 @@ class DelegationPromptReceiptService {
|
|
|
103
151
|
}
|
|
104
152
|
read() {
|
|
105
153
|
try {
|
|
106
|
-
const raw = this.document.defaultView?.localStorage.getItem(STORAGE_KEY);
|
|
154
|
+
const raw = this.document.defaultView?.localStorage.getItem(STORAGE_KEY$1);
|
|
107
155
|
const parsed = raw ? JSON.parse(raw) : [];
|
|
108
156
|
return Array.isArray(parsed)
|
|
109
157
|
? parsed.filter((item) => typeof item === 'string')
|
|
@@ -116,7 +164,7 @@ class DelegationPromptReceiptService {
|
|
|
116
164
|
write(receipts) {
|
|
117
165
|
try {
|
|
118
166
|
const bounded = [...new Set(receipts)].slice(-MAX_RECEIPTS);
|
|
119
|
-
this.document.defaultView?.localStorage.setItem(STORAGE_KEY, JSON.stringify(bounded));
|
|
167
|
+
this.document.defaultView?.localStorage.setItem(STORAGE_KEY$1, JSON.stringify(bounded));
|
|
120
168
|
}
|
|
121
169
|
catch {
|
|
122
170
|
// Storage can be unavailable; the in-memory claim still coalesces dialogs.
|
|
@@ -130,49 +178,99 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
130
178
|
args: [{ providedIn: 'root' }]
|
|
131
179
|
}] });
|
|
132
180
|
|
|
133
|
-
/** Fetch the active delegations the current user may start (user/activedelegations). */
|
|
134
|
-
class LoadDelegationCandidates {
|
|
135
|
-
page;
|
|
136
|
-
pageSize;
|
|
137
|
-
append;
|
|
138
|
-
static type = '[DelegationSession] Load Candidates';
|
|
139
|
-
constructor(page = 1, pageSize = 25, append = false) {
|
|
140
|
-
this.page = page;
|
|
141
|
-
this.pageSize = pageSize;
|
|
142
|
-
this.append = append;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
/** Start a delegated session for the given assignment row. */
|
|
146
|
-
class StartDelegationSession {
|
|
147
|
-
delegation;
|
|
148
|
-
static type = '[DelegationSession] Start';
|
|
149
|
-
constructor(delegation) {
|
|
150
|
-
this.delegation = delegation;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
/** End the current delegated session. Client-local only (doc 05). */
|
|
154
|
-
class EndDelegationSession {
|
|
155
|
-
reason;
|
|
156
|
-
static type = '[DelegationSession] End';
|
|
157
|
-
constructor(reason = 'Manual') {
|
|
158
|
-
this.reason = reason;
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
/** Switch directly from the current session to another delegation. */
|
|
162
|
-
class SwitchDelegationSession {
|
|
163
|
-
delegation;
|
|
164
|
-
static type = '[DelegationSession] Switch';
|
|
165
|
-
constructor(delegation) {
|
|
166
|
-
this.delegation = delegation;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
181
|
var DelegationSessionActionKey;
|
|
171
182
|
(function (DelegationSessionActionKey) {
|
|
172
183
|
DelegationSessionActionKey["LoadCandidates"] = "loadCandidates";
|
|
173
184
|
DelegationSessionActionKey["StartSession"] = "startSession";
|
|
174
185
|
})(DelegationSessionActionKey || (DelegationSessionActionKey = {}));
|
|
175
186
|
|
|
187
|
+
const STORAGE_KEY = 'mt.delegation.session-resume.v1';
|
|
188
|
+
/**
|
|
189
|
+
* Lets a delegated session survive a page reload **without persisting the
|
|
190
|
+
* token**.
|
|
191
|
+
*
|
|
192
|
+
* Doc 05 is unambiguous: the delegation access token is memory-only, and it
|
|
193
|
+
* stays that way — this store keeps a single opaque `delegationId` plus the
|
|
194
|
+
* owning user/app/tenant namespace. On the next boot the runtime calls
|
|
195
|
+
* `POST identity/delegations/{id}/session` again and mints a *fresh* token,
|
|
196
|
+
* which is what doc 09 already prescribes for restarting a session ("use the
|
|
197
|
+
* newly returned token because the scope hash or delegation version may have
|
|
198
|
+
* changed"). The backend re-authorizes from scratch every time, so a delegation
|
|
199
|
+
* that expired, was cancelled, or had its scope revoked while the tab was shut
|
|
200
|
+
* simply fails to resume and the user stays themselves.
|
|
201
|
+
*
|
|
202
|
+
* Never holds a token, a candidate record, a name, or an email.
|
|
203
|
+
*/
|
|
204
|
+
class DelegationSessionResumeStore {
|
|
205
|
+
document = inject(DOCUMENT);
|
|
206
|
+
config = inject(DELEGATION_RUNTIME_CONFIG);
|
|
207
|
+
remember(delegationId) {
|
|
208
|
+
this.write({ ns: this.namespace(), delegationId });
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The delegation to resume for the *current* user/app/tenant, or `null`. A
|
|
212
|
+
* marker left by a different actor is dropped rather than honoured, so
|
|
213
|
+
* signing in as someone else on a shared browser never resumes their
|
|
214
|
+
* delegation.
|
|
215
|
+
*/
|
|
216
|
+
pending() {
|
|
217
|
+
const marker = this.read();
|
|
218
|
+
if (!marker) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
if (marker.ns !== this.namespace()) {
|
|
222
|
+
this.clear();
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
return marker.delegationId;
|
|
226
|
+
}
|
|
227
|
+
clear() {
|
|
228
|
+
try {
|
|
229
|
+
this.storage()?.removeItem(STORAGE_KEY);
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
// Storage can be unavailable (private mode, blocked cookies).
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
namespace() {
|
|
236
|
+
const { userId, applicationKey, tenantKey } = this.config.resolvePromptNamespace();
|
|
237
|
+
return [userId, applicationKey, tenantKey ?? ''].join('|');
|
|
238
|
+
}
|
|
239
|
+
read() {
|
|
240
|
+
try {
|
|
241
|
+
const raw = this.storage()?.getItem(STORAGE_KEY);
|
|
242
|
+
const parsed = raw ? JSON.parse(raw) : null;
|
|
243
|
+
if (typeof parsed !== 'object' ||
|
|
244
|
+
parsed === null ||
|
|
245
|
+
typeof parsed.ns !== 'string' ||
|
|
246
|
+
typeof parsed.delegationId !== 'number') {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
return parsed;
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
write(marker) {
|
|
256
|
+
try {
|
|
257
|
+
this.storage()?.setItem(STORAGE_KEY, JSON.stringify(marker));
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// Storage can be unavailable; delegated mode then simply ends on reload.
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
storage() {
|
|
264
|
+
return this.document.defaultView?.localStorage ?? null;
|
|
265
|
+
}
|
|
266
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionResumeStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
267
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionResumeStore, providedIn: 'root' });
|
|
268
|
+
}
|
|
269
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionResumeStore, decorators: [{
|
|
270
|
+
type: Injectable,
|
|
271
|
+
args: [{ providedIn: 'root' }]
|
|
272
|
+
}] });
|
|
273
|
+
|
|
176
274
|
/** Package-private: raw delegated credentials never enter NGXS or public models. */
|
|
177
275
|
class DelegationTokenVault {
|
|
178
276
|
value = signal(null, ...(ngDevMode ? [{ debugName: "value" }] : /* istanbul ignore next */ []));
|
|
@@ -198,6 +296,9 @@ var __decorate$1 = (this && this.__decorate) || function (decorators, target, ke
|
|
|
198
296
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
199
297
|
};
|
|
200
298
|
const BASE$1 = 'identity/delegations';
|
|
299
|
+
/** `setTimeout` stores its delay in a signed 32-bit int; anything larger wraps
|
|
300
|
+
* around and fires immediately. Clamp so a long-lived session never self-ends. */
|
|
301
|
+
const MAX_TIMEOUT_MS = 2 ** 31 - 1;
|
|
201
302
|
// Action shells matching gateway-auth type strings - loose coupling, matched by
|
|
202
303
|
// `type` only so this package does not depend on @masterteam/gateway-auth.
|
|
203
304
|
class GatewayLoginSuccessShell {
|
|
@@ -244,6 +345,17 @@ let DelegationSessionState = class DelegationSessionState {
|
|
|
244
345
|
vault = inject(DelegationTokenVault);
|
|
245
346
|
runtime = inject(DELEGATION_RUNTIME_CONFIG);
|
|
246
347
|
expiryTimer = null;
|
|
348
|
+
/**
|
|
349
|
+
* Bumped whenever candidates are cleared (logout / user / tenant / app
|
|
350
|
+
* switch). A `LoadDelegationCandidates` response that was already in flight
|
|
351
|
+
* when the actor went away resolves against a stale generation and is
|
|
352
|
+
* dropped, so the previous user's delegators can never repopulate the menu —
|
|
353
|
+
* or open the availability prompt — after sign-out.
|
|
354
|
+
*/
|
|
355
|
+
candidatesGeneration = 0;
|
|
356
|
+
resumeStore = inject(DelegationSessionResumeStore);
|
|
357
|
+
/** One resume attempt per page load — a dead delegation must not retry. */
|
|
358
|
+
resumeAttempted = false;
|
|
247
359
|
constructor() {
|
|
248
360
|
this.actions$
|
|
249
361
|
.pipe(ofActionSuccessful(GatewayLoginSuccessShell))
|
|
@@ -289,6 +401,7 @@ let DelegationSessionState = class DelegationSessionState {
|
|
|
289
401
|
// Actions
|
|
290
402
|
// ---------------------------------------------------------------------------
|
|
291
403
|
loadCandidates(ctx, { page, pageSize, append }) {
|
|
404
|
+
const generation = this.candidatesGeneration;
|
|
292
405
|
const req$ = this.http.get(`${BASE$1}/user/activedelegations`, {
|
|
293
406
|
params: new HttpParams().set('page', page).set('pageSize', pageSize),
|
|
294
407
|
});
|
|
@@ -297,6 +410,10 @@ let DelegationSessionState = class DelegationSessionState {
|
|
|
297
410
|
key: DelegationSessionActionKey.LoadCandidates,
|
|
298
411
|
request$: req$,
|
|
299
412
|
onSuccess: (response) => {
|
|
413
|
+
// The actor changed while this request was in flight — discard it.
|
|
414
|
+
if (generation !== this.candidatesGeneration) {
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
300
417
|
const next = (response.data?.items ?? []).filter(canStart);
|
|
301
418
|
const current = append ? ctx.getState().candidates : [];
|
|
302
419
|
const candidates = [...current, ...next].filter((row, index, rows) => rows.findIndex((candidate) => candidate.delegationId === row.delegationId) === index);
|
|
@@ -307,6 +424,9 @@ let DelegationSessionState = class DelegationSessionState {
|
|
|
307
424
|
(response.data?.totalCount ?? 0) <= pageSize) {
|
|
308
425
|
queueMicrotask(() => this.store.dispatch(new EndDelegationSession('ScopeChanged')));
|
|
309
426
|
}
|
|
427
|
+
if (!active && page === 1) {
|
|
428
|
+
this.tryResume(candidates);
|
|
429
|
+
}
|
|
310
430
|
return {
|
|
311
431
|
candidates,
|
|
312
432
|
candidatesPage: response.data?.page ?? page,
|
|
@@ -316,6 +436,39 @@ let DelegationSessionState = class DelegationSessionState {
|
|
|
316
436
|
},
|
|
317
437
|
});
|
|
318
438
|
}
|
|
439
|
+
/**
|
|
440
|
+
* Re-mints the delegated session the user was in before they reloaded.
|
|
441
|
+
*
|
|
442
|
+
* The token itself was never persisted (doc 05); only the delegation id was,
|
|
443
|
+
* so this is a full `POST .../session` and the backend re-authorizes from
|
|
444
|
+
* scratch. Attempted at most once per page load — a delegation that has since
|
|
445
|
+
* expired or been revoked must not retry on every candidate refresh.
|
|
446
|
+
*/
|
|
447
|
+
tryResume(candidates) {
|
|
448
|
+
if (this.resumeAttempted) {
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
const delegationId = this.resumeStore.pending();
|
|
452
|
+
if (delegationId == null) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
const row = candidates.find((candidate) => candidate.delegationId === delegationId);
|
|
456
|
+
this.resumeAttempted = true;
|
|
457
|
+
if (!row) {
|
|
458
|
+
// No longer startable (expired, cancelled, scope revoked) — forget it.
|
|
459
|
+
this.resumeStore.clear();
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
queueMicrotask(() => this.store.dispatch(new ResumeDelegationSession(row)).subscribe({
|
|
463
|
+
error: () => this.resumeStore.clear(),
|
|
464
|
+
}));
|
|
465
|
+
}
|
|
466
|
+
resume(ctx, { delegation }) {
|
|
467
|
+
if (ctx.getState().active) {
|
|
468
|
+
return EMPTY;
|
|
469
|
+
}
|
|
470
|
+
return this.startSession(ctx, delegation, null, 'ResumeSession');
|
|
471
|
+
}
|
|
319
472
|
start(ctx, { delegation }) {
|
|
320
473
|
const previous = ctx.getState().active;
|
|
321
474
|
return this.startSession(ctx, delegation, previous, 'StartSession');
|
|
@@ -336,6 +489,9 @@ let DelegationSessionState = class DelegationSessionState {
|
|
|
336
489
|
delegationVersion: response.data.delegationVersion,
|
|
337
490
|
};
|
|
338
491
|
this.scheduleExpiry(active);
|
|
492
|
+
// Id only — see `DelegationSessionResumeStore`. Never the token.
|
|
493
|
+
this.resumeStore.remember(delegation.delegationId);
|
|
494
|
+
this.resumeAttempted = true;
|
|
339
495
|
return { active };
|
|
340
496
|
},
|
|
341
497
|
}).pipe(switchMap(() => from(Promise.resolve(this.runtime.onContextChanged({
|
|
@@ -347,18 +503,31 @@ let DelegationSessionState = class DelegationSessionState {
|
|
|
347
503
|
end(ctx, { reason }) {
|
|
348
504
|
// Client-local only — there is no server end-session endpoint (doc 05).
|
|
349
505
|
const previous = ctx.getState().active;
|
|
506
|
+
const clearCandidates = shouldClearCandidates(reason);
|
|
507
|
+
// Whatever the reason, delegated mode is over — a reload must not bring it
|
|
508
|
+
// back. Also blocks a resume that is still queued behind this action.
|
|
509
|
+
this.resumeStore.clear();
|
|
510
|
+
this.resumeAttempted = true;
|
|
511
|
+
// Logout / user / tenant / app switch must always drop the candidate list,
|
|
512
|
+
// even when no delegated session was ever started. Leaving it behind kept
|
|
513
|
+
// the previous actor's delegators in memory across sign-out, which is both
|
|
514
|
+
// a leak into the next session and the reason the availability prompt could
|
|
515
|
+
// reopen over the login page.
|
|
516
|
+
if (clearCandidates) {
|
|
517
|
+
this.candidatesGeneration++;
|
|
518
|
+
ctx.patchState({
|
|
519
|
+
candidates: [],
|
|
520
|
+
candidatesTotalCount: 0,
|
|
521
|
+
candidatesPage: 1,
|
|
522
|
+
});
|
|
523
|
+
}
|
|
350
524
|
if (!previous && !this.vault.token()) {
|
|
351
525
|
return EMPTY;
|
|
352
526
|
}
|
|
353
527
|
this.clearExpiry();
|
|
354
528
|
this.vault.clear();
|
|
355
|
-
ctx.patchState({
|
|
356
|
-
|
|
357
|
-
...(shouldClearCandidates(reason)
|
|
358
|
-
? { candidates: [], candidatesTotalCount: 0, candidatesPage: 1 }
|
|
359
|
-
: {}),
|
|
360
|
-
});
|
|
361
|
-
return from(Promise.resolve(this.runtime.onContextChanged({ previous, current: null, reason }))).pipe(switchMap(() => shouldClearCandidates(reason)
|
|
529
|
+
ctx.patchState({ active: null });
|
|
530
|
+
return from(Promise.resolve(this.runtime.onContextChanged({ previous, current: null, reason }))).pipe(switchMap(() => clearCandidates
|
|
362
531
|
? EMPTY
|
|
363
532
|
: this.store.dispatch(new LoadDelegationCandidates())));
|
|
364
533
|
}
|
|
@@ -368,7 +537,15 @@ let DelegationSessionState = class DelegationSessionState {
|
|
|
368
537
|
}
|
|
369
538
|
scheduleExpiry(active) {
|
|
370
539
|
this.clearExpiry();
|
|
371
|
-
const
|
|
540
|
+
const expiresAt = Date.parse(active.expiresAtUtc ?? '');
|
|
541
|
+
// A missing or unparseable expiry used to produce a NaN delay, which
|
|
542
|
+
// `setTimeout` coerces to 0 — the session ended on the very next tick and
|
|
543
|
+
// the user silently fell back to their own identity. Fail open instead and
|
|
544
|
+
// let the backend's `delegationReasonCode` end the session (doc 05).
|
|
545
|
+
if (!Number.isFinite(expiresAt)) {
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
const delay = Math.min(Math.max(0, expiresAt - Date.now()), MAX_TIMEOUT_MS);
|
|
372
549
|
this.expiryTimer = setTimeout(() => this.store.dispatch(new EndDelegationSession('Expired')), delay);
|
|
373
550
|
}
|
|
374
551
|
clearExpiry() {
|
|
@@ -383,6 +560,9 @@ let DelegationSessionState = class DelegationSessionState {
|
|
|
383
560
|
__decorate$1([
|
|
384
561
|
Action(LoadDelegationCandidates)
|
|
385
562
|
], DelegationSessionState.prototype, "loadCandidates", null);
|
|
563
|
+
__decorate$1([
|
|
564
|
+
Action(ResumeDelegationSession)
|
|
565
|
+
], DelegationSessionState.prototype, "resume", null);
|
|
386
566
|
__decorate$1([
|
|
387
567
|
Action(StartDelegationSession)
|
|
388
568
|
], DelegationSessionState.prototype, "start", null);
|
|
@@ -426,7 +606,7 @@ DelegationSessionState = __decorate$1([
|
|
|
426
606
|
], DelegationSessionState);
|
|
427
607
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionState, decorators: [{
|
|
428
608
|
type: Injectable
|
|
429
|
-
}], ctorParameters: () => [], propDecorators: { loadCandidates: [], start: [], end: [], switch: [] } });
|
|
609
|
+
}], ctorParameters: () => [], propDecorators: { loadCandidates: [], resume: [], start: [], end: [], switch: [] } });
|
|
430
610
|
|
|
431
611
|
class DelegationSessionFacade {
|
|
432
612
|
store = inject(Store);
|
|
@@ -445,8 +625,15 @@ class DelegationSessionFacade {
|
|
|
445
625
|
// ---------------------------------------------------------------------------
|
|
446
626
|
/** On-behalf-of (delegator). */
|
|
447
627
|
onBehalfOf = computed(() => this.active()?.delegation.delegator ?? null, ...(ngDevMode ? [{ debugName: "onBehalfOf" }] : /* istanbul ignore next */ []));
|
|
448
|
-
/**
|
|
449
|
-
|
|
628
|
+
/**
|
|
629
|
+
* Executed-by (the real signed-in user), known whether or not a session is
|
|
630
|
+
* active: every assigned row carries the current user as its `delegatedUser`,
|
|
631
|
+
* so the account switcher can list "you" alongside the delegators without the
|
|
632
|
+
* package depending on the host's auth state.
|
|
633
|
+
*/
|
|
634
|
+
executedBy = computed(() => this.active()?.delegation.delegatedUser ??
|
|
635
|
+
this.candidates()[0]?.delegatedUser ??
|
|
636
|
+
null, ...(ngDevMode ? [{ debugName: "executedBy" }] : /* istanbul ignore next */ []));
|
|
450
637
|
hasCandidates = computed(() => this.candidates().length > 0, ...(ngDevMode ? [{ debugName: "hasCandidates" }] : /* istanbul ignore next */ []));
|
|
451
638
|
hasMoreCandidates = computed(() => this.candidates().length < this.candidatesTotalCount(), ...(ngDevMode ? [{ debugName: "hasMoreCandidates" }] : /* istanbul ignore next */ []));
|
|
452
639
|
isStarting = computed(() => this.loadingActive().includes(DelegationSessionActionKey.StartSession), ...(ngDevMode ? [{ debugName: "isStarting" }] : /* istanbul ignore next */ []));
|
|
@@ -520,6 +707,8 @@ function toDelegationUserEntity(party, options = {}) {
|
|
|
520
707
|
class StartSessionDialog {
|
|
521
708
|
delegation = input.required(...(ngDevMode ? [{ debugName: "delegation" }] : /* istanbul ignore next */ []));
|
|
522
709
|
intent = input('start', ...(ngDevMode ? [{ debugName: "intent" }] : /* istanbul ignore next */ []));
|
|
710
|
+
/** Supplies the shared `mt-modal-content` / `mt-modal-footer` shell classes. */
|
|
711
|
+
modal = inject(ModalService);
|
|
523
712
|
ref = inject(ModalRef);
|
|
524
713
|
facade = inject(DelegationSessionFacade);
|
|
525
714
|
toast = inject(ToastService);
|
|
@@ -556,11 +745,11 @@ class StartSessionDialog {
|
|
|
556
745
|
this.ref.close(false);
|
|
557
746
|
}
|
|
558
747
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: StartSessionDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
559
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.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\">\r\n <div
|
|
748
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.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\">\r\n <div\r\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\r\n >\r\n <mt-entity-preview\r\n [data]=\"userEntity(delegation().delegator)\"\r\n ></mt-entity-preview>\r\n\r\n <p class=\"text-sm leading-relaxed text-surface-600\">\r\n {{ t(bodyKey()) }}\r\n </p>\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.cancel')\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"cancel()\"\r\n ></mt-button>\r\n <mt-button\r\n color=\"primary\"\r\n icon=\"user.users-check\"\r\n [label]=\"t(confirmLabelKey())\"\r\n [loading]=\"isBusy()\"\r\n (click)=\"confirm()\"\r\n ></mt-button>\r\n </div>\r\n</ng-container>\r\n", 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", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
560
749
|
}
|
|
561
750
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: StartSessionDialog, decorators: [{
|
|
562
751
|
type: Component,
|
|
563
|
-
args: [{ selector: 'mt-start-session-dialog', imports: [CommonModule, Button, EntityPreview, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div
|
|
752
|
+
args: [{ selector: 'mt-start-session-dialog', imports: [CommonModule, Button, EntityPreview, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div\r\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\r\n >\r\n <mt-entity-preview\r\n [data]=\"userEntity(delegation().delegator)\"\r\n ></mt-entity-preview>\r\n\r\n <p class=\"text-sm leading-relaxed text-surface-600\">\r\n {{ t(bodyKey()) }}\r\n </p>\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.cancel')\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"cancel()\"\r\n ></mt-button>\r\n <mt-button\r\n color=\"primary\"\r\n icon=\"user.users-check\"\r\n [label]=\"t(confirmLabelKey())\"\r\n [loading]=\"isBusy()\"\r\n (click)=\"confirm()\"\r\n ></mt-button>\r\n </div>\r\n</ng-container>\r\n" }]
|
|
564
753
|
}], propDecorators: { delegation: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegation", required: true }] }], intent: [{ type: i0.Input, args: [{ isSignal: true, alias: "intent", required: false }] }] } });
|
|
565
754
|
|
|
566
755
|
/**
|
|
@@ -569,6 +758,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
569
758
|
*/
|
|
570
759
|
class DelegationCandidatesPromptDialog {
|
|
571
760
|
candidates = input.required(...(ngDevMode ? [{ debugName: "candidates" }] : /* istanbul ignore next */ []));
|
|
761
|
+
/** Supplies the shared `mt-modal-content` / `mt-modal-footer` shell classes. */
|
|
762
|
+
modal = inject(ModalService);
|
|
572
763
|
ref = inject(ModalRef);
|
|
573
764
|
userEntity(party) {
|
|
574
765
|
return toDelegationUserEntity(party, { showEmail: false });
|
|
@@ -580,74 +771,25 @@ class DelegationCandidatesPromptDialog {
|
|
|
580
771
|
this.ref.close(null);
|
|
581
772
|
}
|
|
582
773
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationCandidatesPromptDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
583
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationCandidatesPromptDialog, isStandalone: true, selector: "mt-delegation-candidates-prompt-dialog", inputs: { candidates: { classPropertyName: "candidates", publicName: "candidates", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div
|
|
774
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationCandidatesPromptDialog, isStandalone: true, selector: "mt-delegation-candidates-prompt-dialog", inputs: { candidates: { classPropertyName: "candidates", publicName: "candidates", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div\r\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\r\n >\r\n <p class=\"text-sm leading-relaxed text-surface-600\">\r\n {{ t(\"delegations.session.promptBody\") }}\r\n </p>\r\n\r\n <div class=\"flex flex-col gap-2\">\r\n @for (cand of candidates(); track cand.delegationId) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-center justify-between gap-3 rounded-lg border border-solid border-surface-200 bg-surface-0 px-3 py-2.5 text-start transition-colors hover:border-primary-300 hover:bg-primary-50/50 focus-visible:border-primary-400 focus-visible:outline-none\"\r\n [attr.aria-label]=\"\r\n t('delegations.session.promptStartAria', {\r\n delegatorName: cand.delegator.displayName,\r\n })\r\n \"\r\n (click)=\"select(cand)\"\r\n >\r\n <span class=\"flex min-w-0 items-center gap-3\">\r\n <mt-entity-preview\r\n [data]=\"userEntity(cand.delegator)\"\r\n ></mt-entity-preview>\r\n </span>\r\n <span\r\n class=\"inline-flex shrink-0 items-center gap-1.5 text-sm font-medium text-primary\"\r\n >\r\n <span class=\"hidden sm:inline\">\r\n {{ t(\"delegations.action.startSession\") }}\r\n </span>\r\n <mt-icon icon=\"arrow.arrow-right\" class=\"text-base\"></mt-icon>\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.close')\"\r\n (click)=\"close()\"\r\n ></mt-button>\r\n </div>\r\n</ng-container>\r\n", 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", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
584
775
|
}
|
|
585
776
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationCandidatesPromptDialog, decorators: [{
|
|
586
777
|
type: Component,
|
|
587
|
-
args: [{ selector: 'mt-delegation-candidates-prompt-dialog', imports: [CommonModule, Button, EntityPreview, Icon, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div
|
|
778
|
+
args: [{ selector: 'mt-delegation-candidates-prompt-dialog', imports: [CommonModule, Button, EntityPreview, Icon, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div\r\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\r\n >\r\n <p class=\"text-sm leading-relaxed text-surface-600\">\r\n {{ t(\"delegations.session.promptBody\") }}\r\n </p>\r\n\r\n <div class=\"flex flex-col gap-2\">\r\n @for (cand of candidates(); track cand.delegationId) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-center justify-between gap-3 rounded-lg border border-solid border-surface-200 bg-surface-0 px-3 py-2.5 text-start transition-colors hover:border-primary-300 hover:bg-primary-50/50 focus-visible:border-primary-400 focus-visible:outline-none\"\r\n [attr.aria-label]=\"\r\n t('delegations.session.promptStartAria', {\r\n delegatorName: cand.delegator.displayName,\r\n })\r\n \"\r\n (click)=\"select(cand)\"\r\n >\r\n <span class=\"flex min-w-0 items-center gap-3\">\r\n <mt-entity-preview\r\n [data]=\"userEntity(cand.delegator)\"\r\n ></mt-entity-preview>\r\n </span>\r\n <span\r\n class=\"inline-flex shrink-0 items-center gap-1.5 text-sm font-medium text-primary\"\r\n >\r\n <span class=\"hidden sm:inline\">\r\n {{ t(\"delegations.action.startSession\") }}\r\n </span>\r\n <mt-icon icon=\"arrow.arrow-right\" class=\"text-base\"></mt-icon>\r\n </span>\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.close')\"\r\n (click)=\"close()\"\r\n ></mt-button>\r\n </div>\r\n</ng-container>\r\n" }]
|
|
588
779
|
}], propDecorators: { candidates: [{ type: i0.Input, args: [{ isSignal: true, alias: "candidates", required: true }] }] } });
|
|
589
780
|
|
|
590
|
-
const STATUS_VISUAL = {
|
|
591
|
-
Active: {
|
|
592
|
-
i18nKey: 'delegations.status.active',
|
|
593
|
-
styleClass: 'mt-status-chip mt-status-chip--active',
|
|
594
|
-
},
|
|
595
|
-
Scheduled: {
|
|
596
|
-
i18nKey: 'delegations.status.scheduled',
|
|
597
|
-
styleClass: 'mt-status-chip mt-status-chip--scheduled',
|
|
598
|
-
},
|
|
599
|
-
PendingApproval: {
|
|
600
|
-
i18nKey: 'delegations.status.pendingApproval',
|
|
601
|
-
styleClass: 'mt-status-chip mt-status-chip--pending',
|
|
602
|
-
},
|
|
603
|
-
InactiveToday: {
|
|
604
|
-
i18nKey: 'delegations.status.inactiveToday',
|
|
605
|
-
styleClass: 'mt-status-chip mt-status-chip--scheduled',
|
|
606
|
-
},
|
|
607
|
-
Expired: {
|
|
608
|
-
i18nKey: 'delegations.status.expired',
|
|
609
|
-
styleClass: 'mt-status-chip mt-status-chip--expired',
|
|
610
|
-
},
|
|
611
|
-
Rejected: {
|
|
612
|
-
i18nKey: 'delegations.status.rejected',
|
|
613
|
-
styleClass: 'mt-status-chip mt-status-chip--rejected',
|
|
614
|
-
},
|
|
615
|
-
Cancelled: {
|
|
616
|
-
i18nKey: 'delegations.status.cancelled',
|
|
617
|
-
styleClass: 'mt-status-chip mt-status-chip--cancelled',
|
|
618
|
-
},
|
|
619
|
-
};
|
|
620
|
-
class DelegationStatusChip {
|
|
621
|
-
status = input.required(...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
|
|
622
|
-
reasonCode = input(null, ...(ngDevMode ? [{ debugName: "reasonCode" }] : /* istanbul ignore next */ []));
|
|
623
|
-
visual = computed(() => STATUS_VISUAL[this.status() === 'Scheduled' && this.reasonCode() === 'InactiveToday'
|
|
624
|
-
? 'InactiveToday'
|
|
625
|
-
: this.status()] ?? STATUS_VISUAL.Scheduled, ...(ngDevMode ? [{ debugName: "visual" }] : /* istanbul ignore next */ []));
|
|
626
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationStatusChip, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
627
|
-
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 }, reasonCode: { classPropertyName: "reasonCode", publicName: "reasonCode", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
628
|
-
<ng-container *transloco="let t">
|
|
629
|
-
@let v = visual();
|
|
630
|
-
<mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
|
|
631
|
-
</ng-container>
|
|
632
|
-
`, 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", "size"], outputs: ["onRemove", "onImageError"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] });
|
|
633
|
-
}
|
|
634
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationStatusChip, decorators: [{
|
|
635
|
-
type: Component,
|
|
636
|
-
args: [{ selector: 'mt-delegation-status-chip', standalone: true, imports: [Chip, TranslocoDirective], template: `
|
|
637
|
-
<ng-container *transloco="let t">
|
|
638
|
-
@let v = visual();
|
|
639
|
-
<mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
|
|
640
|
-
</ng-container>
|
|
641
|
-
`, 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"] }]
|
|
642
|
-
}], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: true }] }], reasonCode: [{ type: i0.Input, args: [{ isSignal: true, alias: "reasonCode", required: false }] }] } });
|
|
643
|
-
|
|
644
781
|
/**
|
|
645
|
-
* Embeddable
|
|
646
|
-
*
|
|
647
|
-
*
|
|
648
|
-
*
|
|
649
|
-
*
|
|
650
|
-
*
|
|
782
|
+
* Embeddable "acting as" account switcher (doc 05, 09).
|
|
783
|
+
*
|
|
784
|
+
* Renders one row per identity the user can operate as — themselves first, then
|
|
785
|
+
* every delegator who has an active delegation assigned to them — with the
|
|
786
|
+
* current one checked. Picking a row starts, switches, or ends the delegated
|
|
787
|
+
* session; the {@link StartSessionDialog} still takes explicit consent before
|
|
788
|
+
* any session begins, so this list never changes authority on its own.
|
|
789
|
+
*
|
|
790
|
+
* Designed to be dropped inside a host menu (e.g. the user-avatar dropdown) or
|
|
791
|
+
* the topbar popover. Candidate loading + the post-login prompt stay in the
|
|
792
|
+
* always-mounted `TopbarDelegationMenu` controller.
|
|
651
793
|
*
|
|
652
794
|
* Emits `closeRequested` after any action so the host overlay can dismiss.
|
|
653
795
|
*/
|
|
@@ -655,25 +797,57 @@ class DelegationMenuPanel {
|
|
|
655
797
|
/** Path to the management page, e.g. `/control-panel/delegations` or `/delegations`. */
|
|
656
798
|
managePath = input('/delegations', ...(ngDevMode ? [{ debugName: "managePath" }] : /* istanbul ignore next */ []));
|
|
657
799
|
showManageLink = input(true, ...(ngDevMode ? [{ debugName: "showManageLink" }] : /* istanbul ignore next */ []));
|
|
800
|
+
/**
|
|
801
|
+
* Optional richer profile for the "you" row. Falls back to the current user
|
|
802
|
+
* carried on the assigned delegation rows, so the host may omit it.
|
|
803
|
+
*/
|
|
804
|
+
currentUser = input(null, ...(ngDevMode ? [{ debugName: "currentUser" }] : /* istanbul ignore next */ []));
|
|
658
805
|
closeRequested = output();
|
|
659
806
|
facade = inject(DelegationSessionFacade);
|
|
660
807
|
modal = inject(ModalService);
|
|
661
808
|
transloco = inject(TranslocoService);
|
|
809
|
+
toast = inject(ToastService);
|
|
662
810
|
active = this.facade.active;
|
|
663
811
|
candidates = this.facade.candidates;
|
|
664
812
|
hasCandidates = this.facade.hasCandidates;
|
|
665
813
|
hasMoreCandidates = this.facade.hasMoreCandidates;
|
|
666
814
|
isLoadingCandidates = this.facade.isLoadingCandidates;
|
|
815
|
+
isBusy = this.facade.isStarting;
|
|
667
816
|
onBehalfOf = this.facade.onBehalfOf;
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
817
|
+
/** The real signed-in user — host-supplied when available, derived otherwise. */
|
|
818
|
+
self = computed(() => this.currentUser() ?? this.facade.executedBy(), ...(ngDevMode ? [{ debugName: "self" }] : /* istanbul ignore next */ []));
|
|
819
|
+
/** Nothing to switch between and no session running — render nothing. */
|
|
820
|
+
visible = computed(() => !!this.active() || this.hasCandidates(), ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
|
|
821
|
+
/** "You", then every delegator. The active identity carries the checkmark. */
|
|
822
|
+
identities = computed(() => {
|
|
823
|
+
const activeSession = this.active();
|
|
824
|
+
const activeId = activeSession?.delegation.delegationId ?? null;
|
|
825
|
+
const self = this.self();
|
|
826
|
+
const candidates = this.candidates();
|
|
827
|
+
// The identity in force is always listed, even if it has fallen off the
|
|
828
|
+
// first page of candidates — a switcher that cannot show who you currently
|
|
829
|
+
// are is worse than one extra row.
|
|
830
|
+
const rows = activeSession && !candidates.some((row) => row.delegationId === activeId)
|
|
831
|
+
? [activeSession.delegation, ...candidates]
|
|
832
|
+
: candidates;
|
|
833
|
+
return [
|
|
834
|
+
{
|
|
835
|
+
row: null,
|
|
836
|
+
party: self,
|
|
837
|
+
isSelf: true,
|
|
838
|
+
isActive: activeId === null,
|
|
839
|
+
initials: this.initials(self),
|
|
840
|
+
},
|
|
841
|
+
...rows.map((row) => ({
|
|
842
|
+
row,
|
|
843
|
+
party: row.delegator,
|
|
844
|
+
isSelf: false,
|
|
845
|
+
isActive: row.delegationId === activeId,
|
|
846
|
+
initials: this.initials(row.delegator),
|
|
847
|
+
})),
|
|
848
|
+
];
|
|
849
|
+
}, ...(ngDevMode ? [{ debugName: "identities" }] : /* istanbul ignore next */ []));
|
|
850
|
+
/** Two-letter initials for an avatar (matches the user-menu pattern). */
|
|
677
851
|
initials(party) {
|
|
678
852
|
const name = party?.displayName?.trim() || party?.email?.trim() || '';
|
|
679
853
|
const parts = name.split(/\s+/).filter(Boolean);
|
|
@@ -684,20 +858,24 @@ class DelegationMenuPanel {
|
|
|
684
858
|
}
|
|
685
859
|
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
|
686
860
|
}
|
|
687
|
-
|
|
688
|
-
event.stopPropagation();
|
|
689
|
-
this.closeRequested.emit();
|
|
690
|
-
this.openConfirm(row, 'start');
|
|
691
|
-
}
|
|
692
|
-
switchTo(row, event) {
|
|
693
|
-
event.stopPropagation();
|
|
694
|
-
this.closeRequested.emit();
|
|
695
|
-
this.openConfirm(row, 'switch');
|
|
696
|
-
}
|
|
697
|
-
endSession(event) {
|
|
861
|
+
select(option, event) {
|
|
698
862
|
event.stopPropagation();
|
|
863
|
+
if (option.isActive || this.isBusy()) {
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
699
866
|
this.closeRequested.emit();
|
|
700
|
-
|
|
867
|
+
if (option.isSelf) {
|
|
868
|
+
// Back to your own authority. There is no server call (doc 05) — ending
|
|
869
|
+
// is local — so no confirmation dialog stands between the user and it.
|
|
870
|
+
this.facade.endSession('Manual').subscribe({
|
|
871
|
+
next: () => this.toast.success(this.transloco.translate('delegations.session.ended')),
|
|
872
|
+
});
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
if (!option.row) {
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
this.openConfirm(option.row, this.active() ? 'switch' : 'start');
|
|
701
879
|
}
|
|
702
880
|
onManage() {
|
|
703
881
|
this.closeRequested.emit();
|
|
@@ -718,19 +896,12 @@ class DelegationMenuPanel {
|
|
|
718
896
|
});
|
|
719
897
|
}
|
|
720
898
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationMenuPanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
721
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationMenuPanel, isStandalone: true, selector: "mt-delegation-menu-panel", inputs: { managePath: { classPropertyName: "managePath", publicName: "managePath", isSignal: true, isRequired: false, transformFunction: null }, showManageLink: { classPropertyName: "showManageLink", publicName: "showManageLink", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closeRequested: "closeRequested" }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\
|
|
899
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationMenuPanel, isStandalone: true, selector: "mt-delegation-menu-panel", inputs: { managePath: { classPropertyName: "managePath", publicName: "managePath", isSignal: true, isRequired: false, transformFunction: null }, showManageLink: { classPropertyName: "showManageLink", publicName: "showManageLink", isSignal: true, isRequired: false, transformFunction: null }, currentUser: { classPropertyName: "currentUser", publicName: "currentUser", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closeRequested: "closeRequested" }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\n @if (visible()) {\n <div class=\"mt-delegation-switcher flex w-full flex-col\">\n <!-- Header: what this list is, and the current mode at a glance -->\n <div class=\"flex flex-col gap-0.5 px-4 pb-2 pt-3\">\n <span\n class=\"text-[0.6875rem] font-semibold uppercase tracking-wide text-surface-400\"\n >\n {{ t(\"delegations.session.actingAs\") }}\n </span>\n @if (active()) {\n <span class=\"text-xs text-surface-500\">\n {{\n t(\"delegations.session.switchBackHint\", {\n actualUserName: self()?.displayName,\n })\n }}\n </span>\n } @else {\n <span class=\"text-xs text-surface-500\">\n {{ t(\"delegations.session.chooseDelegatorHint\") }}\n </span>\n }\n </div>\n\n <!-- Identity list: you first, then everyone who delegated to you -->\n <div\n class=\"mt-delegation-switcher__list flex max-h-80 flex-col gap-0.5 overflow-y-auto px-1.5 pb-1.5\"\n role=\"listbox\"\n [attr.aria-label]=\"t('delegations.session.actingAs')\"\n >\n @for (\n option of identities();\n track option.row?.delegationId ?? \"self\"\n ) {\n <button\n type=\"button\"\n role=\"option\"\n class=\"mt-delegation-switcher__row\"\n [class.mt-delegation-switcher__row--active]=\"option.isActive\"\n [attr.aria-selected]=\"option.isActive\"\n [disabled]=\"isBusy() || option.isActive\"\n [attr.aria-label]=\"\n option.isSelf\n ? t('delegations.session.actAsSelfAria', {\n actualUserName: option.party?.displayName,\n })\n : t('delegations.session.promptStartAria', {\n delegatorName: option.party?.displayName,\n })\n \"\n (click)=\"select(option, $event)\"\n >\n <span class=\"relative shrink-0\">\n <mt-avatar\n [label]=\"option.initials\"\n shape=\"circle\"\n [styleClass]=\"\n option.isActive\n ? '!size-9 !text-xs !bg-primary-100 !text-primary-700'\n : '!size-9 !text-xs !bg-surface-100 !text-surface-600'\n \"\n ></mt-avatar>\n @if (option.isActive && !option.isSelf) {\n <span\n class=\"absolute -bottom-0.5 -end-0.5 flex size-4 items-center justify-center rounded-full bg-primary ring-2 ring-surface-0\"\n aria-hidden=\"true\"\n >\n <mt-icon\n icon=\"user.users-check\"\n styleClass=\"text-[0.55rem] text-white\"\n ></mt-icon>\n </span>\n }\n </span>\n\n <span class=\"flex min-w-0 flex-1 flex-col text-start\">\n <span class=\"flex min-w-0 items-center gap-1.5\">\n <span\n class=\"truncate text-sm font-semibold text-surface-800\"\n [class.text-primary-700]=\"option.isActive\"\n >\n {{ option.party?.displayName }}\n </span>\n @if (option.isSelf) {\n <span class=\"mt-delegation-switcher__badge\">\n {{ t(\"delegations.session.you\") }}\n </span>\n }\n </span>\n <span class=\"truncate text-xs text-surface-500\">\n @if (option.isSelf) {\n {{\n option.party?.email ||\n t(\"delegations.session.ownAccountHint\")\n }}\n } @else {\n {{\n t(\"delegations.session.until\", {\n date: option.row?.endsAtUtc | date: \"mediumDate\",\n })\n }}\n }\n </span>\n </span>\n\n @if (option.isActive) {\n <mt-icon\n icon=\"general.check\"\n styleClass=\"text-base text-primary shrink-0\"\n ></mt-icon>\n } @else {\n <mt-icon\n icon=\"arrow.chevron-right\"\n styleClass=\"mt-delegation-switcher__chevron text-base text-surface-400 shrink-0\"\n ></mt-icon>\n }\n </button>\n }\n\n @if (hasMoreCandidates()) {\n <button\n type=\"button\"\n class=\"mx-1.5 my-1 rounded-lg px-3 py-2 text-sm font-medium text-primary hover:bg-primary-50 disabled:opacity-60\"\n [disabled]=\"isLoadingCandidates()\"\n (click)=\"loadMore($event)\"\n >\n {{ t(\"delegations.session.loadMore\") }}\n </button>\n }\n </div>\n\n @if (showManageLink()) {\n <div class=\"border-t border-surface p-1.5\">\n <a\n [routerLink]=\"managePath()\"\n (click)=\"onManage()\"\n class=\"flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-sm text-surface-700 hover:bg-surface-100\"\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 }\n </div>\n }\n</ng-container>\n", styles: [":host{display:block;min-width:0}.mt-delegation-switcher{min-width:15rem}.mt-delegation-switcher__row{display:flex;width:100%;align-items:center;gap:.75rem;padding:.5rem .625rem;border:1px solid transparent;border-radius:.5rem;background:transparent;cursor:pointer;text-align:start;transition:background-color .12s ease-out,border-color .12s ease-out}.mt-delegation-switcher__row:hover:not(:disabled){background-color:var(--p-surface-100)}.mt-delegation-switcher__row:focus-visible{outline:none;border-color:var(--p-primary-400);box-shadow:0 0 0 2px color-mix(in srgb,var(--p-primary-color) 22%,transparent)}.mt-delegation-switcher__row--active,.mt-delegation-switcher__row--active:hover{background-color:var(--p-primary-50);border-color:var(--p-primary-200)}.mt-delegation-switcher__row:disabled{cursor:default}.mt-delegation-switcher__row--active:disabled{opacity:1}.mt-delegation-switcher__row:disabled:not(.mt-delegation-switcher__row--active){opacity:.55}.mt-delegation-switcher__chevron{opacity:0;transition:opacity .12s ease-out}.mt-delegation-switcher__row:hover:not(:disabled) .mt-delegation-switcher__chevron,.mt-delegation-switcher__row:focus-visible .mt-delegation-switcher__chevron{opacity:1}.mt-delegation-switcher__badge{flex-shrink:0;border-radius:999px;background-color:var(--p-surface-200);color:var(--p-surface-600);font-size:.625rem;font-weight:600;line-height:1rem;padding-inline:.375rem;text-transform:uppercase;letter-spacing:.02em}:host-context([dir=\"rtl\"]) .mt-delegation-switcher__chevron{transform:scaleX(-1)}\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: Icon, selector: "mt-icon", inputs: ["icon"] }, { 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: "pipe", type: i1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
722
900
|
}
|
|
723
901
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationMenuPanel, decorators: [{
|
|
724
902
|
type: Component,
|
|
725
|
-
args: [{ selector: 'mt-delegation-menu-panel', standalone: true, imports: [
|
|
726
|
-
|
|
727
|
-
Avatar,
|
|
728
|
-
Icon,
|
|
729
|
-
RouterLink,
|
|
730
|
-
TranslocoDirective,
|
|
731
|
-
DelegationStatusChip,
|
|
732
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n @if (mode() !== \"hidden\") {\r\n <div class=\"flex w-full flex-col\">\r\n @if (mode() === \"active\" && active(); as session) {\r\n <!-- Active session -->\r\n <div class=\"flex items-center gap-3 border-b border-surface px-4 py-3\">\r\n <mt-avatar\r\n [label]=\"initials(onBehalfOf())\"\r\n shape=\"circle\"\r\n styleClass=\"!size-9 !text-sm !bg-primary-100 !text-primary-700\"\r\n ></mt-avatar>\r\n <div class=\"flex min-w-0 flex-1 flex-col\">\r\n <span\r\n class=\"text-[0.625rem] font-medium uppercase text-surface-500\"\r\n >\r\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\r\n </span>\r\n <span class=\"truncate text-sm font-semibold text-surface-900\">\r\n {{ onBehalfOf()?.displayName }}\r\n </span>\r\n <span class=\"truncate text-xs text-surface-500\">\r\n {{\r\n t(\"delegations.session.executedBy\", {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n }}\r\n </span>\r\n </div>\r\n <mt-delegation-status-chip\r\n status=\"Active\"\r\n ></mt-delegation-status-chip>\r\n </div>\r\n\r\n <div class=\"flex flex-col p-1.5\">\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-surface-700 hover:bg-surface-100\"\r\n (click)=\"endSession($event)\"\r\n >\r\n <mt-icon icon=\"arrow.arrow-left\" styleClass=\"text-base\"></mt-icon>\r\n <span>{{ t(\"delegations.action.endSession\") }}</span>\r\n </button>\r\n </div>\r\n\r\n @if (candidates().length > 1) {\r\n <div class=\"border-t border-surface p-1.5\">\r\n <div\r\n class=\"px-2 pb-1 text-[0.625rem] font-medium uppercase text-surface-400\"\r\n >\r\n {{ t(\"delegations.session.switchToAnother\") }}\r\n </div>\r\n @for (cand of candidates(); track cand.delegationId) {\r\n @if (cand.delegationId !== session.delegation.delegationId) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-lg px-2 py-2 text-start hover:bg-surface-100\"\r\n [attr.aria-label]=\"\r\n t('delegations.action.switchSession') +\r\n ': ' +\r\n cand.delegator.displayName\r\n \"\r\n (click)=\"switchTo(cand, $event)\"\r\n >\r\n <mt-avatar\r\n [label]=\"initials(cand.delegator)\"\r\n shape=\"circle\"\r\n styleClass=\"!size-8 !text-xs !bg-surface-100 !text-surface-600\"\r\n ></mt-avatar>\r\n <span\r\n class=\"min-w-0 flex-1 truncate text-sm font-medium text-surface-800\"\r\n >\r\n {{ cand.delegator.displayName }}\r\n </span>\r\n <mt-icon\r\n icon=\"arrow.switch-horizontal-01\"\r\n styleClass=\"text-base text-surface-400\"\r\n ></mt-icon>\r\n </button>\r\n }\r\n }\r\n </div>\r\n }\r\n } @else if (mode() === \"candidates\") {\r\n <!-- Candidates available -->\r\n <div class=\"border-b border-surface px-4 py-3\">\r\n <div class=\"text-sm font-semibold text-surface-900\">\r\n {{ t(\"delegations.session.youCanActAs\") }}\r\n </div>\r\n <div class=\"mt-0.5 text-xs text-surface-500\">\r\n {{ t(\"delegations.session.chooseDelegatorHint\") }}\r\n </div>\r\n </div>\r\n <div class=\"flex max-h-72 flex-col overflow-y-auto p-1.5\">\r\n @for (cand of candidates(); track cand.delegationId) {\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-center gap-3 rounded-lg px-2 py-2 text-start hover:bg-surface-100\"\r\n [attr.aria-label]=\"\r\n t('delegations.action.startSession') +\r\n ': ' +\r\n cand.delegator.displayName\r\n \"\r\n (click)=\"start(cand, $event)\"\r\n >\r\n <mt-avatar\r\n [label]=\"initials(cand.delegator)\"\r\n shape=\"circle\"\r\n styleClass=\"!size-8 !text-xs !bg-primary-100 !text-primary-700\"\r\n ></mt-avatar>\r\n <span class=\"flex min-w-0 flex-1 flex-col\">\r\n <span class=\"truncate text-sm font-medium text-surface-800\">\r\n {{ cand.delegator.displayName }}\r\n </span>\r\n @if (cand.delegator.email) {\r\n <span class=\"truncate text-xs text-surface-500\">\r\n {{ cand.delegator.email }}\r\n </span>\r\n }\r\n </span>\r\n <mt-icon\r\n icon=\"arrow.arrow-right\"\r\n styleClass=\"text-base text-surface-400\"\r\n ></mt-icon>\r\n </button>\r\n }\r\n @if (hasMoreCandidates()) {\r\n <button\r\n type=\"button\"\r\n class=\"mx-2 my-1 rounded-lg px-3 py-2 text-sm font-medium text-primary hover:bg-primary-50 disabled:opacity-60\"\r\n [disabled]=\"isLoadingCandidates()\"\r\n (click)=\"loadMore($event)\"\r\n >\r\n {{ t(\"delegations.session.loadMore\") }}\r\n </button>\r\n }\r\n </div>\r\n }\r\n\r\n @if (showManageLink()) {\r\n <div class=\"border-t border-surface p-1.5\">\r\n <a\r\n [routerLink]=\"managePath()\"\r\n (click)=\"onManage()\"\r\n class=\"flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-sm text-surface-700 hover:bg-surface-100\"\r\n >\r\n <mt-icon\r\n icon=\"general.settings-02\"\r\n styleClass=\"text-base\"\r\n ></mt-icon>\r\n <span>{{ t(\"delegations.session.manage\") }}</span>\r\n </a>\r\n </div>\r\n }\r\n </div>\r\n }\r\n</ng-container>\r\n" }]
|
|
733
|
-
}], propDecorators: { managePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "managePath", required: false }] }], showManageLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "showManageLink", required: false }] }], closeRequested: [{ type: i0.Output, args: ["closeRequested"] }] } });
|
|
903
|
+
args: [{ selector: 'mt-delegation-menu-panel', standalone: true, imports: [CommonModule, Avatar, Icon, RouterLink, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n @if (visible()) {\n <div class=\"mt-delegation-switcher flex w-full flex-col\">\n <!-- Header: what this list is, and the current mode at a glance -->\n <div class=\"flex flex-col gap-0.5 px-4 pb-2 pt-3\">\n <span\n class=\"text-[0.6875rem] font-semibold uppercase tracking-wide text-surface-400\"\n >\n {{ t(\"delegations.session.actingAs\") }}\n </span>\n @if (active()) {\n <span class=\"text-xs text-surface-500\">\n {{\n t(\"delegations.session.switchBackHint\", {\n actualUserName: self()?.displayName,\n })\n }}\n </span>\n } @else {\n <span class=\"text-xs text-surface-500\">\n {{ t(\"delegations.session.chooseDelegatorHint\") }}\n </span>\n }\n </div>\n\n <!-- Identity list: you first, then everyone who delegated to you -->\n <div\n class=\"mt-delegation-switcher__list flex max-h-80 flex-col gap-0.5 overflow-y-auto px-1.5 pb-1.5\"\n role=\"listbox\"\n [attr.aria-label]=\"t('delegations.session.actingAs')\"\n >\n @for (\n option of identities();\n track option.row?.delegationId ?? \"self\"\n ) {\n <button\n type=\"button\"\n role=\"option\"\n class=\"mt-delegation-switcher__row\"\n [class.mt-delegation-switcher__row--active]=\"option.isActive\"\n [attr.aria-selected]=\"option.isActive\"\n [disabled]=\"isBusy() || option.isActive\"\n [attr.aria-label]=\"\n option.isSelf\n ? t('delegations.session.actAsSelfAria', {\n actualUserName: option.party?.displayName,\n })\n : t('delegations.session.promptStartAria', {\n delegatorName: option.party?.displayName,\n })\n \"\n (click)=\"select(option, $event)\"\n >\n <span class=\"relative shrink-0\">\n <mt-avatar\n [label]=\"option.initials\"\n shape=\"circle\"\n [styleClass]=\"\n option.isActive\n ? '!size-9 !text-xs !bg-primary-100 !text-primary-700'\n : '!size-9 !text-xs !bg-surface-100 !text-surface-600'\n \"\n ></mt-avatar>\n @if (option.isActive && !option.isSelf) {\n <span\n class=\"absolute -bottom-0.5 -end-0.5 flex size-4 items-center justify-center rounded-full bg-primary ring-2 ring-surface-0\"\n aria-hidden=\"true\"\n >\n <mt-icon\n icon=\"user.users-check\"\n styleClass=\"text-[0.55rem] text-white\"\n ></mt-icon>\n </span>\n }\n </span>\n\n <span class=\"flex min-w-0 flex-1 flex-col text-start\">\n <span class=\"flex min-w-0 items-center gap-1.5\">\n <span\n class=\"truncate text-sm font-semibold text-surface-800\"\n [class.text-primary-700]=\"option.isActive\"\n >\n {{ option.party?.displayName }}\n </span>\n @if (option.isSelf) {\n <span class=\"mt-delegation-switcher__badge\">\n {{ t(\"delegations.session.you\") }}\n </span>\n }\n </span>\n <span class=\"truncate text-xs text-surface-500\">\n @if (option.isSelf) {\n {{\n option.party?.email ||\n t(\"delegations.session.ownAccountHint\")\n }}\n } @else {\n {{\n t(\"delegations.session.until\", {\n date: option.row?.endsAtUtc | date: \"mediumDate\",\n })\n }}\n }\n </span>\n </span>\n\n @if (option.isActive) {\n <mt-icon\n icon=\"general.check\"\n styleClass=\"text-base text-primary shrink-0\"\n ></mt-icon>\n } @else {\n <mt-icon\n icon=\"arrow.chevron-right\"\n styleClass=\"mt-delegation-switcher__chevron text-base text-surface-400 shrink-0\"\n ></mt-icon>\n }\n </button>\n }\n\n @if (hasMoreCandidates()) {\n <button\n type=\"button\"\n class=\"mx-1.5 my-1 rounded-lg px-3 py-2 text-sm font-medium text-primary hover:bg-primary-50 disabled:opacity-60\"\n [disabled]=\"isLoadingCandidates()\"\n (click)=\"loadMore($event)\"\n >\n {{ t(\"delegations.session.loadMore\") }}\n </button>\n }\n </div>\n\n @if (showManageLink()) {\n <div class=\"border-t border-surface p-1.5\">\n <a\n [routerLink]=\"managePath()\"\n (click)=\"onManage()\"\n class=\"flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-sm text-surface-700 hover:bg-surface-100\"\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 }\n </div>\n }\n</ng-container>\n", styles: [":host{display:block;min-width:0}.mt-delegation-switcher{min-width:15rem}.mt-delegation-switcher__row{display:flex;width:100%;align-items:center;gap:.75rem;padding:.5rem .625rem;border:1px solid transparent;border-radius:.5rem;background:transparent;cursor:pointer;text-align:start;transition:background-color .12s ease-out,border-color .12s ease-out}.mt-delegation-switcher__row:hover:not(:disabled){background-color:var(--p-surface-100)}.mt-delegation-switcher__row:focus-visible{outline:none;border-color:var(--p-primary-400);box-shadow:0 0 0 2px color-mix(in srgb,var(--p-primary-color) 22%,transparent)}.mt-delegation-switcher__row--active,.mt-delegation-switcher__row--active:hover{background-color:var(--p-primary-50);border-color:var(--p-primary-200)}.mt-delegation-switcher__row:disabled{cursor:default}.mt-delegation-switcher__row--active:disabled{opacity:1}.mt-delegation-switcher__row:disabled:not(.mt-delegation-switcher__row--active){opacity:.55}.mt-delegation-switcher__chevron{opacity:0;transition:opacity .12s ease-out}.mt-delegation-switcher__row:hover:not(:disabled) .mt-delegation-switcher__chevron,.mt-delegation-switcher__row:focus-visible .mt-delegation-switcher__chevron{opacity:1}.mt-delegation-switcher__badge{flex-shrink:0;border-radius:999px;background-color:var(--p-surface-200);color:var(--p-surface-600);font-size:.625rem;font-weight:600;line-height:1rem;padding-inline:.375rem;text-transform:uppercase;letter-spacing:.02em}:host-context([dir=\"rtl\"]) .mt-delegation-switcher__chevron{transform:scaleX(-1)}\n"] }]
|
|
904
|
+
}], propDecorators: { managePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "managePath", required: false }] }], showManageLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "showManageLink", required: false }] }], currentUser: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentUser", required: false }] }], closeRequested: [{ type: i0.Output, args: ["closeRequested"] }] } });
|
|
734
905
|
|
|
735
906
|
/**
|
|
736
907
|
* Topbar surface + controller for the delegation runtime (doc 05, 09).
|
|
@@ -758,9 +929,18 @@ class TopbarDelegationMenu {
|
|
|
758
929
|
* e.g. embedded in the user-avatar dropdown via {@link DelegationMenuPanel}.
|
|
759
930
|
*/
|
|
760
931
|
headless = input(false, ...(ngDevMode ? [{ debugName: "headless" }] : /* istanbul ignore next */ []));
|
|
932
|
+
/**
|
|
933
|
+
* Optional richer profile for the "you" row of the switcher. Forwarded to
|
|
934
|
+
* {@link DelegationMenuPanel}; omit it and the current user is derived from
|
|
935
|
+
* the assigned delegation rows.
|
|
936
|
+
*/
|
|
937
|
+
currentUser = input(null, ...(ngDevMode ? [{ debugName: "currentUser" }] : /* istanbul ignore next */ []));
|
|
761
938
|
facade = inject(DelegationSessionFacade);
|
|
762
939
|
modal = inject(ModalService);
|
|
763
940
|
transloco = inject(TranslocoService);
|
|
941
|
+
destroyRef = inject(DestroyRef);
|
|
942
|
+
actions$ = inject(Actions);
|
|
943
|
+
toast = inject(ToastService);
|
|
764
944
|
popover = viewChild('popover', ...(ngDevMode ? [{ debugName: "popover" }] : /* istanbul ignore next */ []));
|
|
765
945
|
active = this.facade.active;
|
|
766
946
|
candidates = this.facade.candidates;
|
|
@@ -771,6 +951,14 @@ class TopbarDelegationMenu {
|
|
|
771
951
|
promptPending = false;
|
|
772
952
|
promptRefreshQueued = false;
|
|
773
953
|
promptFlowOpen = false;
|
|
954
|
+
destroyed = false;
|
|
955
|
+
/**
|
|
956
|
+
* Dialogs opened through `ModalService` live on `document.body`, so they
|
|
957
|
+
* outlive this component. Held so the whole prompt flow can be torn down when
|
|
958
|
+
* the authenticated shell goes away (sign-out) instead of stranding an
|
|
959
|
+
* "act on behalf of" dialog over the login page.
|
|
960
|
+
*/
|
|
961
|
+
promptRefs = [];
|
|
774
962
|
mode = computed(() => {
|
|
775
963
|
if (this.active())
|
|
776
964
|
return 'active';
|
|
@@ -789,6 +977,30 @@ class TopbarDelegationMenu {
|
|
|
789
977
|
}
|
|
790
978
|
this.checkPromptCandidates(candidates);
|
|
791
979
|
});
|
|
980
|
+
// Candidates emptying mid-flow means the actor is gone (sign-out, user /
|
|
981
|
+
// tenant / app switch). Nothing in the prompt is valid any more.
|
|
982
|
+
effect(() => {
|
|
983
|
+
if (!this.candidates().length) {
|
|
984
|
+
this.closePromptFlow();
|
|
985
|
+
}
|
|
986
|
+
});
|
|
987
|
+
// A resumed session is the one state change the user did not just ask for
|
|
988
|
+
// — it happens silently on page load. Say so, or "acting as someone else"
|
|
989
|
+
// looks like the app forgot who they are.
|
|
990
|
+
this.actions$
|
|
991
|
+
.pipe(ofActionSuccessful(ResumeDelegationSession), takeUntilDestroyed())
|
|
992
|
+
.subscribe(() => {
|
|
993
|
+
const delegatorName = this.onBehalfOf()?.displayName;
|
|
994
|
+
if (delegatorName) {
|
|
995
|
+
this.toast.info(this.transloco.translate('delegations.session.resumed', {
|
|
996
|
+
delegatorName,
|
|
997
|
+
}));
|
|
998
|
+
}
|
|
999
|
+
});
|
|
1000
|
+
this.destroyRef.onDestroy(() => {
|
|
1001
|
+
this.destroyed = true;
|
|
1002
|
+
this.closePromptFlow();
|
|
1003
|
+
});
|
|
792
1004
|
}
|
|
793
1005
|
ngOnInit() {
|
|
794
1006
|
this.facade.loadCandidates();
|
|
@@ -819,7 +1031,13 @@ class TopbarDelegationMenu {
|
|
|
819
1031
|
void this.facade
|
|
820
1032
|
.claimPromptCandidates(candidates)
|
|
821
1033
|
.then((unseen) => {
|
|
822
|
-
|
|
1034
|
+
// `claimUnseen` is async (SubtleCrypto). The user can sign out inside
|
|
1035
|
+
// that window, which destroys this controller and empties the candidate
|
|
1036
|
+
// list — opening then would drop the dialog on the login page.
|
|
1037
|
+
if (unseen.length &&
|
|
1038
|
+
!this.destroyed &&
|
|
1039
|
+
!this.active() &&
|
|
1040
|
+
this.candidates().length) {
|
|
823
1041
|
this.openCandidatesPrompt(unseen);
|
|
824
1042
|
}
|
|
825
1043
|
})
|
|
@@ -828,7 +1046,10 @@ class TopbarDelegationMenu {
|
|
|
828
1046
|
if (this.promptRefreshQueued) {
|
|
829
1047
|
this.promptRefreshQueued = false;
|
|
830
1048
|
const latest = this.candidates();
|
|
831
|
-
if (
|
|
1049
|
+
if (!this.destroyed &&
|
|
1050
|
+
this.promptOnCandidates() &&
|
|
1051
|
+
!this.active() &&
|
|
1052
|
+
latest.length) {
|
|
832
1053
|
this.checkPromptCandidates(latest);
|
|
833
1054
|
}
|
|
834
1055
|
}
|
|
@@ -854,27 +1075,51 @@ class TopbarDelegationMenu {
|
|
|
854
1075
|
dismissible: true,
|
|
855
1076
|
inputValues: { candidates },
|
|
856
1077
|
});
|
|
1078
|
+
this.trackPromptRef(ref);
|
|
857
1079
|
ref.onClose.subscribe((row) => {
|
|
858
|
-
if (row) {
|
|
859
|
-
this.openConfirm(row, 'start').onClose.subscribe(() => this.finishPromptFlow());
|
|
1080
|
+
if (row && !this.destroyed) {
|
|
1081
|
+
this.trackPromptRef(this.openConfirm(row, 'start')).onClose.subscribe(() => this.finishPromptFlow());
|
|
860
1082
|
}
|
|
861
1083
|
else {
|
|
862
1084
|
this.finishPromptFlow();
|
|
863
1085
|
}
|
|
864
1086
|
});
|
|
865
1087
|
}
|
|
1088
|
+
trackPromptRef(ref) {
|
|
1089
|
+
this.promptRefs.push(ref);
|
|
1090
|
+
return ref;
|
|
1091
|
+
}
|
|
1092
|
+
/** Tears down anything the prompt flow left on `document.body`. */
|
|
1093
|
+
closePromptFlow() {
|
|
1094
|
+
const refs = this.promptRefs;
|
|
1095
|
+
this.promptRefs = [];
|
|
1096
|
+
this.promptFlowOpen = false;
|
|
1097
|
+
this.promptRefreshQueued = false;
|
|
1098
|
+
for (const ref of refs) {
|
|
1099
|
+
try {
|
|
1100
|
+
ref.close();
|
|
1101
|
+
}
|
|
1102
|
+
catch {
|
|
1103
|
+
// best-effort: a ref already closed by the user throws nothing useful
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
866
1107
|
finishPromptFlow() {
|
|
867
1108
|
this.promptFlowOpen = false;
|
|
1109
|
+
this.promptRefs = [];
|
|
868
1110
|
if (!this.promptRefreshQueued)
|
|
869
1111
|
return;
|
|
870
1112
|
this.promptRefreshQueued = false;
|
|
871
1113
|
const latest = this.candidates();
|
|
872
|
-
if (
|
|
1114
|
+
if (!this.destroyed &&
|
|
1115
|
+
this.promptOnCandidates() &&
|
|
1116
|
+
!this.active() &&
|
|
1117
|
+
latest.length) {
|
|
873
1118
|
this.checkPromptCandidates(latest);
|
|
874
1119
|
}
|
|
875
1120
|
}
|
|
876
1121
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: TopbarDelegationMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
877
|
-
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 }, showManageLink: { classPropertyName: "showManageLink", publicName: "showManageLink", isSignal: true, isRequired: false, transformFunction: null }, promptOnCandidates: { classPropertyName: "promptOnCandidates", publicName: "promptOnCandidates", isSignal: true, isRequired: false, transformFunction: null }, headless: { classPropertyName: "headless", publicName: "headless", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "popover", first: true, predicate: ["popover"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <!-- Headless = controller only (no topbar UI); the menu is surfaced in the\r\n host user dropdown via <mt-delegation-menu-panel>. -->\r\n @if (!headless() && mode() !== \"hidden\") {\r\n @if (mode() === \"active\") {\r\n <span class=\"sr-only\" role=\"status\" aria-live=\"polite\">\r\n {{\r\n t(\"delegations.session.banner\", {\r\n delegatorName: onBehalfOf()?.displayName,\r\n })\r\n }}.\r\n {{\r\n t(\"delegations.session.executedBy\", {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n }}\r\n </span>\r\n }\r\n\r\n <div class=\"mt-delegation-trigger flex items-center\">\r\n @if (mode() === \"active\") {\r\n <!-- Active: show the delegator like a user identity, with a switcher caret -->\r\n <button\r\n type=\"button\"\r\n class=\"mt-delegation-trigger__button mt-delegation-trigger__button--active flex min-w-0 max-w-full cursor-pointer items-center gap-2 rounded-full p-1 pe-2 text-current\"\r\n [attr.aria-label]=\"\r\n t('delegations.session.banner', {\r\n delegatorName: onBehalfOf()?.displayName,\r\n }) +\r\n '. ' +\r\n t('delegations.session.executedBy', {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n \"\r\n [attr.aria-expanded]=\"popoverOpen()\"\r\n aria-haspopup=\"dialog\"\r\n (click)=\"togglePopover($event)\"\r\n >\r\n <span class=\"relative shrink-0\">\r\n <mt-avatar\r\n [label]=\"initials(onBehalfOf())\"\r\n shape=\"circle\"\r\n styleClass=\"!size-7 !text-[0.7rem] !bg-primary-100 !text-primary-700\"\r\n ></mt-avatar>\r\n <span\r\n class=\"absolute -bottom-0.5 -end-0.5 flex size-3.5 items-center justify-center rounded-full bg-emerald-500 ring-2 ring-white\"\r\n aria-hidden=\"true\"\r\n >\r\n <mt-icon\r\n icon=\"user.users-check\"\r\n styleClass=\"text-[0.55rem] text-white\"\r\n ></mt-icon>\r\n </span>\r\n </span>\r\n @if (!compact()) {\r\n <span\r\n class=\"mt-delegation-trigger__label hidden min-w-0 flex-col text-start leading-tight md:flex\"\r\n >\r\n <span class=\"text-[0.625rem] font-medium uppercase opacity-60\">\r\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\r\n </span>\r\n <span class=\"truncate text-sm font-semibold\">\r\n {{ onBehalfOf()?.displayName }}\r\n </span>\r\n </span>\r\n }\r\n <mt-icon\r\n icon=\"arrow.chevron-down\"\r\n styleClass=\"text-sm opacity-70\"\r\n ></mt-icon>\r\n </button>\r\n } @else {\r\n <!-- Candidates: plain icon button matching the other topbar icons -->\r\n <button\r\n type=\"button\"\r\n class=\"mt-delegation-trigger__icon relative flex size-9 cursor-pointer items-center justify-center rounded-full text-current\"\r\n [attr.aria-label]=\"t('delegations.session.availableTitle')\"\r\n [attr.aria-expanded]=\"popoverOpen()\"\r\n aria-haspopup=\"dialog\"\r\n (click)=\"togglePopover($event)\"\r\n >\r\n <mt-icon icon=\"user.users-plus\" styleClass=\"text-xl\"></mt-icon>\r\n @if (candidates().length > 0) {\r\n <span\r\n class=\"absolute -top-0.5 -end-0.5 inline-flex min-w-[1.05rem] items-center justify-center rounded-full bg-primary px-1 text-[0.625rem] font-bold leading-4 text-white ring-2 ring-white\"\r\n >\r\n {{ candidates().length }}\r\n </span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n\r\n <p-popover\r\n #popover\r\n [pt]=\"{ content: { class: 'p-0!' } }\"\r\n (onShow)=\"popoverOpen.set(true)\"\r\n (onHide)=\"popoverOpen.set(false)\"\r\n >\r\n <div class=\"w-
|
|
1122
|
+
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 }, showManageLink: { classPropertyName: "showManageLink", publicName: "showManageLink", isSignal: true, isRequired: false, transformFunction: null }, promptOnCandidates: { classPropertyName: "promptOnCandidates", publicName: "promptOnCandidates", isSignal: true, isRequired: false, transformFunction: null }, headless: { classPropertyName: "headless", publicName: "headless", isSignal: true, isRequired: false, transformFunction: null }, currentUser: { classPropertyName: "currentUser", publicName: "currentUser", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "popover", first: true, predicate: ["popover"], descendants: true, isSignal: true }], ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <!-- Headless = controller only (no topbar UI); the menu is surfaced in the\r\n host user dropdown via <mt-delegation-menu-panel>. -->\r\n @if (!headless() && mode() !== \"hidden\") {\r\n @if (mode() === \"active\") {\r\n <span class=\"sr-only\" role=\"status\" aria-live=\"polite\">\r\n {{\r\n t(\"delegations.session.banner\", {\r\n delegatorName: onBehalfOf()?.displayName,\r\n })\r\n }}.\r\n {{\r\n t(\"delegations.session.executedBy\", {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n }}\r\n </span>\r\n }\r\n\r\n <div class=\"mt-delegation-trigger flex items-center\">\r\n @if (mode() === \"active\") {\r\n <!-- Active: show the delegator like a user identity, with a switcher caret -->\r\n <button\r\n type=\"button\"\r\n class=\"mt-delegation-trigger__button mt-delegation-trigger__button--active flex min-w-0 max-w-full cursor-pointer items-center gap-2 rounded-full p-1 pe-2 text-current\"\r\n [attr.aria-label]=\"\r\n t('delegations.session.banner', {\r\n delegatorName: onBehalfOf()?.displayName,\r\n }) +\r\n '. ' +\r\n t('delegations.session.executedBy', {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n \"\r\n [attr.aria-expanded]=\"popoverOpen()\"\r\n aria-haspopup=\"dialog\"\r\n (click)=\"togglePopover($event)\"\r\n >\r\n <span class=\"relative shrink-0\">\r\n <mt-avatar\r\n [label]=\"initials(onBehalfOf())\"\r\n shape=\"circle\"\r\n styleClass=\"!size-7 !text-[0.7rem] !bg-primary-100 !text-primary-700\"\r\n ></mt-avatar>\r\n <span\r\n class=\"absolute -bottom-0.5 -end-0.5 flex size-3.5 items-center justify-center rounded-full bg-emerald-500 ring-2 ring-white\"\r\n aria-hidden=\"true\"\r\n >\r\n <mt-icon\r\n icon=\"user.users-check\"\r\n styleClass=\"text-[0.55rem] text-white\"\r\n ></mt-icon>\r\n </span>\r\n </span>\r\n @if (!compact()) {\r\n <span\r\n class=\"mt-delegation-trigger__label hidden min-w-0 flex-col text-start leading-tight md:flex\"\r\n >\r\n <span class=\"text-[0.625rem] font-medium uppercase opacity-60\">\r\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\r\n </span>\r\n <span class=\"truncate text-sm font-semibold\">\r\n {{ onBehalfOf()?.displayName }}\r\n </span>\r\n </span>\r\n }\r\n <mt-icon\r\n icon=\"arrow.chevron-down\"\r\n styleClass=\"text-sm opacity-70\"\r\n ></mt-icon>\r\n </button>\r\n } @else {\r\n <!-- Candidates: plain icon button matching the other topbar icons -->\r\n <button\r\n type=\"button\"\r\n class=\"mt-delegation-trigger__icon relative flex size-9 cursor-pointer items-center justify-center rounded-full text-current\"\r\n [attr.aria-label]=\"t('delegations.session.availableTitle')\"\r\n [attr.aria-expanded]=\"popoverOpen()\"\r\n aria-haspopup=\"dialog\"\r\n (click)=\"togglePopover($event)\"\r\n >\r\n <mt-icon icon=\"user.users-plus\" styleClass=\"text-xl\"></mt-icon>\r\n @if (candidates().length > 0) {\r\n <span\r\n class=\"absolute -top-0.5 -end-0.5 inline-flex min-w-[1.05rem] items-center justify-center rounded-full bg-primary px-1 text-[0.625rem] font-bold leading-4 text-white ring-2 ring-white\"\r\n >\r\n {{ candidates().length }}\r\n </span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n\r\n <p-popover\r\n #popover\r\n [pt]=\"{ content: { class: 'p-0!' } }\"\r\n (onShow)=\"popoverOpen.set(true)\"\r\n (onHide)=\"popoverOpen.set(false)\"\r\n >\r\n <div class=\"w-80 max-w-[92vw]\">\r\n <mt-delegation-menu-panel\r\n [managePath]=\"managePath()\"\r\n [showManageLink]=\"showManageLink()\"\r\n [currentUser]=\"currentUser()\"\r\n (closeRequested)=\"closePopover()\"\r\n ></mt-delegation-menu-panel>\r\n </div>\r\n </p-popover>\r\n }\r\n</ng-container>\r\n", styles: [":host{display:inline-flex;align-items:center;min-width:0;max-width:100%}.mt-delegation-trigger{min-width:0;max-width:100%}.mt-delegation-trigger__button,.mt-delegation-trigger__icon{transition:background-color .15s ease-out;border:1px solid transparent}.mt-delegation-trigger__button:hover,.mt-delegation-trigger__icon:hover{background-color:var(--p-surface-100, rgba(0, 0, 0, .05))}.mt-delegation-trigger__button--active{background-color:var(--p-surface-100, rgba(0, 0, 0, .05));border-color:var(--p-surface-200, rgba(0, 0, 0, .08))}.mt-delegation-trigger__button--active:hover{background-color:var(--p-surface-200, rgba(0, 0, 0, .08))}.mt-delegation-trigger__label{max-width:min(12rem,20vw);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media(max-width:1280px){.mt-delegation-trigger__label{max-width:8rem}}\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: 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: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }, { kind: "component", type: DelegationMenuPanel, selector: "mt-delegation-menu-panel", inputs: ["managePath", "showManageLink", "currentUser"], outputs: ["closeRequested"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
878
1123
|
}
|
|
879
1124
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: TopbarDelegationMenu, decorators: [{
|
|
880
1125
|
type: Component,
|
|
@@ -885,8 +1130,62 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
885
1130
|
Popover,
|
|
886
1131
|
TranslocoDirective,
|
|
887
1132
|
DelegationMenuPanel,
|
|
888
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <!-- Headless = controller only (no topbar UI); the menu is surfaced in the\r\n host user dropdown via <mt-delegation-menu-panel>. -->\r\n @if (!headless() && mode() !== \"hidden\") {\r\n @if (mode() === \"active\") {\r\n <span class=\"sr-only\" role=\"status\" aria-live=\"polite\">\r\n {{\r\n t(\"delegations.session.banner\", {\r\n delegatorName: onBehalfOf()?.displayName,\r\n })\r\n }}.\r\n {{\r\n t(\"delegations.session.executedBy\", {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n }}\r\n </span>\r\n }\r\n\r\n <div class=\"mt-delegation-trigger flex items-center\">\r\n @if (mode() === \"active\") {\r\n <!-- Active: show the delegator like a user identity, with a switcher caret -->\r\n <button\r\n type=\"button\"\r\n class=\"mt-delegation-trigger__button mt-delegation-trigger__button--active flex min-w-0 max-w-full cursor-pointer items-center gap-2 rounded-full p-1 pe-2 text-current\"\r\n [attr.aria-label]=\"\r\n t('delegations.session.banner', {\r\n delegatorName: onBehalfOf()?.displayName,\r\n }) +\r\n '. ' +\r\n t('delegations.session.executedBy', {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n \"\r\n [attr.aria-expanded]=\"popoverOpen()\"\r\n aria-haspopup=\"dialog\"\r\n (click)=\"togglePopover($event)\"\r\n >\r\n <span class=\"relative shrink-0\">\r\n <mt-avatar\r\n [label]=\"initials(onBehalfOf())\"\r\n shape=\"circle\"\r\n styleClass=\"!size-7 !text-[0.7rem] !bg-primary-100 !text-primary-700\"\r\n ></mt-avatar>\r\n <span\r\n class=\"absolute -bottom-0.5 -end-0.5 flex size-3.5 items-center justify-center rounded-full bg-emerald-500 ring-2 ring-white\"\r\n aria-hidden=\"true\"\r\n >\r\n <mt-icon\r\n icon=\"user.users-check\"\r\n styleClass=\"text-[0.55rem] text-white\"\r\n ></mt-icon>\r\n </span>\r\n </span>\r\n @if (!compact()) {\r\n <span\r\n class=\"mt-delegation-trigger__label hidden min-w-0 flex-col text-start leading-tight md:flex\"\r\n >\r\n <span class=\"text-[0.625rem] font-medium uppercase opacity-60\">\r\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\r\n </span>\r\n <span class=\"truncate text-sm font-semibold\">\r\n {{ onBehalfOf()?.displayName }}\r\n </span>\r\n </span>\r\n }\r\n <mt-icon\r\n icon=\"arrow.chevron-down\"\r\n styleClass=\"text-sm opacity-70\"\r\n ></mt-icon>\r\n </button>\r\n } @else {\r\n <!-- Candidates: plain icon button matching the other topbar icons -->\r\n <button\r\n type=\"button\"\r\n class=\"mt-delegation-trigger__icon relative flex size-9 cursor-pointer items-center justify-center rounded-full text-current\"\r\n [attr.aria-label]=\"t('delegations.session.availableTitle')\"\r\n [attr.aria-expanded]=\"popoverOpen()\"\r\n aria-haspopup=\"dialog\"\r\n (click)=\"togglePopover($event)\"\r\n >\r\n <mt-icon icon=\"user.users-plus\" styleClass=\"text-xl\"></mt-icon>\r\n @if (candidates().length > 0) {\r\n <span\r\n class=\"absolute -top-0.5 -end-0.5 inline-flex min-w-[1.05rem] items-center justify-center rounded-full bg-primary px-1 text-[0.625rem] font-bold leading-4 text-white ring-2 ring-white\"\r\n >\r\n {{ candidates().length }}\r\n </span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n\r\n <p-popover\r\n #popover\r\n [pt]=\"{ content: { class: 'p-0!' } }\"\r\n (onShow)=\"popoverOpen.set(true)\"\r\n (onHide)=\"popoverOpen.set(false)\"\r\n >\r\n <div class=\"w-
|
|
889
|
-
}], ctorParameters: () => [], propDecorators: { managePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "managePath", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], showManageLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "showManageLink", required: false }] }], promptOnCandidates: [{ type: i0.Input, args: [{ isSignal: true, alias: "promptOnCandidates", required: false }] }], headless: [{ type: i0.Input, args: [{ isSignal: true, alias: "headless", required: false }] }], popover: [{ type: i0.ViewChild, args: ['popover', { isSignal: true }] }] } });
|
|
1133
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <!-- Headless = controller only (no topbar UI); the menu is surfaced in the\r\n host user dropdown via <mt-delegation-menu-panel>. -->\r\n @if (!headless() && mode() !== \"hidden\") {\r\n @if (mode() === \"active\") {\r\n <span class=\"sr-only\" role=\"status\" aria-live=\"polite\">\r\n {{\r\n t(\"delegations.session.banner\", {\r\n delegatorName: onBehalfOf()?.displayName,\r\n })\r\n }}.\r\n {{\r\n t(\"delegations.session.executedBy\", {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n }}\r\n </span>\r\n }\r\n\r\n <div class=\"mt-delegation-trigger flex items-center\">\r\n @if (mode() === \"active\") {\r\n <!-- Active: show the delegator like a user identity, with a switcher caret -->\r\n <button\r\n type=\"button\"\r\n class=\"mt-delegation-trigger__button mt-delegation-trigger__button--active flex min-w-0 max-w-full cursor-pointer items-center gap-2 rounded-full p-1 pe-2 text-current\"\r\n [attr.aria-label]=\"\r\n t('delegations.session.banner', {\r\n delegatorName: onBehalfOf()?.displayName,\r\n }) +\r\n '. ' +\r\n t('delegations.session.executedBy', {\r\n actualUserName: executedBy()?.displayName,\r\n })\r\n \"\r\n [attr.aria-expanded]=\"popoverOpen()\"\r\n aria-haspopup=\"dialog\"\r\n (click)=\"togglePopover($event)\"\r\n >\r\n <span class=\"relative shrink-0\">\r\n <mt-avatar\r\n [label]=\"initials(onBehalfOf())\"\r\n shape=\"circle\"\r\n styleClass=\"!size-7 !text-[0.7rem] !bg-primary-100 !text-primary-700\"\r\n ></mt-avatar>\r\n <span\r\n class=\"absolute -bottom-0.5 -end-0.5 flex size-3.5 items-center justify-center rounded-full bg-emerald-500 ring-2 ring-white\"\r\n aria-hidden=\"true\"\r\n >\r\n <mt-icon\r\n icon=\"user.users-check\"\r\n styleClass=\"text-[0.55rem] text-white\"\r\n ></mt-icon>\r\n </span>\r\n </span>\r\n @if (!compact()) {\r\n <span\r\n class=\"mt-delegation-trigger__label hidden min-w-0 flex-col text-start leading-tight md:flex\"\r\n >\r\n <span class=\"text-[0.625rem] font-medium uppercase opacity-60\">\r\n {{ t(\"delegations.session.actingOnBehalfOf\") }}\r\n </span>\r\n <span class=\"truncate text-sm font-semibold\">\r\n {{ onBehalfOf()?.displayName }}\r\n </span>\r\n </span>\r\n }\r\n <mt-icon\r\n icon=\"arrow.chevron-down\"\r\n styleClass=\"text-sm opacity-70\"\r\n ></mt-icon>\r\n </button>\r\n } @else {\r\n <!-- Candidates: plain icon button matching the other topbar icons -->\r\n <button\r\n type=\"button\"\r\n class=\"mt-delegation-trigger__icon relative flex size-9 cursor-pointer items-center justify-center rounded-full text-current\"\r\n [attr.aria-label]=\"t('delegations.session.availableTitle')\"\r\n [attr.aria-expanded]=\"popoverOpen()\"\r\n aria-haspopup=\"dialog\"\r\n (click)=\"togglePopover($event)\"\r\n >\r\n <mt-icon icon=\"user.users-plus\" styleClass=\"text-xl\"></mt-icon>\r\n @if (candidates().length > 0) {\r\n <span\r\n class=\"absolute -top-0.5 -end-0.5 inline-flex min-w-[1.05rem] items-center justify-center rounded-full bg-primary px-1 text-[0.625rem] font-bold leading-4 text-white ring-2 ring-white\"\r\n >\r\n {{ candidates().length }}\r\n </span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n\r\n <p-popover\r\n #popover\r\n [pt]=\"{ content: { class: 'p-0!' } }\"\r\n (onShow)=\"popoverOpen.set(true)\"\r\n (onHide)=\"popoverOpen.set(false)\"\r\n >\r\n <div class=\"w-80 max-w-[92vw]\">\r\n <mt-delegation-menu-panel\r\n [managePath]=\"managePath()\"\r\n [showManageLink]=\"showManageLink()\"\r\n [currentUser]=\"currentUser()\"\r\n (closeRequested)=\"closePopover()\"\r\n ></mt-delegation-menu-panel>\r\n </div>\r\n </p-popover>\r\n }\r\n</ng-container>\r\n", styles: [":host{display:inline-flex;align-items:center;min-width:0;max-width:100%}.mt-delegation-trigger{min-width:0;max-width:100%}.mt-delegation-trigger__button,.mt-delegation-trigger__icon{transition:background-color .15s ease-out;border:1px solid transparent}.mt-delegation-trigger__button:hover,.mt-delegation-trigger__icon:hover{background-color:var(--p-surface-100, rgba(0, 0, 0, .05))}.mt-delegation-trigger__button--active{background-color:var(--p-surface-100, rgba(0, 0, 0, .05));border-color:var(--p-surface-200, rgba(0, 0, 0, .08))}.mt-delegation-trigger__button--active:hover{background-color:var(--p-surface-200, rgba(0, 0, 0, .08))}.mt-delegation-trigger__label{max-width:min(12rem,20vw);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}@media(max-width:1280px){.mt-delegation-trigger__label{max-width:8rem}}\n"] }]
|
|
1134
|
+
}], ctorParameters: () => [], propDecorators: { managePath: [{ type: i0.Input, args: [{ isSignal: true, alias: "managePath", required: false }] }], compact: [{ type: i0.Input, args: [{ isSignal: true, alias: "compact", required: false }] }], showManageLink: [{ type: i0.Input, args: [{ isSignal: true, alias: "showManageLink", required: false }] }], promptOnCandidates: [{ type: i0.Input, args: [{ isSignal: true, alias: "promptOnCandidates", required: false }] }], headless: [{ type: i0.Input, args: [{ isSignal: true, alias: "headless", required: false }] }], currentUser: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentUser", required: false }] }], popover: [{ type: i0.ViewChild, args: ['popover', { isSignal: true }] }] } });
|
|
1135
|
+
|
|
1136
|
+
const STATUS_VISUAL = {
|
|
1137
|
+
Active: {
|
|
1138
|
+
i18nKey: 'delegations.status.active',
|
|
1139
|
+
styleClass: 'mt-status-chip mt-status-chip--active',
|
|
1140
|
+
},
|
|
1141
|
+
Scheduled: {
|
|
1142
|
+
i18nKey: 'delegations.status.scheduled',
|
|
1143
|
+
styleClass: 'mt-status-chip mt-status-chip--scheduled',
|
|
1144
|
+
},
|
|
1145
|
+
PendingApproval: {
|
|
1146
|
+
i18nKey: 'delegations.status.pendingApproval',
|
|
1147
|
+
styleClass: 'mt-status-chip mt-status-chip--pending',
|
|
1148
|
+
},
|
|
1149
|
+
InactiveToday: {
|
|
1150
|
+
i18nKey: 'delegations.status.inactiveToday',
|
|
1151
|
+
styleClass: 'mt-status-chip mt-status-chip--scheduled',
|
|
1152
|
+
},
|
|
1153
|
+
Expired: {
|
|
1154
|
+
i18nKey: 'delegations.status.expired',
|
|
1155
|
+
styleClass: 'mt-status-chip mt-status-chip--expired',
|
|
1156
|
+
},
|
|
1157
|
+
Rejected: {
|
|
1158
|
+
i18nKey: 'delegations.status.rejected',
|
|
1159
|
+
styleClass: 'mt-status-chip mt-status-chip--rejected',
|
|
1160
|
+
},
|
|
1161
|
+
Cancelled: {
|
|
1162
|
+
i18nKey: 'delegations.status.cancelled',
|
|
1163
|
+
styleClass: 'mt-status-chip mt-status-chip--cancelled',
|
|
1164
|
+
},
|
|
1165
|
+
};
|
|
1166
|
+
class DelegationStatusChip {
|
|
1167
|
+
status = input.required(...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
|
|
1168
|
+
reasonCode = input(null, ...(ngDevMode ? [{ debugName: "reasonCode" }] : /* istanbul ignore next */ []));
|
|
1169
|
+
visual = computed(() => STATUS_VISUAL[this.status() === 'Scheduled' && this.reasonCode() === 'InactiveToday'
|
|
1170
|
+
? 'InactiveToday'
|
|
1171
|
+
: this.status()] ?? STATUS_VISUAL.Scheduled, ...(ngDevMode ? [{ debugName: "visual" }] : /* istanbul ignore next */ []));
|
|
1172
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationStatusChip, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1173
|
+
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 }, reasonCode: { classPropertyName: "reasonCode", publicName: "reasonCode", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
|
|
1174
|
+
<ng-container *transloco="let t">
|
|
1175
|
+
@let v = visual();
|
|
1176
|
+
<mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
|
|
1177
|
+
</ng-container>
|
|
1178
|
+
`, 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", "size"], outputs: ["onRemove", "onImageError"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }] });
|
|
1179
|
+
}
|
|
1180
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationStatusChip, decorators: [{
|
|
1181
|
+
type: Component,
|
|
1182
|
+
args: [{ selector: 'mt-delegation-status-chip', standalone: true, imports: [Chip, TranslocoDirective], template: `
|
|
1183
|
+
<ng-container *transloco="let t">
|
|
1184
|
+
@let v = visual();
|
|
1185
|
+
<mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
|
|
1186
|
+
</ng-container>
|
|
1187
|
+
`, 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"] }]
|
|
1188
|
+
}], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: true }] }], reasonCode: [{ type: i0.Input, args: [{ isSignal: true, alias: "reasonCode", required: false }] }] } });
|
|
890
1189
|
|
|
891
1190
|
// ---------------------------------------------------------------------------
|
|
892
1191
|
// Lists / detail
|
|
@@ -1604,6 +1903,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
1604
1903
|
*/
|
|
1605
1904
|
class RejectDelegationDialog {
|
|
1606
1905
|
delegation = input.required(...(ngDevMode ? [{ debugName: "delegation" }] : /* istanbul ignore next */ []));
|
|
1906
|
+
/** Supplies the shared `mt-modal-content` / `mt-modal-footer` shell classes. */
|
|
1907
|
+
modal = inject(ModalService);
|
|
1607
1908
|
ref = inject(ModalRef);
|
|
1608
1909
|
facade = inject(DelegationsFacade);
|
|
1609
1910
|
reason = signal('', ...(ngDevMode ? [{ debugName: "reason" }] : /* istanbul ignore next */ []));
|
|
@@ -1637,11 +1938,11 @@ class RejectDelegationDialog {
|
|
|
1637
1938
|
this.ref.close(false);
|
|
1638
1939
|
}
|
|
1639
1940
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: RejectDelegationDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1640
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: RejectDelegationDialog, isStandalone: true, selector: "mt-reject-delegation-dialog", inputs: { delegation: { classPropertyName: "delegation", publicName: "delegation", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div
|
|
1941
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: RejectDelegationDialog, isStandalone: true, selector: "mt-reject-delegation-dialog", inputs: { delegation: { classPropertyName: "delegation", publicName: "delegation", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div\r\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\r\n >\r\n <div class=\"flex flex-col gap-1\">\r\n <div class=\"text-sm text-surface-700\">\r\n {{ t(\"delegations.confirm.reject\") }}\r\n </div>\r\n <mt-entity-preview\r\n [data]=\"userEntity(delegation().delegator)\"\r\n ></mt-entity-preview>\r\n </div>\r\n\r\n <div class=\"flex flex-col gap-1\">\r\n <label\r\n class=\"text-sm font-medium text-surface-800\"\r\n for=\"mt-reject-reason\"\r\n >\r\n {{ t(\"delegations.form.rejectionReason\") }}\r\n <span class=\"text-red-600\">*</span>\r\n </label>\r\n <textarea\r\n id=\"mt-reject-reason\"\r\n rows=\"3\"\r\n class=\"w-full rounded-md border border-surface-300 px-3 py-2 text-sm focus:border-primary focus:outline-none\"\r\n [class.border-red-400]=\"submitted() && !isValid()\"\r\n [value]=\"reason()\"\r\n (input)=\"onReasonInput($event)\"\r\n [attr.aria-invalid]=\"submitted() && !isValid()\"\r\n [placeholder]=\"t('delegations.form.rejectionReason')\"\r\n ></textarea>\r\n @if (submitted() && !isValid()) {\r\n <span class=\"text-xs text-red-600\">\r\n {{ t(\"delegations.form.rejectReasonRequired\") }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.cancel')\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"cancel()\"\r\n ></mt-button>\r\n <mt-button\r\n color=\"danger\"\r\n icon=\"general.x-close\"\r\n [label]=\"t('delegations.action.reject')\"\r\n [loading]=\"isBusy()\"\r\n [disabled]=\"!isValid()\"\r\n (click)=\"confirm()\"\r\n ></mt-button>\r\n </div>\r\n</ng-container>\r\n", 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", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
1641
1942
|
}
|
|
1642
1943
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: RejectDelegationDialog, decorators: [{
|
|
1643
1944
|
type: Component,
|
|
1644
|
-
args: [{ selector: 'mt-reject-delegation-dialog', imports: [CommonModule, Button, EntityPreview, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div
|
|
1945
|
+
args: [{ selector: 'mt-reject-delegation-dialog', imports: [CommonModule, Button, EntityPreview, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\r\n <div\r\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\r\n >\r\n <div class=\"flex flex-col gap-1\">\r\n <div class=\"text-sm text-surface-700\">\r\n {{ t(\"delegations.confirm.reject\") }}\r\n </div>\r\n <mt-entity-preview\r\n [data]=\"userEntity(delegation().delegator)\"\r\n ></mt-entity-preview>\r\n </div>\r\n\r\n <div class=\"flex flex-col gap-1\">\r\n <label\r\n class=\"text-sm font-medium text-surface-800\"\r\n for=\"mt-reject-reason\"\r\n >\r\n {{ t(\"delegations.form.rejectionReason\") }}\r\n <span class=\"text-red-600\">*</span>\r\n </label>\r\n <textarea\r\n id=\"mt-reject-reason\"\r\n rows=\"3\"\r\n class=\"w-full rounded-md border border-surface-300 px-3 py-2 text-sm focus:border-primary focus:outline-none\"\r\n [class.border-red-400]=\"submitted() && !isValid()\"\r\n [value]=\"reason()\"\r\n (input)=\"onReasonInput($event)\"\r\n [attr.aria-invalid]=\"submitted() && !isValid()\"\r\n [placeholder]=\"t('delegations.form.rejectionReason')\"\r\n ></textarea>\r\n @if (submitted() && !isValid()) {\r\n <span class=\"text-xs text-red-600\">\r\n {{ t(\"delegations.form.rejectReasonRequired\") }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n variant=\"outlined\"\r\n color=\"secondary\"\r\n [label]=\"t('delegations.common.cancel')\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"cancel()\"\r\n ></mt-button>\r\n <mt-button\r\n color=\"danger\"\r\n icon=\"general.x-close\"\r\n [label]=\"t('delegations.action.reject')\"\r\n [loading]=\"isBusy()\"\r\n [disabled]=\"!isValid()\"\r\n (click)=\"confirm()\"\r\n ></mt-button>\r\n </div>\r\n</ng-container>\r\n" }]
|
|
1645
1946
|
}], propDecorators: { delegation: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegation", required: true }] }] } });
|
|
1646
1947
|
|
|
1647
1948
|
class Delegations {
|
|
@@ -2677,7 +2978,7 @@ class ScopePicker {
|
|
|
2677
2978
|
return this.activeLang() ?? this.transloco.getActiveLang();
|
|
2678
2979
|
}
|
|
2679
2980
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ScopePicker, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2680
|
-
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 }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null }, delegatorUserId: { classPropertyName: "delegatorUserId", publicName: "delegatorUserId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { scope: "scopeChange" }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <!-- Reusable tri-state checkbox box -->\r\n <ng-template #checkbox let-state=\"state\">\r\n <span\r\n class=\"flex size-[18px] shrink-0 items-center justify-center rounded-[5px] border transition-colors\"\r\n [class.border-primary-500]=\"state !== 'unchecked'\"\r\n [class.bg-primary-500]=\"state !== 'unchecked'\"\r\n [class.text-white]=\"state !== 'unchecked'\"\r\n [class.border-surface-300]=\"state === 'unchecked'\"\r\n [class.bg-surface-0]=\"state === 'unchecked'\"\r\n >\r\n @if (state === \"checked\") {\r\n <svg\r\n viewBox=\"0 0 16 16\"\r\n class=\"size-3\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n aria-hidden=\"true\"\r\n >\r\n <path\r\n d=\"M3.5 8.5L6.5 11.5L12.5 4.5\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n />\r\n </svg>\r\n } @else if (state === \"indeterminate\") {\r\n <span class=\"h-[2px] w-2.5 rounded-full bg-white\"></span>\r\n }\r\n </span>\r\n </ng-template>\r\n\r\n <div class=\"flex flex-col gap-3\">\r\n @if (isLoadingOptions()) {\r\n <mt-card [paddingless]=\"true\">\r\n <div class=\"flex flex-col gap-3 p-4\">\r\n <p-skeleton height=\"2.5rem\"></p-skeleton>\r\n @for (item of [0, 1, 2, 3, 4, 5]; track $index) {\r\n <p-skeleton height=\"2rem\"></p-skeleton>\r\n }\r\n </div>\r\n </mt-card>\r\n } @else {\r\n @if (errorOptions(); as errorMessage) {\r\n <div\r\n class=\"rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700\"\r\n >\r\n {{ errorMessage }}\r\n </div>\r\n }\r\n\r\n @if (!hasOptions() && !errorOptions()) {\r\n <mt-card [paddingless]=\"true\">\r\n <div\r\n class=\"flex min-h-72 flex-col items-center justify-center gap-4 px-6 py-8 text-center\"\r\n >\r\n <div\r\n class=\"flex size-16 items-center justify-center rounded-3xl bg-surface-50 text-primary\"\r\n >\r\n <svg\r\n viewBox=\"0 0 64 64\"\r\n class=\"size-9\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n aria-hidden=\"true\"\r\n >\r\n <rect\r\n x=\"10\"\r\n y=\"12\"\r\n width=\"44\"\r\n height=\"38\"\r\n rx=\"12\"\r\n class=\"fill-primary/10 stroke-primary/35\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M21 27H43\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M21 35H37\"\r\n class=\"stroke-primary/70\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n </svg>\r\n </div>\r\n <div class=\"space-y-1\">\r\n <p class=\"text-base font-medium text-surface-900\">\r\n {{ t(\"delegations.scope.permissionsTitle\") }}\r\n </p>\r\n <p class=\"text-sm text-surface-500\">\r\n {{ t(\"delegations.scope.noGrantableOptions\") }}\r\n </p>\r\n </div>\r\n </div>\r\n </mt-card>\r\n }\r\n\r\n @if (hasOptions()) {\r\n <mt-card [paddingless]=\"true\" class=\"overflow-hidden\">\r\n <!-- Inner view switch: Permissions / Pages & accessibility -->\r\n @if (hasAccessibility()) {\r\n <div class=\"border-b border-surface px-3 pt-2.5 pb-0\">\r\n <mt-tabs\r\n mode=\"underline\"\r\n [(active)]=\"activeScopeTab\"\r\n [options]=\"[\r\n {\r\n value: 'permissions',\r\n label: t('delegations.scope.permissionsTab'),\r\n badge: selectedCount() || null,\r\n },\r\n {\r\n value: 'accessibility',\r\n label: t('delegations.scope.accessibilityTitle'),\r\n badge: selectedAccessibilityCount() || null,\r\n },\r\n ]\"\r\n fluid\r\n ></mt-tabs>\r\n </div>\r\n }\r\n\r\n @if (activeScopeTab() === \"permissions\" || !hasAccessibility()) {\r\n <!-- Toolbar: table-style search + clear -->\r\n <div\r\n class=\"flex items-center gap-2 border-b border-surface px-3 py-2\"\r\n >\r\n <div class=\"min-w-0 flex-1\">\r\n <mt-text-field\r\n [ngModel]=\"searchTerm()\"\r\n (ngModelChange)=\"searchTerm.set($event)\"\r\n icon=\"general.search-lg\"\r\n [placeholder]=\"t('delegations.scope.searchPlaceholder')\"\r\n ></mt-text-field>\r\n </div>\r\n @if (selectedCount() > 0) {\r\n <span class=\"shrink-0 text-xs font-medium text-surface-500\">\r\n {{ selectedCount() }} {{ t(\"delegations.scope.selected\") }}\r\n </span>\r\n <mt-button\r\n variant=\"text\"\r\n size=\"small\"\r\n [label]=\"t('delegations.scope.clear')\"\r\n [disabled]=\"readonly()\"\r\n (click)=\"clearSelection()\"\r\n ></mt-button>\r\n }\r\n </div>\r\n\r\n <!-- Select-all header -->\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-center gap-2 border-b border-surface px-3 py-2 text-start hover:bg-surface-50\"\r\n role=\"checkbox\"\r\n [attr.aria-checked]=\"\r\n selectAllState() === 'indeterminate'\r\n ? 'mixed'\r\n : selectAllState() === 'checked'\r\n \"\r\n [disabled]=\"readonly()\"\r\n (click)=\"toggleAll()\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n checkbox;\r\n context: { state: selectAllState() }\r\n \"\r\n ></ng-container>\r\n <span\r\n class=\"text-xs font-semibold uppercase tracking-wide text-surface-500\"\r\n >\r\n {{ t(\"delegations.scope.selectAll\") }}\r\n </span>\r\n </button>\r\n\r\n <!-- Virtualized permission tree -->\r\n @if (visibleNodes().length > 0) {\r\n <cdk-virtual-scroll-viewport\r\n itemSize=\"40\"\r\n class=\"block h-[20rem]\"\r\n >\r\n <div\r\n *cdkVirtualFor=\"\r\n let item of visibleNodes();\r\n trackBy: trackVisible\r\n \"\r\n class=\"group flex h-10 items-center gap-2 pe-2 transition-colors hover:bg-surface-50\"\r\n [style.padding-inline-start.rem]=\"0.5 + item.depth * 1.25\"\r\n >\r\n @if (hasChildren(item.node)) {\r\n <button\r\n type=\"button\"\r\n class=\"flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-surface-400 hover:bg-surface-100 hover:text-surface-600\"\r\n [attr.aria-expanded]=\"isExpanded(item.node)\"\r\n [attr.aria-label]=\"nodeLabel(item.node)\"\r\n (click)=\"\r\n toggleExpand(item.node); $event.stopPropagation()\r\n \"\r\n >\r\n <mt-icon\r\n [icon]=\"\r\n isExpanded(item.node)\r\n ? 'arrow.chevron-down'\r\n : 'arrow.chevron-right'\r\n \"\r\n styleClass=\"text-base\"\r\n ></mt-icon>\r\n </button>\r\n } @else {\r\n <span class=\"size-6 shrink-0\"></span>\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n role=\"checkbox\"\r\n [attr.aria-checked]=\"\r\n nodeState(item.node) === 'indeterminate'\r\n ? 'mixed'\r\n : nodeState(item.node) === 'checked'\r\n \"\r\n [attr.aria-label]=\"nodeLabel(item.node)\"\r\n class=\"shrink-0\"\r\n [class.cursor-pointer]=\"!readonly()\"\r\n [disabled]=\"readonly()\"\r\n (click)=\"toggle(item.node); $event.stopPropagation()\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n checkbox;\r\n context: { state: nodeState(item.node) }\r\n \"\r\n ></ng-container>\r\n </button>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"flex min-w-0 flex-1 cursor-pointer items-center gap-2 text-start\"\r\n (click)=\"onRowClick(item.node)\"\r\n >\r\n <span\r\n class=\"truncate text-sm\"\r\n [class.font-semibold]=\"item.node.kind === 'template'\"\r\n [class.text-surface-900]=\"item.node.kind === 'template'\"\r\n [class.font-medium]=\"\r\n item.node.kind === 'level' ||\r\n item.node.kind === 'module'\r\n \"\r\n [class.text-surface-800]=\"\r\n item.node.kind === 'level' ||\r\n item.node.kind === 'module'\r\n \"\r\n [class.text-surface-600]=\"item.node.kind === 'operation'\"\r\n >\r\n {{ nodeLabel(item.node) }}\r\n </span>\r\n @if (\r\n item.node.kind === \"operation\" && item.node.isHighRisk\r\n ) {\r\n <span\r\n class=\"shrink-0 rounded-full bg-amber-100 px-2 py-0.5 text-[0.625rem] font-medium text-amber-700\"\r\n >\r\n {{ t(\"delegations.scope.highRisk\") }}\r\n </span>\r\n }\r\n </button>\r\n </div>\r\n </cdk-virtual-scroll-viewport>\r\n } @else {\r\n <p class=\"px-3 py-8 text-center text-sm text-surface-500\">\r\n {{ t(\"components.table.no-data-found\") }}\r\n </p>\r\n }\r\n } @else {\r\n <!-- App accessibility -->\r\n <div class=\"flex flex-col gap-3 p-4\">\r\n <p class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.scope.accessibilityHint\") }}\r\n </p>\r\n <div class=\"flex flex-wrap gap-2\">\r\n @for (\r\n item of appAccessibilities();\r\n track item.accessibilityKey\r\n ) {\r\n @let selected =\r\n isAccessibilitySelected(item.accessibilityKey);\r\n <button\r\n type=\"button\"\r\n class=\"inline-flex cursor-pointer items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs font-medium transition-colors\"\r\n [class.border-primary-400]=\"selected\"\r\n [class.bg-primary-50]=\"selected\"\r\n [class.text-primary-700]=\"selected\"\r\n [class.border-surface-200]=\"!selected\"\r\n [class.text-surface-600]=\"!selected\"\r\n [class.hover:border-primary-300]=\"!readonly()\"\r\n [disabled]=\"readonly()\"\r\n [attr.aria-pressed]=\"selected\"\r\n (click)=\"toggleAccessibility(item.accessibilityKey)\"\r\n >\r\n <span\r\n class=\"inline-flex size-1.5 rounded-full\"\r\n [class.bg-primary-500]=\"selected\"\r\n [class.bg-surface-300]=\"!selected\"\r\n ></span>\r\n {{ accessibilityLabel(item) }}\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </mt-card>\r\n }\r\n\r\n <!-- Compact live scope preview -->\r\n <div\r\n class=\"flex flex-col gap-1.5 rounded-lg border border-surface bg-surface-50 px-3 py-2\"\r\n >\r\n <div class=\"flex items-center gap-2 text-xs\">\r\n <span class=\"shrink-0 font-semibold text-surface-700\">\r\n {{ t(\"delegations.scope.previewTitle\") }}:\r\n </span>\r\n @if (isPreviewing()) {\r\n <p-skeleton width=\"8rem\" height=\"0.7rem\"></p-skeleton>\r\n } @else if (preview(); as p) {\r\n @if (p.isValid) {\r\n <span class=\"min-w-0 flex-1 truncate text-surface-900\">\r\n {{\r\n getPreviewSummary(p.summary) ||\r\n t(\"delegations.column.scopeSummary\")\r\n }}\r\n </span>\r\n } @else {\r\n <span class=\"font-medium text-red-700\">\r\n {{ t(\"delegations.scope.previewInvalid\") }}\r\n </span>\r\n }\r\n } @else {\r\n <span class=\"text-surface-500\">\r\n {{ t(\"delegations.scope.noSelection\") }}\r\n </span>\r\n }\r\n </div>\r\n @if (preview(); as p) {\r\n @if (p.warnings.length > 0) {\r\n <ul\r\n class=\"list-inside list-disc space-y-0.5 text-xs text-amber-700\"\r\n >\r\n @for (w of p.warnings; track $index) {\r\n <li>{{ w.message }}</li>\r\n }\r\n </ul>\r\n }\r\n @if (p.deniedItems.length > 0) {\r\n <ul class=\"list-inside list-disc space-y-0.5 text-xs text-red-700\">\r\n @for (d of p.deniedItems; track $index) {\r\n <li>\r\n {{ formatDeniedTarget(d.targetKey) }} /\r\n {{ formatDeniedOperation(d.operationKey) }} -\r\n {{ d.reasonCode }}\r\n </li>\r\n }\r\n </ul>\r\n }\r\n }\r\n </div>\r\n }\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i3.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i3.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i3.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { 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: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "maxLength", "icon", "iconPosition"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i4.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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2981
|
+
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 }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null }, delegatorUserId: { classPropertyName: "delegatorUserId", publicName: "delegatorUserId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { scope: "scopeChange" }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <!-- Reusable tri-state checkbox box -->\r\n <ng-template #checkbox let-state=\"state\">\r\n <span\r\n class=\"flex size-[18px] shrink-0 items-center justify-center rounded-[5px] border transition-colors\"\r\n [class.border-primary-500]=\"state !== 'unchecked'\"\r\n [class.bg-primary-500]=\"state !== 'unchecked'\"\r\n [class.text-white]=\"state !== 'unchecked'\"\r\n [class.border-surface-300]=\"state === 'unchecked'\"\r\n [class.bg-surface-0]=\"state === 'unchecked'\"\r\n >\r\n @if (state === \"checked\") {\r\n <svg\r\n viewBox=\"0 0 16 16\"\r\n class=\"size-3\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n aria-hidden=\"true\"\r\n >\r\n <path\r\n d=\"M3.5 8.5L6.5 11.5L12.5 4.5\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n />\r\n </svg>\r\n } @else if (state === \"indeterminate\") {\r\n <span class=\"h-[2px] w-2.5 rounded-full bg-white\"></span>\r\n }\r\n </span>\r\n </ng-template>\r\n\r\n <div class=\"flex flex-col gap-3\">\r\n @if (isLoadingOptions()) {\r\n <mt-card [paddingless]=\"true\">\r\n <div class=\"flex flex-col gap-3 p-4\">\r\n <p-skeleton height=\"2.5rem\"></p-skeleton>\r\n @for (item of [0, 1, 2, 3, 4, 5]; track $index) {\r\n <p-skeleton height=\"2rem\"></p-skeleton>\r\n }\r\n </div>\r\n </mt-card>\r\n } @else {\r\n @if (errorOptions(); as errorMessage) {\r\n <div\r\n class=\"rounded-2xl border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700\"\r\n >\r\n {{ errorMessage }}\r\n </div>\r\n }\r\n\r\n @if (!hasOptions() && !errorOptions()) {\r\n <mt-card [paddingless]=\"true\">\r\n <div\r\n class=\"flex min-h-72 flex-col items-center justify-center gap-4 px-6 py-8 text-center\"\r\n >\r\n <div\r\n class=\"flex size-16 items-center justify-center rounded-3xl bg-surface-50 text-primary\"\r\n >\r\n <svg\r\n viewBox=\"0 0 64 64\"\r\n class=\"size-9\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n aria-hidden=\"true\"\r\n >\r\n <rect\r\n x=\"10\"\r\n y=\"12\"\r\n width=\"44\"\r\n height=\"38\"\r\n rx=\"12\"\r\n class=\"fill-primary/10 stroke-primary/35\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M21 27H43\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M21 35H37\"\r\n class=\"stroke-primary/70\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n </svg>\r\n </div>\r\n <div class=\"space-y-1\">\r\n <p class=\"text-base font-medium text-surface-900\">\r\n {{ t(\"delegations.scope.permissionsTitle\") }}\r\n </p>\r\n <p class=\"text-sm text-surface-500\">\r\n {{ t(\"delegations.scope.noGrantableOptions\") }}\r\n </p>\r\n </div>\r\n </div>\r\n </mt-card>\r\n }\r\n\r\n @if (hasOptions()) {\r\n <mt-card [paddingless]=\"true\" class=\"overflow-hidden\">\r\n <!-- Inner view switch: Permissions / Pages & accessibility -->\r\n @if (hasAccessibility()) {\r\n <div class=\"border-b border-surface px-3 pt-2.5 pb-0\">\r\n <mt-tabs\r\n mode=\"underline\"\r\n [(active)]=\"activeScopeTab\"\r\n [options]=\"[\r\n {\r\n value: 'permissions',\r\n label: t('delegations.scope.permissionsTab'),\r\n badge: selectedCount() || null,\r\n },\r\n {\r\n value: 'accessibility',\r\n label: t('delegations.scope.accessibilityTitle'),\r\n badge: selectedAccessibilityCount() || null,\r\n },\r\n ]\"\r\n fluid\r\n ></mt-tabs>\r\n </div>\r\n }\r\n\r\n @if (activeScopeTab() === \"permissions\" || !hasAccessibility()) {\r\n <!-- Toolbar: table-style search + clear -->\r\n <div\r\n class=\"flex items-center gap-2 border-b border-surface px-3 py-2\"\r\n >\r\n <div class=\"min-w-0 flex-1\">\r\n <mt-text-field\r\n [ngModel]=\"searchTerm()\"\r\n (ngModelChange)=\"searchTerm.set($event)\"\r\n icon=\"general.search-lg\"\r\n [placeholder]=\"t('delegations.scope.searchPlaceholder')\"\r\n ></mt-text-field>\r\n </div>\r\n @if (selectedCount() > 0) {\r\n <span class=\"shrink-0 text-xs font-medium text-surface-500\">\r\n {{ selectedCount() }} {{ t(\"delegations.scope.selected\") }}\r\n </span>\r\n <mt-button\r\n variant=\"text\"\r\n size=\"small\"\r\n [label]=\"t('delegations.scope.clear')\"\r\n [disabled]=\"readonly()\"\r\n (click)=\"clearSelection()\"\r\n ></mt-button>\r\n }\r\n </div>\r\n\r\n <!-- Select-all header -->\r\n <button\r\n type=\"button\"\r\n class=\"flex w-full cursor-pointer items-center gap-2 border-b border-surface px-3 py-2 text-start hover:bg-surface-50\"\r\n role=\"checkbox\"\r\n [attr.aria-checked]=\"\r\n selectAllState() === 'indeterminate'\r\n ? 'mixed'\r\n : selectAllState() === 'checked'\r\n \"\r\n [disabled]=\"readonly()\"\r\n (click)=\"toggleAll()\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n checkbox;\r\n context: { state: selectAllState() }\r\n \"\r\n ></ng-container>\r\n <span\r\n class=\"text-xs font-semibold uppercase tracking-wide text-surface-500\"\r\n >\r\n {{ t(\"delegations.scope.selectAll\") }}\r\n </span>\r\n </button>\r\n\r\n <!-- Virtualized permission tree -->\r\n @if (visibleNodes().length > 0) {\r\n <cdk-virtual-scroll-viewport\r\n itemSize=\"40\"\r\n class=\"block h-[20rem]\"\r\n >\r\n <div\r\n *cdkVirtualFor=\"\r\n let item of visibleNodes();\r\n trackBy: trackVisible\r\n \"\r\n class=\"group flex h-10 items-center gap-2 pe-2 transition-colors hover:bg-surface-50\"\r\n [style.padding-inline-start.rem]=\"0.5 + item.depth * 1.25\"\r\n >\r\n @if (hasChildren(item.node)) {\r\n <button\r\n type=\"button\"\r\n class=\"flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-md text-surface-400 hover:bg-surface-100 hover:text-surface-600\"\r\n [attr.aria-expanded]=\"isExpanded(item.node)\"\r\n [attr.aria-label]=\"nodeLabel(item.node)\"\r\n (click)=\"\r\n toggleExpand(item.node); $event.stopPropagation()\r\n \"\r\n >\r\n <mt-icon\r\n [icon]=\"\r\n isExpanded(item.node)\r\n ? 'arrow.chevron-down'\r\n : 'arrow.chevron-right'\r\n \"\r\n styleClass=\"text-base\"\r\n ></mt-icon>\r\n </button>\r\n } @else {\r\n <span class=\"size-6 shrink-0\"></span>\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n role=\"checkbox\"\r\n [attr.aria-checked]=\"\r\n nodeState(item.node) === 'indeterminate'\r\n ? 'mixed'\r\n : nodeState(item.node) === 'checked'\r\n \"\r\n [attr.aria-label]=\"nodeLabel(item.node)\"\r\n class=\"shrink-0\"\r\n [class.cursor-pointer]=\"!readonly()\"\r\n [disabled]=\"readonly()\"\r\n (click)=\"toggle(item.node); $event.stopPropagation()\"\r\n >\r\n <ng-container\r\n *ngTemplateOutlet=\"\r\n checkbox;\r\n context: { state: nodeState(item.node) }\r\n \"\r\n ></ng-container>\r\n </button>\r\n\r\n <button\r\n type=\"button\"\r\n class=\"flex min-w-0 flex-1 cursor-pointer items-center gap-2 text-start\"\r\n (click)=\"onRowClick(item.node)\"\r\n >\r\n <span\r\n class=\"truncate text-sm\"\r\n [class.font-semibold]=\"item.node.kind === 'template'\"\r\n [class.text-surface-900]=\"item.node.kind === 'template'\"\r\n [class.font-medium]=\"\r\n item.node.kind === 'level' ||\r\n item.node.kind === 'module'\r\n \"\r\n [class.text-surface-800]=\"\r\n item.node.kind === 'level' ||\r\n item.node.kind === 'module'\r\n \"\r\n [class.text-surface-600]=\"item.node.kind === 'operation'\"\r\n >\r\n {{ nodeLabel(item.node) }}\r\n </span>\r\n @if (\r\n item.node.kind === \"operation\" && item.node.isHighRisk\r\n ) {\r\n <span\r\n class=\"shrink-0 rounded-full bg-amber-100 px-2 py-0.5 text-[0.625rem] font-medium text-amber-700\"\r\n >\r\n {{ t(\"delegations.scope.highRisk\") }}\r\n </span>\r\n }\r\n </button>\r\n </div>\r\n </cdk-virtual-scroll-viewport>\r\n } @else {\r\n <p class=\"px-3 py-8 text-center text-sm text-surface-500\">\r\n {{ t(\"components.table.no-data-found\") }}\r\n </p>\r\n }\r\n } @else {\r\n <!-- App accessibility -->\r\n <div class=\"flex flex-col gap-3 p-4\">\r\n <p class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.scope.accessibilityHint\") }}\r\n </p>\r\n <div class=\"flex flex-wrap gap-2\">\r\n @for (\r\n item of appAccessibilities();\r\n track item.accessibilityKey\r\n ) {\r\n @let selected =\r\n isAccessibilitySelected(item.accessibilityKey);\r\n <button\r\n type=\"button\"\r\n class=\"inline-flex cursor-pointer items-center gap-1.5 rounded-full border px-3 py-1.5 text-xs font-medium transition-colors\"\r\n [class.border-primary-400]=\"selected\"\r\n [class.bg-primary-50]=\"selected\"\r\n [class.text-primary-700]=\"selected\"\r\n [class.border-surface-200]=\"!selected\"\r\n [class.text-surface-600]=\"!selected\"\r\n [class.hover:border-primary-300]=\"!readonly()\"\r\n [disabled]=\"readonly()\"\r\n [attr.aria-pressed]=\"selected\"\r\n (click)=\"toggleAccessibility(item.accessibilityKey)\"\r\n >\r\n <span\r\n class=\"inline-flex size-1.5 rounded-full\"\r\n [class.bg-primary-500]=\"selected\"\r\n [class.bg-surface-300]=\"!selected\"\r\n ></span>\r\n {{ accessibilityLabel(item) }}\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </mt-card>\r\n }\r\n\r\n <!-- Compact live scope preview -->\r\n <div\r\n class=\"flex flex-col gap-1.5 rounded-lg border border-surface bg-surface-50 px-3 py-2\"\r\n >\r\n <div class=\"flex items-center gap-2 text-xs\">\r\n <span class=\"shrink-0 font-semibold text-surface-700\">\r\n {{ t(\"delegations.scope.previewTitle\") }}:\r\n </span>\r\n @if (isPreviewing()) {\r\n <p-skeleton width=\"8rem\" height=\"0.7rem\"></p-skeleton>\r\n } @else if (preview(); as p) {\r\n @if (p.isValid) {\r\n <span class=\"min-w-0 flex-1 truncate text-surface-900\">\r\n {{\r\n getPreviewSummary(p.summary) ||\r\n t(\"delegations.column.scopeSummary\")\r\n }}\r\n </span>\r\n } @else {\r\n <span class=\"font-medium text-red-700\">\r\n {{ t(\"delegations.scope.previewInvalid\") }}\r\n </span>\r\n }\r\n } @else {\r\n <span class=\"text-surface-500\">\r\n {{ t(\"delegations.scope.noSelection\") }}\r\n </span>\r\n }\r\n </div>\r\n @if (preview(); as p) {\r\n @if (p.warnings.length > 0) {\r\n <ul\r\n class=\"list-inside list-disc space-y-0.5 text-xs text-amber-700\"\r\n >\r\n @for (w of p.warnings; track $index) {\r\n <li>{{ w.message }}</li>\r\n }\r\n </ul>\r\n }\r\n @if (p.deniedItems.length > 0) {\r\n <ul class=\"list-inside list-disc space-y-0.5 text-xs text-red-700\">\r\n @for (d of p.deniedItems; track $index) {\r\n <li>\r\n {{ formatDeniedTarget(d.targetKey) }} /\r\n {{ formatDeniedOperation(d.operationKey) }} -\r\n {{ d.reasonCode }}\r\n </li>\r\n }\r\n </ul>\r\n }\r\n }\r\n </div>\r\n }\r\n </div>\r\n</ng-container>\r\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ScrollingModule }, { kind: "directive", type: i3.CdkFixedSizeVirtualScroll, selector: "cdk-virtual-scroll-viewport[itemSize]", inputs: ["itemSize", "minBufferPx", "maxBufferPx"] }, { kind: "directive", type: i3.CdkVirtualForOf, selector: "[cdkVirtualFor][cdkVirtualForOf]", inputs: ["cdkVirtualForOf", "cdkVirtualForTrackBy", "cdkVirtualForTemplate", "cdkVirtualForTemplateCacheSize"] }, { kind: "component", type: i3.CdkVirtualScrollViewport, selector: "cdk-virtual-scroll-viewport", inputs: ["orientation", "appendOnly"], outputs: ["scrolledIndexChange"] }, { 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", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: Icon, selector: "mt-icon", inputs: ["icon"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { kind: "component", type: TextField, selector: "mt-text-field", inputs: ["field", "hint", "label", "placeholder", "class", "type", "readonly", "pInputs", "required", "maxLength", "icon", "iconPosition"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i4.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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2681
2982
|
}
|
|
2682
2983
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: ScopePicker, decorators: [{
|
|
2683
2984
|
type: Component,
|
|
@@ -3133,7 +3434,7 @@ class DelegationForm {
|
|
|
3133
3434
|
};
|
|
3134
3435
|
}
|
|
3135
3436
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationForm, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3136
|
-
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 }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div\r\n [class]=\"\r\n 'flex h-full min-h-0 min-w-0 flex-col overflow-hidden ' +\r\n modal.contentClass\r\n \"\r\n >\r\n <div\r\n class=\"grid min-h-0 flex-1 gap-4 overflow-y-auto p-4 max-[640px]:p-3 lg:grid-cols-2 lg:items-start\"\r\n >\r\n <section\r\n class=\"flex flex-col gap-4 rounded-3xl border border-surface-200 bg-surface-0 p-4 shadow-xs lg:sticky lg:top-0\"\r\n >\r\n <mt-dynamic-form\r\n [formConfig]=\"formConfig()\"\r\n [formControl]=\"delegationFormControl\"\r\n />\r\n </section>\r\n\r\n <section class=\"flex min-h-0 flex-col gap-3\">\r\n <div class=\"flex flex-col gap-1\">\r\n <h3 class=\"text-xl font-semibold text-surface-900\">\r\n {{ t(\"delegations.scope.permissionsTitle\") }}\r\n </h3>\r\n <p class=\"text-sm text-surface-500\">\r\n {{\r\n adminMode() && !showScopePicker()\r\n ? t(\"delegations.form.selectDelegatorFirst\")\r\n : t(\"delegations.scope.permissionsSubtitle\")\r\n }}\r\n </p>\r\n </div>\r\n\r\n @if (showScopePicker()) {\r\n <mt-scope-picker\r\n [(scope)]=\"scope\"\r\n [readonly]=\"readonly()\"\r\n [adminMode]=\"adminMode()\"\r\n [delegatorUserId]=\"selectedDelegatorId()\"\r\n ></mt-scope-picker>\r\n } @else {\r\n <div\r\n class=\"flex min-h-72 flex-col items-center justify-center gap-4 rounded-3xl border border-dashed border-surface-300 bg-surface-50 px-6 py-8 text-center\"\r\n >\r\n <div\r\n class=\"flex size-18 items-center justify-center rounded-3xl bg-surface-0 text-primary shadow-sm\"\r\n >\r\n <svg\r\n viewBox=\"0 0 64 64\"\r\n class=\"size-10\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n aria-hidden=\"true\"\r\n >\r\n <rect\r\n x=\"10\"\r\n y=\"12\"\r\n width=\"44\"\r\n height=\"40\"\r\n rx=\"12\"\r\n class=\"fill-primary/10 stroke-primary/35\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M22 28H42\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M22 36H34\"\r\n class=\"stroke-primary/70\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <circle\r\n cx=\"45\"\r\n cy=\"20\"\r\n r=\"7\"\r\n class=\"fill-surface-0 stroke-primary\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M45 17V23\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"2.5\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M42 20H48\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"2.5\"\r\n stroke-linecap=\"round\"\r\n />\r\n </svg>\r\n </div>\r\n <div class=\"space-y-1\">\r\n <p class=\"text-base font-medium text-surface-900\">\r\n {{ t(\"delegations.scope.permissionsTitle\") }}\r\n </p>\r\n <p class=\"text-sm text-surface-500\">\r\n {{ t(\"delegations.form.selectDelegatorFirst\") }}\r\n </p>\r\n </div>\r\n </div>\r\n }\r\n </section>\r\n </div>\r\n </div>\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n [label]=\"t('delegations.common.cancel')\"\r\n variant=\"outlined\"\r\n (click)=\"ref.close()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n />\r\n @if (!readonly()) {\r\n <mt-button\r\n [label]=\"\r\n delegationForEdit()\r\n ? t('delegations.common.update')\r\n : t('delegations.common.create')\r\n \"\r\n [loading]=\"isSaving()\"\r\n [disabled]=\"!delegationFormControl.valid || !canSubmit()\"\r\n (click)=\"onSubmit()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n />\r\n }\r\n </div>\r\n</ng-container>\r\n", 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", "forcedDisabledFieldKeys", "preserveForcedHiddenValues", "visibleSectionKeys", "externalValues"], outputs: ["runtimeMessagesChange"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: ScopePicker, selector: "mt-scope-picker", inputs: ["scope", "readonly", "adminMode", "delegatorUserId"], outputs: ["scopeChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3437
|
+
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 }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div\r\n [class]=\"\r\n 'flex h-full min-h-0 min-w-0 flex-col overflow-hidden ' +\r\n modal.contentClass\r\n \"\r\n >\r\n <div\r\n class=\"grid min-h-0 flex-1 gap-4 overflow-y-auto p-4 max-[640px]:p-3 lg:grid-cols-2 lg:items-start\"\r\n >\r\n <section\r\n class=\"flex flex-col gap-4 rounded-3xl border border-surface-200 bg-surface-0 p-4 shadow-xs lg:sticky lg:top-0\"\r\n >\r\n <mt-dynamic-form\r\n [formConfig]=\"formConfig()\"\r\n [formControl]=\"delegationFormControl\"\r\n />\r\n </section>\r\n\r\n <section class=\"flex min-h-0 flex-col gap-3\">\r\n <div class=\"flex flex-col gap-1\">\r\n <h3 class=\"text-xl font-semibold text-surface-900\">\r\n {{ t(\"delegations.scope.permissionsTitle\") }}\r\n </h3>\r\n <p class=\"text-sm text-surface-500\">\r\n {{\r\n adminMode() && !showScopePicker()\r\n ? t(\"delegations.form.selectDelegatorFirst\")\r\n : t(\"delegations.scope.permissionsSubtitle\")\r\n }}\r\n </p>\r\n </div>\r\n\r\n @if (showScopePicker()) {\r\n <mt-scope-picker\r\n [(scope)]=\"scope\"\r\n [readonly]=\"readonly()\"\r\n [adminMode]=\"adminMode()\"\r\n [delegatorUserId]=\"selectedDelegatorId()\"\r\n ></mt-scope-picker>\r\n } @else {\r\n <div\r\n class=\"flex min-h-72 flex-col items-center justify-center gap-4 rounded-3xl border border-dashed border-surface-300 bg-surface-50 px-6 py-8 text-center\"\r\n >\r\n <div\r\n class=\"flex size-18 items-center justify-center rounded-3xl bg-surface-0 text-primary shadow-sm\"\r\n >\r\n <svg\r\n viewBox=\"0 0 64 64\"\r\n class=\"size-10\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n aria-hidden=\"true\"\r\n >\r\n <rect\r\n x=\"10\"\r\n y=\"12\"\r\n width=\"44\"\r\n height=\"40\"\r\n rx=\"12\"\r\n class=\"fill-primary/10 stroke-primary/35\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M22 28H42\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M22 36H34\"\r\n class=\"stroke-primary/70\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <circle\r\n cx=\"45\"\r\n cy=\"20\"\r\n r=\"7\"\r\n class=\"fill-surface-0 stroke-primary\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M45 17V23\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"2.5\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M42 20H48\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"2.5\"\r\n stroke-linecap=\"round\"\r\n />\r\n </svg>\r\n </div>\r\n <div class=\"space-y-1\">\r\n <p class=\"text-base font-medium text-surface-900\">\r\n {{ t(\"delegations.scope.permissionsTitle\") }}\r\n </p>\r\n <p class=\"text-sm text-surface-500\">\r\n {{ t(\"delegations.form.selectDelegatorFirst\") }}\r\n </p>\r\n </div>\r\n </div>\r\n }\r\n </section>\r\n </div>\r\n </div>\r\n <div [class]=\"modal.footerClass\">\r\n <mt-button\r\n [label]=\"t('delegations.common.cancel')\"\r\n variant=\"outlined\"\r\n (click)=\"ref.close()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n />\r\n @if (!readonly()) {\r\n <mt-button\r\n [label]=\"\r\n delegationForEdit()\r\n ? t('delegations.common.update')\r\n : t('delegations.common.create')\r\n \"\r\n [loading]=\"isSaving()\"\r\n [disabled]=\"!delegationFormControl.valid || !canSubmit()\"\r\n (click)=\"onSubmit()\"\r\n styleClass=\"w-full sm:w-auto\"\r\n />\r\n }\r\n </div>\r\n</ng-container>\r\n", 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", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: DynamicForm, selector: "mt-dynamic-form", inputs: ["formConfig", "forcedHiddenFieldKeys", "forcedDisabledFieldKeys", "preserveForcedHiddenValues", "visibleSectionKeys", "externalValues"], outputs: ["runtimeMessagesChange"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "component", type: ScopePicker, selector: "mt-scope-picker", inputs: ["scope", "readonly", "adminMode", "delegatorUserId"], outputs: ["scopeChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3137
3438
|
}
|
|
3138
3439
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationForm, decorators: [{
|
|
3139
3440
|
type: Component,
|
|
@@ -3298,7 +3599,7 @@ class DelegationDetailDrawer {
|
|
|
3298
3599
|
return 'children' in node && node.children.length > 0;
|
|
3299
3600
|
}
|
|
3300
3601
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationDetailDrawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3301
|
-
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 }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col h-full\">\r\n <!-- Tabs -->\r\n <div class=\"border-b border-surface-200 px-4 pt-2\">\r\n <mt-tabs\r\n mode=\"underline\"\r\n [(active)]=\"activeTab\"\r\n [options]=\"tabOptions()\"\r\n fluid\r\n ></mt-tabs>\r\n </div>\r\n\r\n <!-- Body -->\r\n <div class=\"flex-1 overflow-y-auto p-4\">\r\n @if (isLoading() && !detail()) {\r\n <p-skeleton height=\"2rem\" class=\"mb-3\"></p-skeleton>\r\n <p-skeleton height=\"6rem\" class=\"mb-3\"></p-skeleton>\r\n <p-skeleton height=\"6rem\"></p-skeleton>\r\n } @else if (detail(); as d) {\r\n @if (activeTab() === \"overview\") {\r\n <div class=\"flex flex-col gap-4\">\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <div class=\"flex items-center gap-3 min-w-0\">\r\n <mt-entity-preview\r\n [data]=\"\r\n userEntity(\r\n d.row.delegator,\r\n t('delegations.column.delegatorName')\r\n )\r\n \"\r\n ></mt-entity-preview>\r\n </div>\r\n <mt-delegation-status-chip\r\n [status]=\"d.status.effectiveStatus\"\r\n [reasonCode]=\"d.status.reasonCode\"\r\n ></mt-delegation-status-chip>\r\n </div>\r\n\r\n <div class=\"grid grid-cols-2 gap-4\">\r\n <div class=\"flex flex-col\">\r\n <mt-entity-preview\r\n [data]=\"\r\n userEntity(\r\n d.row.delegatedUser,\r\n t('delegations.column.delegatedTo')\r\n )\r\n \"\r\n ></mt-entity-preview>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.approval\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">{{\r\n approvalLabel()\r\n }}</span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.startDate\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n {{ d.row.startsAtUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.endDate\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n {{ d.row.endsAtUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.form.timeZone\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">{{\r\n d.row.timeZoneId\r\n }}</span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.days\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n @if (d.row.dayRuleMode === \"FullRange\") {\r\n {{ t(\"delegations.form.fullRange\") }}\r\n } @else {\r\n {{ formatSpecificDays(d.row.specificDays) }}\r\n }\r\n </span>\r\n </div>\r\n </div>\r\n\r\n @if (d.row.cancellation?.cancellationReason) {\r\n <div\r\n class=\"rounded-md border border-surface-200 bg-surface-50 p-3 text-sm text-surface-800\"\r\n >\r\n <div class=\"font-medium\">\r\n {{ t(\"delegations.form.cancellationReason\") }}\r\n </div>\r\n <div>{{ d.row.cancellation.cancellationReason }}</div>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Scope tab -->\r\n <div\r\n class=\"flex flex-col overflow-hidden rounded-lg border border-surface\"\r\n >\r\n <div\r\n class=\"flex items-center justify-between gap-2 border-b border-surface bg-surface-50 px-3 py-2\"\r\n >\r\n <span class=\"text-xs font-semibold uppercase text-surface-500\">\r\n {{ t(\"delegations.scope.permissionsTitle\") }}\r\n </span>\r\n @if (selectedOperationCount() > 0) {\r\n <span class=\"text-xs font-medium text-surface-500\">\r\n {{ selectedOperationCount() }}\r\n {{ t(\"delegations.scope.selected\") }}\r\n </span>\r\n }\r\n </div>\r\n\r\n @if (scopeVisibleNodes().length > 0) {\r\n <div class=\"flex max-h-[34rem] flex-col overflow-y-auto\">\r\n @for (\r\n node of scopeVisibleNodes();\r\n track trackScopeNode($index, node)\r\n ) {\r\n <div\r\n class=\"group flex min-h-10 items-center gap-2 pe-3 transition-colors hover:bg-surface-50\"\r\n [style.padding-inline-start.rem]=\"0.75 + node.depth * 1.25\"\r\n >\r\n @if (hasChildren(node)) {\r\n <span\r\n class=\"flex size-6 shrink-0 items-center justify-center rounded-md text-surface-400\"\r\n aria-hidden=\"true\"\r\n >\r\n <svg\r\n viewBox=\"0 0 20 20\"\r\n class=\"size-4\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path\r\n d=\"M5 7L10 12L15 7\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"1.8\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n />\r\n </svg>\r\n </span>\r\n } @else {\r\n <span class=\"size-6 shrink-0\"></span>\r\n }\r\n\r\n <span\r\n class=\"flex size-[18px] shrink-0 items-center justify-center rounded-[5px] border border-primary-500 bg-primary-500 text-white\"\r\n aria-hidden=\"true\"\r\n >\r\n <svg\r\n viewBox=\"0 0 16 16\"\r\n class=\"size-3\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path\r\n d=\"M3.5 8.5L6.5 11.5L12.5 4.5\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n />\r\n </svg>\r\n </span>\r\n\r\n <span\r\n class=\"min-w-0 flex-1 truncate text-sm\"\r\n [class.font-semibold]=\"node.kind === 'template'\"\r\n [class.text-surface-900]=\"node.kind === 'template'\"\r\n [class.font-medium]=\"\r\n node.kind === 'level' || node.kind === 'module'\r\n \"\r\n [class.text-surface-800]=\"\r\n node.kind === 'level' || node.kind === 'module'\r\n \"\r\n [class.text-surface-600]=\"node.kind === 'operation'\"\r\n >\r\n {{ node.label }}\r\n </span>\r\n\r\n @if (node.kind === \"operation\" && node.isHighRisk) {\r\n <span\r\n class=\"shrink-0 rounded-full bg-amber-100 px-2 py-0.5 text-[0.625rem] font-medium text-amber-700\"\r\n >\r\n {{ t(\"delegations.scope.highRisk\") }}\r\n </span>\r\n }\r\n\r\n @if (!node.isAvailable) {\r\n <span\r\n class=\"shrink-0 rounded-full bg-surface-100 px-2 py-0.5 text-[0.625rem] font-medium text-surface-500\"\r\n >\r\n {{ t(\"delegations.scope.unavailable\") }}\r\n </span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <p class=\"px-3 py-8 text-center text-sm text-surface-500\">\r\n {{ t(\"delegations.scope.noSelection\") }}\r\n </p>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div\r\n class=\"border-t border-surface-200 px-4 py-3 flex items-center justify-end\"\r\n >\r\n <mt-button\r\n [label]=\"t('delegations.common.cancel')\"\r\n variant=\"outlined\"\r\n (click)=\"ref.close()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: 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: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i4.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { 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", "reasonCode"] }, { kind: "pipe", type: i1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3602
|
+
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 }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <div class=\"flex flex-col h-full\">\r\n <!-- Tabs -->\r\n <div class=\"border-b border-surface-200 px-4 pt-2\">\r\n <mt-tabs\r\n mode=\"underline\"\r\n [(active)]=\"activeTab\"\r\n [options]=\"tabOptions()\"\r\n fluid\r\n ></mt-tabs>\r\n </div>\r\n\r\n <!-- Body -->\r\n <div class=\"flex-1 overflow-y-auto p-4\">\r\n @if (isLoading() && !detail()) {\r\n <p-skeleton height=\"2rem\" class=\"mb-3\"></p-skeleton>\r\n <p-skeleton height=\"6rem\" class=\"mb-3\"></p-skeleton>\r\n <p-skeleton height=\"6rem\"></p-skeleton>\r\n } @else if (detail(); as d) {\r\n @if (activeTab() === \"overview\") {\r\n <div class=\"flex flex-col gap-4\">\r\n <div class=\"flex items-center justify-between gap-3\">\r\n <div class=\"flex items-center gap-3 min-w-0\">\r\n <mt-entity-preview\r\n [data]=\"\r\n userEntity(\r\n d.row.delegator,\r\n t('delegations.column.delegatorName')\r\n )\r\n \"\r\n ></mt-entity-preview>\r\n </div>\r\n <mt-delegation-status-chip\r\n [status]=\"d.status.effectiveStatus\"\r\n [reasonCode]=\"d.status.reasonCode\"\r\n ></mt-delegation-status-chip>\r\n </div>\r\n\r\n <div class=\"grid grid-cols-2 gap-4\">\r\n <div class=\"flex flex-col\">\r\n <mt-entity-preview\r\n [data]=\"\r\n userEntity(\r\n d.row.delegatedUser,\r\n t('delegations.column.delegatedTo')\r\n )\r\n \"\r\n ></mt-entity-preview>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.approval\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">{{\r\n approvalLabel()\r\n }}</span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.startDate\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n {{ d.row.startsAtUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.endDate\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n {{ d.row.endsAtUtc | date: \"medium\" }}\r\n </span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.form.timeZone\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">{{\r\n d.row.timeZoneId\r\n }}</span>\r\n </div>\r\n <div class=\"flex flex-col\">\r\n <span class=\"text-xs text-surface-500\">\r\n {{ t(\"delegations.column.days\") }}\r\n </span>\r\n <span class=\"text-sm text-surface-900\">\r\n @if (d.row.dayRuleMode === \"FullRange\") {\r\n {{ t(\"delegations.form.fullRange\") }}\r\n } @else {\r\n {{ formatSpecificDays(d.row.specificDays) }}\r\n }\r\n </span>\r\n </div>\r\n </div>\r\n\r\n @if (d.row.cancellation?.cancellationReason) {\r\n <div\r\n class=\"rounded-md border border-surface-200 bg-surface-50 p-3 text-sm text-surface-800\"\r\n >\r\n <div class=\"font-medium\">\r\n {{ t(\"delegations.form.cancellationReason\") }}\r\n </div>\r\n <div>{{ d.row.cancellation.cancellationReason }}</div>\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Scope tab -->\r\n <div\r\n class=\"flex flex-col overflow-hidden rounded-lg border border-surface\"\r\n >\r\n <div\r\n class=\"flex items-center justify-between gap-2 border-b border-surface bg-surface-50 px-3 py-2\"\r\n >\r\n <span class=\"text-xs font-semibold uppercase text-surface-500\">\r\n {{ t(\"delegations.scope.permissionsTitle\") }}\r\n </span>\r\n @if (selectedOperationCount() > 0) {\r\n <span class=\"text-xs font-medium text-surface-500\">\r\n {{ selectedOperationCount() }}\r\n {{ t(\"delegations.scope.selected\") }}\r\n </span>\r\n }\r\n </div>\r\n\r\n @if (scopeVisibleNodes().length > 0) {\r\n <div class=\"flex max-h-[34rem] flex-col overflow-y-auto\">\r\n @for (\r\n node of scopeVisibleNodes();\r\n track trackScopeNode($index, node)\r\n ) {\r\n <div\r\n class=\"group flex min-h-10 items-center gap-2 pe-3 transition-colors hover:bg-surface-50\"\r\n [style.padding-inline-start.rem]=\"0.75 + node.depth * 1.25\"\r\n >\r\n @if (hasChildren(node)) {\r\n <span\r\n class=\"flex size-6 shrink-0 items-center justify-center rounded-md text-surface-400\"\r\n aria-hidden=\"true\"\r\n >\r\n <svg\r\n viewBox=\"0 0 20 20\"\r\n class=\"size-4\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path\r\n d=\"M5 7L10 12L15 7\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"1.8\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n />\r\n </svg>\r\n </span>\r\n } @else {\r\n <span class=\"size-6 shrink-0\"></span>\r\n }\r\n\r\n <span\r\n class=\"flex size-[18px] shrink-0 items-center justify-center rounded-[5px] border border-primary-500 bg-primary-500 text-white\"\r\n aria-hidden=\"true\"\r\n >\r\n <svg\r\n viewBox=\"0 0 16 16\"\r\n class=\"size-3\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n >\r\n <path\r\n d=\"M3.5 8.5L6.5 11.5L12.5 4.5\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\"\r\n />\r\n </svg>\r\n </span>\r\n\r\n <span\r\n class=\"min-w-0 flex-1 truncate text-sm\"\r\n [class.font-semibold]=\"node.kind === 'template'\"\r\n [class.text-surface-900]=\"node.kind === 'template'\"\r\n [class.font-medium]=\"\r\n node.kind === 'level' || node.kind === 'module'\r\n \"\r\n [class.text-surface-800]=\"\r\n node.kind === 'level' || node.kind === 'module'\r\n \"\r\n [class.text-surface-600]=\"node.kind === 'operation'\"\r\n >\r\n {{ node.label }}\r\n </span>\r\n\r\n @if (node.kind === \"operation\" && node.isHighRisk) {\r\n <span\r\n class=\"shrink-0 rounded-full bg-amber-100 px-2 py-0.5 text-[0.625rem] font-medium text-amber-700\"\r\n >\r\n {{ t(\"delegations.scope.highRisk\") }}\r\n </span>\r\n }\r\n\r\n @if (!node.isAvailable) {\r\n <span\r\n class=\"shrink-0 rounded-full bg-surface-100 px-2 py-0.5 text-[0.625rem] font-medium text-surface-500\"\r\n >\r\n {{ t(\"delegations.scope.unavailable\") }}\r\n </span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n } @else {\r\n <p class=\"px-3 py-8 text-center text-sm text-surface-500\">\r\n {{ t(\"delegations.scope.noSelection\") }}\r\n </p>\r\n }\r\n </div>\r\n }\r\n }\r\n </div>\r\n\r\n <!-- Footer -->\r\n <div\r\n class=\"border-t border-surface-200 px-4 py-3 flex items-center justify-end\"\r\n >\r\n <mt-button\r\n [label]=\"t('delegations.common.cancel')\"\r\n variant=\"outlined\"\r\n (click)=\"ref.close()\"\r\n ></mt-button>\r\n </div>\r\n </div>\r\n</ng-container>\r\n", styles: [":host{display:block;height:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: 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", "ariaLabel"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: EntityPreview, selector: "mt-entity-preview", inputs: ["data", "attachmentShape"] }, { kind: "ngmodule", type: SkeletonModule }, { kind: "component", type: i4.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { 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", "reasonCode"] }, { kind: "pipe", type: i1.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3302
3603
|
}
|
|
3303
3604
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationDetailDrawer, decorators: [{
|
|
3304
3605
|
type: Component,
|
|
@@ -3941,7 +4242,7 @@ class DelegationsList {
|
|
|
3941
4242
|
this.busyIds.update((ids) => on ? [...ids, key] : ids.filter((id) => id !== key));
|
|
3942
4243
|
}
|
|
3943
4244
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsList, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3944
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationsList, isStandalone: true, selector: "mt-delegations-list", inputs: { showBreadcrumb: { classPropertyName: "showBreadcrumb", publicName: "showBreadcrumb", isSignal: true, isRequired: false, transformFunction: null }, showPageShell: { classPropertyName: "showPageShell", publicName: "showPageShell", isSignal: true, isRequired: false, transformFunction: null }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null }, surfaceTitleKey: { classPropertyName: "surfaceTitleKey", publicName: "surfaceTitleKey", isSignal: true, isRequired: false, transformFunction: null }, assignedTabLabelKey: { classPropertyName: "assignedTabLabelKey", publicName: "assignedTabLabelKey", isSignal: true, isRequired: false, transformFunction: null }, assignedDecisionTab: { classPropertyName: "assignedDecisionTab", publicName: "assignedDecisionTab", isSignal: true, isRequired: false, transformFunction: null }, assignedEmptyStateKey: { classPropertyName: "assignedEmptyStateKey", publicName: "assignedEmptyStateKey", isSignal: true, isRequired: false, transformFunction: null }, assignedStatus: { classPropertyName: "assignedStatus", publicName: "assignedStatus", isSignal: true, isRequired: false, transformFunction: null }, showApprovalTab: { classPropertyName: "showApprovalTab", publicName: "showApprovalTab", isSignal: true, isRequired: false, transformFunction: null }, approvalTabLabelKey: { classPropertyName: "approvalTabLabelKey", publicName: "approvalTabLabelKey", isSignal: true, isRequired: false, transformFunction: null }, approvalEmptyStateKey: { classPropertyName: "approvalEmptyStateKey", publicName: "approvalEmptyStateKey", isSignal: true, isRequired: false, transformFunction: null }, approvalStatus: { classPropertyName: "approvalStatus", publicName: "approvalStatus", isSignal: true, isRequired: false, transformFunction: null }, routed: { classPropertyName: "routed", publicName: "routed", isSignal: true, isRequired: false, transformFunction: null }, tab: { classPropertyName: "tab", publicName: "tab", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, itemSelected: { classPropertyName: "itemSelected", publicName: "itemSelected", isSignal: true, isRequired: false, transformFunction: null }, selectedItemId: { classPropertyName: "selectedItemId", publicName: "selectedItemId", isSignal: true, isRequired: false, transformFunction: null }, selectedItem: { classPropertyName: "selectedItem", publicName: "selectedItem", isSignal: true, isRequired: false, transformFunction: null }, itemId: { classPropertyName: "itemId", publicName: "itemId", isSignal: true, isRequired: false, transformFunction: null }, delegationId: { classPropertyName: "delegationId", publicName: "delegationId", isSignal: true, isRequired: false, transformFunction: null }, edit: { classPropertyName: "edit", publicName: "edit", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <ng-template #tableContent>\r\n <div class=\"flex flex-col gap-4\">\r\n @if (!adminMode()) {\r\n @let assignedLabel =\r\n assignedDecisionTab()\r\n ? t(\"delegations.action.approve\") +\r\n \" / \" +\r\n t(\"delegations.action.reject\")\r\n : t(assignedTabLabelKey());\r\n\r\n <mt-tabs\r\n mode=\"underline\"\r\n [active]=\"activeTab()\"\r\n [options]=\"\r\n showApprovalTab()\r\n ? [\r\n { value: 'my', label: t('delegations.myDelegations') },\r\n { value: 'assigned', label: assignedLabel },\r\n { value: 'approvals', label: t(approvalTabLabelKey()) },\r\n ]\r\n : [\r\n { value: 'my', label: t('delegations.myDelegations') },\r\n { value: 'assigned', label: assignedLabel },\r\n ]\r\n \"\r\n fluid\r\n (onChange)=\"switchTab($event)\"\r\n ></mt-tabs>\r\n }\r\n\r\n <mt-table\r\n [data]=\"tableRows()\"\r\n [columns]=\"tableColumns()\"\r\n [actions]=\"tableActions()\"\r\n [rowActions]=\"rowActions()\"\r\n [loading]=\"isLoading()\"\r\n [lazy]=\"true\"\r\n [lazyTotalRecords]=\"currentPage()?.totalCount ?? 0\"\r\n dataKey=\"delegationId\"\r\n [noCard]=\"true\"\r\n (lazyLoad)=\"onLazyLoad($event)\"\r\n >\r\n <ng-template #empty>\r\n <div\r\n class=\"flex min-h-64 flex-col items-center justify-center gap-4 px-6 py-10 text-center\"\r\n >\r\n <div\r\n class=\"flex size-16 items-center justify-center rounded-3xl bg-surface-0 text-primary shadow-sm\"\r\n >\r\n <svg\r\n viewBox=\"0 0 64 64\"\r\n class=\"size-9\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n aria-hidden=\"true\"\r\n >\r\n <rect\r\n x=\"10\"\r\n y=\"14\"\r\n width=\"44\"\r\n height=\"36\"\r\n rx=\"12\"\r\n class=\"fill-primary/10 stroke-primary/35\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M21 28H43\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M21 36H35\"\r\n class=\"stroke-primary/70\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n </svg>\r\n </div>\r\n <p class=\"max-w-sm text-sm text-surface-500\">\r\n {{ t(emptyStateKey()) }}\r\n </p>\r\n </div>\r\n </ng-template>\r\n </mt-table>\r\n </div>\r\n </ng-template>\r\n\r\n <ng-template #adminContent>\r\n <div class=\"flex min-h-full flex-col gap-4\">\r\n @if (showBreadcrumb()) {\r\n <mt-breadcrumb [items]=\"breadcrumbItems()\"></mt-breadcrumb>\r\n }\r\n <ng-container *ngTemplateOutlet=\"tableContent\"></ng-container>\r\n </div>\r\n </ng-template>\r\n\r\n @if (adminMode()) {\r\n @if (showPageShell()) {\r\n <mt-page\r\n [title]=\"surfaceTitle()\"\r\n [avatarIcon]=\"'custom.hierarchy-structure'\"\r\n [contentClass]=\"'max-[1025px]:p-4 max-[640px]:p-3'\"\r\n [avatarStyle]=\"{\r\n '--p-avatar-background': 'var(--p-indigo-50)',\r\n '--p-avatar-color': 'var(--p-indigo-700)',\r\n }\"\r\n (backButtonClick)=\"goBack()\"\r\n backButton\r\n >\r\n <ng-container *ngTemplateOutlet=\"adminContent\"></ng-container>\r\n </mt-page>\r\n } @else {\r\n <ng-container *ngTemplateOutlet=\"adminContent\"></ng-container>\r\n }\r\n } @else {\r\n <div class=\"flex h-full flex-col gap-4 ps-4 pt-4\">\r\n <mt-card class=\"rounded-e-none!\" [title]=\"surfaceTitle()\">\r\n <ng-container *ngTemplateOutlet=\"tableContent\"></ng-container>\r\n </mt-card>\r\n </div>\r\n }\r\n</ng-container>\r\n", styles: [":host{display:block;min-width:0}:host ::ng-deep mt-table td.mt-actions-column>div{flex-wrap:nowrap;justify-content:flex-end;white-space:nowrap}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Breadcrumb, selector: "mt-breadcrumb", inputs: ["items", "styleClass"], outputs: ["onItemClick"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: Page, selector: "mt-page", inputs: ["backButton", "backButtonIcon", "avatarIcon", "avatarStyle", "avatarShape", "title", "tabs", "activeTab", "contentClass", "contentId"], outputs: ["backButtonClick", "tabChange"] }, { 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", "persistStateExclude", "exportable", "printable", "groupable", "groupCountMap", "cellClickFilter", "freezeActions", "virtualScroll", "virtualScrollItemSize", "scrollHeight", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy", "sortField", "sortDirection"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange", "sortFieldChange", "sortDirectionChange"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
4245
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.8", type: DelegationsList, isStandalone: true, selector: "mt-delegations-list", inputs: { showBreadcrumb: { classPropertyName: "showBreadcrumb", publicName: "showBreadcrumb", isSignal: true, isRequired: false, transformFunction: null }, showPageShell: { classPropertyName: "showPageShell", publicName: "showPageShell", isSignal: true, isRequired: false, transformFunction: null }, adminMode: { classPropertyName: "adminMode", publicName: "adminMode", isSignal: true, isRequired: false, transformFunction: null }, surfaceTitleKey: { classPropertyName: "surfaceTitleKey", publicName: "surfaceTitleKey", isSignal: true, isRequired: false, transformFunction: null }, assignedTabLabelKey: { classPropertyName: "assignedTabLabelKey", publicName: "assignedTabLabelKey", isSignal: true, isRequired: false, transformFunction: null }, assignedDecisionTab: { classPropertyName: "assignedDecisionTab", publicName: "assignedDecisionTab", isSignal: true, isRequired: false, transformFunction: null }, assignedEmptyStateKey: { classPropertyName: "assignedEmptyStateKey", publicName: "assignedEmptyStateKey", isSignal: true, isRequired: false, transformFunction: null }, assignedStatus: { classPropertyName: "assignedStatus", publicName: "assignedStatus", isSignal: true, isRequired: false, transformFunction: null }, showApprovalTab: { classPropertyName: "showApprovalTab", publicName: "showApprovalTab", isSignal: true, isRequired: false, transformFunction: null }, approvalTabLabelKey: { classPropertyName: "approvalTabLabelKey", publicName: "approvalTabLabelKey", isSignal: true, isRequired: false, transformFunction: null }, approvalEmptyStateKey: { classPropertyName: "approvalEmptyStateKey", publicName: "approvalEmptyStateKey", isSignal: true, isRequired: false, transformFunction: null }, approvalStatus: { classPropertyName: "approvalStatus", publicName: "approvalStatus", isSignal: true, isRequired: false, transformFunction: null }, routed: { classPropertyName: "routed", publicName: "routed", isSignal: true, isRequired: false, transformFunction: null }, tab: { classPropertyName: "tab", publicName: "tab", isSignal: true, isRequired: false, transformFunction: null }, view: { classPropertyName: "view", publicName: "view", isSignal: true, isRequired: false, transformFunction: null }, selected: { classPropertyName: "selected", publicName: "selected", isSignal: true, isRequired: false, transformFunction: null }, itemSelected: { classPropertyName: "itemSelected", publicName: "itemSelected", isSignal: true, isRequired: false, transformFunction: null }, selectedItemId: { classPropertyName: "selectedItemId", publicName: "selectedItemId", isSignal: true, isRequired: false, transformFunction: null }, selectedItem: { classPropertyName: "selectedItem", publicName: "selectedItem", isSignal: true, isRequired: false, transformFunction: null }, itemId: { classPropertyName: "itemId", publicName: "itemId", isSignal: true, isRequired: false, transformFunction: null }, delegationId: { classPropertyName: "delegationId", publicName: "delegationId", isSignal: true, isRequired: false, transformFunction: null }, edit: { classPropertyName: "edit", publicName: "edit", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<ng-container *transloco=\"let t\">\r\n <ng-template #tableContent>\r\n <div class=\"flex flex-col gap-4\">\r\n @if (!adminMode()) {\r\n @let assignedLabel =\r\n assignedDecisionTab()\r\n ? t(\"delegations.action.approve\") +\r\n \" / \" +\r\n t(\"delegations.action.reject\")\r\n : t(assignedTabLabelKey());\r\n\r\n <mt-tabs\r\n mode=\"underline\"\r\n [active]=\"activeTab()\"\r\n [options]=\"\r\n showApprovalTab()\r\n ? [\r\n { value: 'my', label: t('delegations.myDelegations') },\r\n { value: 'assigned', label: assignedLabel },\r\n { value: 'approvals', label: t(approvalTabLabelKey()) },\r\n ]\r\n : [\r\n { value: 'my', label: t('delegations.myDelegations') },\r\n { value: 'assigned', label: assignedLabel },\r\n ]\r\n \"\r\n fluid\r\n (onChange)=\"switchTab($event)\"\r\n ></mt-tabs>\r\n }\r\n\r\n <mt-table\r\n [data]=\"tableRows()\"\r\n [columns]=\"tableColumns()\"\r\n [actions]=\"tableActions()\"\r\n [rowActions]=\"rowActions()\"\r\n [loading]=\"isLoading()\"\r\n [lazy]=\"true\"\r\n [lazyTotalRecords]=\"currentPage()?.totalCount ?? 0\"\r\n dataKey=\"delegationId\"\r\n [noCard]=\"true\"\r\n (lazyLoad)=\"onLazyLoad($event)\"\r\n >\r\n <ng-template #empty>\r\n <div\r\n class=\"flex min-h-64 flex-col items-center justify-center gap-4 px-6 py-10 text-center\"\r\n >\r\n <div\r\n class=\"flex size-16 items-center justify-center rounded-3xl bg-surface-0 text-primary shadow-sm\"\r\n >\r\n <svg\r\n viewBox=\"0 0 64 64\"\r\n class=\"size-9\"\r\n fill=\"none\"\r\n xmlns=\"http://www.w3.org/2000/svg\"\r\n aria-hidden=\"true\"\r\n >\r\n <rect\r\n x=\"10\"\r\n y=\"14\"\r\n width=\"44\"\r\n height=\"36\"\r\n rx=\"12\"\r\n class=\"fill-primary/10 stroke-primary/35\"\r\n stroke-width=\"2\"\r\n />\r\n <path\r\n d=\"M21 28H43\"\r\n class=\"stroke-primary\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n <path\r\n d=\"M21 36H35\"\r\n class=\"stroke-primary/70\"\r\n stroke-width=\"3\"\r\n stroke-linecap=\"round\"\r\n />\r\n </svg>\r\n </div>\r\n <p class=\"max-w-sm text-sm text-surface-500\">\r\n {{ t(emptyStateKey()) }}\r\n </p>\r\n </div>\r\n </ng-template>\r\n </mt-table>\r\n </div>\r\n </ng-template>\r\n\r\n <ng-template #adminContent>\r\n <div class=\"flex min-h-full flex-col gap-4\">\r\n @if (showBreadcrumb()) {\r\n <mt-breadcrumb [items]=\"breadcrumbItems()\"></mt-breadcrumb>\r\n }\r\n <ng-container *ngTemplateOutlet=\"tableContent\"></ng-container>\r\n </div>\r\n </ng-template>\r\n\r\n @if (adminMode()) {\r\n @if (showPageShell()) {\r\n <mt-page\r\n [title]=\"surfaceTitle()\"\r\n [avatarIcon]=\"'custom.hierarchy-structure'\"\r\n [contentClass]=\"'max-[1025px]:p-4 max-[640px]:p-3'\"\r\n [avatarStyle]=\"{\r\n '--p-avatar-background': 'var(--p-indigo-50)',\r\n '--p-avatar-color': 'var(--p-indigo-700)',\r\n }\"\r\n (backButtonClick)=\"goBack()\"\r\n backButton\r\n >\r\n <ng-container *ngTemplateOutlet=\"adminContent\"></ng-container>\r\n </mt-page>\r\n } @else {\r\n <ng-container *ngTemplateOutlet=\"adminContent\"></ng-container>\r\n }\r\n } @else {\r\n <div class=\"flex h-full flex-col gap-4 ps-4 pt-4\">\r\n <mt-card class=\"rounded-e-none!\" [title]=\"surfaceTitle()\">\r\n <ng-container *ngTemplateOutlet=\"tableContent\"></ng-container>\r\n </mt-card>\r\n </div>\r\n }\r\n</ng-container>\r\n", styles: [":host{display:block;min-width:0}:host ::ng-deep mt-table td.mt-actions-column>div{flex-wrap:nowrap;justify-content:flex-end;white-space:nowrap}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: Breadcrumb, selector: "mt-breadcrumb", inputs: ["items", "styleClass"], outputs: ["onItemClick"] }, { kind: "component", type: Card, selector: "mt-card", inputs: ["class", "title", "paddingless"] }, { kind: "component", type: Page, selector: "mt-page", inputs: ["backButton", "backButtonIcon", "avatarIcon", "avatarStyle", "avatarShape", "title", "tabs", "activeTab", "contentClass", "contentId"], outputs: ["backButtonClick", "tabChange"] }, { 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", "persistStateExclude", "exportable", "printable", "groupable", "groupCountMap", "cellClickFilter", "freezeActions", "virtualScroll", "virtualScrollItemSize", "scrollHeight", "printTitle", "exportFilename", "actionShape", "rowActionsLoadingFn", "tableLayout", "noCard", "tabs", "tabsOptionLabel", "tabsOptionValue", "activeTab", "actions", "emptyTitle", "emptyDescription", "emptyActionLabel", "emptyActionIcon", "paginatorPosition", "alwaysShowPaginator", "rowsPerPageOptions", "pageSize", "currentPage", "first", "filterTerm", "groupBy", "sortField", "sortDirection"], outputs: ["selectionChange", "cellChange", "lazyLoad", "columnReorder", "rowReorder", "rowClick", "emptyAction", "rowActionsRequested", "filtersChange", "activeTabChange", "onTabChange", "pageSizeChange", "currentPageChange", "firstChange", "filterTermChange", "groupByChange", "sortFieldChange", "sortDirectionChange"] }, { kind: "component", type: Tabs, selector: "mt-tabs", inputs: ["options", "optionLabel", "optionValue", "active", "mode", "moreLabel", "defaultIcon", "size", "fluid", "disabled", "searchThreshold"], outputs: ["activeChange", "onChange"] }, { kind: "directive", type: TranslocoDirective, selector: "[transloco]", inputs: ["transloco", "translocoParams", "translocoScope", "translocoRead", "translocoPrefix", "translocoLang", "translocoLoadingTpl"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3945
4246
|
}
|
|
3946
4247
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationsList, decorators: [{
|
|
3947
4248
|
type: Component,
|
|
@@ -4054,5 +4355,5 @@ const appDelegationInterceptor = (req, next) => {
|
|
|
4054
4355
|
* Generated bundle index. Do not edit.
|
|
4055
4356
|
*/
|
|
4056
4357
|
|
|
4057
|
-
export { ApproveDelegation, CancelDelegation, ClearDelegationDetail, ClearScopePreview, CreateDelegationLegacy, CreateDelegationV2, DELEGATED_RUNTIME_REQUEST, DELEGATION_RUNTIME_CONFIG, DelegationDetailDrawer, DelegationForm, DelegationMenuPanel, DelegationSessionActionKey, DelegationSessionFacade, DelegationSessionState, DelegationStatusChip, Delegations, DelegationsActionKey, DelegationsFacade, DelegationsList, DelegationsState, EndDelegationSession, GetActiveAssignedDelegations, GetAdminDelegations, GetApprovalDelegations, GetAssignedDelegations, GetDelegationDetail, GetMyDelegations, GetScopeOptions, LoadDelegationCandidates, PreviewScope, RejectDelegation, RejectDelegationDialog, ScopePicker, StartDelegationSession, StartSessionDialog, SwitchDelegationSession, TopbarDelegationMenu, UpdateDelegationLegacy, UpdateDelegationV2, appDelegationInterceptor, provideDelegationRuntime, withDelegatedRuntime };
|
|
4358
|
+
export { ApproveDelegation, CancelDelegation, ClearDelegationDetail, ClearScopePreview, CreateDelegationLegacy, CreateDelegationV2, DELEGATED_RUNTIME_REQUEST, DELEGATION_RUNTIME_CONFIG, DelegationDetailDrawer, DelegationForm, DelegationMenuPanel, DelegationSessionActionKey, DelegationSessionFacade, DelegationSessionResumeStore, DelegationSessionState, DelegationStatusChip, Delegations, DelegationsActionKey, DelegationsFacade, DelegationsList, DelegationsState, EndDelegationSession, GetActiveAssignedDelegations, GetAdminDelegations, GetApprovalDelegations, GetAssignedDelegations, GetDelegationDetail, GetMyDelegations, GetScopeOptions, LoadDelegationCandidates, PreviewScope, RejectDelegation, RejectDelegationDialog, ResumeDelegationSession, ScopePicker, StartDelegationSession, StartSessionDialog, SwitchDelegationSession, TopbarDelegationMenu, UpdateDelegationLegacy, UpdateDelegationV2, appDelegationInterceptor, provideDelegationRuntime, withDelegatedRuntime };
|
|
4058
4359
|
//# sourceMappingURL=masterteam-delegations.mjs.map
|