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.
@@ -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 &rarr;</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 &rarr;</span>
1734
+ </div>
1735
+ </div>
1736
+ <div class="show-more" *ngIf="approvedItems.length >= 10" (click)="showMore('approved')">
1737
+ Show more &rarr;
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 &rarr;</span>
1755
+ </div>
1756
+ </div>
1757
+ <div class="show-more" *ngIf="rejectedItems.length >= 10" (click)="showMore('rejected')">
1758
+ Show more &rarr;
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 &rarr;</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 &rarr;</span>
1842
+ </div>
1843
+ </div>
1844
+ <div class="show-more" *ngIf="approvedItems.length >= 10" (click)="showMore('approved')">
1845
+ Show more &rarr;
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 &rarr;</span>
1863
+ </div>
1864
+ </div>
1865
+ <div class="show-more" *ngIf="rejectedItems.length >= 10" (click)="showMore('rejected')">
1866
+ Show more &rarr;
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 (notificationClick)="notificationPanel.open()"></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
- `, 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"] }, { kind: "component", type: NotificationPanelComponent, selector: "ma-notification-panel", outputs: ["notificationRead"] }] });
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 (notificationClick)="notificationPanel.open()"></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