@masterteam/delegations 0.0.55 → 0.0.57
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 +400 -142
- package/fesm2022/masterteam-delegations.mjs.map +1 -1
- package/package.json +3 -3
- package/types/masterteam-delegations.d.ts +142 -23
|
@@ -2,22 +2,22 @@ import * as i1 from '@angular/common';
|
|
|
2
2
|
import { DOCUMENT, CommonModule, Location } from '@angular/common';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
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, catchError, from, finalize, shareReplay, filter, 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,62 @@ 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, from the
|
|
57
|
+
* persisted delegation id — never a token.
|
|
58
|
+
*
|
|
59
|
+
* Pass `delegation` when the row is already in hand; otherwise the state loads
|
|
60
|
+
* the candidates itself, so a host can await this action **before** the
|
|
61
|
+
* authenticated shell mounts and boot straight into delegated mode instead of
|
|
62
|
+
* loading everything twice.
|
|
63
|
+
*
|
|
64
|
+
* Completes quietly when there is nothing to resume or the delegation is no
|
|
65
|
+
* longer startable — the user simply stays themselves.
|
|
66
|
+
*/
|
|
67
|
+
class ResumeDelegationSession {
|
|
68
|
+
delegation;
|
|
69
|
+
static type = '[DelegationSession] Resume';
|
|
70
|
+
constructor(delegation = null) {
|
|
71
|
+
this.delegation = delegation;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** End the current delegated session. Client-local only (doc 05). */
|
|
75
|
+
class EndDelegationSession {
|
|
76
|
+
reason;
|
|
77
|
+
static type = '[DelegationSession] End';
|
|
78
|
+
constructor(reason = 'Manual') {
|
|
79
|
+
this.reason = reason;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/** Switch directly from the current session to another delegation. */
|
|
83
|
+
class SwitchDelegationSession {
|
|
84
|
+
delegation;
|
|
85
|
+
static type = '[DelegationSession] Switch';
|
|
86
|
+
constructor(delegation) {
|
|
87
|
+
this.delegation = delegation;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
35
91
|
const DEFAULT_CONFIG = {
|
|
36
92
|
resolveApplicationApiBaseUrl: () => '',
|
|
37
93
|
resolvePromptNamespace: () => ({
|
|
@@ -58,7 +114,7 @@ function withDelegatedRuntime(context = new HttpContext()) {
|
|
|
58
114
|
return context.set(DELEGATED_RUNTIME_REQUEST, true);
|
|
59
115
|
}
|
|
60
116
|
|
|
61
|
-
const STORAGE_KEY = 'mt.delegation.prompt-receipts.v1';
|
|
117
|
+
const STORAGE_KEY$1 = 'mt.delegation.prompt-receipts.v1';
|
|
62
118
|
const MAX_RECEIPTS = 100;
|
|
63
119
|
class DelegationPromptReceiptService {
|
|
64
120
|
document = inject(DOCUMENT);
|
|
@@ -103,7 +159,7 @@ class DelegationPromptReceiptService {
|
|
|
103
159
|
}
|
|
104
160
|
read() {
|
|
105
161
|
try {
|
|
106
|
-
const raw = this.document.defaultView?.localStorage.getItem(STORAGE_KEY);
|
|
162
|
+
const raw = this.document.defaultView?.localStorage.getItem(STORAGE_KEY$1);
|
|
107
163
|
const parsed = raw ? JSON.parse(raw) : [];
|
|
108
164
|
return Array.isArray(parsed)
|
|
109
165
|
? parsed.filter((item) => typeof item === 'string')
|
|
@@ -116,7 +172,7 @@ class DelegationPromptReceiptService {
|
|
|
116
172
|
write(receipts) {
|
|
117
173
|
try {
|
|
118
174
|
const bounded = [...new Set(receipts)].slice(-MAX_RECEIPTS);
|
|
119
|
-
this.document.defaultView?.localStorage.setItem(STORAGE_KEY, JSON.stringify(bounded));
|
|
175
|
+
this.document.defaultView?.localStorage.setItem(STORAGE_KEY$1, JSON.stringify(bounded));
|
|
120
176
|
}
|
|
121
177
|
catch {
|
|
122
178
|
// Storage can be unavailable; the in-memory claim still coalesces dialogs.
|
|
@@ -130,42 +186,92 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
130
186
|
args: [{ providedIn: 'root' }]
|
|
131
187
|
}] });
|
|
132
188
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
189
|
+
const STORAGE_KEY = 'mt.delegation.session-resume.v1';
|
|
190
|
+
/**
|
|
191
|
+
* Lets a delegated session survive a page reload **without persisting the
|
|
192
|
+
* token**.
|
|
193
|
+
*
|
|
194
|
+
* Doc 05 is unambiguous: the delegation access token is memory-only, and it
|
|
195
|
+
* stays that way — this store keeps a single opaque `delegationId` plus the
|
|
196
|
+
* owning user/app/tenant namespace. On the next boot the runtime calls
|
|
197
|
+
* `POST identity/delegations/{id}/session` again and mints a *fresh* token,
|
|
198
|
+
* which is what doc 09 already prescribes for restarting a session ("use the
|
|
199
|
+
* newly returned token because the scope hash or delegation version may have
|
|
200
|
+
* changed"). The backend re-authorizes from scratch every time, so a delegation
|
|
201
|
+
* that expired, was cancelled, or had its scope revoked while the tab was shut
|
|
202
|
+
* simply fails to resume and the user stays themselves.
|
|
203
|
+
*
|
|
204
|
+
* Never holds a token, a candidate record, a name, or an email.
|
|
205
|
+
*/
|
|
206
|
+
class DelegationSessionResumeStore {
|
|
207
|
+
document = inject(DOCUMENT);
|
|
208
|
+
config = inject(DELEGATION_RUNTIME_CONFIG);
|
|
209
|
+
remember(delegationId) {
|
|
210
|
+
this.write({ ns: this.namespace(), delegationId });
|
|
143
211
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
212
|
+
/**
|
|
213
|
+
* The delegation to resume for the *current* user/app/tenant, or `null`. A
|
|
214
|
+
* marker left by a different actor is dropped rather than honoured, so
|
|
215
|
+
* signing in as someone else on a shared browser never resumes their
|
|
216
|
+
* delegation.
|
|
217
|
+
*/
|
|
218
|
+
pending() {
|
|
219
|
+
const marker = this.read();
|
|
220
|
+
if (!marker) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
if (marker.ns !== this.namespace()) {
|
|
224
|
+
this.clear();
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
return marker.delegationId;
|
|
151
228
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
229
|
+
clear() {
|
|
230
|
+
try {
|
|
231
|
+
this.storage()?.removeItem(STORAGE_KEY);
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
// Storage can be unavailable (private mode, blocked cookies).
|
|
235
|
+
}
|
|
159
236
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
237
|
+
namespace() {
|
|
238
|
+
const { userId, applicationKey, tenantKey } = this.config.resolvePromptNamespace();
|
|
239
|
+
return [userId, applicationKey, tenantKey ?? ''].join('|');
|
|
240
|
+
}
|
|
241
|
+
read() {
|
|
242
|
+
try {
|
|
243
|
+
const raw = this.storage()?.getItem(STORAGE_KEY);
|
|
244
|
+
const parsed = raw ? JSON.parse(raw) : null;
|
|
245
|
+
if (typeof parsed !== 'object' ||
|
|
246
|
+
parsed === null ||
|
|
247
|
+
typeof parsed.ns !== 'string' ||
|
|
248
|
+
typeof parsed.delegationId !== 'number') {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
return parsed;
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
167
256
|
}
|
|
257
|
+
write(marker) {
|
|
258
|
+
try {
|
|
259
|
+
this.storage()?.setItem(STORAGE_KEY, JSON.stringify(marker));
|
|
260
|
+
}
|
|
261
|
+
catch {
|
|
262
|
+
// Storage can be unavailable; delegated mode then simply ends on reload.
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
storage() {
|
|
266
|
+
return this.document.defaultView?.localStorage ?? null;
|
|
267
|
+
}
|
|
268
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionResumeStore, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
269
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionResumeStore, providedIn: 'root' });
|
|
168
270
|
}
|
|
271
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionResumeStore, decorators: [{
|
|
272
|
+
type: Injectable,
|
|
273
|
+
args: [{ providedIn: 'root' }]
|
|
274
|
+
}] });
|
|
169
275
|
|
|
170
276
|
var DelegationSessionActionKey;
|
|
171
277
|
(function (DelegationSessionActionKey) {
|
|
@@ -255,6 +361,9 @@ let DelegationSessionState = class DelegationSessionState {
|
|
|
255
361
|
* or open the availability prompt — after sign-out.
|
|
256
362
|
*/
|
|
257
363
|
candidatesGeneration = 0;
|
|
364
|
+
resumeStore = inject(DelegationSessionResumeStore);
|
|
365
|
+
/** One resume attempt per page load — a dead delegation must not retry. */
|
|
366
|
+
resumeAttempted = false;
|
|
258
367
|
constructor() {
|
|
259
368
|
this.actions$
|
|
260
369
|
.pipe(ofActionSuccessful(GatewayLoginSuccessShell))
|
|
@@ -323,6 +432,9 @@ let DelegationSessionState = class DelegationSessionState {
|
|
|
323
432
|
(response.data?.totalCount ?? 0) <= pageSize) {
|
|
324
433
|
queueMicrotask(() => this.store.dispatch(new EndDelegationSession('ScopeChanged')));
|
|
325
434
|
}
|
|
435
|
+
if (!active && page === 1) {
|
|
436
|
+
this.tryResume(candidates);
|
|
437
|
+
}
|
|
326
438
|
return {
|
|
327
439
|
candidates,
|
|
328
440
|
candidatesPage: response.data?.page ?? page,
|
|
@@ -332,6 +444,62 @@ let DelegationSessionState = class DelegationSessionState {
|
|
|
332
444
|
},
|
|
333
445
|
});
|
|
334
446
|
}
|
|
447
|
+
/**
|
|
448
|
+
* Fallback resume for hosts that do not await {@link ResumeDelegationSession}
|
|
449
|
+
* before mounting their authenticated shell: pick the session back up as soon
|
|
450
|
+
* as the candidates arrive.
|
|
451
|
+
*
|
|
452
|
+
* Prefer the awaited path — resuming after the shell has already loaded means
|
|
453
|
+
* every actor-scoped read runs twice, once as the real user and again under
|
|
454
|
+
* delegated authority.
|
|
455
|
+
*/
|
|
456
|
+
tryResume(candidates) {
|
|
457
|
+
if (this.resumeAttempted || this.resumeStore.pending() == null) {
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
const delegationId = this.resumeStore.pending();
|
|
461
|
+
const row = candidates.find((candidate) => candidate.delegationId === delegationId);
|
|
462
|
+
if (!row) {
|
|
463
|
+
// No longer startable (expired, cancelled, scope revoked) — forget it.
|
|
464
|
+
this.resumeAttempted = true;
|
|
465
|
+
this.resumeStore.clear();
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
queueMicrotask(() => this.store.dispatch(new ResumeDelegationSession(row)).subscribe());
|
|
469
|
+
}
|
|
470
|
+
resume(ctx, { delegation }) {
|
|
471
|
+
const delegationId = this.resumeStore.pending();
|
|
472
|
+
if (ctx.getState().active || delegationId == null) {
|
|
473
|
+
return EMPTY;
|
|
474
|
+
}
|
|
475
|
+
// Claim the attempt up front so the nested candidate load below does not
|
|
476
|
+
// also fire `tryResume` for the same delegation.
|
|
477
|
+
this.resumeAttempted = true;
|
|
478
|
+
const known = delegation?.delegationId === delegationId
|
|
479
|
+
? delegation
|
|
480
|
+
: ctx
|
|
481
|
+
.getState()
|
|
482
|
+
.candidates.find((candidate) => candidate.delegationId === delegationId);
|
|
483
|
+
const start$ = known
|
|
484
|
+
? this.startSession(ctx, known, null, 'ResumeSession')
|
|
485
|
+
: this.store.dispatch(new LoadDelegationCandidates()).pipe(switchMap(() => {
|
|
486
|
+
const row = ctx
|
|
487
|
+
.getState()
|
|
488
|
+
.candidates.find((candidate) => candidate.delegationId === delegationId);
|
|
489
|
+
if (!row) {
|
|
490
|
+
// Expired, cancelled, or scope revoked while the tab was closed.
|
|
491
|
+
this.resumeStore.clear();
|
|
492
|
+
return EMPTY;
|
|
493
|
+
}
|
|
494
|
+
return this.startSession(ctx, row, null, 'ResumeSession');
|
|
495
|
+
}));
|
|
496
|
+
// A failed resume is not the caller's problem: drop the marker and let the
|
|
497
|
+
// user carry on as themselves rather than blocking the boot on an error.
|
|
498
|
+
return start$.pipe(catchError(() => {
|
|
499
|
+
this.resumeStore.clear();
|
|
500
|
+
return EMPTY;
|
|
501
|
+
}));
|
|
502
|
+
}
|
|
335
503
|
start(ctx, { delegation }) {
|
|
336
504
|
const previous = ctx.getState().active;
|
|
337
505
|
return this.startSession(ctx, delegation, previous, 'StartSession');
|
|
@@ -352,6 +520,9 @@ let DelegationSessionState = class DelegationSessionState {
|
|
|
352
520
|
delegationVersion: response.data.delegationVersion,
|
|
353
521
|
};
|
|
354
522
|
this.scheduleExpiry(active);
|
|
523
|
+
// Id only — see `DelegationSessionResumeStore`. Never the token.
|
|
524
|
+
this.resumeStore.remember(delegation.delegationId);
|
|
525
|
+
this.resumeAttempted = true;
|
|
355
526
|
return { active };
|
|
356
527
|
},
|
|
357
528
|
}).pipe(switchMap(() => from(Promise.resolve(this.runtime.onContextChanged({
|
|
@@ -364,6 +535,10 @@ let DelegationSessionState = class DelegationSessionState {
|
|
|
364
535
|
// Client-local only — there is no server end-session endpoint (doc 05).
|
|
365
536
|
const previous = ctx.getState().active;
|
|
366
537
|
const clearCandidates = shouldClearCandidates(reason);
|
|
538
|
+
// Whatever the reason, delegated mode is over — a reload must not bring it
|
|
539
|
+
// back. Also blocks a resume that is still queued behind this action.
|
|
540
|
+
this.resumeStore.clear();
|
|
541
|
+
this.resumeAttempted = true;
|
|
367
542
|
// Logout / user / tenant / app switch must always drop the candidate list,
|
|
368
543
|
// even when no delegated session was ever started. Leaving it behind kept
|
|
369
544
|
// the previous actor's delegators in memory across sign-out, which is both
|
|
@@ -416,6 +591,9 @@ let DelegationSessionState = class DelegationSessionState {
|
|
|
416
591
|
__decorate$1([
|
|
417
592
|
Action(LoadDelegationCandidates)
|
|
418
593
|
], DelegationSessionState.prototype, "loadCandidates", null);
|
|
594
|
+
__decorate$1([
|
|
595
|
+
Action(ResumeDelegationSession)
|
|
596
|
+
], DelegationSessionState.prototype, "resume", null);
|
|
419
597
|
__decorate$1([
|
|
420
598
|
Action(StartDelegationSession)
|
|
421
599
|
], DelegationSessionState.prototype, "start", null);
|
|
@@ -459,11 +637,12 @@ DelegationSessionState = __decorate$1([
|
|
|
459
637
|
], DelegationSessionState);
|
|
460
638
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationSessionState, decorators: [{
|
|
461
639
|
type: Injectable
|
|
462
|
-
}], ctorParameters: () => [], propDecorators: { loadCandidates: [], start: [], end: [], switch: [] } });
|
|
640
|
+
}], ctorParameters: () => [], propDecorators: { loadCandidates: [], resume: [], start: [], end: [], switch: [] } });
|
|
463
641
|
|
|
464
642
|
class DelegationSessionFacade {
|
|
465
643
|
store = inject(Store);
|
|
466
644
|
promptReceipts = inject(DelegationPromptReceiptService);
|
|
645
|
+
resumeStore = inject(DelegationSessionResumeStore);
|
|
467
646
|
endInFlight = null;
|
|
468
647
|
// ---------------------------------------------------------------------------
|
|
469
648
|
// Data slices
|
|
@@ -478,8 +657,15 @@ class DelegationSessionFacade {
|
|
|
478
657
|
// ---------------------------------------------------------------------------
|
|
479
658
|
/** On-behalf-of (delegator). */
|
|
480
659
|
onBehalfOf = computed(() => this.active()?.delegation.delegator ?? null, ...(ngDevMode ? [{ debugName: "onBehalfOf" }] : /* istanbul ignore next */ []));
|
|
481
|
-
/**
|
|
482
|
-
|
|
660
|
+
/**
|
|
661
|
+
* Executed-by (the real signed-in user), known whether or not a session is
|
|
662
|
+
* active: every assigned row carries the current user as its `delegatedUser`,
|
|
663
|
+
* so the account switcher can list "you" alongside the delegators without the
|
|
664
|
+
* package depending on the host's auth state.
|
|
665
|
+
*/
|
|
666
|
+
executedBy = computed(() => this.active()?.delegation.delegatedUser ??
|
|
667
|
+
this.candidates()[0]?.delegatedUser ??
|
|
668
|
+
null, ...(ngDevMode ? [{ debugName: "executedBy" }] : /* istanbul ignore next */ []));
|
|
483
669
|
hasCandidates = computed(() => this.candidates().length > 0, ...(ngDevMode ? [{ debugName: "hasCandidates" }] : /* istanbul ignore next */ []));
|
|
484
670
|
hasMoreCandidates = computed(() => this.candidates().length < this.candidatesTotalCount(), ...(ngDevMode ? [{ debugName: "hasMoreCandidates" }] : /* istanbul ignore next */ []));
|
|
485
671
|
isStarting = computed(() => this.loadingActive().includes(DelegationSessionActionKey.StartSession), ...(ngDevMode ? [{ debugName: "isStarting" }] : /* istanbul ignore next */ []));
|
|
@@ -500,6 +686,23 @@ class DelegationSessionFacade {
|
|
|
500
686
|
claimPromptCandidates(candidates) {
|
|
501
687
|
return this.promptReceipts.claimUnseen(candidates);
|
|
502
688
|
}
|
|
689
|
+
/**
|
|
690
|
+
* True when a previous session is waiting to be re-minted after a reload.
|
|
691
|
+
* Synchronous (a `localStorage` read), so a route guard can check it without
|
|
692
|
+
* paying for a round-trip on the overwhelmingly common no-delegation boot.
|
|
693
|
+
*/
|
|
694
|
+
hasPendingResume() {
|
|
695
|
+
return this.resumeStore.pending() != null;
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Re-mints the delegated session the user reloaded out of, and completes when
|
|
699
|
+
* delegated mode is in force (or when there is nothing to resume). Await this
|
|
700
|
+
* **before** mounting the authenticated shell so its reads run once, already
|
|
701
|
+
* delegated.
|
|
702
|
+
*/
|
|
703
|
+
resumeSession() {
|
|
704
|
+
return this.store.dispatch(new ResumeDelegationSession());
|
|
705
|
+
}
|
|
503
706
|
startSession(delegation) {
|
|
504
707
|
return this.store.dispatch(new StartDelegationSession(delegation));
|
|
505
708
|
}
|
|
@@ -591,11 +794,11 @@ class StartSessionDialog {
|
|
|
591
794
|
this.ref.close(false);
|
|
592
795
|
}
|
|
593
796
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: StartSessionDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
594
|
-
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\">\n <div\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\n >\n <mt-entity-preview\n [data]=\"userEntity(delegation().delegator)\"\n ></mt-entity-preview>\n\n <p class=\"text-sm leading-relaxed text-surface-600\">\n {{ t(bodyKey()) }}\n </p>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n variant=\"outlined\"\n color=\"secondary\"\n [label]=\"t('delegations.common.cancel')\"\n [disabled]=\"isBusy()\"\n (click)=\"cancel()\"\n ></mt-button>\n <mt-button\n color=\"primary\"\n icon=\"user.users-check\"\n [label]=\"t(confirmLabelKey())\"\n [loading]=\"isBusy()\"\n (click)=\"confirm()\"\n ></mt-button>\n </div>\n</ng-container>\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 });
|
|
797
|
+
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 });
|
|
595
798
|
}
|
|
596
799
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: StartSessionDialog, decorators: [{
|
|
597
800
|
type: Component,
|
|
598
|
-
args: [{ selector: 'mt-start-session-dialog', imports: [CommonModule, Button, EntityPreview, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\n >\n <mt-entity-preview\n [data]=\"userEntity(delegation().delegator)\"\n ></mt-entity-preview>\n\n <p class=\"text-sm leading-relaxed text-surface-600\">\n {{ t(bodyKey()) }}\n </p>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n variant=\"outlined\"\n color=\"secondary\"\n [label]=\"t('delegations.common.cancel')\"\n [disabled]=\"isBusy()\"\n (click)=\"cancel()\"\n ></mt-button>\n <mt-button\n color=\"primary\"\n icon=\"user.users-check\"\n [label]=\"t(confirmLabelKey())\"\n [loading]=\"isBusy()\"\n (click)=\"confirm()\"\n ></mt-button>\n </div>\n</ng-container>\n" }]
|
|
801
|
+
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" }]
|
|
599
802
|
}], propDecorators: { delegation: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegation", required: true }] }], intent: [{ type: i0.Input, args: [{ isSignal: true, alias: "intent", required: false }] }] } });
|
|
600
803
|
|
|
601
804
|
/**
|
|
@@ -617,74 +820,25 @@ class DelegationCandidatesPromptDialog {
|
|
|
617
820
|
this.ref.close(null);
|
|
618
821
|
}
|
|
619
822
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationCandidatesPromptDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
620
|
-
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\">\n <div\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\n >\n <p class=\"text-sm leading-relaxed text-surface-600\">\n {{ t(\"delegations.session.promptBody\") }}\n </p>\n\n <div class=\"flex flex-col gap-2\">\n @for (cand of candidates(); track cand.delegationId) {\n <button\n type=\"button\"\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\"\n [attr.aria-label]=\"\n t('delegations.session.promptStartAria', {\n delegatorName: cand.delegator.displayName,\n })\n \"\n (click)=\"select(cand)\"\n >\n <span class=\"flex min-w-0 items-center gap-3\">\n <mt-entity-preview\n [data]=\"userEntity(cand.delegator)\"\n ></mt-entity-preview>\n </span>\n <span\n class=\"inline-flex shrink-0 items-center gap-1.5 text-sm font-medium text-primary\"\n >\n <span class=\"hidden sm:inline\">\n {{ t(\"delegations.action.startSession\") }}\n </span>\n <mt-icon icon=\"arrow.arrow-right\" class=\"text-base\"></mt-icon>\n </span>\n </button>\n }\n </div>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n variant=\"outlined\"\n color=\"secondary\"\n [label]=\"t('delegations.common.close')\"\n (click)=\"close()\"\n ></mt-button>\n </div>\n</ng-container>\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 });
|
|
823
|
+
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 });
|
|
621
824
|
}
|
|
622
825
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationCandidatesPromptDialog, decorators: [{
|
|
623
826
|
type: Component,
|
|
624
|
-
args: [{ selector: 'mt-delegation-candidates-prompt-dialog', imports: [CommonModule, Button, EntityPreview, Icon, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\n >\n <p class=\"text-sm leading-relaxed text-surface-600\">\n {{ t(\"delegations.session.promptBody\") }}\n </p>\n\n <div class=\"flex flex-col gap-2\">\n @for (cand of candidates(); track cand.delegationId) {\n <button\n type=\"button\"\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\"\n [attr.aria-label]=\"\n t('delegations.session.promptStartAria', {\n delegatorName: cand.delegator.displayName,\n })\n \"\n (click)=\"select(cand)\"\n >\n <span class=\"flex min-w-0 items-center gap-3\">\n <mt-entity-preview\n [data]=\"userEntity(cand.delegator)\"\n ></mt-entity-preview>\n </span>\n <span\n class=\"inline-flex shrink-0 items-center gap-1.5 text-sm font-medium text-primary\"\n >\n <span class=\"hidden sm:inline\">\n {{ t(\"delegations.action.startSession\") }}\n </span>\n <mt-icon icon=\"arrow.arrow-right\" class=\"text-base\"></mt-icon>\n </span>\n </button>\n }\n </div>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n variant=\"outlined\"\n color=\"secondary\"\n [label]=\"t('delegations.common.close')\"\n (click)=\"close()\"\n ></mt-button>\n </div>\n</ng-container>\n" }]
|
|
827
|
+
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" }]
|
|
625
828
|
}], propDecorators: { candidates: [{ type: i0.Input, args: [{ isSignal: true, alias: "candidates", required: true }] }] } });
|
|
626
829
|
|
|
627
|
-
const STATUS_VISUAL = {
|
|
628
|
-
Active: {
|
|
629
|
-
i18nKey: 'delegations.status.active',
|
|
630
|
-
styleClass: 'mt-status-chip mt-status-chip--active',
|
|
631
|
-
},
|
|
632
|
-
Scheduled: {
|
|
633
|
-
i18nKey: 'delegations.status.scheduled',
|
|
634
|
-
styleClass: 'mt-status-chip mt-status-chip--scheduled',
|
|
635
|
-
},
|
|
636
|
-
PendingApproval: {
|
|
637
|
-
i18nKey: 'delegations.status.pendingApproval',
|
|
638
|
-
styleClass: 'mt-status-chip mt-status-chip--pending',
|
|
639
|
-
},
|
|
640
|
-
InactiveToday: {
|
|
641
|
-
i18nKey: 'delegations.status.inactiveToday',
|
|
642
|
-
styleClass: 'mt-status-chip mt-status-chip--scheduled',
|
|
643
|
-
},
|
|
644
|
-
Expired: {
|
|
645
|
-
i18nKey: 'delegations.status.expired',
|
|
646
|
-
styleClass: 'mt-status-chip mt-status-chip--expired',
|
|
647
|
-
},
|
|
648
|
-
Rejected: {
|
|
649
|
-
i18nKey: 'delegations.status.rejected',
|
|
650
|
-
styleClass: 'mt-status-chip mt-status-chip--rejected',
|
|
651
|
-
},
|
|
652
|
-
Cancelled: {
|
|
653
|
-
i18nKey: 'delegations.status.cancelled',
|
|
654
|
-
styleClass: 'mt-status-chip mt-status-chip--cancelled',
|
|
655
|
-
},
|
|
656
|
-
};
|
|
657
|
-
class DelegationStatusChip {
|
|
658
|
-
status = input.required(...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
|
|
659
|
-
reasonCode = input(null, ...(ngDevMode ? [{ debugName: "reasonCode" }] : /* istanbul ignore next */ []));
|
|
660
|
-
visual = computed(() => STATUS_VISUAL[this.status() === 'Scheduled' && this.reasonCode() === 'InactiveToday'
|
|
661
|
-
? 'InactiveToday'
|
|
662
|
-
: this.status()] ?? STATUS_VISUAL.Scheduled, ...(ngDevMode ? [{ debugName: "visual" }] : /* istanbul ignore next */ []));
|
|
663
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationStatusChip, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
664
|
-
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: `
|
|
665
|
-
<ng-container *transloco="let t">
|
|
666
|
-
@let v = visual();
|
|
667
|
-
<mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
|
|
668
|
-
</ng-container>
|
|
669
|
-
`, 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"] }] });
|
|
670
|
-
}
|
|
671
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationStatusChip, decorators: [{
|
|
672
|
-
type: Component,
|
|
673
|
-
args: [{ selector: 'mt-delegation-status-chip', standalone: true, imports: [Chip, TranslocoDirective], template: `
|
|
674
|
-
<ng-container *transloco="let t">
|
|
675
|
-
@let v = visual();
|
|
676
|
-
<mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
|
|
677
|
-
</ng-container>
|
|
678
|
-
`, 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"] }]
|
|
679
|
-
}], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: true }] }], reasonCode: [{ type: i0.Input, args: [{ isSignal: true, alias: "reasonCode", required: false }] }] } });
|
|
680
|
-
|
|
681
830
|
/**
|
|
682
|
-
* Embeddable
|
|
683
|
-
*
|
|
684
|
-
*
|
|
685
|
-
*
|
|
686
|
-
*
|
|
687
|
-
*
|
|
831
|
+
* Embeddable "acting as" account switcher (doc 05, 09).
|
|
832
|
+
*
|
|
833
|
+
* Renders one row per identity the user can operate as — themselves first, then
|
|
834
|
+
* every delegator who has an active delegation assigned to them — with the
|
|
835
|
+
* current one checked. Picking a row starts, switches, or ends the delegated
|
|
836
|
+
* session; the {@link StartSessionDialog} still takes explicit consent before
|
|
837
|
+
* any session begins, so this list never changes authority on its own.
|
|
838
|
+
*
|
|
839
|
+
* Designed to be dropped inside a host menu (e.g. the user-avatar dropdown) or
|
|
840
|
+
* the topbar popover. Candidate loading + the post-login prompt stay in the
|
|
841
|
+
* always-mounted `TopbarDelegationMenu` controller.
|
|
688
842
|
*
|
|
689
843
|
* Emits `closeRequested` after any action so the host overlay can dismiss.
|
|
690
844
|
*/
|
|
@@ -692,25 +846,57 @@ class DelegationMenuPanel {
|
|
|
692
846
|
/** Path to the management page, e.g. `/control-panel/delegations` or `/delegations`. */
|
|
693
847
|
managePath = input('/delegations', ...(ngDevMode ? [{ debugName: "managePath" }] : /* istanbul ignore next */ []));
|
|
694
848
|
showManageLink = input(true, ...(ngDevMode ? [{ debugName: "showManageLink" }] : /* istanbul ignore next */ []));
|
|
849
|
+
/**
|
|
850
|
+
* Optional richer profile for the "you" row. Falls back to the current user
|
|
851
|
+
* carried on the assigned delegation rows, so the host may omit it.
|
|
852
|
+
*/
|
|
853
|
+
currentUser = input(null, ...(ngDevMode ? [{ debugName: "currentUser" }] : /* istanbul ignore next */ []));
|
|
695
854
|
closeRequested = output();
|
|
696
855
|
facade = inject(DelegationSessionFacade);
|
|
697
856
|
modal = inject(ModalService);
|
|
698
857
|
transloco = inject(TranslocoService);
|
|
858
|
+
toast = inject(ToastService);
|
|
699
859
|
active = this.facade.active;
|
|
700
860
|
candidates = this.facade.candidates;
|
|
701
861
|
hasCandidates = this.facade.hasCandidates;
|
|
702
862
|
hasMoreCandidates = this.facade.hasMoreCandidates;
|
|
703
863
|
isLoadingCandidates = this.facade.isLoadingCandidates;
|
|
864
|
+
isBusy = this.facade.isStarting;
|
|
704
865
|
onBehalfOf = this.facade.onBehalfOf;
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
866
|
+
/** The real signed-in user — host-supplied when available, derived otherwise. */
|
|
867
|
+
self = computed(() => this.currentUser() ?? this.facade.executedBy(), ...(ngDevMode ? [{ debugName: "self" }] : /* istanbul ignore next */ []));
|
|
868
|
+
/** Nothing to switch between and no session running — render nothing. */
|
|
869
|
+
visible = computed(() => !!this.active() || this.hasCandidates(), ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
|
|
870
|
+
/** "You", then every delegator. The active identity carries the checkmark. */
|
|
871
|
+
identities = computed(() => {
|
|
872
|
+
const activeSession = this.active();
|
|
873
|
+
const activeId = activeSession?.delegation.delegationId ?? null;
|
|
874
|
+
const self = this.self();
|
|
875
|
+
const candidates = this.candidates();
|
|
876
|
+
// The identity in force is always listed, even if it has fallen off the
|
|
877
|
+
// first page of candidates — a switcher that cannot show who you currently
|
|
878
|
+
// are is worse than one extra row.
|
|
879
|
+
const rows = activeSession && !candidates.some((row) => row.delegationId === activeId)
|
|
880
|
+
? [activeSession.delegation, ...candidates]
|
|
881
|
+
: candidates;
|
|
882
|
+
return [
|
|
883
|
+
{
|
|
884
|
+
row: null,
|
|
885
|
+
party: self,
|
|
886
|
+
isSelf: true,
|
|
887
|
+
isActive: activeId === null,
|
|
888
|
+
initials: this.initials(self),
|
|
889
|
+
},
|
|
890
|
+
...rows.map((row) => ({
|
|
891
|
+
row,
|
|
892
|
+
party: row.delegator,
|
|
893
|
+
isSelf: false,
|
|
894
|
+
isActive: row.delegationId === activeId,
|
|
895
|
+
initials: this.initials(row.delegator),
|
|
896
|
+
})),
|
|
897
|
+
];
|
|
898
|
+
}, ...(ngDevMode ? [{ debugName: "identities" }] : /* istanbul ignore next */ []));
|
|
899
|
+
/** Two-letter initials for an avatar (matches the user-menu pattern). */
|
|
714
900
|
initials(party) {
|
|
715
901
|
const name = party?.displayName?.trim() || party?.email?.trim() || '';
|
|
716
902
|
const parts = name.split(/\s+/).filter(Boolean);
|
|
@@ -721,20 +907,24 @@ class DelegationMenuPanel {
|
|
|
721
907
|
}
|
|
722
908
|
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
|
723
909
|
}
|
|
724
|
-
|
|
725
|
-
event.stopPropagation();
|
|
726
|
-
this.closeRequested.emit();
|
|
727
|
-
this.openConfirm(row, 'start');
|
|
728
|
-
}
|
|
729
|
-
switchTo(row, event) {
|
|
730
|
-
event.stopPropagation();
|
|
731
|
-
this.closeRequested.emit();
|
|
732
|
-
this.openConfirm(row, 'switch');
|
|
733
|
-
}
|
|
734
|
-
endSession(event) {
|
|
910
|
+
select(option, event) {
|
|
735
911
|
event.stopPropagation();
|
|
912
|
+
if (option.isActive || this.isBusy()) {
|
|
913
|
+
return;
|
|
914
|
+
}
|
|
736
915
|
this.closeRequested.emit();
|
|
737
|
-
|
|
916
|
+
if (option.isSelf) {
|
|
917
|
+
// Back to your own authority. There is no server call (doc 05) — ending
|
|
918
|
+
// is local — so no confirmation dialog stands between the user and it.
|
|
919
|
+
this.facade.endSession('Manual').subscribe({
|
|
920
|
+
next: () => this.toast.success(this.transloco.translate('delegations.session.ended')),
|
|
921
|
+
});
|
|
922
|
+
return;
|
|
923
|
+
}
|
|
924
|
+
if (!option.row) {
|
|
925
|
+
return;
|
|
926
|
+
}
|
|
927
|
+
this.openConfirm(option.row, this.active() ? 'switch' : 'start');
|
|
738
928
|
}
|
|
739
929
|
onManage() {
|
|
740
930
|
this.closeRequested.emit();
|
|
@@ -755,19 +945,12 @@ class DelegationMenuPanel {
|
|
|
755
945
|
});
|
|
756
946
|
}
|
|
757
947
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationMenuPanel, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
758
|
-
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\">\
|
|
948
|
+
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 });
|
|
759
949
|
}
|
|
760
950
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationMenuPanel, decorators: [{
|
|
761
951
|
type: Component,
|
|
762
|
-
args: [{ selector: 'mt-delegation-menu-panel', standalone: true, imports: [
|
|
763
|
-
|
|
764
|
-
Avatar,
|
|
765
|
-
Icon,
|
|
766
|
-
RouterLink,
|
|
767
|
-
TranslocoDirective,
|
|
768
|
-
DelegationStatusChip,
|
|
769
|
-
], 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" }]
|
|
770
|
-
}], 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"] }] } });
|
|
952
|
+
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"] }]
|
|
953
|
+
}], 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"] }] } });
|
|
771
954
|
|
|
772
955
|
/**
|
|
773
956
|
* Topbar surface + controller for the delegation runtime (doc 05, 09).
|
|
@@ -795,10 +978,18 @@ class TopbarDelegationMenu {
|
|
|
795
978
|
* e.g. embedded in the user-avatar dropdown via {@link DelegationMenuPanel}.
|
|
796
979
|
*/
|
|
797
980
|
headless = input(false, ...(ngDevMode ? [{ debugName: "headless" }] : /* istanbul ignore next */ []));
|
|
981
|
+
/**
|
|
982
|
+
* Optional richer profile for the "you" row of the switcher. Forwarded to
|
|
983
|
+
* {@link DelegationMenuPanel}; omit it and the current user is derived from
|
|
984
|
+
* the assigned delegation rows.
|
|
985
|
+
*/
|
|
986
|
+
currentUser = input(null, ...(ngDevMode ? [{ debugName: "currentUser" }] : /* istanbul ignore next */ []));
|
|
798
987
|
facade = inject(DelegationSessionFacade);
|
|
799
988
|
modal = inject(ModalService);
|
|
800
989
|
transloco = inject(TranslocoService);
|
|
801
990
|
destroyRef = inject(DestroyRef);
|
|
991
|
+
actions$ = inject(Actions);
|
|
992
|
+
toast = inject(ToastService);
|
|
802
993
|
popover = viewChild('popover', ...(ngDevMode ? [{ debugName: "popover" }] : /* istanbul ignore next */ []));
|
|
803
994
|
active = this.facade.active;
|
|
804
995
|
candidates = this.facade.candidates;
|
|
@@ -842,6 +1033,19 @@ class TopbarDelegationMenu {
|
|
|
842
1033
|
this.closePromptFlow();
|
|
843
1034
|
}
|
|
844
1035
|
});
|
|
1036
|
+
// A resumed session is the one state change the user did not just ask for
|
|
1037
|
+
// — it happens silently on page load. Say so, or "acting as someone else"
|
|
1038
|
+
// looks like the app forgot who they are.
|
|
1039
|
+
this.actions$
|
|
1040
|
+
.pipe(ofActionSuccessful(ResumeDelegationSession), takeUntilDestroyed())
|
|
1041
|
+
.subscribe(() => {
|
|
1042
|
+
const delegatorName = this.onBehalfOf()?.displayName;
|
|
1043
|
+
if (delegatorName) {
|
|
1044
|
+
this.toast.info(this.transloco.translate('delegations.session.resumed', {
|
|
1045
|
+
delegatorName,
|
|
1046
|
+
}));
|
|
1047
|
+
}
|
|
1048
|
+
});
|
|
845
1049
|
this.destroyRef.onDestroy(() => {
|
|
846
1050
|
this.destroyed = true;
|
|
847
1051
|
this.closePromptFlow();
|
|
@@ -964,7 +1168,7 @@ class TopbarDelegationMenu {
|
|
|
964
1168
|
}
|
|
965
1169
|
}
|
|
966
1170
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: TopbarDelegationMenu, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
967
|
-
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-
|
|
1171
|
+
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 });
|
|
968
1172
|
}
|
|
969
1173
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: TopbarDelegationMenu, decorators: [{
|
|
970
1174
|
type: Component,
|
|
@@ -975,8 +1179,62 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImpor
|
|
|
975
1179
|
Popover,
|
|
976
1180
|
TranslocoDirective,
|
|
977
1181
|
DelegationMenuPanel,
|
|
978
|
-
], 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-
|
|
979
|
-
}], 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 }] }] } });
|
|
1182
|
+
], 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"] }]
|
|
1183
|
+
}], 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 }] }] } });
|
|
1184
|
+
|
|
1185
|
+
const STATUS_VISUAL = {
|
|
1186
|
+
Active: {
|
|
1187
|
+
i18nKey: 'delegations.status.active',
|
|
1188
|
+
styleClass: 'mt-status-chip mt-status-chip--active',
|
|
1189
|
+
},
|
|
1190
|
+
Scheduled: {
|
|
1191
|
+
i18nKey: 'delegations.status.scheduled',
|
|
1192
|
+
styleClass: 'mt-status-chip mt-status-chip--scheduled',
|
|
1193
|
+
},
|
|
1194
|
+
PendingApproval: {
|
|
1195
|
+
i18nKey: 'delegations.status.pendingApproval',
|
|
1196
|
+
styleClass: 'mt-status-chip mt-status-chip--pending',
|
|
1197
|
+
},
|
|
1198
|
+
InactiveToday: {
|
|
1199
|
+
i18nKey: 'delegations.status.inactiveToday',
|
|
1200
|
+
styleClass: 'mt-status-chip mt-status-chip--scheduled',
|
|
1201
|
+
},
|
|
1202
|
+
Expired: {
|
|
1203
|
+
i18nKey: 'delegations.status.expired',
|
|
1204
|
+
styleClass: 'mt-status-chip mt-status-chip--expired',
|
|
1205
|
+
},
|
|
1206
|
+
Rejected: {
|
|
1207
|
+
i18nKey: 'delegations.status.rejected',
|
|
1208
|
+
styleClass: 'mt-status-chip mt-status-chip--rejected',
|
|
1209
|
+
},
|
|
1210
|
+
Cancelled: {
|
|
1211
|
+
i18nKey: 'delegations.status.cancelled',
|
|
1212
|
+
styleClass: 'mt-status-chip mt-status-chip--cancelled',
|
|
1213
|
+
},
|
|
1214
|
+
};
|
|
1215
|
+
class DelegationStatusChip {
|
|
1216
|
+
status = input.required(...(ngDevMode ? [{ debugName: "status" }] : /* istanbul ignore next */ []));
|
|
1217
|
+
reasonCode = input(null, ...(ngDevMode ? [{ debugName: "reasonCode" }] : /* istanbul ignore next */ []));
|
|
1218
|
+
visual = computed(() => STATUS_VISUAL[this.status() === 'Scheduled' && this.reasonCode() === 'InactiveToday'
|
|
1219
|
+
? 'InactiveToday'
|
|
1220
|
+
: this.status()] ?? STATUS_VISUAL.Scheduled, ...(ngDevMode ? [{ debugName: "visual" }] : /* istanbul ignore next */ []));
|
|
1221
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationStatusChip, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1222
|
+
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: `
|
|
1223
|
+
<ng-container *transloco="let t">
|
|
1224
|
+
@let v = visual();
|
|
1225
|
+
<mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
|
|
1226
|
+
</ng-container>
|
|
1227
|
+
`, 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"] }] });
|
|
1228
|
+
}
|
|
1229
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: DelegationStatusChip, decorators: [{
|
|
1230
|
+
type: Component,
|
|
1231
|
+
args: [{ selector: 'mt-delegation-status-chip', standalone: true, imports: [Chip, TranslocoDirective], template: `
|
|
1232
|
+
<ng-container *transloco="let t">
|
|
1233
|
+
@let v = visual();
|
|
1234
|
+
<mt-chip [label]="t(v.i18nKey)" [styleClass]="v.styleClass"></mt-chip>
|
|
1235
|
+
</ng-container>
|
|
1236
|
+
`, 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"] }]
|
|
1237
|
+
}], propDecorators: { status: [{ type: i0.Input, args: [{ isSignal: true, alias: "status", required: true }] }], reasonCode: [{ type: i0.Input, args: [{ isSignal: true, alias: "reasonCode", required: false }] }] } });
|
|
980
1238
|
|
|
981
1239
|
// ---------------------------------------------------------------------------
|
|
982
1240
|
// Lists / detail
|
|
@@ -1729,11 +1987,11 @@ class RejectDelegationDialog {
|
|
|
1729
1987
|
this.ref.close(false);
|
|
1730
1988
|
}
|
|
1731
1989
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: RejectDelegationDialog, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1732
|
-
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\">\n <div\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\n >\n <div class=\"flex flex-col gap-1\">\n <div class=\"text-sm text-surface-700\">\n {{ t(\"delegations.confirm.reject\") }}\n </div>\n <mt-entity-preview\n [data]=\"userEntity(delegation().delegator)\"\n ></mt-entity-preview>\n </div>\n\n <div class=\"flex flex-col gap-1\">\n <label\n class=\"text-sm font-medium text-surface-800\"\n for=\"mt-reject-reason\"\n >\n {{ t(\"delegations.form.rejectionReason\") }}\n <span class=\"text-red-600\">*</span>\n </label>\n <textarea\n id=\"mt-reject-reason\"\n rows=\"3\"\n class=\"w-full rounded-md border border-surface-300 px-3 py-2 text-sm focus:border-primary focus:outline-none\"\n [class.border-red-400]=\"submitted() && !isValid()\"\n [value]=\"reason()\"\n (input)=\"onReasonInput($event)\"\n [attr.aria-invalid]=\"submitted() && !isValid()\"\n [placeholder]=\"t('delegations.form.rejectionReason')\"\n ></textarea>\n @if (submitted() && !isValid()) {\n <span class=\"text-xs text-red-600\">\n {{ t(\"delegations.form.rejectReasonRequired\") }}\n </span>\n }\n </div>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n variant=\"outlined\"\n color=\"secondary\"\n [label]=\"t('delegations.common.cancel')\"\n [disabled]=\"isBusy()\"\n (click)=\"cancel()\"\n ></mt-button>\n <mt-button\n color=\"danger\"\n icon=\"general.x-close\"\n [label]=\"t('delegations.action.reject')\"\n [loading]=\"isBusy()\"\n [disabled]=\"!isValid()\"\n (click)=\"confirm()\"\n ></mt-button>\n </div>\n</ng-container>\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 });
|
|
1990
|
+
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 });
|
|
1733
1991
|
}
|
|
1734
1992
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.8", ngImport: i0, type: RejectDelegationDialog, decorators: [{
|
|
1735
1993
|
type: Component,
|
|
1736
|
-
args: [{ selector: 'mt-reject-delegation-dialog', imports: [CommonModule, Button, EntityPreview, TranslocoDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<ng-container *transloco=\"let t\">\n <div\n [class]=\"modal.contentClass + ' flex flex-col gap-4 overflow-y-auto p-5'\"\n >\n <div class=\"flex flex-col gap-1\">\n <div class=\"text-sm text-surface-700\">\n {{ t(\"delegations.confirm.reject\") }}\n </div>\n <mt-entity-preview\n [data]=\"userEntity(delegation().delegator)\"\n ></mt-entity-preview>\n </div>\n\n <div class=\"flex flex-col gap-1\">\n <label\n class=\"text-sm font-medium text-surface-800\"\n for=\"mt-reject-reason\"\n >\n {{ t(\"delegations.form.rejectionReason\") }}\n <span class=\"text-red-600\">*</span>\n </label>\n <textarea\n id=\"mt-reject-reason\"\n rows=\"3\"\n class=\"w-full rounded-md border border-surface-300 px-3 py-2 text-sm focus:border-primary focus:outline-none\"\n [class.border-red-400]=\"submitted() && !isValid()\"\n [value]=\"reason()\"\n (input)=\"onReasonInput($event)\"\n [attr.aria-invalid]=\"submitted() && !isValid()\"\n [placeholder]=\"t('delegations.form.rejectionReason')\"\n ></textarea>\n @if (submitted() && !isValid()) {\n <span class=\"text-xs text-red-600\">\n {{ t(\"delegations.form.rejectReasonRequired\") }}\n </span>\n }\n </div>\n </div>\n\n <div [class]=\"modal.footerClass\">\n <mt-button\n variant=\"outlined\"\n color=\"secondary\"\n [label]=\"t('delegations.common.cancel')\"\n [disabled]=\"isBusy()\"\n (click)=\"cancel()\"\n ></mt-button>\n <mt-button\n color=\"danger\"\n icon=\"general.x-close\"\n [label]=\"t('delegations.action.reject')\"\n [loading]=\"isBusy()\"\n [disabled]=\"!isValid()\"\n (click)=\"confirm()\"\n ></mt-button>\n </div>\n</ng-container>\n" }]
|
|
1994
|
+
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" }]
|
|
1737
1995
|
}], propDecorators: { delegation: [{ type: i0.Input, args: [{ isSignal: true, alias: "delegation", required: true }] }] } });
|
|
1738
1996
|
|
|
1739
1997
|
class Delegations {
|
|
@@ -4146,5 +4404,5 @@ const appDelegationInterceptor = (req, next) => {
|
|
|
4146
4404
|
* Generated bundle index. Do not edit.
|
|
4147
4405
|
*/
|
|
4148
4406
|
|
|
4149
|
-
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 };
|
|
4407
|
+
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 };
|
|
4150
4408
|
//# sourceMappingURL=masterteam-delegations.mjs.map
|