mesauth-angular 1.3.5 → 1.5.5

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;
@@ -123,101 +126,6 @@ class MesAuthService {
123
126
  throw new Error('MesAuth not initialized');
124
127
  return this.http.get(`${this.apiBase}/fe-routes/me`, { withCredentials: this.config?.withCredentials ?? true });
125
128
  }
126
- /**
127
- * Get master routes for a specific application
128
- * @param appId - The application ID
129
- */
130
- getRouteMasters(appId) {
131
- if (!this.apiBase)
132
- throw new Error('MesAuth not initialized');
133
- return this.http.get(`${this.apiBase}/fe-routes/masters/${appId}`, { withCredentials: this.config?.withCredentials ?? true });
134
- }
135
- /**
136
- * Register/sync frontend routes for an application
137
- * This is typically called on app startup to sync routes from the frontend app
138
- * @param appId - The application ID (passed via X-App-Id header)
139
- * @param routes - Array of route definitions
140
- */
141
- registerFrontEndRoutes(appId, routes) {
142
- if (!this.apiBase)
143
- throw new Error('MesAuth not initialized');
144
- const headers = { 'X-App-Id': appId };
145
- return this.http.post(`${this.apiBase}/fe-routes/register`, routes, {
146
- headers,
147
- withCredentials: this.config?.withCredentials ?? true
148
- });
149
- }
150
- /**
151
- * Create a new route master
152
- * @param appId - The application ID
153
- * @param route - Route details
154
- */
155
- createRouteMaster(appId, route) {
156
- if (!this.apiBase)
157
- throw new Error('MesAuth not initialized');
158
- return this.http.post(`${this.apiBase}/fe-routes/masters`, {
159
- appId,
160
- ...route
161
- }, { withCredentials: this.config?.withCredentials ?? true });
162
- }
163
- /**
164
- * Update an existing route master
165
- * @param routeId - The route master ID
166
- * @param route - Updated route details
167
- */
168
- updateRouteMaster(routeId, route) {
169
- if (!this.apiBase)
170
- throw new Error('MesAuth not initialized');
171
- return this.http.put(`${this.apiBase}/fe-routes/masters/${routeId}`, route, {
172
- withCredentials: this.config?.withCredentials ?? true
173
- });
174
- }
175
- /**
176
- * Delete a route master
177
- * @param routeId - The route master ID
178
- */
179
- deleteRouteMaster(routeId) {
180
- if (!this.apiBase)
181
- throw new Error('MesAuth not initialized');
182
- return this.http.delete(`${this.apiBase}/fe-routes/masters/${routeId}`, {
183
- withCredentials: this.config?.withCredentials ?? true
184
- });
185
- }
186
- /**
187
- * Assign a route to a role
188
- * @param routeMasterId - The route master ID
189
- * @param roleId - The role ID (GUID)
190
- */
191
- assignRouteToRole(routeMasterId, roleId) {
192
- if (!this.apiBase)
193
- throw new Error('MesAuth not initialized');
194
- return this.http.post(`${this.apiBase}/fe-routes/mappings`, {
195
- routeMasterId,
196
- roleId
197
- }, { withCredentials: this.config?.withCredentials ?? true });
198
- }
199
- /**
200
- * Remove a route assignment from a role
201
- * @param mappingId - The mapping ID
202
- */
203
- removeRouteFromRole(mappingId) {
204
- if (!this.apiBase)
205
- throw new Error('MesAuth not initialized');
206
- return this.http.delete(`${this.apiBase}/fe-routes/mappings/${mappingId}`, {
207
- withCredentials: this.config?.withCredentials ?? true
208
- });
209
- }
210
- /**
211
- * Get route-to-role mappings for a specific role
212
- * @param roleId - The role ID (GUID)
213
- */
214
- getRouteMappingsByRole(roleId) {
215
- if (!this.apiBase)
216
- throw new Error('MesAuth not initialized');
217
- return this.http.get(`${this.apiBase}/fe-routes/mappings?roleId=${roleId}`, {
218
- withCredentials: this.config?.withCredentials ?? true
219
- });
220
- }
221
129
  startConnection(config) {
222
130
  if (this.hubConnection)
223
131
  return;
@@ -235,6 +143,22 @@ class MesAuthService {
235
143
  this._notifications.next(n);
236
144
  }
237
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
+ });
238
162
  this.hubConnection.start().then(() => { }).catch((err) => { });
239
163
  this.hubConnection.onclose(() => { });
240
164
  this.hubConnection.onreconnecting(() => { });
