mesauth-angular 1.4.0 → 1.5.6
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/README.md +13 -0
- package/fesm2022/mesauth-angular.mjs +1135 -17
- package/fesm2022/mesauth-angular.mjs.map +1 -1
- package/index.d.ts +306 -6
- package/package.json +2 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, makeEnvironmentProviders, provideAppInitializer, inject, NgZone, Injectable, NgModule, EventEmitter, signal, HostListener, HostBinding, Output, Component, ViewChild } from '@angular/core';
|
|
2
|
+
import { InjectionToken, makeEnvironmentProviders, provideAppInitializer, inject, NgZone, Injectable, NgModule, EventEmitter, signal, HostListener, HostBinding, Output, Component, ViewChild, Input } from '@angular/core';
|
|
3
|
+
import * as i4 from '@angular/common/http';
|
|
3
4
|
import { HttpClient } from '@angular/common/http';
|
|
4
5
|
import { HubConnectionBuilder, LogLevel } from '@microsoft/signalr';
|
|
5
6
|
import { BehaviorSubject, Subject, EMPTY, of, timer, throwError } from 'rxjs';
|
|
@@ -7,7 +8,7 @@ import { tap, catchError, switchMap, takeUntil } from 'rxjs/operators';
|
|
|
7
8
|
import * as i2 from '@angular/router';
|
|
8
9
|
import { Router } from '@angular/router';
|
|
9
10
|
import * as i3 from '@angular/common';
|
|
10
|
-
import { NgIf, CommonModule, NgFor } from '@angular/common';
|
|
11
|
+
import { NgIf, CommonModule, NgFor, DatePipe } from '@angular/common';
|
|
11
12
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
12
13
|
|
|
13
14
|
/** Injection token for MesAuth configuration */
|
|
@@ -56,6 +57,8 @@ class MesAuthService {
|
|
|
56
57
|
currentUser$ = this._currentUser.asObservable();
|
|
57
58
|
_notifications = new Subject();
|
|
58
59
|
notifications$ = this._notifications.asObservable();
|
|
60
|
+
_approvalEvents = new Subject();
|
|
61
|
+
approvalEvents$ = this._approvalEvents.asObservable();
|
|
59
62
|
apiBase = '';
|
|
60
63
|
config = null;
|
|
61
64
|
http;
|
|
@@ -140,6 +143,22 @@ class MesAuthService {
|
|
|
140
143
|
this._notifications.next(n);
|
|
141
144
|
}
|
|
142
145
|
});
|
|
146
|
+
this.hubConnection.on('ApprovalCompleted', (e) => {
|
|
147
|
+
if (this.ngZone) {
|
|
148
|
+
this.ngZone.run(() => this._approvalEvents.next({ type: 'ApprovalCompleted', ...e }));
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
this._approvalEvents.next({ type: 'ApprovalCompleted', ...e });
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
this.hubConnection.on('ApprovalStepChanged', (e) => {
|
|
155
|
+
if (this.ngZone) {
|
|
156
|
+
this.ngZone.run(() => this._approvalEvents.next({ type: 'ApprovalStepChanged', ...e }));
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
this._approvalEvents.next({ type: 'ApprovalStepChanged', ...e });
|
|
160
|
+
}
|
|
161
|
+
});
|
|
143
162
|
this.hubConnection.start().then(() => { }).catch((err) => { });
|
|
144
163
|
this.hubConnection.onclose(() => { });
|
|
145
164
|
this.hubConnection.onreconnecting(() => { });
|
|
@@ -314,23 +333,27 @@ class UserProfileComponent {
|
|
|
314
333
|
router;
|
|
315
334
|
themeService;
|
|
316
335
|
cdr;
|
|
336
|
+
http;
|
|
317
337
|
notificationClick = new EventEmitter();
|
|
338
|
+
approvalClick = new EventEmitter();
|
|
318
339
|
get themeClass() {
|
|
319
340
|
return `theme-${this.currentTheme}`;
|
|
320
341
|
}
|
|
321
342
|
currentUser = signal(null, ...(ngDevMode ? [{ debugName: "currentUser" }] : []));
|
|
322
343
|
currentTheme = 'light';
|
|
323
344
|
unreadCount = 0;
|
|
345
|
+
pendingApprovalCount = 0;
|
|
324
346
|
dropdownOpen = false;
|
|
325
347
|
hasUser = false;
|
|
326
348
|
destroy$ = new Subject();
|
|
327
349
|
// Signal to force avatar refresh
|
|
328
350
|
avatarRefresh = signal(Date.now(), ...(ngDevMode ? [{ debugName: "avatarRefresh" }] : []));
|
|
329
|
-
constructor(authService, router, themeService, cdr) {
|
|
351
|
+
constructor(authService, router, themeService, cdr, http) {
|
|
330
352
|
this.authService = authService;
|
|
331
353
|
this.router = router;
|
|
332
354
|
this.themeService = themeService;
|
|
333
355
|
this.cdr = cdr;
|
|
356
|
+
this.http = http;
|
|
334
357
|
}
|
|
335
358
|
ngOnInit() {
|
|
336
359
|
this.authService.currentUser$
|
|
@@ -342,9 +365,11 @@ class UserProfileComponent {
|
|
|
342
365
|
this.avatarRefresh.set(Date.now());
|
|
343
366
|
if (!this.hasUser) {
|
|
344
367
|
this.unreadCount = 0;
|
|
368
|
+
this.pendingApprovalCount = 0;
|
|
345
369
|
}
|
|
346
370
|
else {
|
|
347
371
|
this.loadUnreadCount();
|
|
372
|
+
this.loadPendingApprovalCount();
|
|
348
373
|
}
|
|
349
374
|
this.cdr.markForCheck();
|
|
350
375
|
});
|
|
@@ -353,6 +378,13 @@ class UserProfileComponent {
|
|
|
353
378
|
.subscribe(theme => {
|
|
354
379
|
this.currentTheme = theme;
|
|
355
380
|
});
|
|
381
|
+
// Listen for approval events (SignalR) to refresh pending count
|
|
382
|
+
this.authService.approvalEvents$
|
|
383
|
+
.pipe(takeUntil(this.destroy$))
|
|
384
|
+
.subscribe(() => {
|
|
385
|
+
if (this.hasUser)
|
|
386
|
+
this.loadPendingApprovalCount();
|
|
387
|
+
});
|
|
356
388
|
// Listen for new real-time notifications (SignalR only)
|
|
357
389
|
this.authService.notifications$
|
|
358
390
|
.pipe(takeUntil(this.destroy$))
|
|
@@ -378,6 +410,23 @@ class UserProfileComponent {
|
|
|
378
410
|
error: (err) => { }
|
|
379
411
|
});
|
|
380
412
|
}
|
|
413
|
+
loadPendingApprovalCount() {
|
|
414
|
+
if (!this.hasUser) {
|
|
415
|
+
this.pendingApprovalCount = 0;
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const config = this.authService.getConfig();
|
|
419
|
+
if (!config)
|
|
420
|
+
return;
|
|
421
|
+
const url = `${config.apiBaseUrl.replace(/\/$/, '')}/approval/dashboard`;
|
|
422
|
+
this.http.get(url, { withCredentials: config.withCredentials ?? true }).subscribe({
|
|
423
|
+
next: (r) => { this.pendingApprovalCount = r?.pendingCount ?? 0; },
|
|
424
|
+
error: () => { }
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
onApprovalClick() {
|
|
428
|
+
this.approvalClick.emit();
|
|
429
|
+
}
|
|
381
430
|
getAvatarUrl(user) {
|
|
382
431
|
// Use the refresh signal to force update
|
|
383
432
|
const refresh = this.avatarRefresh();
|
|
@@ -449,8 +498,8 @@ class UserProfileComponent {
|
|
|
449
498
|
onNotificationClick() {
|
|
450
499
|
this.notificationClick.emit();
|
|
451
500
|
}
|
|
452
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: UserProfileComponent, deps: [{ token: MesAuthService }, { token: i2.Router }, { token: ThemeService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
453
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: UserProfileComponent, isStandalone: true, selector: "ma-user-profile", outputs: { notificationClick: "notificationClick" }, host: { listeners: { "document:click": "onDocumentClick($event)" }, properties: { "class": "this.themeClass" } }, ngImport: i0, template: `
|
|
501
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: UserProfileComponent, deps: [{ token: MesAuthService }, { token: i2.Router }, { token: ThemeService }, { token: i0.ChangeDetectorRef }, { token: i4.HttpClient }], target: i0.ɵɵFactoryTarget.Component });
|
|
502
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: UserProfileComponent, isStandalone: true, selector: "ma-user-profile", outputs: { notificationClick: "notificationClick", approvalClick: "approvalClick" }, host: { listeners: { "document:click": "onDocumentClick($event)" }, properties: { "class": "this.themeClass" } }, ngImport: i0, template: `
|
|
454
503
|
<div class="user-profile-container">
|
|
455
504
|
<!-- Not logged in -->
|
|
456
505
|
<ng-container *ngIf="!currentUser()">
|
|
@@ -469,6 +518,15 @@ class UserProfileComponent {
|
|
|
469
518
|
<span class="badge" *ngIf="unreadCount > 0">{{ unreadCount > 99 ? '99+' : unreadCount }}</span>
|
|
470
519
|
</button>
|
|
471
520
|
|
|
521
|
+
<!-- Approval Button -->
|
|
522
|
+
<button class="notification-btn" [class.has-unread]="pendingApprovalCount > 0" (click)="onApprovalClick()" title="Approvals" aria-label="Approvals">
|
|
523
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
524
|
+
<path d="M9 11l3 3L22 4"/>
|
|
525
|
+
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
|
|
526
|
+
</svg>
|
|
527
|
+
<span class="badge" *ngIf="pendingApprovalCount > 0">{{ pendingApprovalCount > 99 ? '99+' : pendingApprovalCount }}</span>
|
|
528
|
+
</button>
|
|
529
|
+
|
|
472
530
|
<!-- User Avatar + Dropdown -->
|
|
473
531
|
<div class="user-menu-wrapper">
|
|
474
532
|
<button class="user-menu-btn" (click)="toggleDropdown()" [attr.aria-label]="'User menu for ' + (currentUser().fullName || currentUser().userName)" aria-haspopup="true" [attr.aria-expanded]="dropdownOpen">
|
|
@@ -552,6 +610,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
552
610
|
<span class="badge" *ngIf="unreadCount > 0">{{ unreadCount > 99 ? '99+' : unreadCount }}</span>
|
|
553
611
|
</button>
|
|
554
612
|
|
|
613
|
+
<!-- Approval Button -->
|
|
614
|
+
<button class="notification-btn" [class.has-unread]="pendingApprovalCount > 0" (click)="onApprovalClick()" title="Approvals" aria-label="Approvals">
|
|
615
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
616
|
+
<path d="M9 11l3 3L22 4"/>
|
|
617
|
+
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
|
|
618
|
+
</svg>
|
|
619
|
+
<span class="badge" *ngIf="pendingApprovalCount > 0">{{ pendingApprovalCount > 99 ? '99+' : pendingApprovalCount }}</span>
|
|
620
|
+
</button>
|
|
621
|
+
|
|
555
622
|
<!-- User Avatar + Dropdown -->
|
|
556
623
|
<div class="user-menu-wrapper">
|
|
557
624
|
<button class="user-menu-btn" (click)="toggleDropdown()" [attr.aria-label]="'User menu for ' + (currentUser().fullName || currentUser().userName)" aria-haspopup="true" [attr.aria-expanded]="dropdownOpen">
|
|
@@ -613,7 +680,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
613
680
|
</ng-container>
|
|
614
681
|
</div>
|
|
615
682
|
`, styles: [":host{--primary-color: #1976d2;--primary-hover: #1565c0;--primary-light: rgba(25, 118, 210, .12);--primary-glow: rgba(25, 118, 210, .3);--error-color: #f44336;--error-light: rgba(244, 67, 54, .1);--text-primary: #212121;--text-secondary: #616161;--text-muted: #9e9e9e;--bg-primary: #ffffff;--bg-secondary: #f8f9fa;--bg-hover: #f0f4ff;--border-color: #e0e0e0;--shadow: rgba(0, 0, 0, .12);--shadow-lg: rgba(0, 0, 0, .18)}:host(.theme-dark){--primary-color: #90caf9;--primary-hover: #64b5f6;--primary-light: rgba(144, 202, 249, .12);--primary-glow: rgba(144, 202, 249, .25);--error-color: #ef5350;--error-light: rgba(239, 83, 80, .12);--text-primary: #e0e0e0;--text-secondary: #b0b0b0;--text-muted: #757575;--bg-primary: #1e1e2e;--bg-secondary: #27273a;--bg-hover: #2a2d4a;--border-color: #383850;--shadow: rgba(0, 0, 0, .35);--shadow-lg: rgba(0, 0, 0, .5)}.user-profile-container{display:flex;align-items:center;gap:4px}.login-btn{padding:7px 18px;background-color:var(--primary-color);color:#fff;border:none;border-radius:8px;cursor:pointer;font-weight:500;font-size:13px;letter-spacing:.2px;transition:background-color .2s,transform .15s}.login-btn:hover{background-color:var(--primary-hover);transform:translateY(-1px)}.user-header{display:flex;align-items:center;gap:4px}.notification-btn{position:relative;background:none;border:none;cursor:pointer;padding:8px;border-radius:10px;color:var(--text-secondary);display:flex;align-items:center;justify-content:center;transition:color .2s,background-color .2s}.notification-btn:hover{background-color:var(--primary-light);color:var(--primary-color)}.notification-btn.has-unread{color:var(--primary-color)}.bell-icon{display:block;transition:transform .35s cubic-bezier(.34,1.56,.64,1)}.notification-btn:hover .bell-icon{transform:rotate(-20deg) scale(1.15)}.badge{position:absolute;top:2px;right:2px;background-color:var(--error-color);color:#fff;border-radius:10px;min-width:17px;height:17px;padding:0 4px;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;line-height:1;box-shadow:0 0 0 2px var(--bg-primary);animation:badge-pop .25s cubic-bezier(.34,1.56,.64,1)}@keyframes badge-pop{0%{transform:scale(0)}to{transform:scale(1)}}.user-menu-wrapper{position:relative}.user-menu-btn{background:none;border:none;cursor:pointer;padding:2px;border-radius:50%;display:flex;align-items:center;justify-content:center;transition:transform .2s}.user-menu-btn:hover{transform:scale(1.06)}.avatar-ring{border-radius:50%;padding:2px;border:2px solid transparent;transition:border-color .25s,box-shadow .25s}.avatar-ring.active,.user-menu-btn:hover .avatar-ring{border-color:var(--primary-color);box-shadow:0 0 0 3px var(--primary-glow)}.avatar{width:36px;height:36px;border-radius:50%;object-fit:cover;display:block}.avatar-initial{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,var(--primary-color),var(--primary-hover));color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:15px}.mes-dropdown-menu{position:absolute;top:calc(100% + 10px);right:0;background:var(--bg-primary);border:1px solid var(--border-color);border-radius:14px;box-shadow:0 8px 32px var(--shadow-lg),0 2px 8px var(--shadow);min-width:220px;z-index:1000;overflow:hidden;animation:dropdown-in .16s cubic-bezier(.16,1,.3,1)}@keyframes dropdown-in{0%{opacity:0;transform:translateY(-8px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.mes-dropdown-header{display:flex;align-items:center;gap:12px;padding:16px;background:var(--bg-secondary)}.dropdown-avatar-wrap{flex-shrink:0}.dropdown-avatar{width:46px;height:46px;border-radius:50%;object-fit:cover;border:2px solid var(--primary-color);display:block}.dropdown-avatar-initial{width:46px;height:46px;border-radius:50%;background:linear-gradient(135deg,var(--primary-color),var(--primary-hover));color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:18px;border:2px solid var(--primary-color)}.dropdown-user-info{display:flex;flex-direction:column;gap:3px;min-width:0}.dropdown-user-name{font-weight:600;font-size:14px;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.dropdown-user-sub{font-size:11px;color:var(--primary-color);font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.mes-dropdown-divider{height:1px;background:var(--border-color)}.mes-dropdown-item{display:flex;align-items:center;gap:10px;width:100%;padding:11px 16px;border:none;background:none;text-align:left;cursor:pointer;font-size:13.5px;font-weight:500;transition:background-color .15s}.item-icon{flex-shrink:0;opacity:.8}.profile-link{color:var(--primary-color)}.profile-link:hover{background-color:var(--primary-light)}.logout-item{color:var(--error-color)}.logout-item:hover{background-color:var(--error-light)}@media(max-width:768px){.avatar,.avatar-initial{width:32px;height:32px;font-size:13px}}\n"] }]
|
|
616
|
-
}], ctorParameters: () => [{ type: MesAuthService }, { type: i2.Router }, { type: ThemeService }, { type: i0.ChangeDetectorRef }], propDecorators: { notificationClick: [{
|
|
683
|
+
}], ctorParameters: () => [{ type: MesAuthService }, { type: i2.Router }, { type: ThemeService }, { type: i0.ChangeDetectorRef }, { type: i4.HttpClient }], propDecorators: { notificationClick: [{
|
|
684
|
+
type: Output
|
|
685
|
+
}], approvalClick: [{
|
|
617
686
|
type: Output
|
|
618
687
|
}], themeClass: [{
|
|
619
688
|
type: HostBinding,
|
|
@@ -806,6 +875,7 @@ class NotificationPanelComponent {
|
|
|
806
875
|
}
|
|
807
876
|
selectedNotification = null;
|
|
808
877
|
selectedNotificationHtml = null;
|
|
878
|
+
selectedNotificationUrl = null;
|
|
809
879
|
selectedNotificationDate = '';
|
|
810
880
|
// Returns plain text message for list display
|
|
811
881
|
getNotificationMessage(notification) {
|
|
@@ -886,6 +956,9 @@ class NotificationPanelComponent {
|
|
|
886
956
|
// Cache computed values to avoid re-rendering on every change detection cycle
|
|
887
957
|
const html = notification.messageHtml || notification.message || '';
|
|
888
958
|
this.selectedNotificationHtml = this.sanitizer.bypassSecurityTrustHtml(html);
|
|
959
|
+
this.selectedNotificationUrl = notification.url?.trim()
|
|
960
|
+
? this.sanitizer.bypassSecurityTrustResourceUrl(notification.url.trim())
|
|
961
|
+
: null;
|
|
889
962
|
this.selectedNotificationDate = this.formatDate(notification.createdAt);
|
|
890
963
|
// Mark as read when opening details (if not already read)
|
|
891
964
|
if (!notification.isRead) {
|
|
@@ -902,6 +975,7 @@ class NotificationPanelComponent {
|
|
|
902
975
|
closeDetails() {
|
|
903
976
|
this.selectedNotification = null;
|
|
904
977
|
this.selectedNotificationHtml = null;
|
|
978
|
+
this.selectedNotificationUrl = null;
|
|
905
979
|
this.selectedNotificationDate = '';
|
|
906
980
|
}
|
|
907
981
|
markAsRead(notificationId, event) {
|
|
@@ -1177,13 +1251,16 @@ class NotificationPanelComponent {
|
|
|
1177
1251
|
<span class="app-name">{{ selectedNotification.sourceAppName }}</span>
|
|
1178
1252
|
<span class="time">{{ selectedNotificationDate }}</span>
|
|
1179
1253
|
</div>
|
|
1180
|
-
<div class="modal-body" [innerHTML]="selectedNotificationHtml"></div>
|
|
1254
|
+
<div class="modal-body" *ngIf="!selectedNotificationUrl" [innerHTML]="selectedNotificationHtml"></div>
|
|
1255
|
+
<div class="modal-body modal-body-iframe" *ngIf="selectedNotificationUrl">
|
|
1256
|
+
<iframe [src]="selectedNotificationUrl" sandbox="allow-same-origin allow-scripts" class="notification-iframe"></iframe>
|
|
1257
|
+
</div>
|
|
1181
1258
|
<div class="modal-footer">
|
|
1182
1259
|
<button class="action-btn" (click)="closeDetails()">Close</button>
|
|
1183
1260
|
</div>
|
|
1184
1261
|
</div>
|
|
1185
1262
|
</div>
|
|
1186
|
-
`, isInline: true, styles: [":host{display:block;position:relative;--primary-color: #1976d2;--primary-hover: #1565c0;--primary-light: rgba(25, 118, 210, .1);--success-color: #43a047;--error-color: #f44336;--error-hover: #d32f2f;--unread-accent: #1976d2;--info-color: #2196f3;--info-bg: rgba(33, 150, 243, .1);--success-bg: rgba(67, 160, 71, .1);--warning-color: #f57c00;--warning-bg: rgba(245, 124, 0, .1);--error-bg: rgba(244, 67, 54, .1);--text-primary: #212121;--text-secondary: #616161;--text-muted: #9e9e9e;--bg-primary: #ffffff;--bg-secondary: #f8f9fa;--bg-hover: #f0f4ff;--bg-unread: rgba(25, 118, 210, .06);--border-color: #e0e0e0;--border-light: #eeeeee;--shadow: rgba(0, 0, 0, .15)}:host(.theme-dark){display:block;position:relative;--primary-color: #90caf9;--primary-hover: #64b5f6;--primary-light: rgba(144, 202, 249, .1);--success-color: #66bb6a;--error-color: #ef5350;--error-hover: #c62828;--unread-accent: #90caf9;--info-color: #64b5f6;--info-bg: rgba(100, 181, 246, .12);--success-bg: rgba(102, 187, 106, .12);--warning-color: #ffb74d;--warning-bg: rgba(255, 183, 77, .12);--error-bg: rgba(239, 83, 80, .12);--text-primary: #e0e0e0;--text-secondary: #b0b0b0;--text-muted: #757575;--bg-primary: #1e1e2e;--bg-secondary: #27273a;--bg-hover: #2a2d4a;--bg-unread: rgba(144, 202, 249, .08);--border-color: #383850;--border-light: #2e2e42;--shadow: rgba(0, 0, 0, .4)}.notification-panel{position:fixed;top:0;right:-360px;width:360px;height:100vh;background:var(--bg-primary);box-shadow:-4px 0 24px var(--shadow);display:flex;flex-direction:column;z-index:1030;transition:right .3s cubic-bezier(.16,1,.3,1)}.notification-panel.open{right:0}.panel-header{display:flex;justify-content:space-between;align-items:center;padding:16px 18px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.panel-header-left{display:flex;align-items:center;gap:9px;color:var(--primary-color)}.panel-header h3{margin:0;font-size:16px;font-weight:700;color:var(--text-primary);letter-spacing:.1px}.close-btn{background:none;border:none;cursor:pointer;color:var(--text-muted);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:color .2s,background-color .2s}.close-btn:hover{color:var(--text-primary);background-color:var(--bg-hover)}.tabs{display:flex;gap:6px;padding:10px 14px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.tab-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:7px 12px;background:none;border:1px solid var(--border-color);border-radius:20px;color:var(--text-secondary);cursor:pointer;font-size:13px;font-weight:500;transition:all .18s}.tab-btn:hover{background:var(--bg-hover);color:var(--primary-color);border-color:var(--primary-color)}.tab-btn.active{background:var(--primary-color);border-color:var(--primary-color);color:#fff}.tab-count{background:#ffffff40;border-radius:10px;min-width:18px;height:18px;padding:0 5px;font-size:10px;font-weight:700;display:flex;align-items:center;justify-content:center}.tab-btn:not(.active) .tab-count{background:var(--error-color);color:#fff}.read-count{background:#ffffff40}.tab-btn:not(.active) .read-count{background:var(--text-muted)}.notifications-list{flex:1;overflow-y:auto}.notification-item{display:flex;align-items:flex-start;gap:0;border-bottom:1px solid var(--border-light);cursor:pointer;background:var(--bg-primary);transition:background-color .15s;position:relative}.notification-item:hover{background:var(--bg-hover)}.notification-item.unread{background:var(--bg-unread)}.notif-accent{width:3px;align-self:stretch;flex-shrink:0;background:transparent;border-radius:0 2px 2px 0;opacity:.3}.notification-item.unread .notif-accent{opacity:1}.notif-accent.type-info{background:var(--info-color)}.notif-accent.type-success{background:var(--success-color)}.notif-accent.type-warning{background:var(--warning-color)}.notif-accent.type-error{background:var(--error-color)}.notif-type-icon{flex-shrink:0;width:26px;height:26px;border-radius:7px;display:flex;align-items:center;justify-content:center;align-self:center;margin-left:10px}.notif-type-icon.type-info{color:var(--info-color);background:var(--info-bg)}.notif-type-icon.type-success{color:var(--success-color);background:var(--success-bg)}.notif-type-icon.type-warning{color:var(--warning-color);background:var(--warning-bg)}.notif-type-icon.type-error{color:var(--error-color);background:var(--error-bg)}.notification-content{flex:1;min-width:0;padding:12px 8px 12px 12px}.notification-title{font-weight:600;color:var(--text-primary);font-size:13.5px;margin-bottom:3px;line-height:1.35}.notification-message{color:var(--text-secondary);font-size:12px;line-height:1.45;margin-bottom:7px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.notification-meta{display:flex;justify-content:space-between;font-size:11px;color:var(--text-muted)}.app-name{font-weight:600;color:var(--primary-color)}.icon-btn{background:none;border:none;cursor:pointer;width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0;align-self:center;margin-right:8px;transition:color .15s,background-color .15s;color:var(--text-muted)}.read-btn:hover{color:var(--success-color);background:#43a0471a}.delete-btn:hover{color:var(--error-color);background:#f443361a}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;height:100%;padding:40px 20px;color:var(--text-muted)}.empty-icon{opacity:.35}.empty-state p{margin:0;font-size:13px}.panel-footer{padding:10px 14px;border-top:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.footer-actions{display:flex;gap:8px}.footer-actions .action-btn{flex:1}.action-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px 12px;background:var(--primary-color);color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:12.5px;font-weight:600;transition:background-color .18s,transform .12s}.action-btn:hover{background:var(--primary-hover);transform:translateY(-1px)}.delete-all-btn{background:var(--error-color)}.delete-all-btn:hover{background:var(--error-hover)}.modal-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1060;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.modal-container{background:var(--bg-primary);border-radius:14px;width:90%;max-width:580px;max-height:80vh;display:flex;flex-direction:column;box-shadow:0 16px 48px #00000040;animation:modal-in .2s cubic-bezier(.16,1,.3,1)}@keyframes modal-in{0%{opacity:0;transform:scale(.94) translateY(8px)}to{opacity:1;transform:scale(1) translateY(0)}}.modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);border-radius:14px 14px 0 0;border-top:3px solid transparent}.modal-header.modal-type-info{border-top-color:var(--info-color)}.modal-header.modal-type-success{border-top-color:var(--success-color)}.modal-header.modal-type-warning{border-top-color:var(--warning-color)}.modal-header.modal-type-error{border-top-color:var(--error-color)}.modal-header-left{display:flex;align-items:center;gap:10px;min-width:0}.modal-type-icon{flex-shrink:0;width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center}.modal-type-info .modal-type-icon{color:var(--info-color);background:var(--info-bg)}.modal-type-success .modal-type-icon{color:var(--success-color);background:var(--success-bg)}.modal-type-warning .modal-type-icon{color:var(--warning-color);background:var(--warning-bg)}.modal-type-error .modal-type-icon{color:var(--error-color);background:var(--error-bg)}.modal-header h3{margin:0;font-size:15px;font-weight:700;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.modal-meta{display:flex;justify-content:space-between;padding:8px 20px;font-size:11.5px;color:var(--text-muted);border-bottom:1px solid var(--border-light)}.modal-body{padding:20px;overflow-y:auto;flex:1;color:var(--text-primary);font-size:14px;line-height:1.65}.modal-footer{padding:12px 20px;border-top:1px solid var(--border-color);background:var(--bg-secondary);border-radius:0 0 14px 14px;display:flex;justify-content:flex-end}.modal-footer .action-btn{width:auto;padding:8px 24px}@media(max-width:600px){.notification-panel{width:100%;right:-100%}.modal-container{width:95%;max-height:90vh}}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] });
|
|
1263
|
+
`, isInline: true, styles: [":host{display:block;position:relative;--primary-color: #1976d2;--primary-hover: #1565c0;--primary-light: rgba(25, 118, 210, .1);--success-color: #43a047;--error-color: #f44336;--error-hover: #d32f2f;--unread-accent: #1976d2;--info-color: #2196f3;--info-bg: rgba(33, 150, 243, .1);--success-bg: rgba(67, 160, 71, .1);--warning-color: #f57c00;--warning-bg: rgba(245, 124, 0, .1);--error-bg: rgba(244, 67, 54, .1);--text-primary: #212121;--text-secondary: #616161;--text-muted: #9e9e9e;--bg-primary: #ffffff;--bg-secondary: #f8f9fa;--bg-hover: #f0f4ff;--bg-unread: rgba(25, 118, 210, .06);--border-color: #e0e0e0;--border-light: #eeeeee;--shadow: rgba(0, 0, 0, .15)}:host(.theme-dark){display:block;position:relative;--primary-color: #90caf9;--primary-hover: #64b5f6;--primary-light: rgba(144, 202, 249, .1);--success-color: #66bb6a;--error-color: #ef5350;--error-hover: #c62828;--unread-accent: #90caf9;--info-color: #64b5f6;--info-bg: rgba(100, 181, 246, .12);--success-bg: rgba(102, 187, 106, .12);--warning-color: #ffb74d;--warning-bg: rgba(255, 183, 77, .12);--error-bg: rgba(239, 83, 80, .12);--text-primary: #e0e0e0;--text-secondary: #b0b0b0;--text-muted: #757575;--bg-primary: #1e1e2e;--bg-secondary: #27273a;--bg-hover: #2a2d4a;--bg-unread: rgba(144, 202, 249, .08);--border-color: #383850;--border-light: #2e2e42;--shadow: rgba(0, 0, 0, .4)}.notification-panel{position:fixed;top:0;right:-360px;width:360px;height:100vh;background:var(--bg-primary);box-shadow:-4px 0 24px var(--shadow);display:flex;flex-direction:column;z-index:1030;transition:right .3s cubic-bezier(.16,1,.3,1)}.notification-panel.open{right:0}.panel-header{display:flex;justify-content:space-between;align-items:center;padding:16px 18px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.panel-header-left{display:flex;align-items:center;gap:9px;color:var(--primary-color)}.panel-header h3{margin:0;font-size:16px;font-weight:700;color:var(--text-primary);letter-spacing:.1px}.close-btn{background:none;border:none;cursor:pointer;color:var(--text-muted);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:color .2s,background-color .2s}.close-btn:hover{color:var(--text-primary);background-color:var(--bg-hover)}.tabs{display:flex;gap:6px;padding:10px 14px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.tab-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:7px 12px;background:none;border:1px solid var(--border-color);border-radius:20px;color:var(--text-secondary);cursor:pointer;font-size:13px;font-weight:500;transition:all .18s}.tab-btn:hover{background:var(--bg-hover);color:var(--primary-color);border-color:var(--primary-color)}.tab-btn.active{background:var(--primary-color);border-color:var(--primary-color);color:#fff}.tab-count{background:#ffffff40;border-radius:10px;min-width:18px;height:18px;padding:0 5px;font-size:10px;font-weight:700;display:flex;align-items:center;justify-content:center}.tab-btn:not(.active) .tab-count{background:var(--error-color);color:#fff}.read-count{background:#ffffff40}.tab-btn:not(.active) .read-count{background:var(--text-muted)}.notifications-list{flex:1;overflow-y:auto}.notification-item{display:flex;align-items:flex-start;gap:0;border-bottom:1px solid var(--border-light);cursor:pointer;background:var(--bg-primary);transition:background-color .15s;position:relative}.notification-item:hover{background:var(--bg-hover)}.notification-item.unread{background:var(--bg-unread)}.notif-accent{width:3px;align-self:stretch;flex-shrink:0;background:transparent;border-radius:0 2px 2px 0;opacity:.3}.notification-item.unread .notif-accent{opacity:1}.notif-accent.type-info{background:var(--info-color)}.notif-accent.type-success{background:var(--success-color)}.notif-accent.type-warning{background:var(--warning-color)}.notif-accent.type-error{background:var(--error-color)}.notif-type-icon{flex-shrink:0;width:26px;height:26px;border-radius:7px;display:flex;align-items:center;justify-content:center;align-self:center;margin-left:10px}.notif-type-icon.type-info{color:var(--info-color);background:var(--info-bg)}.notif-type-icon.type-success{color:var(--success-color);background:var(--success-bg)}.notif-type-icon.type-warning{color:var(--warning-color);background:var(--warning-bg)}.notif-type-icon.type-error{color:var(--error-color);background:var(--error-bg)}.notification-content{flex:1;min-width:0;padding:12px 8px 12px 12px}.notification-title{font-weight:600;color:var(--text-primary);font-size:13.5px;margin-bottom:3px;line-height:1.35}.notification-message{color:var(--text-secondary);font-size:12px;line-height:1.45;margin-bottom:7px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.notification-meta{display:flex;justify-content:space-between;font-size:11px;color:var(--text-muted)}.app-name{font-weight:600;color:var(--primary-color)}.icon-btn{background:none;border:none;cursor:pointer;width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0;align-self:center;margin-right:8px;transition:color .15s,background-color .15s;color:var(--text-muted)}.read-btn:hover{color:var(--success-color);background:#43a0471a}.delete-btn:hover{color:var(--error-color);background:#f443361a}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;height:100%;padding:40px 20px;color:var(--text-muted)}.empty-icon{opacity:.35}.empty-state p{margin:0;font-size:13px}.panel-footer{padding:10px 14px;border-top:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.footer-actions{display:flex;gap:8px}.footer-actions .action-btn{flex:1}.action-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px 12px;background:var(--primary-color);color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:12.5px;font-weight:600;transition:background-color .18s,transform .12s}.action-btn:hover{background:var(--primary-hover);transform:translateY(-1px)}.delete-all-btn{background:var(--error-color)}.delete-all-btn:hover{background:var(--error-hover)}.modal-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1060;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.modal-container{background:var(--bg-primary);border-radius:14px;width:90%;max-width:580px;max-height:80vh;display:flex;flex-direction:column;box-shadow:0 16px 48px #00000040;animation:modal-in .2s cubic-bezier(.16,1,.3,1)}@keyframes modal-in{0%{opacity:0;transform:scale(.94) translateY(8px)}to{opacity:1;transform:scale(1) translateY(0)}}.modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);border-radius:14px 14px 0 0;border-top:3px solid transparent}.modal-header.modal-type-info{border-top-color:var(--info-color)}.modal-header.modal-type-success{border-top-color:var(--success-color)}.modal-header.modal-type-warning{border-top-color:var(--warning-color)}.modal-header.modal-type-error{border-top-color:var(--error-color)}.modal-header-left{display:flex;align-items:center;gap:10px;min-width:0}.modal-type-icon{flex-shrink:0;width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center}.modal-type-info .modal-type-icon{color:var(--info-color);background:var(--info-bg)}.modal-type-success .modal-type-icon{color:var(--success-color);background:var(--success-bg)}.modal-type-warning .modal-type-icon{color:var(--warning-color);background:var(--warning-bg)}.modal-type-error .modal-type-icon{color:var(--error-color);background:var(--error-bg)}.modal-header h3{margin:0;font-size:15px;font-weight:700;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.modal-meta{display:flex;justify-content:space-between;padding:8px 20px;font-size:11.5px;color:var(--text-muted);border-bottom:1px solid var(--border-light)}.modal-body{padding:20px;overflow-y:auto;flex:1;color:var(--text-primary);font-size:14px;line-height:1.65}.modal-body-iframe{padding:0}.notification-iframe{width:100%;height:100%;border:none;display:block}.modal-footer{padding:12px 20px;border-top:1px solid var(--border-color);background:var(--bg-secondary);border-radius:0 0 14px 14px;display:flex;justify-content:flex-end}.modal-footer .action-btn{width:auto;padding:8px 24px}@media(max-width:600px){.notification-panel{width:100%;right:-100%}.modal-container{width:95%;max-height:90vh}}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] });
|
|
1187
1264
|
}
|
|
1188
1265
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: NotificationPanelComponent, decorators: [{
|
|
1189
1266
|
type: Component,
|
|
@@ -1336,13 +1413,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
1336
1413
|
<span class="app-name">{{ selectedNotification.sourceAppName }}</span>
|
|
1337
1414
|
<span class="time">{{ selectedNotificationDate }}</span>
|
|
1338
1415
|
</div>
|
|
1339
|
-
<div class="modal-body" [innerHTML]="selectedNotificationHtml"></div>
|
|
1416
|
+
<div class="modal-body" *ngIf="!selectedNotificationUrl" [innerHTML]="selectedNotificationHtml"></div>
|
|
1417
|
+
<div class="modal-body modal-body-iframe" *ngIf="selectedNotificationUrl">
|
|
1418
|
+
<iframe [src]="selectedNotificationUrl" sandbox="allow-same-origin allow-scripts" class="notification-iframe"></iframe>
|
|
1419
|
+
</div>
|
|
1340
1420
|
<div class="modal-footer">
|
|
1341
1421
|
<button class="action-btn" (click)="closeDetails()">Close</button>
|
|
1342
1422
|
</div>
|
|
1343
1423
|
</div>
|
|
1344
1424
|
</div>
|
|
1345
|
-
`, styles: [":host{display:block;position:relative;--primary-color: #1976d2;--primary-hover: #1565c0;--primary-light: rgba(25, 118, 210, .1);--success-color: #43a047;--error-color: #f44336;--error-hover: #d32f2f;--unread-accent: #1976d2;--info-color: #2196f3;--info-bg: rgba(33, 150, 243, .1);--success-bg: rgba(67, 160, 71, .1);--warning-color: #f57c00;--warning-bg: rgba(245, 124, 0, .1);--error-bg: rgba(244, 67, 54, .1);--text-primary: #212121;--text-secondary: #616161;--text-muted: #9e9e9e;--bg-primary: #ffffff;--bg-secondary: #f8f9fa;--bg-hover: #f0f4ff;--bg-unread: rgba(25, 118, 210, .06);--border-color: #e0e0e0;--border-light: #eeeeee;--shadow: rgba(0, 0, 0, .15)}:host(.theme-dark){display:block;position:relative;--primary-color: #90caf9;--primary-hover: #64b5f6;--primary-light: rgba(144, 202, 249, .1);--success-color: #66bb6a;--error-color: #ef5350;--error-hover: #c62828;--unread-accent: #90caf9;--info-color: #64b5f6;--info-bg: rgba(100, 181, 246, .12);--success-bg: rgba(102, 187, 106, .12);--warning-color: #ffb74d;--warning-bg: rgba(255, 183, 77, .12);--error-bg: rgba(239, 83, 80, .12);--text-primary: #e0e0e0;--text-secondary: #b0b0b0;--text-muted: #757575;--bg-primary: #1e1e2e;--bg-secondary: #27273a;--bg-hover: #2a2d4a;--bg-unread: rgba(144, 202, 249, .08);--border-color: #383850;--border-light: #2e2e42;--shadow: rgba(0, 0, 0, .4)}.notification-panel{position:fixed;top:0;right:-360px;width:360px;height:100vh;background:var(--bg-primary);box-shadow:-4px 0 24px var(--shadow);display:flex;flex-direction:column;z-index:1030;transition:right .3s cubic-bezier(.16,1,.3,1)}.notification-panel.open{right:0}.panel-header{display:flex;justify-content:space-between;align-items:center;padding:16px 18px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.panel-header-left{display:flex;align-items:center;gap:9px;color:var(--primary-color)}.panel-header h3{margin:0;font-size:16px;font-weight:700;color:var(--text-primary);letter-spacing:.1px}.close-btn{background:none;border:none;cursor:pointer;color:var(--text-muted);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:color .2s,background-color .2s}.close-btn:hover{color:var(--text-primary);background-color:var(--bg-hover)}.tabs{display:flex;gap:6px;padding:10px 14px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.tab-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:7px 12px;background:none;border:1px solid var(--border-color);border-radius:20px;color:var(--text-secondary);cursor:pointer;font-size:13px;font-weight:500;transition:all .18s}.tab-btn:hover{background:var(--bg-hover);color:var(--primary-color);border-color:var(--primary-color)}.tab-btn.active{background:var(--primary-color);border-color:var(--primary-color);color:#fff}.tab-count{background:#ffffff40;border-radius:10px;min-width:18px;height:18px;padding:0 5px;font-size:10px;font-weight:700;display:flex;align-items:center;justify-content:center}.tab-btn:not(.active) .tab-count{background:var(--error-color);color:#fff}.read-count{background:#ffffff40}.tab-btn:not(.active) .read-count{background:var(--text-muted)}.notifications-list{flex:1;overflow-y:auto}.notification-item{display:flex;align-items:flex-start;gap:0;border-bottom:1px solid var(--border-light);cursor:pointer;background:var(--bg-primary);transition:background-color .15s;position:relative}.notification-item:hover{background:var(--bg-hover)}.notification-item.unread{background:var(--bg-unread)}.notif-accent{width:3px;align-self:stretch;flex-shrink:0;background:transparent;border-radius:0 2px 2px 0;opacity:.3}.notification-item.unread .notif-accent{opacity:1}.notif-accent.type-info{background:var(--info-color)}.notif-accent.type-success{background:var(--success-color)}.notif-accent.type-warning{background:var(--warning-color)}.notif-accent.type-error{background:var(--error-color)}.notif-type-icon{flex-shrink:0;width:26px;height:26px;border-radius:7px;display:flex;align-items:center;justify-content:center;align-self:center;margin-left:10px}.notif-type-icon.type-info{color:var(--info-color);background:var(--info-bg)}.notif-type-icon.type-success{color:var(--success-color);background:var(--success-bg)}.notif-type-icon.type-warning{color:var(--warning-color);background:var(--warning-bg)}.notif-type-icon.type-error{color:var(--error-color);background:var(--error-bg)}.notification-content{flex:1;min-width:0;padding:12px 8px 12px 12px}.notification-title{font-weight:600;color:var(--text-primary);font-size:13.5px;margin-bottom:3px;line-height:1.35}.notification-message{color:var(--text-secondary);font-size:12px;line-height:1.45;margin-bottom:7px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.notification-meta{display:flex;justify-content:space-between;font-size:11px;color:var(--text-muted)}.app-name{font-weight:600;color:var(--primary-color)}.icon-btn{background:none;border:none;cursor:pointer;width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0;align-self:center;margin-right:8px;transition:color .15s,background-color .15s;color:var(--text-muted)}.read-btn:hover{color:var(--success-color);background:#43a0471a}.delete-btn:hover{color:var(--error-color);background:#f443361a}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;height:100%;padding:40px 20px;color:var(--text-muted)}.empty-icon{opacity:.35}.empty-state p{margin:0;font-size:13px}.panel-footer{padding:10px 14px;border-top:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.footer-actions{display:flex;gap:8px}.footer-actions .action-btn{flex:1}.action-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px 12px;background:var(--primary-color);color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:12.5px;font-weight:600;transition:background-color .18s,transform .12s}.action-btn:hover{background:var(--primary-hover);transform:translateY(-1px)}.delete-all-btn{background:var(--error-color)}.delete-all-btn:hover{background:var(--error-hover)}.modal-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1060;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.modal-container{background:var(--bg-primary);border-radius:14px;width:90%;max-width:580px;max-height:80vh;display:flex;flex-direction:column;box-shadow:0 16px 48px #00000040;animation:modal-in .2s cubic-bezier(.16,1,.3,1)}@keyframes modal-in{0%{opacity:0;transform:scale(.94) translateY(8px)}to{opacity:1;transform:scale(1) translateY(0)}}.modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);border-radius:14px 14px 0 0;border-top:3px solid transparent}.modal-header.modal-type-info{border-top-color:var(--info-color)}.modal-header.modal-type-success{border-top-color:var(--success-color)}.modal-header.modal-type-warning{border-top-color:var(--warning-color)}.modal-header.modal-type-error{border-top-color:var(--error-color)}.modal-header-left{display:flex;align-items:center;gap:10px;min-width:0}.modal-type-icon{flex-shrink:0;width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center}.modal-type-info .modal-type-icon{color:var(--info-color);background:var(--info-bg)}.modal-type-success .modal-type-icon{color:var(--success-color);background:var(--success-bg)}.modal-type-warning .modal-type-icon{color:var(--warning-color);background:var(--warning-bg)}.modal-type-error .modal-type-icon{color:var(--error-color);background:var(--error-bg)}.modal-header h3{margin:0;font-size:15px;font-weight:700;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.modal-meta{display:flex;justify-content:space-between;padding:8px 20px;font-size:11.5px;color:var(--text-muted);border-bottom:1px solid var(--border-light)}.modal-body{padding:20px;overflow-y:auto;flex:1;color:var(--text-primary);font-size:14px;line-height:1.65}.modal-footer{padding:12px 20px;border-top:1px solid var(--border-color);background:var(--bg-secondary);border-radius:0 0 14px 14px;display:flex;justify-content:flex-end}.modal-footer .action-btn{width:auto;padding:8px 24px}@media(max-width:600px){.notification-panel{width:100%;right:-100%}.modal-container{width:95%;max-height:90vh}}\n"] }]
|
|
1425
|
+
`, styles: [":host{display:block;position:relative;--primary-color: #1976d2;--primary-hover: #1565c0;--primary-light: rgba(25, 118, 210, .1);--success-color: #43a047;--error-color: #f44336;--error-hover: #d32f2f;--unread-accent: #1976d2;--info-color: #2196f3;--info-bg: rgba(33, 150, 243, .1);--success-bg: rgba(67, 160, 71, .1);--warning-color: #f57c00;--warning-bg: rgba(245, 124, 0, .1);--error-bg: rgba(244, 67, 54, .1);--text-primary: #212121;--text-secondary: #616161;--text-muted: #9e9e9e;--bg-primary: #ffffff;--bg-secondary: #f8f9fa;--bg-hover: #f0f4ff;--bg-unread: rgba(25, 118, 210, .06);--border-color: #e0e0e0;--border-light: #eeeeee;--shadow: rgba(0, 0, 0, .15)}:host(.theme-dark){display:block;position:relative;--primary-color: #90caf9;--primary-hover: #64b5f6;--primary-light: rgba(144, 202, 249, .1);--success-color: #66bb6a;--error-color: #ef5350;--error-hover: #c62828;--unread-accent: #90caf9;--info-color: #64b5f6;--info-bg: rgba(100, 181, 246, .12);--success-bg: rgba(102, 187, 106, .12);--warning-color: #ffb74d;--warning-bg: rgba(255, 183, 77, .12);--error-bg: rgba(239, 83, 80, .12);--text-primary: #e0e0e0;--text-secondary: #b0b0b0;--text-muted: #757575;--bg-primary: #1e1e2e;--bg-secondary: #27273a;--bg-hover: #2a2d4a;--bg-unread: rgba(144, 202, 249, .08);--border-color: #383850;--border-light: #2e2e42;--shadow: rgba(0, 0, 0, .4)}.notification-panel{position:fixed;top:0;right:-360px;width:360px;height:100vh;background:var(--bg-primary);box-shadow:-4px 0 24px var(--shadow);display:flex;flex-direction:column;z-index:1030;transition:right .3s cubic-bezier(.16,1,.3,1)}.notification-panel.open{right:0}.panel-header{display:flex;justify-content:space-between;align-items:center;padding:16px 18px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.panel-header-left{display:flex;align-items:center;gap:9px;color:var(--primary-color)}.panel-header h3{margin:0;font-size:16px;font-weight:700;color:var(--text-primary);letter-spacing:.1px}.close-btn{background:none;border:none;cursor:pointer;color:var(--text-muted);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:color .2s,background-color .2s}.close-btn:hover{color:var(--text-primary);background-color:var(--bg-hover)}.tabs{display:flex;gap:6px;padding:10px 14px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.tab-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:7px 12px;background:none;border:1px solid var(--border-color);border-radius:20px;color:var(--text-secondary);cursor:pointer;font-size:13px;font-weight:500;transition:all .18s}.tab-btn:hover{background:var(--bg-hover);color:var(--primary-color);border-color:var(--primary-color)}.tab-btn.active{background:var(--primary-color);border-color:var(--primary-color);color:#fff}.tab-count{background:#ffffff40;border-radius:10px;min-width:18px;height:18px;padding:0 5px;font-size:10px;font-weight:700;display:flex;align-items:center;justify-content:center}.tab-btn:not(.active) .tab-count{background:var(--error-color);color:#fff}.read-count{background:#ffffff40}.tab-btn:not(.active) .read-count{background:var(--text-muted)}.notifications-list{flex:1;overflow-y:auto}.notification-item{display:flex;align-items:flex-start;gap:0;border-bottom:1px solid var(--border-light);cursor:pointer;background:var(--bg-primary);transition:background-color .15s;position:relative}.notification-item:hover{background:var(--bg-hover)}.notification-item.unread{background:var(--bg-unread)}.notif-accent{width:3px;align-self:stretch;flex-shrink:0;background:transparent;border-radius:0 2px 2px 0;opacity:.3}.notification-item.unread .notif-accent{opacity:1}.notif-accent.type-info{background:var(--info-color)}.notif-accent.type-success{background:var(--success-color)}.notif-accent.type-warning{background:var(--warning-color)}.notif-accent.type-error{background:var(--error-color)}.notif-type-icon{flex-shrink:0;width:26px;height:26px;border-radius:7px;display:flex;align-items:center;justify-content:center;align-self:center;margin-left:10px}.notif-type-icon.type-info{color:var(--info-color);background:var(--info-bg)}.notif-type-icon.type-success{color:var(--success-color);background:var(--success-bg)}.notif-type-icon.type-warning{color:var(--warning-color);background:var(--warning-bg)}.notif-type-icon.type-error{color:var(--error-color);background:var(--error-bg)}.notification-content{flex:1;min-width:0;padding:12px 8px 12px 12px}.notification-title{font-weight:600;color:var(--text-primary);font-size:13.5px;margin-bottom:3px;line-height:1.35}.notification-message{color:var(--text-secondary);font-size:12px;line-height:1.45;margin-bottom:7px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.notification-meta{display:flex;justify-content:space-between;font-size:11px;color:var(--text-muted)}.app-name{font-weight:600;color:var(--primary-color)}.icon-btn{background:none;border:none;cursor:pointer;width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0;align-self:center;margin-right:8px;transition:color .15s,background-color .15s;color:var(--text-muted)}.read-btn:hover{color:var(--success-color);background:#43a0471a}.delete-btn:hover{color:var(--error-color);background:#f443361a}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;height:100%;padding:40px 20px;color:var(--text-muted)}.empty-icon{opacity:.35}.empty-state p{margin:0;font-size:13px}.panel-footer{padding:10px 14px;border-top:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.footer-actions{display:flex;gap:8px}.footer-actions .action-btn{flex:1}.action-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px 12px;background:var(--primary-color);color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:12.5px;font-weight:600;transition:background-color .18s,transform .12s}.action-btn:hover{background:var(--primary-hover);transform:translateY(-1px)}.delete-all-btn{background:var(--error-color)}.delete-all-btn:hover{background:var(--error-hover)}.modal-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1060;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.modal-container{background:var(--bg-primary);border-radius:14px;width:90%;max-width:580px;max-height:80vh;display:flex;flex-direction:column;box-shadow:0 16px 48px #00000040;animation:modal-in .2s cubic-bezier(.16,1,.3,1)}@keyframes modal-in{0%{opacity:0;transform:scale(.94) translateY(8px)}to{opacity:1;transform:scale(1) translateY(0)}}.modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);border-radius:14px 14px 0 0;border-top:3px solid transparent}.modal-header.modal-type-info{border-top-color:var(--info-color)}.modal-header.modal-type-success{border-top-color:var(--success-color)}.modal-header.modal-type-warning{border-top-color:var(--warning-color)}.modal-header.modal-type-error{border-top-color:var(--error-color)}.modal-header-left{display:flex;align-items:center;gap:10px;min-width:0}.modal-type-icon{flex-shrink:0;width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center}.modal-type-info .modal-type-icon{color:var(--info-color);background:var(--info-bg)}.modal-type-success .modal-type-icon{color:var(--success-color);background:var(--success-bg)}.modal-type-warning .modal-type-icon{color:var(--warning-color);background:var(--warning-bg)}.modal-type-error .modal-type-icon{color:var(--error-color);background:var(--error-bg)}.modal-header h3{margin:0;font-size:15px;font-weight:700;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.modal-meta{display:flex;justify-content:space-between;padding:8px 20px;font-size:11.5px;color:var(--text-muted);border-bottom:1px solid var(--border-light)}.modal-body{padding:20px;overflow-y:auto;flex:1;color:var(--text-primary);font-size:14px;line-height:1.65}.modal-body-iframe{padding:0}.notification-iframe{width:100%;height:100%;border:none;display:block}.modal-footer{padding:12px 20px;border-top:1px solid var(--border-color);background:var(--bg-secondary);border-radius:0 0 14px 14px;display:flex;justify-content:flex-end}.modal-footer .action-btn{width:auto;padding:8px 24px}@media(max-width:600px){.notification-panel{width:100%;right:-100%}.modal-container{width:95%;max-height:90vh}}\n"] }]
|
|
1346
1426
|
}], ctorParameters: () => [{ type: MesAuthService }, { type: ToastService }, { type: ThemeService }], propDecorators: { notificationRead: [{
|
|
1347
1427
|
type: Output
|
|
1348
1428
|
}], themeClass: [{
|
|
@@ -1350,12 +1430,456 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
1350
1430
|
args: ['class']
|
|
1351
1431
|
}] } });
|
|
1352
1432
|
|
|
1433
|
+
class MaApprovalService {
|
|
1434
|
+
apiBase = '';
|
|
1435
|
+
http;
|
|
1436
|
+
config = null;
|
|
1437
|
+
constructor() { }
|
|
1438
|
+
init(config, httpClient) {
|
|
1439
|
+
this.config = config;
|
|
1440
|
+
this.http = httpClient;
|
|
1441
|
+
this.apiBase = config.apiBaseUrl.replace(/\/$/, '');
|
|
1442
|
+
}
|
|
1443
|
+
get opts() {
|
|
1444
|
+
return { withCredentials: this.config?.withCredentials ?? true };
|
|
1445
|
+
}
|
|
1446
|
+
// ====================== Dashboard ======================
|
|
1447
|
+
getDashboard() {
|
|
1448
|
+
return this.http.get(`${this.apiBase}/approval/dashboard`, this.opts);
|
|
1449
|
+
}
|
|
1450
|
+
// ====================== Pending & My Requests ======================
|
|
1451
|
+
getPendingApprovals(page = 1, pageSize = 20) {
|
|
1452
|
+
return this.http.get(`${this.apiBase}/approval/pending?page=${page}&pageSize=${pageSize}`, this.opts);
|
|
1453
|
+
}
|
|
1454
|
+
getMyRequests(page = 1, pageSize = 20, status) {
|
|
1455
|
+
let url = `${this.apiBase}/approval/my-requests?page=${page}&pageSize=${pageSize}`;
|
|
1456
|
+
if (status !== undefined)
|
|
1457
|
+
url += `&status=${status}`;
|
|
1458
|
+
return this.http.get(url, this.opts);
|
|
1459
|
+
}
|
|
1460
|
+
// ====================== Document ======================
|
|
1461
|
+
getDocument(id) {
|
|
1462
|
+
return this.http.get(`${this.apiBase}/approval/documents/${id}`, this.opts);
|
|
1463
|
+
}
|
|
1464
|
+
getDocumentHistory(id) {
|
|
1465
|
+
return this.http.get(`${this.apiBase}/approval/documents/${id}/history`, this.opts);
|
|
1466
|
+
}
|
|
1467
|
+
getDocumentContentUrl(id) {
|
|
1468
|
+
return `${this.apiBase}/approval/documents/${id}/content`;
|
|
1469
|
+
}
|
|
1470
|
+
getDocumentThumbnailUrl(id) {
|
|
1471
|
+
return `${this.apiBase}/approval/documents/${id}/thumbnail`;
|
|
1472
|
+
}
|
|
1473
|
+
// ====================== Actions ======================
|
|
1474
|
+
approve(documentId, comment) {
|
|
1475
|
+
const body = { comment };
|
|
1476
|
+
return this.http.post(`${this.apiBase}/approval/documents/${documentId}/approve`, body, this.opts);
|
|
1477
|
+
}
|
|
1478
|
+
reject(documentId, comment) {
|
|
1479
|
+
const body = { comment };
|
|
1480
|
+
return this.http.post(`${this.apiBase}/approval/documents/${documentId}/reject`, body, this.opts);
|
|
1481
|
+
}
|
|
1482
|
+
delegate(documentId, toUserId, reason) {
|
|
1483
|
+
const body = { toUserId, reason };
|
|
1484
|
+
return this.http.post(`${this.apiBase}/approval/documents/${documentId}/delegate`, body, this.opts);
|
|
1485
|
+
}
|
|
1486
|
+
cancel(documentId, reason) {
|
|
1487
|
+
let url = `${this.apiBase}/approval/documents/${documentId}`;
|
|
1488
|
+
if (reason)
|
|
1489
|
+
url += `?reason=${encodeURIComponent(reason)}`;
|
|
1490
|
+
return this.http.delete(url, this.opts);
|
|
1491
|
+
}
|
|
1492
|
+
// ====================== Templates ======================
|
|
1493
|
+
getTemplates(appId) {
|
|
1494
|
+
let url = `${this.apiBase}/approval/templates`;
|
|
1495
|
+
if (appId)
|
|
1496
|
+
url += `?appId=${encodeURIComponent(appId)}`;
|
|
1497
|
+
return this.http.get(url, this.opts);
|
|
1498
|
+
}
|
|
1499
|
+
getTemplate(id) {
|
|
1500
|
+
return this.http.get(`${this.apiBase}/approval/templates/${id}`, this.opts);
|
|
1501
|
+
}
|
|
1502
|
+
createTemplate(request) {
|
|
1503
|
+
return this.http.post(`${this.apiBase}/approval/templates`, request, this.opts);
|
|
1504
|
+
}
|
|
1505
|
+
updateTemplate(id, request) {
|
|
1506
|
+
return this.http.put(`${this.apiBase}/approval/templates/${id}`, request, this.opts);
|
|
1507
|
+
}
|
|
1508
|
+
deleteTemplate(id) {
|
|
1509
|
+
return this.http.delete(`${this.apiBase}/approval/templates/${id}`, this.opts);
|
|
1510
|
+
}
|
|
1511
|
+
// ====================== Create (used by ma-arv-container) ======================
|
|
1512
|
+
createApproval(request) {
|
|
1513
|
+
return this.http.post(`${this.apiBase}/approval/documents`, request, this.opts);
|
|
1514
|
+
}
|
|
1515
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MaApprovalService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1516
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MaApprovalService });
|
|
1517
|
+
}
|
|
1518
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MaApprovalService, decorators: [{
|
|
1519
|
+
type: Injectable
|
|
1520
|
+
}], ctorParameters: () => [] });
|
|
1521
|
+
|
|
1522
|
+
// ====================== Enums ======================
|
|
1523
|
+
var ApprovalStepMode;
|
|
1524
|
+
(function (ApprovalStepMode) {
|
|
1525
|
+
ApprovalStepMode[ApprovalStepMode["Sequential"] = 0] = "Sequential";
|
|
1526
|
+
ApprovalStepMode[ApprovalStepMode["Parallel"] = 1] = "Parallel";
|
|
1527
|
+
})(ApprovalStepMode || (ApprovalStepMode = {}));
|
|
1528
|
+
var ApprovalDocumentStatus;
|
|
1529
|
+
(function (ApprovalDocumentStatus) {
|
|
1530
|
+
ApprovalDocumentStatus[ApprovalDocumentStatus["Draft"] = 0] = "Draft";
|
|
1531
|
+
ApprovalDocumentStatus[ApprovalDocumentStatus["Pending"] = 1] = "Pending";
|
|
1532
|
+
ApprovalDocumentStatus[ApprovalDocumentStatus["Approved"] = 2] = "Approved";
|
|
1533
|
+
ApprovalDocumentStatus[ApprovalDocumentStatus["Rejected"] = 3] = "Rejected";
|
|
1534
|
+
ApprovalDocumentStatus[ApprovalDocumentStatus["Cancelled"] = 4] = "Cancelled";
|
|
1535
|
+
ApprovalDocumentStatus[ApprovalDocumentStatus["Expired"] = 5] = "Expired";
|
|
1536
|
+
})(ApprovalDocumentStatus || (ApprovalDocumentStatus = {}));
|
|
1537
|
+
var ApprovalStepStatus;
|
|
1538
|
+
(function (ApprovalStepStatus) {
|
|
1539
|
+
ApprovalStepStatus[ApprovalStepStatus["Waiting"] = 0] = "Waiting";
|
|
1540
|
+
ApprovalStepStatus[ApprovalStepStatus["Active"] = 1] = "Active";
|
|
1541
|
+
ApprovalStepStatus[ApprovalStepStatus["Approved"] = 2] = "Approved";
|
|
1542
|
+
ApprovalStepStatus[ApprovalStepStatus["Rejected"] = 3] = "Rejected";
|
|
1543
|
+
ApprovalStepStatus[ApprovalStepStatus["Delegated"] = 4] = "Delegated";
|
|
1544
|
+
ApprovalStepStatus[ApprovalStepStatus["Expired"] = 5] = "Expired";
|
|
1545
|
+
ApprovalStepStatus[ApprovalStepStatus["Skipped"] = 6] = "Skipped";
|
|
1546
|
+
})(ApprovalStepStatus || (ApprovalStepStatus = {}));
|
|
1547
|
+
var ApprovalActionType;
|
|
1548
|
+
(function (ApprovalActionType) {
|
|
1549
|
+
ApprovalActionType[ApprovalActionType["Created"] = 0] = "Created";
|
|
1550
|
+
ApprovalActionType[ApprovalActionType["Submitted"] = 1] = "Submitted";
|
|
1551
|
+
ApprovalActionType[ApprovalActionType["Approved"] = 2] = "Approved";
|
|
1552
|
+
ApprovalActionType[ApprovalActionType["Rejected"] = 3] = "Rejected";
|
|
1553
|
+
ApprovalActionType[ApprovalActionType["Delegated"] = 4] = "Delegated";
|
|
1554
|
+
ApprovalActionType[ApprovalActionType["Cancelled"] = 5] = "Cancelled";
|
|
1555
|
+
ApprovalActionType[ApprovalActionType["Commented"] = 6] = "Commented";
|
|
1556
|
+
ApprovalActionType[ApprovalActionType["Expired"] = 7] = "Expired";
|
|
1557
|
+
ApprovalActionType[ApprovalActionType["StepAdvanced"] = 8] = "StepAdvanced";
|
|
1558
|
+
})(ApprovalActionType || (ApprovalActionType = {}));
|
|
1559
|
+
|
|
1560
|
+
class MaApprovalPanelComponent {
|
|
1561
|
+
approvalActioned = new EventEmitter();
|
|
1562
|
+
isOpen = false;
|
|
1563
|
+
loading = false;
|
|
1564
|
+
activeTab = 'processing';
|
|
1565
|
+
processingItems = [];
|
|
1566
|
+
approvedItems = [];
|
|
1567
|
+
rejectedItems = [];
|
|
1568
|
+
destroy$ = new Subject();
|
|
1569
|
+
mesAuth = inject(MesAuthService);
|
|
1570
|
+
http = inject(HttpClient);
|
|
1571
|
+
router = inject(Router);
|
|
1572
|
+
approvalSvc = null;
|
|
1573
|
+
ngOnInit() {
|
|
1574
|
+
const config = this.mesAuth.getConfig();
|
|
1575
|
+
if (config) {
|
|
1576
|
+
this.approvalSvc = new MaApprovalService();
|
|
1577
|
+
this.approvalSvc.init(config, this.http);
|
|
1578
|
+
}
|
|
1579
|
+
this.mesAuth.approvalEvents$.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
|
1580
|
+
if (this.isOpen)
|
|
1581
|
+
this.loadCurrentTab();
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
ngOnDestroy() {
|
|
1585
|
+
this.destroy$.next();
|
|
1586
|
+
this.destroy$.complete();
|
|
1587
|
+
}
|
|
1588
|
+
open() {
|
|
1589
|
+
this.isOpen = true;
|
|
1590
|
+
this.loadAllTabs();
|
|
1591
|
+
}
|
|
1592
|
+
close() {
|
|
1593
|
+
this.isOpen = false;
|
|
1594
|
+
}
|
|
1595
|
+
toggle() {
|
|
1596
|
+
if (this.isOpen)
|
|
1597
|
+
this.close();
|
|
1598
|
+
else
|
|
1599
|
+
this.open();
|
|
1600
|
+
}
|
|
1601
|
+
switchTab(tab) {
|
|
1602
|
+
this.activeTab = tab;
|
|
1603
|
+
this.loadCurrentTab();
|
|
1604
|
+
}
|
|
1605
|
+
loadAllTabs() {
|
|
1606
|
+
this.loading = true;
|
|
1607
|
+
if (!this.approvalSvc) {
|
|
1608
|
+
this.loading = false;
|
|
1609
|
+
return;
|
|
1610
|
+
}
|
|
1611
|
+
let pending = 3;
|
|
1612
|
+
const done = () => { if (--pending === 0)
|
|
1613
|
+
this.loading = false; };
|
|
1614
|
+
this.approvalSvc.getPendingApprovals(1, 100).pipe(takeUntil(this.destroy$)).subscribe({
|
|
1615
|
+
next: r => { this.processingItems = r.items; done(); },
|
|
1616
|
+
error: () => done()
|
|
1617
|
+
});
|
|
1618
|
+
this.approvalSvc.getMyRequests(1, 10, ApprovalDocumentStatus.Approved).pipe(takeUntil(this.destroy$)).subscribe({
|
|
1619
|
+
next: r => { this.approvedItems = r.items; done(); },
|
|
1620
|
+
error: () => done()
|
|
1621
|
+
});
|
|
1622
|
+
this.approvalSvc.getMyRequests(1, 10, ApprovalDocumentStatus.Rejected).pipe(takeUntil(this.destroy$)).subscribe({
|
|
1623
|
+
next: r => { this.rejectedItems = r.items; done(); },
|
|
1624
|
+
error: () => done()
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
loadCurrentTab() {
|
|
1628
|
+
if (!this.approvalSvc)
|
|
1629
|
+
return;
|
|
1630
|
+
if (this.activeTab === 'processing') {
|
|
1631
|
+
this.approvalSvc.getPendingApprovals(1, 100).pipe(takeUntil(this.destroy$)).subscribe({
|
|
1632
|
+
next: r => this.processingItems = r.items,
|
|
1633
|
+
error: () => { }
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
else if (this.activeTab === 'approved') {
|
|
1637
|
+
this.approvalSvc.getMyRequests(1, 10, ApprovalDocumentStatus.Approved).pipe(takeUntil(this.destroy$)).subscribe({
|
|
1638
|
+
next: r => this.approvedItems = r.items,
|
|
1639
|
+
error: () => { }
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
else {
|
|
1643
|
+
this.approvalSvc.getMyRequests(1, 10, ApprovalDocumentStatus.Rejected).pipe(takeUntil(this.destroy$)).subscribe({
|
|
1644
|
+
next: r => this.rejectedItems = r.items,
|
|
1645
|
+
error: () => { }
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
navigateToDetail(id) {
|
|
1650
|
+
this.close();
|
|
1651
|
+
this.router.navigate(['/auth/approval/detail', id]);
|
|
1652
|
+
this.approvalActioned.emit();
|
|
1653
|
+
}
|
|
1654
|
+
showMore(status) {
|
|
1655
|
+
this.close();
|
|
1656
|
+
this.router.navigate(['/auth/approval/my-requests'], { queryParams: { status } });
|
|
1657
|
+
}
|
|
1658
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MaApprovalPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1659
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: MaApprovalPanelComponent, isStandalone: true, selector: "ma-approval-panel", outputs: { approvalActioned: "approvalActioned" }, ngImport: i0, template: `
|
|
1660
|
+
<div class="approval-backdrop" [class.open]="isOpen" (click)="close()"></div>
|
|
1661
|
+
<div class="approval-panel" [class.open]="isOpen">
|
|
1662
|
+
|
|
1663
|
+
<!-- Header -->
|
|
1664
|
+
<div class="panel-header">
|
|
1665
|
+
<div class="panel-header-left">
|
|
1666
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1667
|
+
<path d="M9 11l3 3L22 4"/>
|
|
1668
|
+
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
|
|
1669
|
+
</svg>
|
|
1670
|
+
<h3>Approvals</h3>
|
|
1671
|
+
</div>
|
|
1672
|
+
<button class="close-btn" (click)="close()" aria-label="Close">
|
|
1673
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
1674
|
+
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
1675
|
+
</svg>
|
|
1676
|
+
</button>
|
|
1677
|
+
</div>
|
|
1678
|
+
|
|
1679
|
+
<!-- Tabs -->
|
|
1680
|
+
<div class="tabs">
|
|
1681
|
+
<button class="tab-btn" [class.active]="activeTab === 'processing'" (click)="switchTab('processing')">
|
|
1682
|
+
Processing
|
|
1683
|
+
<span class="tab-badge" *ngIf="processingItems.length > 0">{{ processingItems.length }}</span>
|
|
1684
|
+
</button>
|
|
1685
|
+
<button class="tab-btn" [class.active]="activeTab === 'approved'" (click)="switchTab('approved')">
|
|
1686
|
+
Approved
|
|
1687
|
+
</button>
|
|
1688
|
+
<button class="tab-btn" [class.active]="activeTab === 'rejected'" (click)="switchTab('rejected')">
|
|
1689
|
+
Rejected
|
|
1690
|
+
</button>
|
|
1691
|
+
</div>
|
|
1692
|
+
|
|
1693
|
+
<!-- Content -->
|
|
1694
|
+
<div class="panel-content">
|
|
1695
|
+
<!-- Loading -->
|
|
1696
|
+
<div class="loading-state" *ngIf="loading">
|
|
1697
|
+
<div class="spinner"></div>
|
|
1698
|
+
<span>Loading...</span>
|
|
1699
|
+
</div>
|
|
1700
|
+
|
|
1701
|
+
<!-- Processing tab -->
|
|
1702
|
+
<ng-container *ngIf="!loading && activeTab === 'processing'">
|
|
1703
|
+
<div class="empty-state" *ngIf="processingItems.length === 0">
|
|
1704
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" opacity="0.4"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
|
|
1705
|
+
<p>No pending approvals</p>
|
|
1706
|
+
</div>
|
|
1707
|
+
<div class="approval-item" *ngFor="let item of processingItems" (click)="navigateToDetail(item.id)">
|
|
1708
|
+
<div class="item-title">{{ item.title }}</div>
|
|
1709
|
+
<div class="item-meta">
|
|
1710
|
+
<span class="item-requester">By {{ item.requestedByUserName }}</span>
|
|
1711
|
+
<span class="item-step" *ngIf="item.currentStepName">· {{ item.currentStepName }}</span>
|
|
1712
|
+
</div>
|
|
1713
|
+
<div class="item-footer">
|
|
1714
|
+
<span class="item-time">{{ item.createdAt | date:'shortDate' }}</span>
|
|
1715
|
+
<span class="item-link">View →</span>
|
|
1716
|
+
</div>
|
|
1717
|
+
</div>
|
|
1718
|
+
</ng-container>
|
|
1719
|
+
|
|
1720
|
+
<!-- Approved tab -->
|
|
1721
|
+
<ng-container *ngIf="!loading && activeTab === 'approved'">
|
|
1722
|
+
<div class="empty-state" *ngIf="approvedItems.length === 0">
|
|
1723
|
+
<p>No approved documents</p>
|
|
1724
|
+
</div>
|
|
1725
|
+
<div class="approval-item approved" *ngFor="let item of approvedItems" (click)="navigateToDetail(item.id)">
|
|
1726
|
+
<div class="item-title">{{ item.title }}</div>
|
|
1727
|
+
<div class="item-meta">
|
|
1728
|
+
<span class="item-requester">By {{ item.requestedByUserName }}</span>
|
|
1729
|
+
</div>
|
|
1730
|
+
<div class="item-footer">
|
|
1731
|
+
<span class="status-badge approved-badge">Approved</span>
|
|
1732
|
+
<span class="item-time">{{ item.completedAt | date:'shortDate' }}</span>
|
|
1733
|
+
<span class="item-link">View →</span>
|
|
1734
|
+
</div>
|
|
1735
|
+
</div>
|
|
1736
|
+
<div class="show-more" *ngIf="approvedItems.length >= 10" (click)="showMore('approved')">
|
|
1737
|
+
Show more →
|
|
1738
|
+
</div>
|
|
1739
|
+
</ng-container>
|
|
1740
|
+
|
|
1741
|
+
<!-- Rejected tab -->
|
|
1742
|
+
<ng-container *ngIf="!loading && activeTab === 'rejected'">
|
|
1743
|
+
<div class="empty-state" *ngIf="rejectedItems.length === 0">
|
|
1744
|
+
<p>No rejected documents</p>
|
|
1745
|
+
</div>
|
|
1746
|
+
<div class="approval-item rejected" *ngFor="let item of rejectedItems" (click)="navigateToDetail(item.id)">
|
|
1747
|
+
<div class="item-title">{{ item.title }}</div>
|
|
1748
|
+
<div class="item-meta">
|
|
1749
|
+
<span class="item-requester">By {{ item.requestedByUserName }}</span>
|
|
1750
|
+
</div>
|
|
1751
|
+
<div class="item-footer">
|
|
1752
|
+
<span class="status-badge rejected-badge">Rejected</span>
|
|
1753
|
+
<span class="item-time">{{ item.completedAt | date:'shortDate' }}</span>
|
|
1754
|
+
<span class="item-link">View →</span>
|
|
1755
|
+
</div>
|
|
1756
|
+
</div>
|
|
1757
|
+
<div class="show-more" *ngIf="rejectedItems.length >= 10" (click)="showMore('rejected')">
|
|
1758
|
+
Show more →
|
|
1759
|
+
</div>
|
|
1760
|
+
</ng-container>
|
|
1761
|
+
</div>
|
|
1762
|
+
</div>
|
|
1763
|
+
`, isInline: true, styles: [":host{--primary: #90caf9;--success: #66bb6a;--error: #ef5350;--text-primary: #e0e0e0;--text-secondary: #b0b0b0;--text-muted: #757575;--bg-primary: #1e1e2e;--bg-secondary: #27273a;--bg-hover: #2a2d4a;--border-color: #383850;--shadow: rgba(0,0,0,.4)}:host(.theme-light){--primary: #1565c0;--success: #2e7d32;--error: #c62828;--text-primary: #212121;--text-secondary: #616161;--text-muted: #9e9e9e;--bg-primary: #ffffff;--bg-secondary: #f5f5f5;--bg-hover: #e8eaf6;--border-color: #e0e0e0;--shadow: rgba(0,0,0,.15)}.approval-backdrop{display:none;position:fixed;inset:0;background:#0006;z-index:1029}.approval-backdrop.open{display:block}.approval-panel{position:fixed;top:0;right:-380px;width:380px;height:100vh;background:var(--bg-primary);box-shadow:-4px 0 24px var(--shadow);display:flex;flex-direction:column;z-index:1030;transition:right .3s cubic-bezier(.16,1,.3,1)}.approval-panel.open{right:0}.panel-header{display:flex;justify-content:space-between;align-items:center;padding:16px 18px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.panel-header-left{display:flex;align-items:center;gap:9px;color:var(--primary)}.panel-header h3{margin:0;font-size:16px;font-weight:700;color:var(--text-primary)}.close-btn{background:none;border:none;cursor:pointer;color:var(--text-muted);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s}.close-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.tabs{display:flex;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.tab-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:11px 8px;background:none;border:none;border-bottom:2px solid transparent;cursor:pointer;font-size:13px;font-weight:500;color:var(--text-muted);transition:color .15s,border-color .15s}.tab-btn.active{color:var(--primary);border-bottom-color:var(--primary)}.tab-btn:hover:not(.active){color:var(--text-secondary)}.tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;background:var(--primary);color:#fff;font-size:11px;font-weight:700;border-radius:9px}.panel-content{flex:1;overflow-y:auto;padding:8px 0}.loading-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;gap:12px;color:var(--text-muted);font-size:13px}.spinner{width:24px;height:24px;border:2px solid var(--border-color);border-top-color:var(--primary);border-radius:50%;animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;gap:10px;color:var(--text-muted)}.empty-state p{margin:0;font-size:14px}.approval-item{padding:14px 18px;border-bottom:1px solid var(--border-color);cursor:pointer;transition:background .15s}.approval-item:hover{background:var(--bg-hover)}.approval-item:last-child{border-bottom:none}.item-title{font-size:14px;font-weight:600;color:var(--text-primary);margin-bottom:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.item-meta{font-size:12px;color:var(--text-muted);margin-bottom:8px;display:flex;gap:4px;flex-wrap:wrap}.item-footer{display:flex;align-items:center;gap:8px}.item-time{font-size:11px;color:var(--text-muted);margin-right:auto}.item-link{font-size:12px;color:var(--primary);font-weight:500}.status-badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600;letter-spacing:.3px}.approved-badge{background:#66bb6a26;color:var(--success)}.rejected-badge{background:#ef53501f;color:var(--error)}.show-more{text-align:center;padding:14px;font-size:13px;color:var(--primary);cursor:pointer;font-weight:500}.show-more:hover{text-decoration:underline}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "pipe", type: DatePipe, name: "date" }] });
|
|
1764
|
+
}
|
|
1765
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MaApprovalPanelComponent, decorators: [{
|
|
1766
|
+
type: Component,
|
|
1767
|
+
args: [{ selector: 'ma-approval-panel', standalone: true, imports: [NgIf, NgFor, DatePipe], template: `
|
|
1768
|
+
<div class="approval-backdrop" [class.open]="isOpen" (click)="close()"></div>
|
|
1769
|
+
<div class="approval-panel" [class.open]="isOpen">
|
|
1770
|
+
|
|
1771
|
+
<!-- Header -->
|
|
1772
|
+
<div class="panel-header">
|
|
1773
|
+
<div class="panel-header-left">
|
|
1774
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1775
|
+
<path d="M9 11l3 3L22 4"/>
|
|
1776
|
+
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
|
|
1777
|
+
</svg>
|
|
1778
|
+
<h3>Approvals</h3>
|
|
1779
|
+
</div>
|
|
1780
|
+
<button class="close-btn" (click)="close()" aria-label="Close">
|
|
1781
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
1782
|
+
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
1783
|
+
</svg>
|
|
1784
|
+
</button>
|
|
1785
|
+
</div>
|
|
1786
|
+
|
|
1787
|
+
<!-- Tabs -->
|
|
1788
|
+
<div class="tabs">
|
|
1789
|
+
<button class="tab-btn" [class.active]="activeTab === 'processing'" (click)="switchTab('processing')">
|
|
1790
|
+
Processing
|
|
1791
|
+
<span class="tab-badge" *ngIf="processingItems.length > 0">{{ processingItems.length }}</span>
|
|
1792
|
+
</button>
|
|
1793
|
+
<button class="tab-btn" [class.active]="activeTab === 'approved'" (click)="switchTab('approved')">
|
|
1794
|
+
Approved
|
|
1795
|
+
</button>
|
|
1796
|
+
<button class="tab-btn" [class.active]="activeTab === 'rejected'" (click)="switchTab('rejected')">
|
|
1797
|
+
Rejected
|
|
1798
|
+
</button>
|
|
1799
|
+
</div>
|
|
1800
|
+
|
|
1801
|
+
<!-- Content -->
|
|
1802
|
+
<div class="panel-content">
|
|
1803
|
+
<!-- Loading -->
|
|
1804
|
+
<div class="loading-state" *ngIf="loading">
|
|
1805
|
+
<div class="spinner"></div>
|
|
1806
|
+
<span>Loading...</span>
|
|
1807
|
+
</div>
|
|
1808
|
+
|
|
1809
|
+
<!-- Processing tab -->
|
|
1810
|
+
<ng-container *ngIf="!loading && activeTab === 'processing'">
|
|
1811
|
+
<div class="empty-state" *ngIf="processingItems.length === 0">
|
|
1812
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" opacity="0.4"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
|
|
1813
|
+
<p>No pending approvals</p>
|
|
1814
|
+
</div>
|
|
1815
|
+
<div class="approval-item" *ngFor="let item of processingItems" (click)="navigateToDetail(item.id)">
|
|
1816
|
+
<div class="item-title">{{ item.title }}</div>
|
|
1817
|
+
<div class="item-meta">
|
|
1818
|
+
<span class="item-requester">By {{ item.requestedByUserName }}</span>
|
|
1819
|
+
<span class="item-step" *ngIf="item.currentStepName">· {{ item.currentStepName }}</span>
|
|
1820
|
+
</div>
|
|
1821
|
+
<div class="item-footer">
|
|
1822
|
+
<span class="item-time">{{ item.createdAt | date:'shortDate' }}</span>
|
|
1823
|
+
<span class="item-link">View →</span>
|
|
1824
|
+
</div>
|
|
1825
|
+
</div>
|
|
1826
|
+
</ng-container>
|
|
1827
|
+
|
|
1828
|
+
<!-- Approved tab -->
|
|
1829
|
+
<ng-container *ngIf="!loading && activeTab === 'approved'">
|
|
1830
|
+
<div class="empty-state" *ngIf="approvedItems.length === 0">
|
|
1831
|
+
<p>No approved documents</p>
|
|
1832
|
+
</div>
|
|
1833
|
+
<div class="approval-item approved" *ngFor="let item of approvedItems" (click)="navigateToDetail(item.id)">
|
|
1834
|
+
<div class="item-title">{{ item.title }}</div>
|
|
1835
|
+
<div class="item-meta">
|
|
1836
|
+
<span class="item-requester">By {{ item.requestedByUserName }}</span>
|
|
1837
|
+
</div>
|
|
1838
|
+
<div class="item-footer">
|
|
1839
|
+
<span class="status-badge approved-badge">Approved</span>
|
|
1840
|
+
<span class="item-time">{{ item.completedAt | date:'shortDate' }}</span>
|
|
1841
|
+
<span class="item-link">View →</span>
|
|
1842
|
+
</div>
|
|
1843
|
+
</div>
|
|
1844
|
+
<div class="show-more" *ngIf="approvedItems.length >= 10" (click)="showMore('approved')">
|
|
1845
|
+
Show more →
|
|
1846
|
+
</div>
|
|
1847
|
+
</ng-container>
|
|
1848
|
+
|
|
1849
|
+
<!-- Rejected tab -->
|
|
1850
|
+
<ng-container *ngIf="!loading && activeTab === 'rejected'">
|
|
1851
|
+
<div class="empty-state" *ngIf="rejectedItems.length === 0">
|
|
1852
|
+
<p>No rejected documents</p>
|
|
1853
|
+
</div>
|
|
1854
|
+
<div class="approval-item rejected" *ngFor="let item of rejectedItems" (click)="navigateToDetail(item.id)">
|
|
1855
|
+
<div class="item-title">{{ item.title }}</div>
|
|
1856
|
+
<div class="item-meta">
|
|
1857
|
+
<span class="item-requester">By {{ item.requestedByUserName }}</span>
|
|
1858
|
+
</div>
|
|
1859
|
+
<div class="item-footer">
|
|
1860
|
+
<span class="status-badge rejected-badge">Rejected</span>
|
|
1861
|
+
<span class="item-time">{{ item.completedAt | date:'shortDate' }}</span>
|
|
1862
|
+
<span class="item-link">View →</span>
|
|
1863
|
+
</div>
|
|
1864
|
+
</div>
|
|
1865
|
+
<div class="show-more" *ngIf="rejectedItems.length >= 10" (click)="showMore('rejected')">
|
|
1866
|
+
Show more →
|
|
1867
|
+
</div>
|
|
1868
|
+
</ng-container>
|
|
1869
|
+
</div>
|
|
1870
|
+
</div>
|
|
1871
|
+
`, styles: [":host{--primary: #90caf9;--success: #66bb6a;--error: #ef5350;--text-primary: #e0e0e0;--text-secondary: #b0b0b0;--text-muted: #757575;--bg-primary: #1e1e2e;--bg-secondary: #27273a;--bg-hover: #2a2d4a;--border-color: #383850;--shadow: rgba(0,0,0,.4)}:host(.theme-light){--primary: #1565c0;--success: #2e7d32;--error: #c62828;--text-primary: #212121;--text-secondary: #616161;--text-muted: #9e9e9e;--bg-primary: #ffffff;--bg-secondary: #f5f5f5;--bg-hover: #e8eaf6;--border-color: #e0e0e0;--shadow: rgba(0,0,0,.15)}.approval-backdrop{display:none;position:fixed;inset:0;background:#0006;z-index:1029}.approval-backdrop.open{display:block}.approval-panel{position:fixed;top:0;right:-380px;width:380px;height:100vh;background:var(--bg-primary);box-shadow:-4px 0 24px var(--shadow);display:flex;flex-direction:column;z-index:1030;transition:right .3s cubic-bezier(.16,1,.3,1)}.approval-panel.open{right:0}.panel-header{display:flex;justify-content:space-between;align-items:center;padding:16px 18px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.panel-header-left{display:flex;align-items:center;gap:9px;color:var(--primary)}.panel-header h3{margin:0;font-size:16px;font-weight:700;color:var(--text-primary)}.close-btn{background:none;border:none;cursor:pointer;color:var(--text-muted);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s}.close-btn:hover{background:var(--bg-hover);color:var(--text-primary)}.tabs{display:flex;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.tab-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:11px 8px;background:none;border:none;border-bottom:2px solid transparent;cursor:pointer;font-size:13px;font-weight:500;color:var(--text-muted);transition:color .15s,border-color .15s}.tab-btn.active{color:var(--primary);border-bottom-color:var(--primary)}.tab-btn:hover:not(.active){color:var(--text-secondary)}.tab-badge{display:inline-flex;align-items:center;justify-content:center;min-width:18px;height:18px;padding:0 5px;background:var(--primary);color:#fff;font-size:11px;font-weight:700;border-radius:9px}.panel-content{flex:1;overflow-y:auto;padding:8px 0}.loading-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;gap:12px;color:var(--text-muted);font-size:13px}.spinner{width:24px;height:24px;border:2px solid var(--border-color);border-top-color:var(--primary);border-radius:50%;animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;gap:10px;color:var(--text-muted)}.empty-state p{margin:0;font-size:14px}.approval-item{padding:14px 18px;border-bottom:1px solid var(--border-color);cursor:pointer;transition:background .15s}.approval-item:hover{background:var(--bg-hover)}.approval-item:last-child{border-bottom:none}.item-title{font-size:14px;font-weight:600;color:var(--text-primary);margin-bottom:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.item-meta{font-size:12px;color:var(--text-muted);margin-bottom:8px;display:flex;gap:4px;flex-wrap:wrap}.item-footer{display:flex;align-items:center;gap:8px}.item-time{font-size:11px;color:var(--text-muted);margin-right:auto}.item-link{font-size:12px;color:var(--primary);font-weight:500}.status-badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600;letter-spacing:.3px}.approved-badge{background:#66bb6a26;color:var(--success)}.rejected-badge{background:#ef53501f;color:var(--error)}.show-more{text-align:center;padding:14px;font-size:13px;color:var(--primary);cursor:pointer;font-weight:500}.show-more:hover{text-decoration:underline}\n"] }]
|
|
1872
|
+
}], propDecorators: { approvalActioned: [{
|
|
1873
|
+
type: Output
|
|
1874
|
+
}] } });
|
|
1875
|
+
|
|
1353
1876
|
class MaUserComponent {
|
|
1354
1877
|
userProfile;
|
|
1878
|
+
approvalPanel;
|
|
1355
1879
|
ngAfterViewInit() {
|
|
1356
|
-
// Ensure proper initialization
|
|
1357
1880
|
if (this.userProfile) {
|
|
1358
1881
|
this.userProfile.loadUnreadCount();
|
|
1882
|
+
this.userProfile.loadPendingApprovalCount();
|
|
1359
1883
|
}
|
|
1360
1884
|
}
|
|
1361
1885
|
onNotificationRead() {
|
|
@@ -1363,27 +1887,43 @@ class MaUserComponent {
|
|
|
1363
1887
|
this.userProfile.loadUnreadCount();
|
|
1364
1888
|
}
|
|
1365
1889
|
}
|
|
1890
|
+
onApprovalActioned() {
|
|
1891
|
+
if (this.userProfile) {
|
|
1892
|
+
this.userProfile.loadPendingApprovalCount();
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1366
1895
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MaUserComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1367
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: MaUserComponent, isStandalone: true, selector: "ma-user", viewQueries: [{ propertyName: "userProfile", first: true, predicate: UserProfileComponent, descendants: true }], ngImport: i0, template: `
|
|
1896
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: MaUserComponent, isStandalone: true, selector: "ma-user", viewQueries: [{ propertyName: "userProfile", first: true, predicate: UserProfileComponent, descendants: true }, { propertyName: "approvalPanel", first: true, predicate: MaApprovalPanelComponent, descendants: true }], ngImport: i0, template: `
|
|
1368
1897
|
<ma-toast-container></ma-toast-container>
|
|
1369
1898
|
<div class="user-header">
|
|
1370
|
-
<ma-user-profile
|
|
1899
|
+
<ma-user-profile
|
|
1900
|
+
(notificationClick)="notificationPanel.open()"
|
|
1901
|
+
(approvalClick)="approvalPanel.open()">
|
|
1902
|
+
</ma-user-profile>
|
|
1371
1903
|
</div>
|
|
1372
1904
|
<ma-notification-panel #notificationPanel (notificationRead)="onNotificationRead()"></ma-notification-panel>
|
|
1373
|
-
|
|
1905
|
+
<ma-approval-panel #approvalPanel (approvalActioned)="onApprovalActioned()"></ma-approval-panel>
|
|
1906
|
+
`, isInline: true, styles: [".user-header{display:flex;justify-content:flex-end}\n"], dependencies: [{ kind: "component", type: ToastContainerComponent, selector: "ma-toast-container" }, { kind: "component", type: UserProfileComponent, selector: "ma-user-profile", outputs: ["notificationClick", "approvalClick"] }, { kind: "component", type: NotificationPanelComponent, selector: "ma-notification-panel", outputs: ["notificationRead"] }, { kind: "component", type: MaApprovalPanelComponent, selector: "ma-approval-panel", outputs: ["approvalActioned"] }] });
|
|
1374
1907
|
}
|
|
1375
1908
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MaUserComponent, decorators: [{
|
|
1376
1909
|
type: Component,
|
|
1377
|
-
args: [{ selector: 'ma-user', standalone: true, imports: [ToastContainerComponent, UserProfileComponent, NotificationPanelComponent], template: `
|
|
1910
|
+
args: [{ selector: 'ma-user', standalone: true, imports: [ToastContainerComponent, UserProfileComponent, NotificationPanelComponent, MaApprovalPanelComponent], template: `
|
|
1378
1911
|
<ma-toast-container></ma-toast-container>
|
|
1379
1912
|
<div class="user-header">
|
|
1380
|
-
<ma-user-profile
|
|
1913
|
+
<ma-user-profile
|
|
1914
|
+
(notificationClick)="notificationPanel.open()"
|
|
1915
|
+
(approvalClick)="approvalPanel.open()">
|
|
1916
|
+
</ma-user-profile>
|
|
1381
1917
|
</div>
|
|
1382
1918
|
<ma-notification-panel #notificationPanel (notificationRead)="onNotificationRead()"></ma-notification-panel>
|
|
1919
|
+
<ma-approval-panel #approvalPanel (approvalActioned)="onApprovalActioned()"></ma-approval-panel>
|
|
1383
1920
|
`, styles: [".user-header{display:flex;justify-content:flex-end}\n"] }]
|
|
1384
1921
|
}], propDecorators: { userProfile: [{
|
|
1385
1922
|
type: ViewChild,
|
|
1386
1923
|
args: [UserProfileComponent]
|
|
1924
|
+
}], approvalPanel: [{
|
|
1925
|
+
type: ViewChild,
|
|
1926
|
+
args: [MaApprovalPanelComponent]
|
|
1387
1927
|
}] } });
|
|
1388
1928
|
|
|
1389
1929
|
class NotificationBadgeComponent {
|
|
@@ -1468,9 +2008,587 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
1468
2008
|
args: ['class']
|
|
1469
2009
|
}] } });
|
|
1470
2010
|
|
|
2011
|
+
class MaArvContainerComponent {
|
|
2012
|
+
title = '';
|
|
2013
|
+
description;
|
|
2014
|
+
referenceId = '';
|
|
2015
|
+
templateId;
|
|
2016
|
+
callbackUrl;
|
|
2017
|
+
deadlineHours;
|
|
2018
|
+
approvalSubmitted = new EventEmitter();
|
|
2019
|
+
approvalSubmiting = new EventEmitter();
|
|
2020
|
+
cancelled = new EventEmitter();
|
|
2021
|
+
contentBody;
|
|
2022
|
+
routingMode = 'template';
|
|
2023
|
+
templates = [];
|
|
2024
|
+
selectedTemplateId = null;
|
|
2025
|
+
adHocSteps = [];
|
|
2026
|
+
referenceUserIds = [];
|
|
2027
|
+
refSearchQuery = '';
|
|
2028
|
+
refSearchResults = [];
|
|
2029
|
+
userSearchQuery = [];
|
|
2030
|
+
userSearchResults = [];
|
|
2031
|
+
submitting = false;
|
|
2032
|
+
isSubmitted = false;
|
|
2033
|
+
errorMessage = '';
|
|
2034
|
+
userLabelMap = {};
|
|
2035
|
+
destroy$ = new Subject();
|
|
2036
|
+
mesAuth = inject(MesAuthService);
|
|
2037
|
+
approvalSvc = null;
|
|
2038
|
+
http = inject(HttpClient);
|
|
2039
|
+
ngOnInit() {
|
|
2040
|
+
const config = this.mesAuth.getConfig();
|
|
2041
|
+
if (config) {
|
|
2042
|
+
this.approvalSvc = new MaApprovalService();
|
|
2043
|
+
this.approvalSvc.init(config, this.http);
|
|
2044
|
+
this.loadTemplates();
|
|
2045
|
+
}
|
|
2046
|
+
this.addStep();
|
|
2047
|
+
}
|
|
2048
|
+
ngOnDestroy() {
|
|
2049
|
+
this.destroy$.next();
|
|
2050
|
+
this.destroy$.complete();
|
|
2051
|
+
}
|
|
2052
|
+
loadTemplates() {
|
|
2053
|
+
this.approvalSvc.getTemplates().pipe(takeUntil(this.destroy$)).subscribe({
|
|
2054
|
+
next: (t) => this.templates = t,
|
|
2055
|
+
error: () => { }
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
onTemplateChange(event) {
|
|
2059
|
+
const val = event.target.value;
|
|
2060
|
+
this.selectedTemplateId = val ? parseInt(val, 10) : null;
|
|
2061
|
+
}
|
|
2062
|
+
addStep() {
|
|
2063
|
+
const order = this.adHocSteps.length + 1;
|
|
2064
|
+
this.adHocSteps.push({ stepOrder: order, stepName: `Step ${order}`, approverUserIds: [] });
|
|
2065
|
+
this.userSearchQuery.push('');
|
|
2066
|
+
this.userSearchResults.push([]);
|
|
2067
|
+
}
|
|
2068
|
+
removeStep(i) {
|
|
2069
|
+
this.adHocSteps.splice(i, 1);
|
|
2070
|
+
this.userSearchQuery.splice(i, 1);
|
|
2071
|
+
this.userSearchResults.splice(i, 1);
|
|
2072
|
+
this.adHocSteps.forEach((s, idx) => s.stepOrder = idx + 1);
|
|
2073
|
+
}
|
|
2074
|
+
addApprover(stepIndex, userId) {
|
|
2075
|
+
if (!this.adHocSteps[stepIndex].approverUserIds.includes(userId)) {
|
|
2076
|
+
this.adHocSteps[stepIndex].approverUserIds.push(userId);
|
|
2077
|
+
}
|
|
2078
|
+
this.storeLabelFromResults(userId, this.userSearchResults[stepIndex]);
|
|
2079
|
+
this.userSearchQuery[stepIndex] = '';
|
|
2080
|
+
this.userSearchResults[stepIndex] = [];
|
|
2081
|
+
}
|
|
2082
|
+
removeApprover(stepIndex, approverIndex) {
|
|
2083
|
+
this.adHocSteps[stepIndex].approverUserIds.splice(approverIndex, 1);
|
|
2084
|
+
}
|
|
2085
|
+
addReference(userId) {
|
|
2086
|
+
if (!this.referenceUserIds.includes(userId)) {
|
|
2087
|
+
this.referenceUserIds.push(userId);
|
|
2088
|
+
}
|
|
2089
|
+
this.storeLabelFromResults(userId, this.refSearchResults);
|
|
2090
|
+
this.refSearchQuery = '';
|
|
2091
|
+
this.refSearchResults = [];
|
|
2092
|
+
}
|
|
2093
|
+
storeLabelFromResults(userId, results) {
|
|
2094
|
+
const user = results.find((u) => u.id === userId);
|
|
2095
|
+
if (user) {
|
|
2096
|
+
const parts = [user.department, user.position, user.fullName].filter(Boolean);
|
|
2097
|
+
this.userLabelMap[userId] = parts.length > 0 ? parts.join(' - ') : (user.userName || userId);
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
removeReference(j) {
|
|
2101
|
+
this.referenceUserIds.splice(j, 1);
|
|
2102
|
+
}
|
|
2103
|
+
onUserSearchInput(stepIndex, query) {
|
|
2104
|
+
this.userSearchQuery[stepIndex] = query;
|
|
2105
|
+
if (!query || query.length < 2) {
|
|
2106
|
+
this.userSearchResults[stepIndex] = [];
|
|
2107
|
+
return;
|
|
2108
|
+
}
|
|
2109
|
+
this.searchUsers(query).then(r => this.userSearchResults[stepIndex] = r);
|
|
2110
|
+
}
|
|
2111
|
+
onRefSearchInput(query) {
|
|
2112
|
+
this.refSearchQuery = query;
|
|
2113
|
+
if (!query || query.length < 2) {
|
|
2114
|
+
this.refSearchResults = [];
|
|
2115
|
+
return;
|
|
2116
|
+
}
|
|
2117
|
+
this.searchUsers(query).then(r => this.refSearchResults = r);
|
|
2118
|
+
}
|
|
2119
|
+
searchUsers(query) {
|
|
2120
|
+
const config = this.mesAuth.getConfig();
|
|
2121
|
+
if (!config)
|
|
2122
|
+
return Promise.resolve([]);
|
|
2123
|
+
const url = `${config.apiBaseUrl.replace(/\/$/, '')}/urs/users?search=${encodeURIComponent(query)}&page=1&pageSize=20`;
|
|
2124
|
+
return this.http.get(url, { withCredentials: config.withCredentials ?? true })
|
|
2125
|
+
.toPromise()
|
|
2126
|
+
.then((r) => {
|
|
2127
|
+
const items = r?.data || r?.Data || r?.items || r || [];
|
|
2128
|
+
// Normalize PascalCase to camelCase for template binding
|
|
2129
|
+
return (Array.isArray(items) ? items : []).map((u) => ({
|
|
2130
|
+
id: u.id || u.Id,
|
|
2131
|
+
userName: u.userName || u.UserName,
|
|
2132
|
+
fullName: u.fullName || u.FullName,
|
|
2133
|
+
email: u.email || u.Email,
|
|
2134
|
+
department: u.department || u.Department,
|
|
2135
|
+
position: u.position || u.Position
|
|
2136
|
+
}));
|
|
2137
|
+
})
|
|
2138
|
+
.catch(() => []);
|
|
2139
|
+
}
|
|
2140
|
+
async submit() {
|
|
2141
|
+
this.errorMessage = '';
|
|
2142
|
+
if (!this.approvalSvc) {
|
|
2143
|
+
this.errorMessage = 'Approval service not initialized. Ensure provideMesAuth() is configured.';
|
|
2144
|
+
return;
|
|
2145
|
+
}
|
|
2146
|
+
if (!this.referenceId) {
|
|
2147
|
+
this.errorMessage = 'ReferenceId is required.';
|
|
2148
|
+
return;
|
|
2149
|
+
}
|
|
2150
|
+
if (!this.title) {
|
|
2151
|
+
this.errorMessage = 'Title is required.';
|
|
2152
|
+
return;
|
|
2153
|
+
}
|
|
2154
|
+
if (this.routingMode === 'template' && !this.selectedTemplateId && !this.templateId) {
|
|
2155
|
+
this.errorMessage = 'Please select a template or switch to Custom Steps.';
|
|
2156
|
+
return;
|
|
2157
|
+
}
|
|
2158
|
+
if (this.routingMode === 'adhoc' && this.adHocSteps.length === 0) {
|
|
2159
|
+
this.errorMessage = 'Please add at least one approval step.';
|
|
2160
|
+
return;
|
|
2161
|
+
}
|
|
2162
|
+
this.submitting = true;
|
|
2163
|
+
if (this.approvalSubmiting.observers.length > 0) {
|
|
2164
|
+
this.approvalSubmiting.emit();
|
|
2165
|
+
}
|
|
2166
|
+
try {
|
|
2167
|
+
const contentHtml = await this.captureContent();
|
|
2168
|
+
const thumbnailBase64 = await this.captureThumbnail().catch(() => undefined);
|
|
2169
|
+
const request = {
|
|
2170
|
+
title: this.title,
|
|
2171
|
+
description: this.description,
|
|
2172
|
+
contentHtml,
|
|
2173
|
+
thumbnailBase64,
|
|
2174
|
+
referenceId: this.referenceId,
|
|
2175
|
+
callbackUrl: this.callbackUrl,
|
|
2176
|
+
deadlineHours: this.deadlineHours,
|
|
2177
|
+
referenceUserIds: this.referenceUserIds.length > 0 ? this.referenceUserIds : undefined
|
|
2178
|
+
};
|
|
2179
|
+
if (this.routingMode === 'template') {
|
|
2180
|
+
request.templateId = this.selectedTemplateId ?? this.templateId;
|
|
2181
|
+
}
|
|
2182
|
+
else {
|
|
2183
|
+
request.steps = this.adHocSteps.map((s, i) => ({
|
|
2184
|
+
stepOrder: i + 1,
|
|
2185
|
+
stepName: s.stepName,
|
|
2186
|
+
mode: ApprovalStepMode.Sequential,
|
|
2187
|
+
approverUserIds: s.approverUserIds
|
|
2188
|
+
}));
|
|
2189
|
+
}
|
|
2190
|
+
this.approvalSvc.createApproval(request).pipe(takeUntil(this.destroy$)).subscribe({
|
|
2191
|
+
next: (result) => {
|
|
2192
|
+
this.isSubmitted = true;
|
|
2193
|
+
this.submitting = false;
|
|
2194
|
+
this.approvalSubmitted.emit({ documentId: result.documentId, message: result.message });
|
|
2195
|
+
},
|
|
2196
|
+
error: (err) => {
|
|
2197
|
+
this.submitting = false;
|
|
2198
|
+
this.errorMessage = err?.error?.message || 'Failed to submit approval. Please try again.';
|
|
2199
|
+
}
|
|
2200
|
+
});
|
|
2201
|
+
}
|
|
2202
|
+
catch (err) {
|
|
2203
|
+
this.submitting = false;
|
|
2204
|
+
this.errorMessage = err?.message || 'Failed to capture content.';
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
onCancel() {
|
|
2208
|
+
this.cancelled.emit();
|
|
2209
|
+
}
|
|
2210
|
+
// ====================== Content Capture ======================
|
|
2211
|
+
async captureContent() {
|
|
2212
|
+
const host = this.contentBody.nativeElement;
|
|
2213
|
+
// Step 1: Build canvas -> img map from the live DOM
|
|
2214
|
+
const canvasMap = new Map();
|
|
2215
|
+
host.querySelectorAll('canvas').forEach((canvas) => {
|
|
2216
|
+
try {
|
|
2217
|
+
const img = document.createElement('img');
|
|
2218
|
+
img.src = canvas.toDataURL('image/png');
|
|
2219
|
+
img.style.width = canvas.offsetWidth + 'px';
|
|
2220
|
+
img.style.height = canvas.offsetHeight + 'px';
|
|
2221
|
+
canvasMap.set(canvas, img);
|
|
2222
|
+
}
|
|
2223
|
+
catch { }
|
|
2224
|
+
});
|
|
2225
|
+
// Step 2: Deep clone
|
|
2226
|
+
const clone = host.cloneNode(true);
|
|
2227
|
+
// Step 3: Replace canvas nodes in clone with img snapshots
|
|
2228
|
+
const originalCanvases = Array.from(host.querySelectorAll('canvas'));
|
|
2229
|
+
clone.querySelectorAll('canvas').forEach((cloneCanvas, idx) => {
|
|
2230
|
+
const img = canvasMap.get(originalCanvases[idx]);
|
|
2231
|
+
if (img)
|
|
2232
|
+
cloneCanvas.replaceWith(img.cloneNode(true));
|
|
2233
|
+
});
|
|
2234
|
+
// Step 4: Inline only essential layout styles (skip colors, widths, cursors)
|
|
2235
|
+
const liveElements = Array.from(host.querySelectorAll('*'));
|
|
2236
|
+
const cloneElements = Array.from(clone.querySelectorAll('*'));
|
|
2237
|
+
const styleProps = this.getStyleProperties();
|
|
2238
|
+
liveElements.forEach((liveEl, i) => {
|
|
2239
|
+
const cloneEl = cloneElements[i];
|
|
2240
|
+
if (!cloneEl || typeof cloneEl.setAttribute !== 'function')
|
|
2241
|
+
return;
|
|
2242
|
+
try {
|
|
2243
|
+
const computed = window.getComputedStyle(liveEl);
|
|
2244
|
+
const styles = [];
|
|
2245
|
+
styleProps.forEach(prop => {
|
|
2246
|
+
const val = computed.getPropertyValue(prop);
|
|
2247
|
+
if (val && val !== '' && val !== 'initial' && val !== 'normal' && val !== 'none' && val !== 'auto') {
|
|
2248
|
+
styles.push(`${prop}:${val}`);
|
|
2249
|
+
}
|
|
2250
|
+
});
|
|
2251
|
+
if (styles.length)
|
|
2252
|
+
cloneEl.setAttribute('style', styles.join(';'));
|
|
2253
|
+
}
|
|
2254
|
+
catch { }
|
|
2255
|
+
});
|
|
2256
|
+
// Step 5: Strip scripts, Angular attributes, style/link tags, and form elements
|
|
2257
|
+
clone.querySelectorAll('script, link, style').forEach(el => el.remove());
|
|
2258
|
+
clone.querySelectorAll('*').forEach(el => {
|
|
2259
|
+
// Remove Angular-specific attributes
|
|
2260
|
+
Array.from(el.attributes).forEach(attr => {
|
|
2261
|
+
if (attr.name.startsWith('_ngcontent-') ||
|
|
2262
|
+
attr.name.startsWith('_nghost-') ||
|
|
2263
|
+
attr.name.startsWith('ng-reflect-') ||
|
|
2264
|
+
attr.name.startsWith('ng-version') ||
|
|
2265
|
+
attr.name.startsWith('on') ||
|
|
2266
|
+
attr.name === 'cformcontrol' ||
|
|
2267
|
+
attr.name === 'clabel') {
|
|
2268
|
+
el.removeAttribute(attr.name);
|
|
2269
|
+
}
|
|
2270
|
+
});
|
|
2271
|
+
// Remove class attribute (framework-specific classes add no value)
|
|
2272
|
+
el.removeAttribute('class');
|
|
2273
|
+
});
|
|
2274
|
+
// Step 6: Wrap in a clean, print-friendly HTML document
|
|
2275
|
+
return `<!DOCTYPE html>
|
|
2276
|
+
<html>
|
|
2277
|
+
<head>
|
|
2278
|
+
<meta charset="utf-8">
|
|
2279
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
2280
|
+
<style>
|
|
2281
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
2282
|
+
body {
|
|
2283
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
2284
|
+
margin: 0; padding: 24px;
|
|
2285
|
+
color: #212121; background: #fff;
|
|
2286
|
+
font-size: 14px; line-height: 1.6;
|
|
2287
|
+
max-width: 210mm;
|
|
2288
|
+
}
|
|
2289
|
+
h1, h2, h3, h4, h5, h6 { color: #1565c0; margin: 0 0 12px; }
|
|
2290
|
+
p { margin: 0 0 8px; }
|
|
2291
|
+
img { max-width: 100%; }
|
|
2292
|
+
table { border-collapse: collapse; width: 100%; }
|
|
2293
|
+
td, th { padding: 8px 12px; border: 1px solid #ddd; text-align: left; vertical-align: top; }
|
|
2294
|
+
input, textarea, select {
|
|
2295
|
+
border: 1px solid #ccc; border-radius: 4px; padding: 6px 10px;
|
|
2296
|
+
font-size: 14px; width: 100%; background: #fafafa; color: #333;
|
|
2297
|
+
}
|
|
2298
|
+
label { font-weight: 500; display: block; margin-bottom: 4px; color: #333; }
|
|
2299
|
+
div { max-width: 100%; }
|
|
2300
|
+
</style>
|
|
2301
|
+
</head>
|
|
2302
|
+
<body>
|
|
2303
|
+
${clone.outerHTML}
|
|
2304
|
+
</body>
|
|
2305
|
+
</html>`;
|
|
2306
|
+
}
|
|
2307
|
+
async captureThumbnail() {
|
|
2308
|
+
const html2canvas = window['html2canvas'];
|
|
2309
|
+
if (!html2canvas)
|
|
2310
|
+
return undefined;
|
|
2311
|
+
const canvas = await html2canvas(this.contentBody.nativeElement, {
|
|
2312
|
+
scale: 1, useCORS: true, logging: false, width: 600, height: 400
|
|
2313
|
+
});
|
|
2314
|
+
return canvas.toDataURL('image/png').split(',')[1];
|
|
2315
|
+
}
|
|
2316
|
+
getStyleProperties() {
|
|
2317
|
+
// Only capture essential layout/typography — NOT colors, widths, or cursor styles
|
|
2318
|
+
// Colors and backgrounds are handled by the clean document stylesheet instead
|
|
2319
|
+
return [
|
|
2320
|
+
'font-size', 'font-weight', 'font-style', 'line-height', 'text-align', 'text-decoration',
|
|
2321
|
+
'border', 'border-top', 'border-right', 'border-bottom', 'border-left',
|
|
2322
|
+
'border-width', 'border-style', 'border-radius',
|
|
2323
|
+
'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
|
|
2324
|
+
'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
|
|
2325
|
+
'display', 'flex-direction', 'flex-wrap', 'justify-content', 'align-items', 'gap',
|
|
2326
|
+
'vertical-align', 'table-layout', 'border-collapse', 'border-spacing',
|
|
2327
|
+
'text-transform', 'letter-spacing', 'white-space', 'word-break'
|
|
2328
|
+
];
|
|
2329
|
+
}
|
|
2330
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MaArvContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2331
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: MaArvContainerComponent, isStandalone: true, selector: "ma-arv-container", inputs: { title: "title", description: "description", referenceId: "referenceId", templateId: "templateId", callbackUrl: "callbackUrl", deadlineHours: "deadlineHours" }, outputs: { approvalSubmitted: "approvalSubmitted", approvalSubmiting: "approvalSubmiting", cancelled: "cancelled" }, viewQueries: [{ propertyName: "contentBody", first: true, predicate: ["contentBody"], descendants: true, static: true }], ngImport: i0, template: `
|
|
2332
|
+
<div class="arv-container">
|
|
2333
|
+
<!-- Content Area -->
|
|
2334
|
+
<div class="arv-content-body" #contentBody>
|
|
2335
|
+
<ng-content></ng-content>
|
|
2336
|
+
</div>
|
|
2337
|
+
|
|
2338
|
+
<!-- Approval Footer -->
|
|
2339
|
+
<div class="arv-footer" *ngIf="!isSubmitted">
|
|
2340
|
+
<div class="arv-footer-inner">
|
|
2341
|
+
|
|
2342
|
+
<!-- Routing mode -->
|
|
2343
|
+
<div class="arv-routing">
|
|
2344
|
+
<div class="arv-routing-header">
|
|
2345
|
+
<h4 class="arv-section-title">Approval Routing</h4>
|
|
2346
|
+
<div class="arv-routing-mode">
|
|
2347
|
+
<label class="arv-radio">
|
|
2348
|
+
<input type="radio" name="routingMode" value="template" [checked]="routingMode === 'template'" (change)="routingMode = 'template'"> Use Template
|
|
2349
|
+
</label>
|
|
2350
|
+
<label class="arv-radio">
|
|
2351
|
+
<input type="radio" name="routingMode" value="adhoc" [checked]="routingMode === 'adhoc'" (change)="routingMode = 'adhoc'"> Custom Steps
|
|
2352
|
+
</label>
|
|
2353
|
+
</div>
|
|
2354
|
+
</div>
|
|
2355
|
+
|
|
2356
|
+
<!-- Template selector -->
|
|
2357
|
+
<div *ngIf="routingMode === 'template'" class="arv-template-select">
|
|
2358
|
+
<label class="arv-label">Select Template</label>
|
|
2359
|
+
<select class="arv-select" (change)="onTemplateChange($event)">
|
|
2360
|
+
<option value="">-- Select a template --</option>
|
|
2361
|
+
<option *ngFor="let t of templates" [value]="t.id">{{ t.name }}</option>
|
|
2362
|
+
</select>
|
|
2363
|
+
</div>
|
|
2364
|
+
|
|
2365
|
+
<!-- Ad-hoc steps -->
|
|
2366
|
+
<div *ngIf="routingMode === 'adhoc'" class="arv-steps">
|
|
2367
|
+
<div class="arv-step" *ngFor="let step of adHocSteps; let i = index">
|
|
2368
|
+
<div class="arv-step-header">
|
|
2369
|
+
<span class="arv-step-num">Step {{ i + 1 }}</span>
|
|
2370
|
+
<input class="arv-input" [value]="step.stepName" (input)="step.stepName = $any($event.target).value" placeholder="Step name" />
|
|
2371
|
+
<button class="arv-btn-icon arv-btn-danger" (click)="removeStep(i)" title="Remove step">
|
|
2372
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
2373
|
+
</button>
|
|
2374
|
+
</div>
|
|
2375
|
+
<div class="arv-approvers">
|
|
2376
|
+
<div class="arv-approver-tags">
|
|
2377
|
+
<span class="arv-tag" *ngFor="let uid of step.approverUserIds; let j = index">
|
|
2378
|
+
{{ userLabelMap[uid] || uid }}
|
|
2379
|
+
<button class="arv-tag-remove" (click)="removeApprover(i, j)">x</button>
|
|
2380
|
+
</span>
|
|
2381
|
+
</div>
|
|
2382
|
+
<div class="arv-user-search">
|
|
2383
|
+
<input class="arv-input arv-input-sm" [value]="userSearchQuery[i] || ''"
|
|
2384
|
+
(input)="onUserSearchInput(i, $any($event.target).value)"
|
|
2385
|
+
placeholder="Search approver..." />
|
|
2386
|
+
<div class="arv-search-results" *ngIf="userSearchResults[i]?.length">
|
|
2387
|
+
<div class="arv-search-item"
|
|
2388
|
+
*ngFor="let u of userSearchResults[i]"
|
|
2389
|
+
(click)="addApprover(i, u.id)">
|
|
2390
|
+
{{ u.fullName || u.userName }}
|
|
2391
|
+
</div>
|
|
2392
|
+
</div>
|
|
2393
|
+
</div>
|
|
2394
|
+
</div>
|
|
2395
|
+
</div>
|
|
2396
|
+
<button class="arv-btn arv-btn-outline" (click)="addStep()">+ Add Step</button>
|
|
2397
|
+
</div>
|
|
2398
|
+
</div>
|
|
2399
|
+
|
|
2400
|
+
<!-- Reference / CC users -->
|
|
2401
|
+
<div class="arv-references">
|
|
2402
|
+
<h4 class="arv-section-title">CC / Reference</h4>
|
|
2403
|
+
<p class="arv-hint">These users will be notified when the approval completes, but won't be asked to approve.</p>
|
|
2404
|
+
<div class="arv-approver-tags">
|
|
2405
|
+
<span class="arv-tag" *ngFor="let uid of referenceUserIds; let j = index">
|
|
2406
|
+
{{ userLabelMap[uid] || uid }}
|
|
2407
|
+
<button class="arv-tag-remove" (click)="removeReference(j)">x</button>
|
|
2408
|
+
</span>
|
|
2409
|
+
</div>
|
|
2410
|
+
<div class="arv-user-search">
|
|
2411
|
+
<input class="arv-input arv-input-sm" [value]="refSearchQuery"
|
|
2412
|
+
(input)="onRefSearchInput($any($event.target).value)"
|
|
2413
|
+
placeholder="Search CC user..." />
|
|
2414
|
+
<div class="arv-search-results" *ngIf="refSearchResults?.length">
|
|
2415
|
+
<div class="arv-search-item"
|
|
2416
|
+
*ngFor="let u of refSearchResults"
|
|
2417
|
+
(click)="addReference(u.id)">
|
|
2418
|
+
{{ u.fullName || u.userName }}
|
|
2419
|
+
</div>
|
|
2420
|
+
</div>
|
|
2421
|
+
</div>
|
|
2422
|
+
</div>
|
|
2423
|
+
|
|
2424
|
+
<!-- Actions -->
|
|
2425
|
+
<div class="arv-actions">
|
|
2426
|
+
<button class="arv-btn arv-btn-secondary" (click)="onCancel()">Cancel</button>
|
|
2427
|
+
<button class="arv-btn arv-btn-primary" [disabled]="submitting" (click)="submit()">
|
|
2428
|
+
<span *ngIf="submitting" class="arv-spinner"></span>
|
|
2429
|
+
{{ submitting ? 'Submitting...' : 'Submit for Approval' }}
|
|
2430
|
+
</button>
|
|
2431
|
+
</div>
|
|
2432
|
+
|
|
2433
|
+
<div class="arv-error" *ngIf="errorMessage">{{ errorMessage }}</div>
|
|
2434
|
+
</div>
|
|
2435
|
+
</div>
|
|
2436
|
+
|
|
2437
|
+
<!-- Success state -->
|
|
2438
|
+
<div class="arv-success" *ngIf="isSubmitted">
|
|
2439
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#66bb6a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
2440
|
+
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
|
|
2441
|
+
<polyline points="22 4 12 14.01 9 11.01"/>
|
|
2442
|
+
</svg>
|
|
2443
|
+
<p>Submitted for approval successfully.</p>
|
|
2444
|
+
</div>
|
|
2445
|
+
</div>
|
|
2446
|
+
`, isInline: true, styles: [":host{display:block;--arv-primary: #90caf9;--arv-primary-hover: #64b5f6;--arv-success: #66bb6a;--arv-danger: #ef5350;--arv-text: #e0e0e0;--arv-text-muted: #9e9e9e;--arv-bg: #1e1e2e;--arv-bg2: #27273a;--arv-border: #383850;--arv-radius: 8px}:host(.theme-light){--arv-primary: #1565c0;--arv-primary-hover: #0d47a1;--arv-success: #2e7d32;--arv-danger: #c62828;--arv-text: #212121;--arv-text-muted: #616161;--arv-bg: #ffffff;--arv-bg2: #f5f5f5;--arv-border: #e0e0e0}.arv-container{display:flex;flex-direction:column;height:100%}.arv-content-body{flex:1;overflow:auto}.arv-footer{border-top:1px solid var(--arv-border);background:var(--arv-bg2)}.arv-footer-inner{padding:16px;display:flex;flex-direction:column;gap:14px}.arv-section-title{margin:0 0 8px;font-size:13px;font-weight:700;color:var(--arv-primary);text-transform:uppercase;letter-spacing:.5px}.arv-routing-header{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px}.arv-routing-mode{display:flex;gap:12px}.arv-radio{display:flex;align-items:center;gap:6px;font-size:13px;color:var(--arv-text);cursor:pointer}.arv-label{font-size:12px;color:var(--arv-text-muted);display:block;margin-bottom:4px}.arv-select{width:100%;padding:8px 10px;border:1px solid var(--arv-border);border-radius:var(--arv-radius);background:var(--arv-bg);color:var(--arv-text);font-size:13px}.arv-input{padding:7px 10px;border:1px solid var(--arv-border);border-radius:var(--arv-radius);background:var(--arv-bg);color:var(--arv-text);font-size:13px}.arv-input-sm{width:100%;margin-top:4px}.arv-steps{display:flex;flex-direction:column;gap:10px}.arv-step{background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:var(--arv-radius);padding:10px}.arv-step-header{display:flex;align-items:center;gap:8px;margin-bottom:8px}.arv-step-num{font-size:12px;font-weight:700;color:var(--arv-primary);min-width:44px}.arv-approver-tags{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:6px}.arv-tag{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;background:#90caf91f;border:1px solid rgba(144,202,249,.3);border-radius:12px;font-size:12px;color:var(--arv-primary)}.arv-tag-remove{background:none;border:none;cursor:pointer;color:var(--arv-text-muted);font-size:11px;padding:0 2px;line-height:1}.arv-tag-remove:hover{color:var(--arv-danger)}.arv-user-search{position:relative}.arv-search-results{position:absolute;left:0;right:0;z-index:100;background:var(--arv-bg2);border:1px solid var(--arv-border);border-radius:var(--arv-radius);max-height:150px;overflow-y:auto}.arv-search-item{padding:8px 12px;cursor:pointer;font-size:13px;color:var(--arv-text)}.arv-search-item:hover{background:var(--arv-bg)}.arv-hint{font-size:12px;color:var(--arv-text-muted);margin:0 0 8px}.arv-btn-icon{background:none;border:none;cursor:pointer;padding:2px;border-radius:4px;display:inline-flex;align-items:center;justify-content:center}.arv-btn-danger{color:var(--arv-danger)}.arv-btn-danger:hover{background:#ef53501a}.arv-actions{display:flex;justify-content:flex-end;gap:10px}.arv-btn{padding:9px 18px;border-radius:var(--arv-radius);font-size:13px;font-weight:600;cursor:pointer;border:none;transition:background .15s}.arv-btn-primary{background:var(--arv-primary);color:#fff}.arv-btn-primary:hover:not(:disabled){background:var(--arv-primary-hover)}.arv-btn-primary:disabled{opacity:.6;cursor:not-allowed}.arv-btn-secondary{background:transparent;border:1px solid var(--arv-border);color:var(--arv-text-muted)}.arv-btn-secondary:hover{color:var(--arv-text);border-color:var(--arv-text-muted)}.arv-btn-outline{background:transparent;border:1px dashed var(--arv-border);color:var(--arv-text-muted);font-size:12px;padding:6px 12px}.arv-btn-outline:hover{border-color:var(--arv-primary);color:var(--arv-primary)}.arv-spinner{display:inline-block;width:14px;height:14px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:arv-spin .6s linear infinite;margin-right:6px}@keyframes arv-spin{to{transform:rotate(360deg)}}.arv-error{padding:8px 12px;background:#ef53501a;border:1px solid rgba(239,83,80,.3);border-radius:var(--arv-radius);font-size:13px;color:var(--arv-danger)}.arv-success{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:32px;gap:12px;text-align:center}.arv-success p{color:var(--arv-success);font-size:15px;font-weight:600;margin:0}.arv-references{border-top:1px solid var(--arv-border);padding-top:12px}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] });
|
|
2447
|
+
}
|
|
2448
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MaArvContainerComponent, decorators: [{
|
|
2449
|
+
type: Component,
|
|
2450
|
+
args: [{ selector: 'ma-arv-container', standalone: true, imports: [NgIf, NgFor], template: `
|
|
2451
|
+
<div class="arv-container">
|
|
2452
|
+
<!-- Content Area -->
|
|
2453
|
+
<div class="arv-content-body" #contentBody>
|
|
2454
|
+
<ng-content></ng-content>
|
|
2455
|
+
</div>
|
|
2456
|
+
|
|
2457
|
+
<!-- Approval Footer -->
|
|
2458
|
+
<div class="arv-footer" *ngIf="!isSubmitted">
|
|
2459
|
+
<div class="arv-footer-inner">
|
|
2460
|
+
|
|
2461
|
+
<!-- Routing mode -->
|
|
2462
|
+
<div class="arv-routing">
|
|
2463
|
+
<div class="arv-routing-header">
|
|
2464
|
+
<h4 class="arv-section-title">Approval Routing</h4>
|
|
2465
|
+
<div class="arv-routing-mode">
|
|
2466
|
+
<label class="arv-radio">
|
|
2467
|
+
<input type="radio" name="routingMode" value="template" [checked]="routingMode === 'template'" (change)="routingMode = 'template'"> Use Template
|
|
2468
|
+
</label>
|
|
2469
|
+
<label class="arv-radio">
|
|
2470
|
+
<input type="radio" name="routingMode" value="adhoc" [checked]="routingMode === 'adhoc'" (change)="routingMode = 'adhoc'"> Custom Steps
|
|
2471
|
+
</label>
|
|
2472
|
+
</div>
|
|
2473
|
+
</div>
|
|
2474
|
+
|
|
2475
|
+
<!-- Template selector -->
|
|
2476
|
+
<div *ngIf="routingMode === 'template'" class="arv-template-select">
|
|
2477
|
+
<label class="arv-label">Select Template</label>
|
|
2478
|
+
<select class="arv-select" (change)="onTemplateChange($event)">
|
|
2479
|
+
<option value="">-- Select a template --</option>
|
|
2480
|
+
<option *ngFor="let t of templates" [value]="t.id">{{ t.name }}</option>
|
|
2481
|
+
</select>
|
|
2482
|
+
</div>
|
|
2483
|
+
|
|
2484
|
+
<!-- Ad-hoc steps -->
|
|
2485
|
+
<div *ngIf="routingMode === 'adhoc'" class="arv-steps">
|
|
2486
|
+
<div class="arv-step" *ngFor="let step of adHocSteps; let i = index">
|
|
2487
|
+
<div class="arv-step-header">
|
|
2488
|
+
<span class="arv-step-num">Step {{ i + 1 }}</span>
|
|
2489
|
+
<input class="arv-input" [value]="step.stepName" (input)="step.stepName = $any($event.target).value" placeholder="Step name" />
|
|
2490
|
+
<button class="arv-btn-icon arv-btn-danger" (click)="removeStep(i)" title="Remove step">
|
|
2491
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
2492
|
+
</button>
|
|
2493
|
+
</div>
|
|
2494
|
+
<div class="arv-approvers">
|
|
2495
|
+
<div class="arv-approver-tags">
|
|
2496
|
+
<span class="arv-tag" *ngFor="let uid of step.approverUserIds; let j = index">
|
|
2497
|
+
{{ userLabelMap[uid] || uid }}
|
|
2498
|
+
<button class="arv-tag-remove" (click)="removeApprover(i, j)">x</button>
|
|
2499
|
+
</span>
|
|
2500
|
+
</div>
|
|
2501
|
+
<div class="arv-user-search">
|
|
2502
|
+
<input class="arv-input arv-input-sm" [value]="userSearchQuery[i] || ''"
|
|
2503
|
+
(input)="onUserSearchInput(i, $any($event.target).value)"
|
|
2504
|
+
placeholder="Search approver..." />
|
|
2505
|
+
<div class="arv-search-results" *ngIf="userSearchResults[i]?.length">
|
|
2506
|
+
<div class="arv-search-item"
|
|
2507
|
+
*ngFor="let u of userSearchResults[i]"
|
|
2508
|
+
(click)="addApprover(i, u.id)">
|
|
2509
|
+
{{ u.fullName || u.userName }}
|
|
2510
|
+
</div>
|
|
2511
|
+
</div>
|
|
2512
|
+
</div>
|
|
2513
|
+
</div>
|
|
2514
|
+
</div>
|
|
2515
|
+
<button class="arv-btn arv-btn-outline" (click)="addStep()">+ Add Step</button>
|
|
2516
|
+
</div>
|
|
2517
|
+
</div>
|
|
2518
|
+
|
|
2519
|
+
<!-- Reference / CC users -->
|
|
2520
|
+
<div class="arv-references">
|
|
2521
|
+
<h4 class="arv-section-title">CC / Reference</h4>
|
|
2522
|
+
<p class="arv-hint">These users will be notified when the approval completes, but won't be asked to approve.</p>
|
|
2523
|
+
<div class="arv-approver-tags">
|
|
2524
|
+
<span class="arv-tag" *ngFor="let uid of referenceUserIds; let j = index">
|
|
2525
|
+
{{ userLabelMap[uid] || uid }}
|
|
2526
|
+
<button class="arv-tag-remove" (click)="removeReference(j)">x</button>
|
|
2527
|
+
</span>
|
|
2528
|
+
</div>
|
|
2529
|
+
<div class="arv-user-search">
|
|
2530
|
+
<input class="arv-input arv-input-sm" [value]="refSearchQuery"
|
|
2531
|
+
(input)="onRefSearchInput($any($event.target).value)"
|
|
2532
|
+
placeholder="Search CC user..." />
|
|
2533
|
+
<div class="arv-search-results" *ngIf="refSearchResults?.length">
|
|
2534
|
+
<div class="arv-search-item"
|
|
2535
|
+
*ngFor="let u of refSearchResults"
|
|
2536
|
+
(click)="addReference(u.id)">
|
|
2537
|
+
{{ u.fullName || u.userName }}
|
|
2538
|
+
</div>
|
|
2539
|
+
</div>
|
|
2540
|
+
</div>
|
|
2541
|
+
</div>
|
|
2542
|
+
|
|
2543
|
+
<!-- Actions -->
|
|
2544
|
+
<div class="arv-actions">
|
|
2545
|
+
<button class="arv-btn arv-btn-secondary" (click)="onCancel()">Cancel</button>
|
|
2546
|
+
<button class="arv-btn arv-btn-primary" [disabled]="submitting" (click)="submit()">
|
|
2547
|
+
<span *ngIf="submitting" class="arv-spinner"></span>
|
|
2548
|
+
{{ submitting ? 'Submitting...' : 'Submit for Approval' }}
|
|
2549
|
+
</button>
|
|
2550
|
+
</div>
|
|
2551
|
+
|
|
2552
|
+
<div class="arv-error" *ngIf="errorMessage">{{ errorMessage }}</div>
|
|
2553
|
+
</div>
|
|
2554
|
+
</div>
|
|
2555
|
+
|
|
2556
|
+
<!-- Success state -->
|
|
2557
|
+
<div class="arv-success" *ngIf="isSubmitted">
|
|
2558
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="#66bb6a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
2559
|
+
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
|
|
2560
|
+
<polyline points="22 4 12 14.01 9 11.01"/>
|
|
2561
|
+
</svg>
|
|
2562
|
+
<p>Submitted for approval successfully.</p>
|
|
2563
|
+
</div>
|
|
2564
|
+
</div>
|
|
2565
|
+
`, styles: [":host{display:block;--arv-primary: #90caf9;--arv-primary-hover: #64b5f6;--arv-success: #66bb6a;--arv-danger: #ef5350;--arv-text: #e0e0e0;--arv-text-muted: #9e9e9e;--arv-bg: #1e1e2e;--arv-bg2: #27273a;--arv-border: #383850;--arv-radius: 8px}:host(.theme-light){--arv-primary: #1565c0;--arv-primary-hover: #0d47a1;--arv-success: #2e7d32;--arv-danger: #c62828;--arv-text: #212121;--arv-text-muted: #616161;--arv-bg: #ffffff;--arv-bg2: #f5f5f5;--arv-border: #e0e0e0}.arv-container{display:flex;flex-direction:column;height:100%}.arv-content-body{flex:1;overflow:auto}.arv-footer{border-top:1px solid var(--arv-border);background:var(--arv-bg2)}.arv-footer-inner{padding:16px;display:flex;flex-direction:column;gap:14px}.arv-section-title{margin:0 0 8px;font-size:13px;font-weight:700;color:var(--arv-primary);text-transform:uppercase;letter-spacing:.5px}.arv-routing-header{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px}.arv-routing-mode{display:flex;gap:12px}.arv-radio{display:flex;align-items:center;gap:6px;font-size:13px;color:var(--arv-text);cursor:pointer}.arv-label{font-size:12px;color:var(--arv-text-muted);display:block;margin-bottom:4px}.arv-select{width:100%;padding:8px 10px;border:1px solid var(--arv-border);border-radius:var(--arv-radius);background:var(--arv-bg);color:var(--arv-text);font-size:13px}.arv-input{padding:7px 10px;border:1px solid var(--arv-border);border-radius:var(--arv-radius);background:var(--arv-bg);color:var(--arv-text);font-size:13px}.arv-input-sm{width:100%;margin-top:4px}.arv-steps{display:flex;flex-direction:column;gap:10px}.arv-step{background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:var(--arv-radius);padding:10px}.arv-step-header{display:flex;align-items:center;gap:8px;margin-bottom:8px}.arv-step-num{font-size:12px;font-weight:700;color:var(--arv-primary);min-width:44px}.arv-approver-tags{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:6px}.arv-tag{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;background:#90caf91f;border:1px solid rgba(144,202,249,.3);border-radius:12px;font-size:12px;color:var(--arv-primary)}.arv-tag-remove{background:none;border:none;cursor:pointer;color:var(--arv-text-muted);font-size:11px;padding:0 2px;line-height:1}.arv-tag-remove:hover{color:var(--arv-danger)}.arv-user-search{position:relative}.arv-search-results{position:absolute;left:0;right:0;z-index:100;background:var(--arv-bg2);border:1px solid var(--arv-border);border-radius:var(--arv-radius);max-height:150px;overflow-y:auto}.arv-search-item{padding:8px 12px;cursor:pointer;font-size:13px;color:var(--arv-text)}.arv-search-item:hover{background:var(--arv-bg)}.arv-hint{font-size:12px;color:var(--arv-text-muted);margin:0 0 8px}.arv-btn-icon{background:none;border:none;cursor:pointer;padding:2px;border-radius:4px;display:inline-flex;align-items:center;justify-content:center}.arv-btn-danger{color:var(--arv-danger)}.arv-btn-danger:hover{background:#ef53501a}.arv-actions{display:flex;justify-content:flex-end;gap:10px}.arv-btn{padding:9px 18px;border-radius:var(--arv-radius);font-size:13px;font-weight:600;cursor:pointer;border:none;transition:background .15s}.arv-btn-primary{background:var(--arv-primary);color:#fff}.arv-btn-primary:hover:not(:disabled){background:var(--arv-primary-hover)}.arv-btn-primary:disabled{opacity:.6;cursor:not-allowed}.arv-btn-secondary{background:transparent;border:1px solid var(--arv-border);color:var(--arv-text-muted)}.arv-btn-secondary:hover{color:var(--arv-text);border-color:var(--arv-text-muted)}.arv-btn-outline{background:transparent;border:1px dashed var(--arv-border);color:var(--arv-text-muted);font-size:12px;padding:6px 12px}.arv-btn-outline:hover{border-color:var(--arv-primary);color:var(--arv-primary)}.arv-spinner{display:inline-block;width:14px;height:14px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:arv-spin .6s linear infinite;margin-right:6px}@keyframes arv-spin{to{transform:rotate(360deg)}}.arv-error{padding:8px 12px;background:#ef53501a;border:1px solid rgba(239,83,80,.3);border-radius:var(--arv-radius);font-size:13px;color:var(--arv-danger)}.arv-success{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:32px;gap:12px;text-align:center}.arv-success p{color:var(--arv-success);font-size:15px;font-weight:600;margin:0}.arv-references{border-top:1px solid var(--arv-border);padding-top:12px}\n"] }]
|
|
2566
|
+
}], propDecorators: { title: [{
|
|
2567
|
+
type: Input
|
|
2568
|
+
}], description: [{
|
|
2569
|
+
type: Input
|
|
2570
|
+
}], referenceId: [{
|
|
2571
|
+
type: Input
|
|
2572
|
+
}], templateId: [{
|
|
2573
|
+
type: Input
|
|
2574
|
+
}], callbackUrl: [{
|
|
2575
|
+
type: Input
|
|
2576
|
+
}], deadlineHours: [{
|
|
2577
|
+
type: Input
|
|
2578
|
+
}], approvalSubmitted: [{
|
|
2579
|
+
type: Output
|
|
2580
|
+
}], approvalSubmiting: [{
|
|
2581
|
+
type: Output
|
|
2582
|
+
}], cancelled: [{
|
|
2583
|
+
type: Output
|
|
2584
|
+
}], contentBody: [{
|
|
2585
|
+
type: ViewChild,
|
|
2586
|
+
args: ['contentBody', { static: true }]
|
|
2587
|
+
}] } });
|
|
2588
|
+
|
|
1471
2589
|
/**
|
|
1472
2590
|
* Generated bundle index. Do not edit.
|
|
1473
2591
|
*/
|
|
1474
2592
|
|
|
1475
|
-
export { MES_AUTH_CONFIG, MaUserComponent, MesAuthModule, MesAuthService, NotificationBadgeComponent, NotificationPanelComponent, NotificationType, ThemeService, ToastContainerComponent, ToastService, UserProfileComponent, mesAuthInterceptor, provideMesAuth };
|
|
2593
|
+
export { ApprovalActionType, ApprovalDocumentStatus, ApprovalStepMode, ApprovalStepStatus, MES_AUTH_CONFIG, MaApprovalPanelComponent, MaApprovalService, MaArvContainerComponent, MaUserComponent, MesAuthModule, MesAuthService, NotificationBadgeComponent, NotificationPanelComponent, NotificationType, ThemeService, ToastContainerComponent, ToastService, UserProfileComponent, mesAuthInterceptor, provideMesAuth };
|
|
1476
2594
|
//# sourceMappingURL=mesauth-angular.mjs.map
|