@@ -409,23 +333,27 @@ class UserProfileComponent {
409
333
  router;
410
334
  themeService;
411
335
  cdr;
336
+ http;
412
337
  notificationClick = new EventEmitter();
338
+ approvalClick = new EventEmitter();
413
339
  get themeClass() {
414
340
  return `theme-${this.currentTheme}`;
415
341
  }
416
342
  currentUser = signal(null, ...(ngDevMode ? [{ debugName: "currentUser" }] : []));
417
343
  currentTheme = 'light';
418
344
  unreadCount = 0;
345
+ pendingApprovalCount = 0;
419
346
  dropdownOpen = false;
420
347
  hasUser = false;
421
348
  destroy$ = new Subject();
422
349
  // Signal to force avatar refresh
423
350
  avatarRefresh = signal(Date.now(), ...(ngDevMode ? [{ debugName: "avatarRefresh" }] : []));
424
- constructor(authService, router, themeService, cdr) {
351
+ constructor(authService, router, themeService, cdr, http) {
425
352
  this.authService = authService;
426
353
  this.router = router;
427
354
  this.themeService = themeService;
428
355
  this.cdr = cdr;
356
+ this.http = http;
429
357
  }
430
358
  ngOnInit() {
431
359
  this.authService.currentUser$
@@ -437,9 +365,11 @@ class UserProfileComponent {
437
365
  this.avatarRefresh.set(Date.now());
438
366
  if (!this.hasUser) {
439
367
  this.unreadCount = 0;
368
+ this.pendingApprovalCount = 0;
440
369
  }
441
370
  else {
442
371
  this.loadUnreadCount();
372
+ this.loadPendingApprovalCount();
443
373
  }
444
374
  this.cdr.markForCheck();
445
375
  });
@@ -448,6 +378,13 @@ class UserProfileComponent {
448
378
  .subscribe(theme => {
449
379
  this.currentTheme = theme;
450
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
+ });
451
388
  // Listen for new real-time notifications (SignalR only)
452
389
  this.authService.notifications$
453
390
  .pipe(takeUntil(this.destroy$))
@@ -473,6 +410,23 @@ class UserProfileComponent {
473
410
  error: (err) => { }
474
411
  });
475
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
+ }
476
430
  getAvatarUrl(user) {
477
431
  // Use the refresh signal to force update
478
432
  const refresh = this.avatarRefresh();
@@ -544,8 +498,8 @@ class UserProfileComponent {
544
498
  onNotificationClick() {
545
499
  this.notificationClick.emit();
546
500
  }
547
- 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 });
548
- 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: `
549
503
  <div class="user-profile-container">
550
504
  <!-- Not logged in -->
551
505
  <ng-container *ngIf="!currentUser()">
@@ -564,6 +518,15 @@ class UserProfileComponent {
564
518
  <span class="badge" *ngIf="unreadCount > 0">{{ unreadCount > 99 ? '99+' : unreadCount }}</span>
565
519
  </button>
566
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
+
567
530
  <!-- User Avatar + Dropdown -->
568
531
  <div class="user-menu-wrapper">
569
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">
@@ -647,6 +610,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
647
610
  <span class="badge" *ngIf="unreadCount > 0">{{ unreadCount > 99 ? '99+' : unreadCount }}</span>
648
611
  </button>
649
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
+
650
622
  <!-- User Avatar + Dropdown -->
651
623
  <div class="user-menu-wrapper">
652
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">
@@ -708,7 +680,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
708
680
  </ng-container>
709
681
  </div>
710
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"] }]
711
- }], 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: [{
712
686
  type: Output
713
687
  }], themeClass: [{
714
688
  type: HostBinding,
@@ -901,6 +875,7 @@ class NotificationPanelComponent {
901
875
  }
902
876
  selectedNotification = null;
903
877
  selectedNotificationHtml = null;
878
+ selectedNotificationUrl = null;
904
879
  selectedNotificationDate = '';
905
880
  // Returns plain text message for list display
906
881
  getNotificationMessage(notification) {
@@ -981,6 +956,9 @@ class NotificationPanelComponent {
981
956
  // Cache computed values to avoid re-rendering on every change detection cycle
982
957
  const html = notification.messageHtml || notification.message || '';
983
958
  this.selectedNotificationHtml = this.sanitizer.bypassSecurityTrustHtml(html);
959
+ this.selectedNotificationUrl = notification.url?.trim()
960
+ ? this.sanitizer.bypassSecurityTrustResourceUrl(notification.url.trim())
961
+ : null;
984
962
  this.selectedNotificationDate = this.formatDate(notification.createdAt);
985
963
  // Mark as read when opening details (if not already read)
986
964
  if (!notification.isRead) {
@@ -997,6 +975,7 @@ class NotificationPanelComponent {
997
975
  closeDetails() {
998
976
  this.selectedNotification = null;
999
977
  this.selectedNotificationHtml = null;
978
+ this.selectedNotificationUrl = null;
1000
979
  this.selectedNotificationDate = '';
1001
980
  }
1002
981
  markAsRead(notificationId, event) {
@@ -1272,13 +1251,16 @@ class NotificationPanelComponent {
1272
1251
  <span class="app-name">{{ selectedNotification.sourceAppName }}</span>
1273
1252
  <span class="time">{{ selectedNotificationDate }}</span>
1274
1253
  </div>
1275
- <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" class="notification-iframe"></iframe>
1257
+ </div>
1276
1258
  <div class="modal-footer">
1277
1259
  <button class="action-btn" (click)="closeDetails()">Close</button>
1278
1260
  </div>
1279
1261
  </div>
1280
1262
  </div>
1281
- `, 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"] }] });
1282
1264
  }
1283
1265
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: NotificationPanelComponent, decorators: [{
1284
1266
  type: Component,
@@ -1431,13 +1413,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
1431
1413
  <span class="app-name">{{ selectedNotification.sourceAppName }}</span>
1432
1414
  <span class="time">{{ selectedNotificationDate }}</span>
1433
1415
  </div>
1434
- <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" class="notification-iframe"></iframe>
1419
+ </div>
1435
1420
  <div class="modal-footer">
1436
1421
  <button class="action-btn" (click)="closeDetails()">Close</button>
1437
1422
  </div>
1438
1423
  </div>
1439
1424
  </div>
1440
- `, 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"] }]
1441
1426
  }], ctorParameters: () => [{ type: MesAuthService }, { type: ToastService }, { type: ThemeService }], propDecorators: { notificationRead: [{
1442
1427
  type: Output
1443
1428
  }], themeClass: [{
@@ -1445,12 +1430,456 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
1445
1430
  args: ['class']
1446
1431
  }] } });
1447
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
+
1448
1876
  class MaUserComponent {
1449
1877
  userProfile;
1878
+ approvalPanel;
1450
1879
  ngAfterViewInit() {
1451
- // Ensure proper initialization
1452
1880
  if (this.userProfile) {
1453
1881
  this.userProfile.loadUnreadCount();
1882
+ this.userProfile.loadPendingApprovalCount();
1454
1883
  }
1455
1884
  }
1456
1885
  onNotificationRead() {
@@ -1458,27 +1887,43 @@ class MaUserComponent {
1458
1887
  this.userProfile.loadUnreadCount();
1459
1888
  }
1460
1889
  }
1890
+ onApprovalActioned() {
1891
+ if (this.userProfile) {
1892
+ this.userProfile.loadPendingApprovalCount();
1893
+ }
1894
+ }
1461
1895
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MaUserComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1462
- 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: `
1463
1897
  <ma-toast-container></ma-toast-container>
1464
1898
  <div class="user-header">
1465
- <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>
1466
1903
  </div>
1467
1904
  <ma-notification-panel #notificationPanel (notificationRead)="onNotificationRead()"></ma-notification-panel>
1468
- `, 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"] }] });
1469
1907
  }
1470
1908
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MaUserComponent, decorators: [{
1471
1909
  type: Component,
1472
- args: [{ selector: 'ma-user', standalone: true, imports: [ToastContainerComponent, UserProfileComponent, NotificationPanelComponent], template: `
1910
+ args: [{ selector: 'ma-user', standalone: true, imports: [ToastContainerComponent, UserProfileComponent, NotificationPanelComponent, MaApprovalPanelComponent], template: `
1473
1911
  <ma-toast-container></ma-toast-container>
1474
1912
  <div class="user-header">
1475
- <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>
1476
1917
  </div>
1477
1918
  <ma-notification-panel #notificationPanel (notificationRead)="onNotificationRead()"></ma-notification-panel>
1919
+ <ma-approval-panel #approvalPanel (approvalActioned)="onApprovalActioned()"></ma-approval-panel>
1478
1920
  `, styles: [".user-header{display:flex;justify-content:flex-end}\n"] }]
1479
1921
  }], propDecorators: { userProfile: [{
1480
1922
  type: ViewChild,
1481
1923
  args: [UserProfileComponent]
1924
+ }], approvalPanel: [{
1925
+ type: ViewChild,
1926
+ args: [MaApprovalPanelComponent]
1482
1927
  }] } });
1483
1928
 
1484
1929
  class NotificationBadgeComponent {
@@ -1563,9 +2008,587 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
1563
2008
  args: ['class']
1564
2009
  }] } });
1565
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
+
1566
2589
  /**
1567
2590
  * Generated bundle index. Do not edit.
1568
2591
  */
1569
2592
 
1570
- 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 };
1571
2594
  //# sourceMappingURL=mesauth-angular.mjs.map