mesauth-angular 1.7.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/mesauth-angular.mjs +230 -1157
- package/fesm2022/mesauth-angular.mjs.map +1 -1
- package/package.json +1 -1
- package/types/mesauth-angular.d.ts +114 -4
|
@@ -1,17 +1,82 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, makeEnvironmentProviders, provideAppInitializer, inject, NgZone,
|
|
2
|
+
import { signal, Injectable, InjectionToken, makeEnvironmentProviders, provideAppInitializer, inject, NgZone, NgModule, EventEmitter, HostListener, HostBinding, Output, Component, ViewChild, Input, Directive } from '@angular/core';
|
|
3
|
+
import { catchError, of, BehaviorSubject, Subject, EMPTY, timer, throwError, forkJoin } from 'rxjs';
|
|
3
4
|
import * as i4 from '@angular/common/http';
|
|
4
5
|
import { HttpClient } from '@angular/common/http';
|
|
5
6
|
import { HubConnectionBuilder, LogLevel } from '@microsoft/signalr';
|
|
6
|
-
import {
|
|
7
|
-
import { tap, catchError, switchMap, takeUntil, map } from 'rxjs/operators';
|
|
7
|
+
import { tap, catchError as catchError$1, switchMap, takeUntil, map } from 'rxjs/operators';
|
|
8
8
|
import * as i2 from '@angular/router';
|
|
9
9
|
import { Router } from '@angular/router';
|
|
10
|
-
import * as i3 from '@angular/common';
|
|
11
|
-
import { NgIf, CommonModule, NgFor, DatePipe } from '@angular/common';
|
|
12
10
|
import { DomSanitizer } from '@angular/platform-browser';
|
|
11
|
+
import { DatePipe } from '@angular/common';
|
|
13
12
|
import { rxResource } from '@angular/core/rxjs-interop';
|
|
14
13
|
|
|
14
|
+
/** Current installed package version — keep in sync with package.json. */
|
|
15
|
+
const PACKAGE_VERSION = '1.8.0';
|
|
16
|
+
/**
|
|
17
|
+
* Provides server-driven UI configuration loaded from the hosted manifest.
|
|
18
|
+
* Components read `labels()` and `features()` signals instead of hardcoded strings.
|
|
19
|
+
* Update manifest.json on the server to change labels or toggle features without
|
|
20
|
+
* republishing the npm package.
|
|
21
|
+
*/
|
|
22
|
+
class MaUiConfigService {
|
|
23
|
+
_labels = signal({}, ...(ngDevMode ? [{ debugName: "_labels" }] : /* istanbul ignore next */ []));
|
|
24
|
+
_features = signal({
|
|
25
|
+
showApprovalPanel: true,
|
|
26
|
+
showNotificationPanel: true
|
|
27
|
+
}, ...(ngDevMode ? [{ debugName: "_features" }] : /* istanbul ignore next */ []));
|
|
28
|
+
_updateRequired = signal(false, ...(ngDevMode ? [{ debugName: "_updateRequired" }] : /* istanbul ignore next */ []));
|
|
29
|
+
/** Reactive map of UI string overrides from the server manifest. */
|
|
30
|
+
labels = this._labels.asReadonly();
|
|
31
|
+
/** Reactive map of feature flags from the server manifest. */
|
|
32
|
+
features = this._features.asReadonly();
|
|
33
|
+
/** True when the server signals that a package upgrade is needed. */
|
|
34
|
+
updateRequired = this._updateRequired.asReadonly();
|
|
35
|
+
/**
|
|
36
|
+
* Returns a label by key, falling back to `defaultValue` when the server
|
|
37
|
+
* manifest has not provided an override.
|
|
38
|
+
*/
|
|
39
|
+
label(key, defaultValue) {
|
|
40
|
+
return this._labels()[key] ?? defaultValue;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Returns a feature flag by key, falling back to `defaultValue`.
|
|
44
|
+
*/
|
|
45
|
+
feature(key, defaultValue = true) {
|
|
46
|
+
const val = this._features()[key];
|
|
47
|
+
return val !== undefined ? val : defaultValue;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Called by MesAuthService.init() — fetches the manifest and applies any
|
|
51
|
+
* label/feature overrides. Failures are silently ignored so the library
|
|
52
|
+
* works without a manifest endpoint.
|
|
53
|
+
*/
|
|
54
|
+
loadManifest(assetsUrl, http) {
|
|
55
|
+
http.get(`${assetsUrl}/manifest.json`).pipe(catchError(() => of(null))).subscribe(manifest => {
|
|
56
|
+
if (!manifest)
|
|
57
|
+
return;
|
|
58
|
+
if (manifest.labels)
|
|
59
|
+
this._labels.set(manifest.labels);
|
|
60
|
+
if (manifest.features)
|
|
61
|
+
this._features.update(f => ({ ...f, ...manifest.features }));
|
|
62
|
+
if (manifest.updateRequired)
|
|
63
|
+
this._updateRequired.set(true);
|
|
64
|
+
// Version-check: warn dev team when a newer package is available
|
|
65
|
+
const latest = manifest.latestPackageVersion;
|
|
66
|
+
if (latest && latest !== PACKAGE_VERSION) {
|
|
67
|
+
console.warn(`[mesauth-angular] A newer version (${latest}) is available. ` +
|
|
68
|
+
`Installed: ${PACKAGE_VERSION}. Please update the package.`);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: MaUiConfigService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
73
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: MaUiConfigService, providedIn: 'root' });
|
|
74
|
+
}
|
|
75
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: MaUiConfigService, decorators: [{
|
|
76
|
+
type: Injectable,
|
|
77
|
+
args: [{ providedIn: 'root' }]
|
|
78
|
+
}] });
|
|
79
|
+
|
|
15
80
|
/** Injection token for MesAuth configuration */
|
|
16
81
|
const MES_AUTH_CONFIG = new InjectionToken('MES_AUTH_CONFIG');
|
|
17
82
|
/**
|
|
@@ -74,11 +139,26 @@ class MesAuthService {
|
|
|
74
139
|
this.router = router;
|
|
75
140
|
this.ngZone = ngZone ?? null;
|
|
76
141
|
this.apiBase = config.apiBaseUrl.replace(/\/$/, '');
|
|
142
|
+
// Inject server-hosted theme CSS and manifest so visual/config changes
|
|
143
|
+
// don't require a package republish.
|
|
144
|
+
const assetsUrl = config.uiAssetsUrl || `${this.apiBase}/mesauth-angular/v1`;
|
|
145
|
+
this.injectThemeStylesheet(assetsUrl);
|
|
146
|
+
inject(MaUiConfigService).loadManifest(assetsUrl, httpClient);
|
|
77
147
|
// Fetch user once on init. Route changes do NOT re-fetch the user.
|
|
78
148
|
// Auth state is maintained via cookies; 401 errors are handled by HTTP interceptors.
|
|
79
149
|
// SignalR handles real-time notification delivery without polling.
|
|
80
150
|
this.fetchCurrentUser().subscribe();
|
|
81
151
|
}
|
|
152
|
+
injectThemeStylesheet(assetsUrl) {
|
|
153
|
+
const id = 'ma-theme-css';
|
|
154
|
+
if (typeof document === 'undefined' || document.getElementById(id))
|
|
155
|
+
return;
|
|
156
|
+
const link = document.createElement('link');
|
|
157
|
+
link.id = id;
|
|
158
|
+
link.rel = 'stylesheet';
|
|
159
|
+
link.href = `${assetsUrl}/themes/default.css`;
|
|
160
|
+
document.head.appendChild(link);
|
|
161
|
+
}
|
|
82
162
|
getConfig() {
|
|
83
163
|
return this.config;
|
|
84
164
|
}
|
|
@@ -91,7 +171,7 @@ class MesAuthService {
|
|
|
91
171
|
if (u && this.config) {
|
|
92
172
|
this.startConnection(this.config);
|
|
93
173
|
}
|
|
94
|
-
}), catchError((err) => {
|
|
174
|
+
}), catchError$1((err) => {
|
|
95
175
|
// Silently handle auth errors (401/403) - user is not logged in
|
|
96
176
|
if (err.status === 401 || err.status === 403) {
|
|
97
177
|
this._currentUser.next(null);
|
|
@@ -209,7 +289,7 @@ let isRedirecting = false;
|
|
|
209
289
|
const mesAuthInterceptor = (req, next) => {
|
|
210
290
|
const authService = inject(MesAuthService);
|
|
211
291
|
const router = inject(Router);
|
|
212
|
-
return next(req).pipe(catchError((error) => {
|
|
292
|
+
return next(req).pipe(catchError$1((error) => {
|
|
213
293
|
const status = error.status;
|
|
214
294
|
// Check if we should handle this error and prevent loopback
|
|
215
295
|
if ((status === 401 || status === 403) && !isRedirecting) {
|
|
@@ -230,7 +310,7 @@ const mesAuthInterceptor = (req, next) => {
|
|
|
230
310
|
if (status === 401 && !isLoginPage && !isAuthPage && !isMeAuthPage && !isPublicPage) {
|
|
231
311
|
// Wait 1.5s for the concurrent refresh's Set-Cookie to be processed, then retry once.
|
|
232
312
|
// If retry also gets 401, redirect to login.
|
|
233
|
-
return timer(1500).pipe(switchMap(() => next(req)), catchError((retryError) => {
|
|
313
|
+
return timer(1500).pipe(switchMap(() => next(req)), catchError$1((retryError) => {
|
|
234
314
|
if (retryError.status === 401) {
|
|
235
315
|
isRedirecting = true;
|
|
236
316
|
setTimeout(() => { isRedirecting = false; }, 5000);
|
|
@@ -513,187 +593,11 @@ class UserProfileComponent {
|
|
|
513
593
|
this.notificationClick.emit();
|
|
514
594
|
}
|
|
515
595
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: UserProfileComponent, deps: [{ token: MesAuthService }, { token: i2.Router }, { token: ThemeService }, { token: i0.ChangeDetectorRef }, { token: i4.HttpClient }], target: i0.ɵɵFactoryTarget.Component });
|
|
516
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
517
|
-
<div class="user-profile-container">
|
|
518
|
-
<!-- Not logged in -->
|
|
519
|
-
<ng-container *ngIf="!currentUser()">
|
|
520
|
-
<button class="login-btn" (click)="onLogin()">Login</button>
|
|
521
|
-
</ng-container>
|
|
522
|
-
|
|
523
|
-
<!-- Logged in -->
|
|
524
|
-
<ng-container *ngIf="currentUser()">
|
|
525
|
-
<div class="user-header">
|
|
526
|
-
<!-- Notification Bell -->
|
|
527
|
-
<button class="notification-btn" [class.has-unread]="unreadCount > 0" (click)="onNotificationClick()" title="Notifications" aria-label="Notifications">
|
|
528
|
-
<svg class="bell-icon" 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">
|
|
529
|
-
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
|
|
530
|
-
<path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
|
531
|
-
</svg>
|
|
532
|
-
<span class="badge" *ngIf="unreadCount > 0">{{ unreadCount > 99 ? '99+' : unreadCount }}</span>
|
|
533
|
-
</button>
|
|
534
|
-
|
|
535
|
-
<!-- Approval Button -->
|
|
536
|
-
<button class="notification-btn" [class.has-unread]="pendingApprovalCount > 0" (click)="onApprovalClick()" title="Approvals" aria-label="Approvals">
|
|
537
|
-
<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">
|
|
538
|
-
<path d="M9 11l3 3L22 4"/>
|
|
539
|
-
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
|
|
540
|
-
</svg>
|
|
541
|
-
<span class="badge" *ngIf="pendingApprovalCount > 0">{{ pendingApprovalCount > 99 ? '99+' : pendingApprovalCount }}</span>
|
|
542
|
-
</button>
|
|
543
|
-
|
|
544
|
-
<!-- User Avatar + Dropdown -->
|
|
545
|
-
<div class="user-menu-wrapper">
|
|
546
|
-
<button class="user-menu-btn" (click)="toggleDropdown()" [attr.aria-label]="'User menu for ' + (currentUser().fullName || currentUser().userName)" aria-haspopup="true" [attr.aria-expanded]="dropdownOpen">
|
|
547
|
-
<div class="avatar-ring" [class.active]="dropdownOpen">
|
|
548
|
-
<img
|
|
549
|
-
*ngIf="currentUser().fullName || currentUser().userName"
|
|
550
|
-
[src]="getAvatarUrl(currentUser())"
|
|
551
|
-
[alt]="currentUser().fullName || currentUser().userName"
|
|
552
|
-
class="avatar"
|
|
553
|
-
/>
|
|
554
|
-
<span *ngIf="!(currentUser().fullName || currentUser().userName)" class="avatar-initial">
|
|
555
|
-
{{ getLastNameInitial(currentUser()) }}
|
|
556
|
-
</span>
|
|
557
|
-
</div>
|
|
558
|
-
</button>
|
|
559
|
-
|
|
560
|
-
<div class="mes-dropdown-menu" *ngIf="dropdownOpen">
|
|
561
|
-
<!-- User info header -->
|
|
562
|
-
<div class="mes-dropdown-header">
|
|
563
|
-
<div class="dropdown-avatar-wrap">
|
|
564
|
-
<img
|
|
565
|
-
*ngIf="currentUser().fullName || currentUser().userName"
|
|
566
|
-
[src]="getAvatarUrl(currentUser())"
|
|
567
|
-
[alt]="currentUser().fullName || currentUser().userName"
|
|
568
|
-
class="dropdown-avatar"
|
|
569
|
-
/>
|
|
570
|
-
<span *ngIf="!(currentUser().fullName || currentUser().userName)" class="dropdown-avatar-initial">
|
|
571
|
-
{{ getLastNameInitial(currentUser()) }}
|
|
572
|
-
</span>
|
|
573
|
-
</div>
|
|
574
|
-
<div class="dropdown-user-info">
|
|
575
|
-
<span class="dropdown-user-name">{{ currentUser().fullName || currentUser().userName }}</span>
|
|
576
|
-
<span class="dropdown-user-sub" *ngIf="currentUser().position || currentUser().department">
|
|
577
|
-
{{ currentUser().position || currentUser().department }}
|
|
578
|
-
</span>
|
|
579
|
-
</div>
|
|
580
|
-
</div>
|
|
581
|
-
|
|
582
|
-
<div class="mes-dropdown-divider"></div>
|
|
583
|
-
|
|
584
|
-
<button class="mes-dropdown-item profile-link" (click)="onViewProfile()">
|
|
585
|
-
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
586
|
-
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>
|
|
587
|
-
</svg>
|
|
588
|
-
View Profile
|
|
589
|
-
</button>
|
|
590
|
-
|
|
591
|
-
<div class="mes-dropdown-divider"></div>
|
|
592
|
-
|
|
593
|
-
<button class="mes-dropdown-item logout-item" (click)="onLogout()">
|
|
594
|
-
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
595
|
-
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/>
|
|
596
|
-
</svg>
|
|
597
|
-
Logout
|
|
598
|
-
</button>
|
|
599
|
-
</div>
|
|
600
|
-
</div>
|
|
601
|
-
</div>
|
|
602
|
-
</ng-container>
|
|
603
|
-
</div>
|
|
604
|
-
`, isInline: true, 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"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
596
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", 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: "<div class=\"user-profile-container\">\n @if (!currentUser()) {\n <!-- Not logged in -->\n <button class=\"login-btn\" (click)=\"onLogin()\">Login</button>\n } @else {\n <!-- Logged in -->\n <div class=\"user-header\">\n <!-- Notification Bell -->\n <button class=\"notification-btn\" [class.has-unread]=\"unreadCount > 0\" (click)=\"onNotificationClick()\" title=\"Notifications\" aria-label=\"Notifications\">\n <svg class=\"bell-icon\" 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\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/>\n <path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n @if (unreadCount > 0) {\n <span class=\"badge\">{{ unreadCount > 99 ? '99+' : unreadCount }}</span>\n }\n </button>\n\n <!-- Approval Button -->\n <button class=\"notification-btn\" [class.has-unread]=\"pendingApprovalCount > 0\" (click)=\"onApprovalClick()\" title=\"Approvals\" aria-label=\"Approvals\">\n <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\">\n <path d=\"M9 11l3 3L22 4\"/>\n <path d=\"M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11\"/>\n </svg>\n @if (pendingApprovalCount > 0) {\n <span class=\"badge\">{{ pendingApprovalCount > 99 ? '99+' : pendingApprovalCount }}</span>\n }\n </button>\n\n <!-- User Avatar + Dropdown -->\n <div class=\"user-menu-wrapper\">\n <button class=\"user-menu-btn\" (click)=\"toggleDropdown()\" [attr.aria-label]=\"'User menu for ' + (currentUser().fullName || currentUser().userName)\" aria-haspopup=\"true\" [attr.aria-expanded]=\"dropdownOpen\">\n <div class=\"avatar-ring\" [class.active]=\"dropdownOpen\">\n @if (currentUser().fullName || currentUser().userName) {\n <img\n [src]=\"getAvatarUrl(currentUser())\"\n [alt]=\"currentUser().fullName || currentUser().userName\"\n class=\"avatar\"\n />\n } @else {\n <span class=\"avatar-initial\">{{ getLastNameInitial(currentUser()) }}</span>\n }\n </div>\n </button>\n\n @if (dropdownOpen) {\n <div class=\"mes-dropdown-menu\">\n <!-- User info header -->\n <div class=\"mes-dropdown-header\">\n <div class=\"dropdown-avatar-wrap\">\n @if (currentUser().fullName || currentUser().userName) {\n <img\n [src]=\"getAvatarUrl(currentUser())\"\n [alt]=\"currentUser().fullName || currentUser().userName\"\n class=\"dropdown-avatar\"\n />\n } @else {\n <span class=\"dropdown-avatar-initial\">{{ getLastNameInitial(currentUser()) }}</span>\n }\n </div>\n <div class=\"dropdown-user-info\">\n <span class=\"dropdown-user-name\">{{ currentUser().fullName || currentUser().userName }}</span>\n @if (currentUser().position || currentUser().department) {\n <span class=\"dropdown-user-sub\">{{ currentUser().position || currentUser().department }}</span>\n }\n </div>\n </div>\n\n <div class=\"mes-dropdown-divider\"></div>\n\n <button class=\"mes-dropdown-item profile-link\" (click)=\"onViewProfile()\">\n <svg class=\"item-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/>\n </svg>\n View Profile\n </button>\n\n <div class=\"mes-dropdown-divider\"></div>\n\n <button class=\"mes-dropdown-item logout-item\" (click)=\"onLogout()\">\n <svg class=\"item-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\"/><polyline points=\"16 17 21 12 16 7\"/><line x1=\"21\" y1=\"12\" x2=\"9\" y2=\"12\"/>\n </svg>\n Logout\n </button>\n </div>\n }\n </div>\n </div>\n }\n</div>\n", styles: [".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"] });
|
|
605
597
|
}
|
|
606
598
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: UserProfileComponent, decorators: [{
|
|
607
599
|
type: Component,
|
|
608
|
-
args: [{ selector: 'ma-user-profile', standalone: true, imports: [
|
|
609
|
-
<div class="user-profile-container">
|
|
610
|
-
<!-- Not logged in -->
|
|
611
|
-
<ng-container *ngIf="!currentUser()">
|
|
612
|
-
<button class="login-btn" (click)="onLogin()">Login</button>
|
|
613
|
-
</ng-container>
|
|
614
|
-
|
|
615
|
-
<!-- Logged in -->
|
|
616
|
-
<ng-container *ngIf="currentUser()">
|
|
617
|
-
<div class="user-header">
|
|
618
|
-
<!-- Notification Bell -->
|
|
619
|
-
<button class="notification-btn" [class.has-unread]="unreadCount > 0" (click)="onNotificationClick()" title="Notifications" aria-label="Notifications">
|
|
620
|
-
<svg class="bell-icon" 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">
|
|
621
|
-
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
|
|
622
|
-
<path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
|
623
|
-
</svg>
|
|
624
|
-
<span class="badge" *ngIf="unreadCount > 0">{{ unreadCount > 99 ? '99+' : unreadCount }}</span>
|
|
625
|
-
</button>
|
|
626
|
-
|
|
627
|
-
<!-- Approval Button -->
|
|
628
|
-
<button class="notification-btn" [class.has-unread]="pendingApprovalCount > 0" (click)="onApprovalClick()" title="Approvals" aria-label="Approvals">
|
|
629
|
-
<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">
|
|
630
|
-
<path d="M9 11l3 3L22 4"/>
|
|
631
|
-
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
|
|
632
|
-
</svg>
|
|
633
|
-
<span class="badge" *ngIf="pendingApprovalCount > 0">{{ pendingApprovalCount > 99 ? '99+' : pendingApprovalCount }}</span>
|
|
634
|
-
</button>
|
|
635
|
-
|
|
636
|
-
<!-- User Avatar + Dropdown -->
|
|
637
|
-
<div class="user-menu-wrapper">
|
|
638
|
-
<button class="user-menu-btn" (click)="toggleDropdown()" [attr.aria-label]="'User menu for ' + (currentUser().fullName || currentUser().userName)" aria-haspopup="true" [attr.aria-expanded]="dropdownOpen">
|
|
639
|
-
<div class="avatar-ring" [class.active]="dropdownOpen">
|
|
640
|
-
<img
|
|
641
|
-
*ngIf="currentUser().fullName || currentUser().userName"
|
|
642
|
-
[src]="getAvatarUrl(currentUser())"
|
|
643
|
-
[alt]="currentUser().fullName || currentUser().userName"
|
|
644
|
-
class="avatar"
|
|
645
|
-
/>
|
|
646
|
-
<span *ngIf="!(currentUser().fullName || currentUser().userName)" class="avatar-initial">
|
|
647
|
-
{{ getLastNameInitial(currentUser()) }}
|
|
648
|
-
</span>
|
|
649
|
-
</div>
|
|
650
|
-
</button>
|
|
651
|
-
|
|
652
|
-
<div class="mes-dropdown-menu" *ngIf="dropdownOpen">
|
|
653
|
-
<!-- User info header -->
|
|
654
|
-
<div class="mes-dropdown-header">
|
|
655
|
-
<div class="dropdown-avatar-wrap">
|
|
656
|
-
<img
|
|
657
|
-
*ngIf="currentUser().fullName || currentUser().userName"
|
|
658
|
-
[src]="getAvatarUrl(currentUser())"
|
|
659
|
-
[alt]="currentUser().fullName || currentUser().userName"
|
|
660
|
-
class="dropdown-avatar"
|
|
661
|
-
/>
|
|
662
|
-
<span *ngIf="!(currentUser().fullName || currentUser().userName)" class="dropdown-avatar-initial">
|
|
663
|
-
{{ getLastNameInitial(currentUser()) }}
|
|
664
|
-
</span>
|
|
665
|
-
</div>
|
|
666
|
-
<div class="dropdown-user-info">
|
|
667
|
-
<span class="dropdown-user-name">{{ currentUser().fullName || currentUser().userName }}</span>
|
|
668
|
-
<span class="dropdown-user-sub" *ngIf="currentUser().position || currentUser().department">
|
|
669
|
-
{{ currentUser().position || currentUser().department }}
|
|
670
|
-
</span>
|
|
671
|
-
</div>
|
|
672
|
-
</div>
|
|
673
|
-
|
|
674
|
-
<div class="mes-dropdown-divider"></div>
|
|
675
|
-
|
|
676
|
-
<button class="mes-dropdown-item profile-link" (click)="onViewProfile()">
|
|
677
|
-
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
678
|
-
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>
|
|
679
|
-
</svg>
|
|
680
|
-
View Profile
|
|
681
|
-
</button>
|
|
682
|
-
|
|
683
|
-
<div class="mes-dropdown-divider"></div>
|
|
684
|
-
|
|
685
|
-
<button class="mes-dropdown-item logout-item" (click)="onLogout()">
|
|
686
|
-
<svg class="item-icon" xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
687
|
-
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/>
|
|
688
|
-
</svg>
|
|
689
|
-
Logout
|
|
690
|
-
</button>
|
|
691
|
-
</div>
|
|
692
|
-
</div>
|
|
693
|
-
</div>
|
|
694
|
-
</ng-container>
|
|
695
|
-
</div>
|
|
696
|
-
`, 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"] }]
|
|
600
|
+
args: [{ selector: 'ma-user-profile', standalone: true, imports: [], template: "<div class=\"user-profile-container\">\n @if (!currentUser()) {\n <!-- Not logged in -->\n <button class=\"login-btn\" (click)=\"onLogin()\">Login</button>\n } @else {\n <!-- Logged in -->\n <div class=\"user-header\">\n <!-- Notification Bell -->\n <button class=\"notification-btn\" [class.has-unread]=\"unreadCount > 0\" (click)=\"onNotificationClick()\" title=\"Notifications\" aria-label=\"Notifications\">\n <svg class=\"bell-icon\" 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\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/>\n <path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n @if (unreadCount > 0) {\n <span class=\"badge\">{{ unreadCount > 99 ? '99+' : unreadCount }}</span>\n }\n </button>\n\n <!-- Approval Button -->\n <button class=\"notification-btn\" [class.has-unread]=\"pendingApprovalCount > 0\" (click)=\"onApprovalClick()\" title=\"Approvals\" aria-label=\"Approvals\">\n <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\">\n <path d=\"M9 11l3 3L22 4\"/>\n <path d=\"M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11\"/>\n </svg>\n @if (pendingApprovalCount > 0) {\n <span class=\"badge\">{{ pendingApprovalCount > 99 ? '99+' : pendingApprovalCount }}</span>\n }\n </button>\n\n <!-- User Avatar + Dropdown -->\n <div class=\"user-menu-wrapper\">\n <button class=\"user-menu-btn\" (click)=\"toggleDropdown()\" [attr.aria-label]=\"'User menu for ' + (currentUser().fullName || currentUser().userName)\" aria-haspopup=\"true\" [attr.aria-expanded]=\"dropdownOpen\">\n <div class=\"avatar-ring\" [class.active]=\"dropdownOpen\">\n @if (currentUser().fullName || currentUser().userName) {\n <img\n [src]=\"getAvatarUrl(currentUser())\"\n [alt]=\"currentUser().fullName || currentUser().userName\"\n class=\"avatar\"\n />\n } @else {\n <span class=\"avatar-initial\">{{ getLastNameInitial(currentUser()) }}</span>\n }\n </div>\n </button>\n\n @if (dropdownOpen) {\n <div class=\"mes-dropdown-menu\">\n <!-- User info header -->\n <div class=\"mes-dropdown-header\">\n <div class=\"dropdown-avatar-wrap\">\n @if (currentUser().fullName || currentUser().userName) {\n <img\n [src]=\"getAvatarUrl(currentUser())\"\n [alt]=\"currentUser().fullName || currentUser().userName\"\n class=\"dropdown-avatar\"\n />\n } @else {\n <span class=\"dropdown-avatar-initial\">{{ getLastNameInitial(currentUser()) }}</span>\n }\n </div>\n <div class=\"dropdown-user-info\">\n <span class=\"dropdown-user-name\">{{ currentUser().fullName || currentUser().userName }}</span>\n @if (currentUser().position || currentUser().department) {\n <span class=\"dropdown-user-sub\">{{ currentUser().position || currentUser().department }}</span>\n }\n </div>\n </div>\n\n <div class=\"mes-dropdown-divider\"></div>\n\n <button class=\"mes-dropdown-item profile-link\" (click)=\"onViewProfile()\">\n <svg class=\"item-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/>\n </svg>\n View Profile\n </button>\n\n <div class=\"mes-dropdown-divider\"></div>\n\n <button class=\"mes-dropdown-item logout-item\" (click)=\"onLogout()\">\n <svg class=\"item-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"15\" height=\"15\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4\"/><polyline points=\"16 17 21 12 16 7\"/><line x1=\"21\" y1=\"12\" x2=\"9\" y2=\"12\"/>\n </svg>\n Logout\n </button>\n </div>\n }\n </div>\n </div>\n }\n</div>\n", styles: [".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"] }]
|
|
697
601
|
}], ctorParameters: () => [{ type: MesAuthService }, { type: i2.Router }, { type: ThemeService }, { type: i0.ChangeDetectorRef }, { type: i4.HttpClient }], propDecorators: { notificationClick: [{
|
|
698
602
|
type: Output
|
|
699
603
|
}], approvalClick: [{
|
|
@@ -775,85 +679,11 @@ class ToastContainerComponent {
|
|
|
775
679
|
this.toastService.remove(id);
|
|
776
680
|
}
|
|
777
681
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ToastContainerComponent, deps: [{ token: ToastService }, { token: ThemeService }], target: i0.ɵɵFactoryTarget.Component });
|
|
778
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
779
|
-
<div class="toast-container">
|
|
780
|
-
<div *ngFor="let toast of toasts" class="toast toast-{{ toast.type }}">
|
|
781
|
-
|
|
782
|
-
<!-- Type icon -->
|
|
783
|
-
<div class="toast-icon">
|
|
784
|
-
<svg *ngIf="toast.type === 'info'" 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">
|
|
785
|
-
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
|
786
|
-
</svg>
|
|
787
|
-
<svg *ngIf="toast.type === 'success'" 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">
|
|
788
|
-
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>
|
|
789
|
-
</svg>
|
|
790
|
-
<svg *ngIf="toast.type === 'warning'" 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">
|
|
791
|
-
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>
|
|
792
|
-
</svg>
|
|
793
|
-
<svg *ngIf="toast.type === 'error'" 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">
|
|
794
|
-
<circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/>
|
|
795
|
-
</svg>
|
|
796
|
-
</div>
|
|
797
|
-
|
|
798
|
-
<!-- Content -->
|
|
799
|
-
<div class="toast-content">
|
|
800
|
-
<div *ngIf="toast.title" class="toast-title">{{ toast.title }}</div>
|
|
801
|
-
<div class="toast-message" [innerHTML]="toast.message"></div>
|
|
802
|
-
</div>
|
|
803
|
-
|
|
804
|
-
<!-- Close -->
|
|
805
|
-
<button class="toast-close" (click)="close(toast.id)" aria-label="Close">
|
|
806
|
-
<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">
|
|
807
|
-
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
808
|
-
</svg>
|
|
809
|
-
</button>
|
|
810
|
-
|
|
811
|
-
<!-- Auto-dismiss progress bar — hidden when duration <= 0 (persistent toast) -->
|
|
812
|
-
<div class="toast-progress" *ngIf="toast.duration == null || toast.duration > 0" [style.animation-duration]="(toast.duration ?? 5000) + 'ms'"></div>
|
|
813
|
-
</div>
|
|
814
|
-
</div>
|
|
815
|
-
`, isInline: true, styles: [":host{--info-color: #2196f3;--info-bg: rgba(33, 150, 243, .1);--success-color: #43a047;--success-bg: rgba(67, 160, 71, .1);--warning-color: #f57c00;--warning-bg: rgba(245, 124, 0, .1);--error-color: #e53935;--error-bg: rgba(229, 57, 53, .1);--text-primary: #212121;--text-secondary: #757575;--bg-primary: #ffffff;--border-color: rgba(0, 0, 0, .08);--shadow: rgba(0, 0, 0, .1);--shadow-lg: rgba(0, 0, 0, .18)}:host(.theme-dark){--info-color: #64b5f6;--info-bg: rgba(100, 181, 246, .12);--success-color: #66bb6a;--success-bg: rgba(102, 187, 106, .12);--warning-color: #ffb74d;--warning-bg: rgba(255, 183, 77, .12);--error-color: #ef5350;--error-bg: rgba(239, 83, 80, .12);--text-primary: #e0e0e0;--text-secondary: #9e9e9e;--bg-primary: #1e1e2e;--border-color: rgba(255, 255, 255, .08);--shadow: rgba(0, 0, 0, .35);--shadow-lg: rgba(0, 0, 0, .5)}.toast-container{position:fixed;top:20px;right:20px;z-index:9999;pointer-events:none;display:flex;flex-direction:column;gap:10px}.toast{position:relative;display:flex;align-items:flex-start;gap:11px;padding:13px 13px 16px 16px;border-radius:12px;background:var(--bg-primary);border:1px solid var(--border-color);box-shadow:0 8px 28px var(--shadow-lg),0 2px 8px var(--shadow);pointer-events:auto;min-width:300px;max-width:420px;overflow:hidden;animation:toast-in .35s cubic-bezier(.16,1,.3,1)}@keyframes toast-in{0%{opacity:0;transform:translate(36px) scale(.96)}to{opacity:1;transform:translate(0) scale(1)}}.toast:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:4px;border-radius:12px 0 0 12px}.toast-info:before{background:var(--info-color)}.toast-success:before{background:var(--success-color)}.toast-warning:before{background:var(--warning-color)}.toast-error:before{background:var(--error-color)}.toast-icon{flex-shrink:0;width:34px;height:34px;border-radius:9px;display:flex;align-items:center;justify-content:center;margin-left:2px}.toast-info .toast-icon{color:var(--info-color);background:var(--info-bg)}.toast-success .toast-icon{color:var(--success-color);background:var(--success-bg)}.toast-warning .toast-icon{color:var(--warning-color);background:var(--warning-bg)}.toast-error .toast-icon{color:var(--error-color);background:var(--error-bg)}.toast-content{flex:1;min-width:0;padding-top:1px}.toast-title{font-weight:700;font-size:13.5px;margin-bottom:3px;line-height:1.3}.toast-info .toast-title{color:var(--info-color)}.toast-success .toast-title{color:var(--success-color)}.toast-warning .toast-title{color:var(--warning-color)}.toast-error .toast-title{color:var(--error-color)}.toast-message{font-size:12.5px;line-height:1.45;color:var(--text-secondary)}.toast-close{background:none;border:none;cursor:pointer;color:var(--text-secondary);width:26px;height:26px;border-radius:6px;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:0;transition:color .15s,background-color .15s}.toast-close:hover{color:var(--text-primary);background:var(--border-color)}.toast-progress{position:absolute;bottom:0;left:4px;right:0;height:3px;border-radius:0 0 12px;animation:toast-progress linear forwards;opacity:.7}.toast-info .toast-progress{background:var(--info-color)}.toast-success .toast-progress{background:var(--success-color)}.toast-warning .toast-progress{background:var(--warning-color)}.toast-error .toast-progress{background:var(--error-color)}@keyframes toast-progress{0%{width:calc(100% - 4px)}to{width:0}}@media(max-width:600px){.toast-container{top:10px;right:10px;left:10px}.toast{min-width:auto;max-width:100%}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i3.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
682
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: ToastContainerComponent, isStandalone: true, selector: "ma-toast-container", host: { properties: { "class": "this.themeClass" } }, ngImport: i0, template: "<div class=\"toast-container\">\n @for (toast of toasts; track toast.id) {\n <div class=\"toast toast-{{ toast.type }}\">\n\n <!-- Type icon -->\n <div class=\"toast-icon\">\n @if (toast.type === 'info') {\n <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\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n }\n @if (toast.type === 'success') {\n <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\">\n <path d=\"M22 11.08V12a10 10 0 1 1-5.93-9.14\"/><polyline points=\"22 4 12 14.01 9 11.01\"/>\n </svg>\n }\n @if (toast.type === 'warning') {\n <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\">\n <path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/>\n </svg>\n }\n @if (toast.type === 'error') {\n <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\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\"/><line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\"/>\n </svg>\n }\n </div>\n\n <!-- Content -->\n <div class=\"toast-content\">\n @if (toast.title) {\n <div class=\"toast-title\">{{ toast.title }}</div>\n }\n <div class=\"toast-message\" [innerHTML]=\"toast.message\"></div>\n </div>\n\n <!-- Close -->\n <button class=\"toast-close\" (click)=\"close(toast.id)\" aria-label=\"Close\">\n <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\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n\n <!-- Auto-dismiss progress bar \u2014 hidden when duration <= 0 (persistent toast) -->\n @if (toast.duration == null || toast.duration > 0) {\n <div class=\"toast-progress\" [style.animation-duration]=\"(toast.duration ?? 5000) + 'ms'\"></div>\n }\n </div>\n }\n</div>\n", styles: [".toast-container{position:fixed;top:20px;right:20px;z-index:9999;pointer-events:none;display:flex;flex-direction:column;gap:10px}.toast{position:relative;display:flex;align-items:flex-start;gap:11px;padding:13px 13px 16px 16px;border-radius:12px;background:var(--bg-primary);border:1px solid var(--border-color);box-shadow:0 8px 28px var(--shadow-lg),0 2px 8px var(--shadow);pointer-events:auto;min-width:300px;max-width:420px;overflow:hidden;animation:toast-in .35s cubic-bezier(.16,1,.3,1)}@keyframes toast-in{0%{opacity:0;transform:translate(36px) scale(.96)}to{opacity:1;transform:translate(0) scale(1)}}.toast:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:4px;border-radius:12px 0 0 12px}.toast-info:before{background:var(--info-color)}.toast-success:before{background:var(--success-color)}.toast-warning:before{background:var(--warning-color)}.toast-error:before{background:var(--error-color)}.toast-icon{flex-shrink:0;width:34px;height:34px;border-radius:9px;display:flex;align-items:center;justify-content:center;margin-left:2px}.toast-info .toast-icon{color:var(--info-color);background:var(--info-bg)}.toast-success .toast-icon{color:var(--success-color);background:var(--success-bg)}.toast-warning .toast-icon{color:var(--warning-color);background:var(--warning-bg)}.toast-error .toast-icon{color:var(--error-color);background:var(--error-bg)}.toast-content{flex:1;min-width:0;padding-top:1px}.toast-title{font-weight:700;font-size:13.5px;margin-bottom:3px;line-height:1.3}.toast-info .toast-title{color:var(--info-color)}.toast-success .toast-title{color:var(--success-color)}.toast-warning .toast-title{color:var(--warning-color)}.toast-error .toast-title{color:var(--error-color)}.toast-message{font-size:12.5px;line-height:1.45;color:var(--text-secondary)}.toast-close{background:none;border:none;cursor:pointer;color:var(--text-secondary);width:26px;height:26px;border-radius:6px;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:0;transition:color .15s,background-color .15s}.toast-close:hover{color:var(--text-primary);background:var(--border-color)}.toast-progress{position:absolute;bottom:0;left:4px;right:0;height:3px;border-radius:0 0 12px;animation:toast-progress linear forwards;opacity:.7}.toast-info .toast-progress{background:var(--info-color)}.toast-success .toast-progress{background:var(--success-color)}.toast-warning .toast-progress{background:var(--warning-color)}.toast-error .toast-progress{background:var(--error-color)}@keyframes toast-progress{0%{width:calc(100% - 4px)}to{width:0}}@media(max-width:600px){.toast-container{top:10px;right:10px;left:10px}.toast{min-width:auto;max-width:100%}}\n"] });
|
|
816
683
|
}
|
|
817
684
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: ToastContainerComponent, decorators: [{
|
|
818
685
|
type: Component,
|
|
819
|
-
args: [{ selector: 'ma-toast-container', standalone: true, imports: [
|
|
820
|
-
<div class="toast-container">
|
|
821
|
-
<div *ngFor="let toast of toasts" class="toast toast-{{ toast.type }}">
|
|
822
|
-
|
|
823
|
-
<!-- Type icon -->
|
|
824
|
-
<div class="toast-icon">
|
|
825
|
-
<svg *ngIf="toast.type === 'info'" 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">
|
|
826
|
-
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
|
827
|
-
</svg>
|
|
828
|
-
<svg *ngIf="toast.type === 'success'" 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">
|
|
829
|
-
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>
|
|
830
|
-
</svg>
|
|
831
|
-
<svg *ngIf="toast.type === 'warning'" 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">
|
|
832
|
-
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>
|
|
833
|
-
</svg>
|
|
834
|
-
<svg *ngIf="toast.type === 'error'" 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">
|
|
835
|
-
<circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/>
|
|
836
|
-
</svg>
|
|
837
|
-
</div>
|
|
838
|
-
|
|
839
|
-
<!-- Content -->
|
|
840
|
-
<div class="toast-content">
|
|
841
|
-
<div *ngIf="toast.title" class="toast-title">{{ toast.title }}</div>
|
|
842
|
-
<div class="toast-message" [innerHTML]="toast.message"></div>
|
|
843
|
-
</div>
|
|
844
|
-
|
|
845
|
-
<!-- Close -->
|
|
846
|
-
<button class="toast-close" (click)="close(toast.id)" aria-label="Close">
|
|
847
|
-
<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">
|
|
848
|
-
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
849
|
-
</svg>
|
|
850
|
-
</button>
|
|
851
|
-
|
|
852
|
-
<!-- Auto-dismiss progress bar — hidden when duration <= 0 (persistent toast) -->
|
|
853
|
-
<div class="toast-progress" *ngIf="toast.duration == null || toast.duration > 0" [style.animation-duration]="(toast.duration ?? 5000) + 'ms'"></div>
|
|
854
|
-
</div>
|
|
855
|
-
</div>
|
|
856
|
-
`, styles: [":host{--info-color: #2196f3;--info-bg: rgba(33, 150, 243, .1);--success-color: #43a047;--success-bg: rgba(67, 160, 71, .1);--warning-color: #f57c00;--warning-bg: rgba(245, 124, 0, .1);--error-color: #e53935;--error-bg: rgba(229, 57, 53, .1);--text-primary: #212121;--text-secondary: #757575;--bg-primary: #ffffff;--border-color: rgba(0, 0, 0, .08);--shadow: rgba(0, 0, 0, .1);--shadow-lg: rgba(0, 0, 0, .18)}:host(.theme-dark){--info-color: #64b5f6;--info-bg: rgba(100, 181, 246, .12);--success-color: #66bb6a;--success-bg: rgba(102, 187, 106, .12);--warning-color: #ffb74d;--warning-bg: rgba(255, 183, 77, .12);--error-color: #ef5350;--error-bg: rgba(239, 83, 80, .12);--text-primary: #e0e0e0;--text-secondary: #9e9e9e;--bg-primary: #1e1e2e;--border-color: rgba(255, 255, 255, .08);--shadow: rgba(0, 0, 0, .35);--shadow-lg: rgba(0, 0, 0, .5)}.toast-container{position:fixed;top:20px;right:20px;z-index:9999;pointer-events:none;display:flex;flex-direction:column;gap:10px}.toast{position:relative;display:flex;align-items:flex-start;gap:11px;padding:13px 13px 16px 16px;border-radius:12px;background:var(--bg-primary);border:1px solid var(--border-color);box-shadow:0 8px 28px var(--shadow-lg),0 2px 8px var(--shadow);pointer-events:auto;min-width:300px;max-width:420px;overflow:hidden;animation:toast-in .35s cubic-bezier(.16,1,.3,1)}@keyframes toast-in{0%{opacity:0;transform:translate(36px) scale(.96)}to{opacity:1;transform:translate(0) scale(1)}}.toast:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:4px;border-radius:12px 0 0 12px}.toast-info:before{background:var(--info-color)}.toast-success:before{background:var(--success-color)}.toast-warning:before{background:var(--warning-color)}.toast-error:before{background:var(--error-color)}.toast-icon{flex-shrink:0;width:34px;height:34px;border-radius:9px;display:flex;align-items:center;justify-content:center;margin-left:2px}.toast-info .toast-icon{color:var(--info-color);background:var(--info-bg)}.toast-success .toast-icon{color:var(--success-color);background:var(--success-bg)}.toast-warning .toast-icon{color:var(--warning-color);background:var(--warning-bg)}.toast-error .toast-icon{color:var(--error-color);background:var(--error-bg)}.toast-content{flex:1;min-width:0;padding-top:1px}.toast-title{font-weight:700;font-size:13.5px;margin-bottom:3px;line-height:1.3}.toast-info .toast-title{color:var(--info-color)}.toast-success .toast-title{color:var(--success-color)}.toast-warning .toast-title{color:var(--warning-color)}.toast-error .toast-title{color:var(--error-color)}.toast-message{font-size:12.5px;line-height:1.45;color:var(--text-secondary)}.toast-close{background:none;border:none;cursor:pointer;color:var(--text-secondary);width:26px;height:26px;border-radius:6px;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:0;transition:color .15s,background-color .15s}.toast-close:hover{color:var(--text-primary);background:var(--border-color)}.toast-progress{position:absolute;bottom:0;left:4px;right:0;height:3px;border-radius:0 0 12px;animation:toast-progress linear forwards;opacity:.7}.toast-info .toast-progress{background:var(--info-color)}.toast-success .toast-progress{background:var(--success-color)}.toast-warning .toast-progress{background:var(--warning-color)}.toast-error .toast-progress{background:var(--error-color)}@keyframes toast-progress{0%{width:calc(100% - 4px)}to{width:0}}@media(max-width:600px){.toast-container{top:10px;right:10px;left:10px}.toast{min-width:auto;max-width:100%}}\n"] }]
|
|
686
|
+
args: [{ selector: 'ma-toast-container', standalone: true, imports: [], template: "<div class=\"toast-container\">\n @for (toast of toasts; track toast.id) {\n <div class=\"toast toast-{{ toast.type }}\">\n\n <!-- Type icon -->\n <div class=\"toast-icon\">\n @if (toast.type === 'info') {\n <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\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n }\n @if (toast.type === 'success') {\n <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\">\n <path d=\"M22 11.08V12a10 10 0 1 1-5.93-9.14\"/><polyline points=\"22 4 12 14.01 9 11.01\"/>\n </svg>\n }\n @if (toast.type === 'warning') {\n <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\">\n <path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/>\n </svg>\n }\n @if (toast.type === 'error') {\n <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\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\"/><line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\"/>\n </svg>\n }\n </div>\n\n <!-- Content -->\n <div class=\"toast-content\">\n @if (toast.title) {\n <div class=\"toast-title\">{{ toast.title }}</div>\n }\n <div class=\"toast-message\" [innerHTML]=\"toast.message\"></div>\n </div>\n\n <!-- Close -->\n <button class=\"toast-close\" (click)=\"close(toast.id)\" aria-label=\"Close\">\n <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\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n\n <!-- Auto-dismiss progress bar \u2014 hidden when duration <= 0 (persistent toast) -->\n @if (toast.duration == null || toast.duration > 0) {\n <div class=\"toast-progress\" [style.animation-duration]=\"(toast.duration ?? 5000) + 'ms'\"></div>\n }\n </div>\n }\n</div>\n", styles: [".toast-container{position:fixed;top:20px;right:20px;z-index:9999;pointer-events:none;display:flex;flex-direction:column;gap:10px}.toast{position:relative;display:flex;align-items:flex-start;gap:11px;padding:13px 13px 16px 16px;border-radius:12px;background:var(--bg-primary);border:1px solid var(--border-color);box-shadow:0 8px 28px var(--shadow-lg),0 2px 8px var(--shadow);pointer-events:auto;min-width:300px;max-width:420px;overflow:hidden;animation:toast-in .35s cubic-bezier(.16,1,.3,1)}@keyframes toast-in{0%{opacity:0;transform:translate(36px) scale(.96)}to{opacity:1;transform:translate(0) scale(1)}}.toast:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:4px;border-radius:12px 0 0 12px}.toast-info:before{background:var(--info-color)}.toast-success:before{background:var(--success-color)}.toast-warning:before{background:var(--warning-color)}.toast-error:before{background:var(--error-color)}.toast-icon{flex-shrink:0;width:34px;height:34px;border-radius:9px;display:flex;align-items:center;justify-content:center;margin-left:2px}.toast-info .toast-icon{color:var(--info-color);background:var(--info-bg)}.toast-success .toast-icon{color:var(--success-color);background:var(--success-bg)}.toast-warning .toast-icon{color:var(--warning-color);background:var(--warning-bg)}.toast-error .toast-icon{color:var(--error-color);background:var(--error-bg)}.toast-content{flex:1;min-width:0;padding-top:1px}.toast-title{font-weight:700;font-size:13.5px;margin-bottom:3px;line-height:1.3}.toast-info .toast-title{color:var(--info-color)}.toast-success .toast-title{color:var(--success-color)}.toast-warning .toast-title{color:var(--warning-color)}.toast-error .toast-title{color:var(--error-color)}.toast-message{font-size:12.5px;line-height:1.45;color:var(--text-secondary)}.toast-close{background:none;border:none;cursor:pointer;color:var(--text-secondary);width:26px;height:26px;border-radius:6px;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:0;transition:color .15s,background-color .15s}.toast-close:hover{color:var(--text-primary);background:var(--border-color)}.toast-progress{position:absolute;bottom:0;left:4px;right:0;height:3px;border-radius:0 0 12px;animation:toast-progress linear forwards;opacity:.7}.toast-info .toast-progress{background:var(--info-color)}.toast-success .toast-progress{background:var(--success-color)}.toast-warning .toast-progress{background:var(--warning-color)}.toast-error .toast-progress{background:var(--error-color)}@keyframes toast-progress{0%{width:calc(100% - 4px)}to{width:0}}@media(max-width:600px){.toast-container{top:10px;right:10px;left:10px}.toast{min-width:auto;max-width:100%}}\n"] }]
|
|
857
687
|
}], ctorParameters: () => [{ type: ToastService }, { type: ThemeService }], propDecorators: { themeClass: [{
|
|
858
688
|
type: HostBinding,
|
|
859
689
|
args: ['class']
|
|
@@ -1023,14 +853,11 @@ class NotificationPanelComponent {
|
|
|
1023
853
|
const readNotificationIds = this.notifications
|
|
1024
854
|
.filter(n => n.isRead)
|
|
1025
855
|
.map(n => n.id);
|
|
1026
|
-
// Delete all read notifications
|
|
1027
856
|
const deletePromises = readNotificationIds.map(id => this.authService.deleteNotification(id).toPromise());
|
|
1028
857
|
Promise.all(deletePromises).then(() => {
|
|
1029
|
-
// Remove all read notifications from the local array
|
|
1030
858
|
this.notifications = this.notifications.filter(n => !n.isRead);
|
|
1031
859
|
this.onNotificationsChanged();
|
|
1032
860
|
}).catch((err) => {
|
|
1033
|
-
// If bulk delete fails, reload notifications to get current state
|
|
1034
861
|
this.loadNotifications();
|
|
1035
862
|
});
|
|
1036
863
|
}
|
|
@@ -1038,15 +865,12 @@ class NotificationPanelComponent {
|
|
|
1038
865
|
const unreadNotificationIds = this.notifications
|
|
1039
866
|
.filter(n => !n.isRead)
|
|
1040
867
|
.map(n => n.id);
|
|
1041
|
-
// Delete all unread notifications
|
|
1042
868
|
const deletePromises = unreadNotificationIds.map(id => this.authService.deleteNotification(id).toPromise());
|
|
1043
869
|
Promise.all(deletePromises).then(() => {
|
|
1044
|
-
// Remove all unread notifications from the local array
|
|
1045
870
|
this.notifications = this.notifications.filter(n => n.isRead);
|
|
1046
871
|
this.notificationRead.emit();
|
|
1047
872
|
this.onNotificationsChanged();
|
|
1048
873
|
}).catch((err) => {
|
|
1049
|
-
// If bulk delete fails, reload notifications to get current state
|
|
1050
874
|
this.loadNotifications();
|
|
1051
875
|
});
|
|
1052
876
|
}
|
|
@@ -1107,8 +931,6 @@ class NotificationPanelComponent {
|
|
|
1107
931
|
}
|
|
1108
932
|
// Parse date string from server (stored in UTC but without 'Z' suffix or 'T' separator)
|
|
1109
933
|
parseUtcDate(dateStr) {
|
|
1110
|
-
// Handle date strings that might be missing the 'T' separator
|
|
1111
|
-
// Convert formats like "2023-12-01 12:30:45" to "2023-12-01T12:30:45"
|
|
1112
934
|
let normalized = dateStr.includes('T') ? dateStr : dateStr.replace(' ', 'T');
|
|
1113
935
|
// If no timezone indicator, assume UTC by appending 'Z'
|
|
1114
936
|
if (!normalized.endsWith('Z') && !normalized.includes('+') && !normalized.includes('-', 10)) {
|
|
@@ -1117,323 +939,11 @@ class NotificationPanelComponent {
|
|
|
1117
939
|
return normalized;
|
|
1118
940
|
}
|
|
1119
941
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: NotificationPanelComponent, deps: [{ token: MesAuthService }, { token: ToastService }, { token: ThemeService }], target: i0.ɵɵFactoryTarget.Component });
|
|
1120
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.7", type: NotificationPanelComponent, isStandalone: true, selector: "ma-notification-panel", outputs: { notificationRead: "notificationRead" }, host: { properties: { "class": "this.themeClass" } }, ngImport: i0, template: `
|
|
1121
|
-
<div class="notification-panel" [class.open]="isOpen">
|
|
1122
|
-
<!-- Header -->
|
|
1123
|
-
<div class="panel-header">
|
|
1124
|
-
<div class="panel-header-left">
|
|
1125
|
-
<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">
|
|
1126
|
-
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
|
|
1127
|
-
<path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
|
1128
|
-
</svg>
|
|
1129
|
-
<h3>Notifications</h3>
|
|
1130
|
-
</div>
|
|
1131
|
-
<button class="close-btn" (click)="close()" title="Close" aria-label="Close notifications">
|
|
1132
|
-
<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">
|
|
1133
|
-
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
1134
|
-
</svg>
|
|
1135
|
-
</button>
|
|
1136
|
-
</div>
|
|
1137
|
-
|
|
1138
|
-
<!-- Tabs -->
|
|
1139
|
-
<div class="tabs">
|
|
1140
|
-
<button class="tab-btn" [class.active]="activeTab === 'unread'" (click)="switchTab('unread')">
|
|
1141
|
-
Unread
|
|
1142
|
-
<span class="tab-badge" *ngIf="unreadNotifications.length > 0">{{ unreadNotifications.length }}</span>
|
|
1143
|
-
</button>
|
|
1144
|
-
<button class="tab-btn" [class.active]="activeTab === 'read'" (click)="switchTab('read')">
|
|
1145
|
-
Read
|
|
1146
|
-
<span class="tab-badge read-badge" *ngIf="readNotifications.length > 0">{{ readNotifications.length }}</span>
|
|
1147
|
-
</button>
|
|
1148
|
-
</div>
|
|
1149
|
-
|
|
1150
|
-
<!-- Notifications List -->
|
|
1151
|
-
<div class="notifications-list">
|
|
1152
|
-
<ng-container *ngIf="currentNotifications.length > 0">
|
|
1153
|
-
<div
|
|
1154
|
-
*ngFor="let notification of currentNotifications"
|
|
1155
|
-
class="notification-item"
|
|
1156
|
-
[class.unread]="!notification.isRead"
|
|
1157
|
-
(click)="openDetails(notification)"
|
|
1158
|
-
>
|
|
1159
|
-
@let t = typeOf(notification);
|
|
1160
|
-
<div class="notif-accent"
|
|
1161
|
-
[class.type-info]="t === 'Info'"
|
|
1162
|
-
[class.type-success]="t === 'Success'"
|
|
1163
|
-
[class.type-warning]="t === 'Warning'"
|
|
1164
|
-
[class.type-error]="t === 'Error'"></div>
|
|
1165
|
-
<div class="notif-type-icon"
|
|
1166
|
-
[class.type-info]="t === 'Info'"
|
|
1167
|
-
[class.type-success]="t === 'Success'"
|
|
1168
|
-
[class.type-warning]="t === 'Warning'"
|
|
1169
|
-
[class.type-error]="t === 'Error'">
|
|
1170
|
-
<svg *ngIf="t === 'Info'" xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
1171
|
-
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
|
1172
|
-
</svg>
|
|
1173
|
-
<svg *ngIf="t === 'Success'" xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
1174
|
-
<polyline points="20 6 9 17 4 12"/>
|
|
1175
|
-
</svg>
|
|
1176
|
-
<svg *ngIf="t === 'Warning'" xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
1177
|
-
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>
|
|
1178
|
-
</svg>
|
|
1179
|
-
<svg *ngIf="t === 'Error'" xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
1180
|
-
<circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/>
|
|
1181
|
-
</svg>
|
|
1182
|
-
</div>
|
|
1183
|
-
<div class="notification-content">
|
|
1184
|
-
<div class="notification-title">{{ notification.title }}</div>
|
|
1185
|
-
<div class="notification-message">{{ getNotificationMessage(notification) }}</div>
|
|
1186
|
-
<div class="notification-meta">
|
|
1187
|
-
<span class="app-name">{{ notification.sourceAppName }}</span>
|
|
1188
|
-
<span class="time">{{ dateLabels.get(notification.id) }}</span>
|
|
1189
|
-
</div>
|
|
1190
|
-
</div>
|
|
1191
|
-
<button class="icon-btn read-btn" (click)="markAsRead(notification.id, $event)" title="Mark as read" aria-label="Mark as read" *ngIf="!notification.isRead">
|
|
1192
|
-
<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">
|
|
1193
|
-
<polyline points="20 6 9 17 4 12"/>
|
|
1194
|
-
</svg>
|
|
1195
|
-
</button>
|
|
1196
|
-
<button class="icon-btn delete-btn" (click)="delete(notification.id, $event)" title="Delete" aria-label="Delete notification" *ngIf="notification.isRead">
|
|
1197
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1198
|
-
<polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/>
|
|
1199
|
-
</svg>
|
|
1200
|
-
</button>
|
|
1201
|
-
</div>
|
|
1202
|
-
</ng-container>
|
|
1203
|
-
|
|
1204
|
-
<ng-container *ngIf="currentNotifications.length === 0">
|
|
1205
|
-
<div class="empty-state">
|
|
1206
|
-
<svg class="empty-icon" xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
1207
|
-
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
|
|
1208
|
-
<path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
|
1209
|
-
</svg>
|
|
1210
|
-
<p>No {{ activeTab }} notifications</p>
|
|
1211
|
-
</div>
|
|
1212
|
-
</ng-container>
|
|
1213
|
-
</div>
|
|
1214
|
-
|
|
1215
|
-
<!-- Footer Actions -->
|
|
1216
|
-
<div class="panel-footer" *ngIf="currentNotifications.length > 0">
|
|
1217
|
-
<div class="footer-actions" *ngIf="activeTab === 'unread'">
|
|
1218
|
-
<button class="action-btn" (click)="markAllAsRead()" *ngIf="unreadNotifications.length > 0">
|
|
1219
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
|
1220
|
-
Mark all read
|
|
1221
|
-
</button>
|
|
1222
|
-
<button class="action-btn delete-all-btn" (click)="deleteAllUnread()" *ngIf="unreadNotifications.length > 0">
|
|
1223
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/></svg>
|
|
1224
|
-
Delete all
|
|
1225
|
-
</button>
|
|
1226
|
-
</div>
|
|
1227
|
-
<button class="action-btn delete-all-btn" (click)="deleteAllRead()" *ngIf="activeTab === 'read' && readNotifications.length > 0">
|
|
1228
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/></svg>
|
|
1229
|
-
Delete all
|
|
1230
|
-
</button>
|
|
1231
|
-
</div>
|
|
1232
|
-
</div>
|
|
1233
|
-
|
|
1234
|
-
<!-- Details Modal -->
|
|
1235
|
-
<div class="modal-overlay" *ngIf="selectedNotification" (click)="closeDetails()">
|
|
1236
|
-
<div class="modal-container" (click)="$event.stopPropagation()">
|
|
1237
|
-
<div class="modal-header"
|
|
1238
|
-
[class.modal-type-info]="typeOf(selectedNotification) === 'Info'"
|
|
1239
|
-
[class.modal-type-success]="typeOf(selectedNotification) === 'Success'"
|
|
1240
|
-
[class.modal-type-warning]="typeOf(selectedNotification) === 'Warning'"
|
|
1241
|
-
[class.modal-type-error]="typeOf(selectedNotification) === 'Error'">
|
|
1242
|
-
<div class="modal-header-left">
|
|
1243
|
-
<div class="modal-type-icon">
|
|
1244
|
-
<svg *ngIf="typeOf(selectedNotification) === 'Info'" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1245
|
-
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
|
1246
|
-
</svg>
|
|
1247
|
-
<svg *ngIf="typeOf(selectedNotification) === 'Success'" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1248
|
-
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>
|
|
1249
|
-
</svg>
|
|
1250
|
-
<svg *ngIf="typeOf(selectedNotification) === 'Warning'" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1251
|
-
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/>
|
|
1252
|
-
</svg>
|
|
1253
|
-
<svg *ngIf="typeOf(selectedNotification) === 'Error'" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1254
|
-
<circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/>
|
|
1255
|
-
</svg>
|
|
1256
|
-
</div>
|
|
1257
|
-
<h3>{{ selectedNotification.title }}</h3>
|
|
1258
|
-
</div>
|
|
1259
|
-
<button class="close-btn" (click)="closeDetails()" title="Close" aria-label="Close notification detail">
|
|
1260
|
-
<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">
|
|
1261
|
-
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
1262
|
-
</svg>
|
|
1263
|
-
</button>
|
|
1264
|
-
</div>
|
|
1265
|
-
<div class="modal-meta">
|
|
1266
|
-
<span class="app-name">{{ selectedNotification.sourceAppName }}</span>
|
|
1267
|
-
<span class="time">{{ selectedNotificationDate }}</span>
|
|
1268
|
-
</div>
|
|
1269
|
-
<div class="modal-body" [innerHTML]="selectedNotificationHtml"></div>
|
|
1270
|
-
<div class="modal-footer">
|
|
1271
|
-
<button class="action-btn see-details-btn" *ngIf="selectedNotification.url?.trim()" (click)="openUrl()">See Details</button>
|
|
1272
|
-
<button class="action-btn" (click)="closeDetails()">Close</button>
|
|
1273
|
-
</div>
|
|
1274
|
-
</div>
|
|
1275
|
-
</div>
|
|
1276
|
-
`, isInline: true, styles: [".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;color:var(--text-muted);cursor:pointer;font-size:13px;font-weight:500;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}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;gap:12px;color:var(--text-muted)}.empty-state p{margin:0;font-size:13px}.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)}}\n", ":host{display:block;position:relative;--primary: #1976d2;--primary-hover: #1565c0;--success: #43a047;--error: #f44336;--error-hover: #d32f2f;--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){--primary: #90caf9;--primary-hover: #64b5f6;--success: #66bb6a;--error: #ef5350;--error-hover: #c62828;--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)}.tab-btn:not(.active) .tab-badge{background:var(--error)}.read-badge{background:var(--text-muted)}.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}.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)}.notif-accent.type-warning{background:var(--warning-color)}.notif-accent.type-error{background:var(--error)}.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);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);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)}.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);background:#43a0471a}.delete-btn:hover{color:var(--error);background:#f443361a}.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:#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)}.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)}.modal-header.modal-type-warning{border-top-color:var(--warning-color)}.modal-header.modal-type-error{border-top-color:var(--error)}.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);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);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;gap:8px}.modal-footer .action-btn{width:auto;padding:8px 24px}.modal-footer .see-details-btn{background:var(--info-bg);color:var(--info-color);border:1px solid var(--info-color)}.modal-footer .see-details-btn:hover{opacity:.85}@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"] }] });
|
|
942
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: NotificationPanelComponent, isStandalone: true, selector: "ma-notification-panel", outputs: { notificationRead: "notificationRead" }, host: { properties: { "class": "this.themeClass" } }, ngImport: i0, template: "<div class=\"notification-panel\" [class.open]=\"isOpen\">\n <!-- Header -->\n <div class=\"panel-header\">\n <div class=\"panel-header-left\">\n <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\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/>\n <path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n <h3>Notifications</h3>\n </div>\n <button class=\"close-btn\" (click)=\"close()\" title=\"Close\" aria-label=\"Close notifications\">\n <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\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n\n <!-- Tabs -->\n <div class=\"tabs\">\n <button class=\"tab-btn\" [class.active]=\"activeTab === 'unread'\" (click)=\"switchTab('unread')\">\n Unread\n @if (unreadNotifications.length > 0) {\n <span class=\"tab-badge\">{{ unreadNotifications.length }}</span>\n }\n </button>\n <button class=\"tab-btn\" [class.active]=\"activeTab === 'read'\" (click)=\"switchTab('read')\">\n Read\n @if (readNotifications.length > 0) {\n <span class=\"tab-badge read-badge\">{{ readNotifications.length }}</span>\n }\n </button>\n </div>\n\n <!-- Notifications List -->\n <div class=\"notifications-list\">\n @if (currentNotifications.length > 0) {\n @for (notification of currentNotifications; track notification.id) {\n <div\n class=\"notification-item\"\n [class.unread]=\"!notification.isRead\"\n (click)=\"openDetails(notification)\"\n >\n @let t = typeOf(notification);\n <div class=\"notif-accent\"\n [class.type-info]=\"t === 'Info'\"\n [class.type-success]=\"t === 'Success'\"\n [class.type-warning]=\"t === 'Warning'\"\n [class.type-error]=\"t === 'Error'\"></div>\n <div class=\"notif-type-icon\"\n [class.type-info]=\"t === 'Info'\"\n [class.type-success]=\"t === 'Success'\"\n [class.type-warning]=\"t === 'Warning'\"\n [class.type-error]=\"t === 'Error'\">\n @if (t === 'Info') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n }\n @if (t === 'Success') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\"/>\n </svg>\n }\n @if (t === 'Warning') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/>\n </svg>\n }\n @if (t === 'Error') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\"/><line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\"/>\n </svg>\n }\n </div>\n <div class=\"notification-content\">\n <div class=\"notification-title\">{{ notification.title }}</div>\n <div class=\"notification-message\">{{ getNotificationMessage(notification) }}</div>\n <div class=\"notification-meta\">\n <span class=\"app-name\">{{ notification.sourceAppName }}</span>\n <span class=\"time\">{{ dateLabels.get(notification.id) }}</span>\n </div>\n </div>\n @if (!notification.isRead) {\n <button class=\"icon-btn read-btn\" (click)=\"markAsRead(notification.id, $event)\" title=\"Mark as read\" aria-label=\"Mark as read\">\n <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\">\n <polyline points=\"20 6 9 17 4 12\"/>\n </svg>\n </button>\n }\n @if (notification.isRead) {\n <button class=\"icon-btn delete-btn\" (click)=\"delete(notification.id, $event)\" title=\"Delete\" aria-label=\"Delete notification\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/><path d=\"M10 11v6\"/><path d=\"M14 11v6\"/><path d=\"M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2\"/>\n </svg>\n </button>\n }\n </div>\n }\n } @else {\n <div class=\"empty-state\">\n <svg class=\"empty-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"44\" height=\"44\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/>\n <path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n <p>No {{ activeTab }} notifications</p>\n </div>\n }\n </div>\n\n <!-- Footer Actions -->\n @if (currentNotifications.length > 0) {\n <div class=\"panel-footer\">\n @if (activeTab === 'unread') {\n <div class=\"footer-actions\">\n @if (unreadNotifications.length > 0) {\n <button class=\"action-btn\" (click)=\"markAllAsRead()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg>\n Mark all read\n </button>\n <button class=\"action-btn delete-all-btn\" (click)=\"deleteAllUnread()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/></svg>\n Delete all\n </button>\n }\n </div>\n }\n @if (activeTab === 'read' && readNotifications.length > 0) {\n <button class=\"action-btn delete-all-btn\" (click)=\"deleteAllRead()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/></svg>\n Delete all\n </button>\n }\n </div>\n }\n</div>\n\n<!-- Details Modal -->\n@if (selectedNotification) {\n <div class=\"modal-overlay\" (click)=\"closeDetails()\">\n <div class=\"modal-container\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\"\n [class.modal-type-info]=\"typeOf(selectedNotification) === 'Info'\"\n [class.modal-type-success]=\"typeOf(selectedNotification) === 'Success'\"\n [class.modal-type-warning]=\"typeOf(selectedNotification) === 'Warning'\"\n [class.modal-type-error]=\"typeOf(selectedNotification) === 'Error'\">\n <div class=\"modal-header-left\">\n <div class=\"modal-type-icon\">\n @if (typeOf(selectedNotification) === 'Info') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n }\n @if (typeOf(selectedNotification) === 'Success') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M22 11.08V12a10 10 0 1 1-5.93-9.14\"/><polyline points=\"22 4 12 14.01 9 11.01\"/>\n </svg>\n }\n @if (typeOf(selectedNotification) === 'Warning') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/>\n </svg>\n }\n @if (typeOf(selectedNotification) === 'Error') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\"/><line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\"/>\n </svg>\n }\n </div>\n <h3>{{ selectedNotification.title }}</h3>\n </div>\n <button class=\"close-btn\" (click)=\"closeDetails()\" title=\"Close\" aria-label=\"Close notification detail\">\n <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\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n <div class=\"modal-meta\">\n <span class=\"app-name\">{{ selectedNotification.sourceAppName }}</span>\n <span class=\"time\">{{ selectedNotificationDate }}</span>\n </div>\n <div class=\"modal-body\" [innerHTML]=\"selectedNotificationHtml\"></div>\n <div class=\"modal-footer\">\n @if (selectedNotification.url?.trim()) {\n <button class=\"action-btn see-details-btn\" (click)=\"openUrl()\">See Details</button>\n }\n <button class=\"action-btn\" (click)=\"closeDetails()\">Close</button>\n </div>\n </div>\n </div>\n}\n", styles: [".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;color:var(--text-muted);cursor:pointer;font-size:13px;font-weight:500;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}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;gap:12px;color:var(--text-muted)}.empty-state p{margin:0;font-size:13px}.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)}}\n", ":host{display:block;position:relative;--primary: #1976d2;--primary-hover: #1565c0;--success: #43a047;--error: #f44336;--error-hover: #d32f2f;--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)}.tab-btn:not(.active) .tab-badge{background:var(--error)}.read-badge{background:var(--text-muted)}.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}.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)}.notif-accent.type-warning{background:var(--warning-color)}.notif-accent.type-error{background:var(--error)}.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);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);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)}.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);background:#43a0471a}.delete-btn:hover{color:var(--error);background:#f443361a}.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:#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)}.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)}.modal-header.modal-type-warning{border-top-color:var(--warning-color)}.modal-header.modal-type-error{border-top-color:var(--error)}.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);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);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;gap:8px}.modal-footer .action-btn{width:auto;padding:8px 24px}.modal-footer .see-details-btn{background:var(--info-bg);color:var(--info-color);border:1px solid var(--info-color)}.modal-footer .see-details-btn:hover{opacity:.85}@media(max-width:600px){.notification-panel{width:100%;right:-100%}.modal-container{width:95%;max-height:90vh}}\n"] });
|
|
1277
943
|
}
|
|
1278
944
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: NotificationPanelComponent, decorators: [{
|
|
1279
945
|
type: Component,
|
|
1280
|
-
args: [{ selector: 'ma-notification-panel', standalone: true, imports: [NgIf, NgFor], template: `
|
|
1281
|
-
<div class="notification-panel" [class.open]="isOpen">
|
|
1282
|
-
<!-- Header -->
|
|
1283
|
-
<div class="panel-header">
|
|
1284
|
-
<div class="panel-header-left">
|
|
1285
|
-
<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">
|
|
1286
|
-
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
|
|
1287
|
-
<path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
|
1288
|
-
</svg>
|
|
1289
|
-
<h3>Notifications</h3>
|
|
1290
|
-
</div>
|
|
1291
|
-
<button class="close-btn" (click)="close()" title="Close" aria-label="Close notifications">
|
|
1292
|
-
<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">
|
|
1293
|
-
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
1294
|
-
</svg>
|
|
1295
|
-
</button>
|
|
1296
|
-
</div>
|
|
1297
|
-
|
|
1298
|
-
<!-- Tabs -->
|
|
1299
|
-
<div class="tabs">
|
|
1300
|
-
<button class="tab-btn" [class.active]="activeTab === 'unread'" (click)="switchTab('unread')">
|
|
1301
|
-
Unread
|
|
1302
|
-
<span class="tab-badge" *ngIf="unreadNotifications.length > 0">{{ unreadNotifications.length }}</span>
|
|
1303
|
-
</button>
|
|
1304
|
-
<button class="tab-btn" [class.active]="activeTab === 'read'" (click)="switchTab('read')">
|
|
1305
|
-
Read
|
|
1306
|
-
<span class="tab-badge read-badge" *ngIf="readNotifications.length > 0">{{ readNotifications.length }}</span>
|
|
1307
|
-
</button>
|
|
1308
|
-
</div>
|
|
1309
|
-
|
|
1310
|
-
<!-- Notifications List -->
|
|
1311
|
-
<div class="notifications-list">
|
|
1312
|
-
<ng-container *ngIf="currentNotifications.length > 0">
|
|
1313
|
-
<div
|
|
1314
|
-
*ngFor="let notification of currentNotifications"
|
|
1315
|
-
class="notification-item"
|
|
1316
|
-
[class.unread]="!notification.isRead"
|
|
1317
|
-
(click)="openDetails(notification)"
|
|
1318
|
-
>
|
|
1319
|
-
@let t = typeOf(notification);
|
|
1320
|
-
<div class="notif-accent"
|
|
1321
|
-
[class.type-info]="t === 'Info'"
|
|
1322
|
-
[class.type-success]="t === 'Success'"
|
|
1323
|
-
[class.type-warning]="t === 'Warning'"
|
|
1324
|
-
[class.type-error]="t === 'Error'"></div>
|
|
1325
|
-
<div class="notif-type-icon"
|
|
1326
|
-
[class.type-info]="t === 'Info'"
|
|
1327
|
-
[class.type-success]="t === 'Success'"
|
|
1328
|
-
[class.type-warning]="t === 'Warning'"
|
|
1329
|
-
[class.type-error]="t === 'Error'">
|
|
1330
|
-
<svg *ngIf="t === 'Info'" xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
1331
|
-
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
|
1332
|
-
</svg>
|
|
1333
|
-
<svg *ngIf="t === 'Success'" xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
1334
|
-
<polyline points="20 6 9 17 4 12"/>
|
|
1335
|
-
</svg>
|
|
1336
|
-
<svg *ngIf="t === 'Warning'" xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
1337
|
-
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>
|
|
1338
|
-
</svg>
|
|
1339
|
-
<svg *ngIf="t === 'Error'" xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
|
|
1340
|
-
<circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/>
|
|
1341
|
-
</svg>
|
|
1342
|
-
</div>
|
|
1343
|
-
<div class="notification-content">
|
|
1344
|
-
<div class="notification-title">{{ notification.title }}</div>
|
|
1345
|
-
<div class="notification-message">{{ getNotificationMessage(notification) }}</div>
|
|
1346
|
-
<div class="notification-meta">
|
|
1347
|
-
<span class="app-name">{{ notification.sourceAppName }}</span>
|
|
1348
|
-
<span class="time">{{ dateLabels.get(notification.id) }}</span>
|
|
1349
|
-
</div>
|
|
1350
|
-
</div>
|
|
1351
|
-
<button class="icon-btn read-btn" (click)="markAsRead(notification.id, $event)" title="Mark as read" aria-label="Mark as read" *ngIf="!notification.isRead">
|
|
1352
|
-
<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">
|
|
1353
|
-
<polyline points="20 6 9 17 4 12"/>
|
|
1354
|
-
</svg>
|
|
1355
|
-
</button>
|
|
1356
|
-
<button class="icon-btn delete-btn" (click)="delete(notification.id, $event)" title="Delete" aria-label="Delete notification" *ngIf="notification.isRead">
|
|
1357
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1358
|
-
<polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/>
|
|
1359
|
-
</svg>
|
|
1360
|
-
</button>
|
|
1361
|
-
</div>
|
|
1362
|
-
</ng-container>
|
|
1363
|
-
|
|
1364
|
-
<ng-container *ngIf="currentNotifications.length === 0">
|
|
1365
|
-
<div class="empty-state">
|
|
1366
|
-
<svg class="empty-icon" xmlns="http://www.w3.org/2000/svg" width="44" height="44" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
|
1367
|
-
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
|
|
1368
|
-
<path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
|
1369
|
-
</svg>
|
|
1370
|
-
<p>No {{ activeTab }} notifications</p>
|
|
1371
|
-
</div>
|
|
1372
|
-
</ng-container>
|
|
1373
|
-
</div>
|
|
1374
|
-
|
|
1375
|
-
<!-- Footer Actions -->
|
|
1376
|
-
<div class="panel-footer" *ngIf="currentNotifications.length > 0">
|
|
1377
|
-
<div class="footer-actions" *ngIf="activeTab === 'unread'">
|
|
1378
|
-
<button class="action-btn" (click)="markAllAsRead()" *ngIf="unreadNotifications.length > 0">
|
|
1379
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
|
1380
|
-
Mark all read
|
|
1381
|
-
</button>
|
|
1382
|
-
<button class="action-btn delete-all-btn" (click)="deleteAllUnread()" *ngIf="unreadNotifications.length > 0">
|
|
1383
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/></svg>
|
|
1384
|
-
Delete all
|
|
1385
|
-
</button>
|
|
1386
|
-
</div>
|
|
1387
|
-
<button class="action-btn delete-all-btn" (click)="deleteAllRead()" *ngIf="activeTab === 'read' && readNotifications.length > 0">
|
|
1388
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/></svg>
|
|
1389
|
-
Delete all
|
|
1390
|
-
</button>
|
|
1391
|
-
</div>
|
|
1392
|
-
</div>
|
|
1393
|
-
|
|
1394
|
-
<!-- Details Modal -->
|
|
1395
|
-
<div class="modal-overlay" *ngIf="selectedNotification" (click)="closeDetails()">
|
|
1396
|
-
<div class="modal-container" (click)="$event.stopPropagation()">
|
|
1397
|
-
<div class="modal-header"
|
|
1398
|
-
[class.modal-type-info]="typeOf(selectedNotification) === 'Info'"
|
|
1399
|
-
[class.modal-type-success]="typeOf(selectedNotification) === 'Success'"
|
|
1400
|
-
[class.modal-type-warning]="typeOf(selectedNotification) === 'Warning'"
|
|
1401
|
-
[class.modal-type-error]="typeOf(selectedNotification) === 'Error'">
|
|
1402
|
-
<div class="modal-header-left">
|
|
1403
|
-
<div class="modal-type-icon">
|
|
1404
|
-
<svg *ngIf="typeOf(selectedNotification) === 'Info'" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1405
|
-
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
|
1406
|
-
</svg>
|
|
1407
|
-
<svg *ngIf="typeOf(selectedNotification) === 'Success'" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1408
|
-
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>
|
|
1409
|
-
</svg>
|
|
1410
|
-
<svg *ngIf="typeOf(selectedNotification) === 'Warning'" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1411
|
-
<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/>
|
|
1412
|
-
</svg>
|
|
1413
|
-
<svg *ngIf="typeOf(selectedNotification) === 'Error'" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
1414
|
-
<circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/>
|
|
1415
|
-
</svg>
|
|
1416
|
-
</div>
|
|
1417
|
-
<h3>{{ selectedNotification.title }}</h3>
|
|
1418
|
-
</div>
|
|
1419
|
-
<button class="close-btn" (click)="closeDetails()" title="Close" aria-label="Close notification detail">
|
|
1420
|
-
<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">
|
|
1421
|
-
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
1422
|
-
</svg>
|
|
1423
|
-
</button>
|
|
1424
|
-
</div>
|
|
1425
|
-
<div class="modal-meta">
|
|
1426
|
-
<span class="app-name">{{ selectedNotification.sourceAppName }}</span>
|
|
1427
|
-
<span class="time">{{ selectedNotificationDate }}</span>
|
|
1428
|
-
</div>
|
|
1429
|
-
<div class="modal-body" [innerHTML]="selectedNotificationHtml"></div>
|
|
1430
|
-
<div class="modal-footer">
|
|
1431
|
-
<button class="action-btn see-details-btn" *ngIf="selectedNotification.url?.trim()" (click)="openUrl()">See Details</button>
|
|
1432
|
-
<button class="action-btn" (click)="closeDetails()">Close</button>
|
|
1433
|
-
</div>
|
|
1434
|
-
</div>
|
|
1435
|
-
</div>
|
|
1436
|
-
`, styles: [".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;color:var(--text-muted);cursor:pointer;font-size:13px;font-weight:500;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}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;gap:12px;color:var(--text-muted)}.empty-state p{margin:0;font-size:13px}.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)}}\n", ":host{display:block;position:relative;--primary: #1976d2;--primary-hover: #1565c0;--success: #43a047;--error: #f44336;--error-hover: #d32f2f;--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){--primary: #90caf9;--primary-hover: #64b5f6;--success: #66bb6a;--error: #ef5350;--error-hover: #c62828;--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)}.tab-btn:not(.active) .tab-badge{background:var(--error)}.read-badge{background:var(--text-muted)}.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}.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)}.notif-accent.type-warning{background:var(--warning-color)}.notif-accent.type-error{background:var(--error)}.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);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);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)}.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);background:#43a0471a}.delete-btn:hover{color:var(--error);background:#f443361a}.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:#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)}.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)}.modal-header.modal-type-warning{border-top-color:var(--warning-color)}.modal-header.modal-type-error{border-top-color:var(--error)}.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);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);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;gap:8px}.modal-footer .action-btn{width:auto;padding:8px 24px}.modal-footer .see-details-btn{background:var(--info-bg);color:var(--info-color);border:1px solid var(--info-color)}.modal-footer .see-details-btn:hover{opacity:.85}@media(max-width:600px){.notification-panel{width:100%;right:-100%}.modal-container{width:95%;max-height:90vh}}\n"] }]
|
|
946
|
+
args: [{ selector: 'ma-notification-panel', standalone: true, imports: [], template: "<div class=\"notification-panel\" [class.open]=\"isOpen\">\n <!-- Header -->\n <div class=\"panel-header\">\n <div class=\"panel-header-left\">\n <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\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/>\n <path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n <h3>Notifications</h3>\n </div>\n <button class=\"close-btn\" (click)=\"close()\" title=\"Close\" aria-label=\"Close notifications\">\n <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\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n\n <!-- Tabs -->\n <div class=\"tabs\">\n <button class=\"tab-btn\" [class.active]=\"activeTab === 'unread'\" (click)=\"switchTab('unread')\">\n Unread\n @if (unreadNotifications.length > 0) {\n <span class=\"tab-badge\">{{ unreadNotifications.length }}</span>\n }\n </button>\n <button class=\"tab-btn\" [class.active]=\"activeTab === 'read'\" (click)=\"switchTab('read')\">\n Read\n @if (readNotifications.length > 0) {\n <span class=\"tab-badge read-badge\">{{ readNotifications.length }}</span>\n }\n </button>\n </div>\n\n <!-- Notifications List -->\n <div class=\"notifications-list\">\n @if (currentNotifications.length > 0) {\n @for (notification of currentNotifications; track notification.id) {\n <div\n class=\"notification-item\"\n [class.unread]=\"!notification.isRead\"\n (click)=\"openDetails(notification)\"\n >\n @let t = typeOf(notification);\n <div class=\"notif-accent\"\n [class.type-info]=\"t === 'Info'\"\n [class.type-success]=\"t === 'Success'\"\n [class.type-warning]=\"t === 'Warning'\"\n [class.type-error]=\"t === 'Error'\"></div>\n <div class=\"notif-type-icon\"\n [class.type-info]=\"t === 'Info'\"\n [class.type-success]=\"t === 'Success'\"\n [class.type-warning]=\"t === 'Warning'\"\n [class.type-error]=\"t === 'Error'\">\n @if (t === 'Info') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n }\n @if (t === 'Success') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"20 6 9 17 4 12\"/>\n </svg>\n }\n @if (t === 'Warning') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/><line x1=\"12\" y1=\"17\" x2=\"12.01\" y2=\"17\"/>\n </svg>\n }\n @if (t === 'Error') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\"/><line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\"/>\n </svg>\n }\n </div>\n <div class=\"notification-content\">\n <div class=\"notification-title\">{{ notification.title }}</div>\n <div class=\"notification-message\">{{ getNotificationMessage(notification) }}</div>\n <div class=\"notification-meta\">\n <span class=\"app-name\">{{ notification.sourceAppName }}</span>\n <span class=\"time\">{{ dateLabels.get(notification.id) }}</span>\n </div>\n </div>\n @if (!notification.isRead) {\n <button class=\"icon-btn read-btn\" (click)=\"markAsRead(notification.id, $event)\" title=\"Mark as read\" aria-label=\"Mark as read\">\n <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\">\n <polyline points=\"20 6 9 17 4 12\"/>\n </svg>\n </button>\n }\n @if (notification.isRead) {\n <button class=\"icon-btn delete-btn\" (click)=\"delete(notification.id, $event)\" title=\"Delete\" aria-label=\"Delete notification\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/><path d=\"M10 11v6\"/><path d=\"M14 11v6\"/><path d=\"M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2\"/>\n </svg>\n </button>\n }\n </div>\n }\n } @else {\n <div class=\"empty-state\">\n <svg class=\"empty-icon\" xmlns=\"http://www.w3.org/2000/svg\" width=\"44\" height=\"44\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/>\n <path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n <p>No {{ activeTab }} notifications</p>\n </div>\n }\n </div>\n\n <!-- Footer Actions -->\n @if (currentNotifications.length > 0) {\n <div class=\"panel-footer\">\n @if (activeTab === 'unread') {\n <div class=\"footer-actions\">\n @if (unreadNotifications.length > 0) {\n <button class=\"action-btn\" (click)=\"markAllAsRead()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"20 6 9 17 4 12\"/></svg>\n Mark all read\n </button>\n <button class=\"action-btn delete-all-btn\" (click)=\"deleteAllUnread()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/></svg>\n Delete all\n </button>\n }\n </div>\n }\n @if (activeTab === 'read' && readNotifications.length > 0) {\n <button class=\"action-btn delete-all-btn\" (click)=\"deleteAllRead()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><polyline points=\"3 6 5 6 21 6\"/><path d=\"M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6\"/></svg>\n Delete all\n </button>\n }\n </div>\n }\n</div>\n\n<!-- Details Modal -->\n@if (selectedNotification) {\n <div class=\"modal-overlay\" (click)=\"closeDetails()\">\n <div class=\"modal-container\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\"\n [class.modal-type-info]=\"typeOf(selectedNotification) === 'Info'\"\n [class.modal-type-success]=\"typeOf(selectedNotification) === 'Success'\"\n [class.modal-type-warning]=\"typeOf(selectedNotification) === 'Warning'\"\n [class.modal-type-error]=\"typeOf(selectedNotification) === 'Error'\">\n <div class=\"modal-header-left\">\n <div class=\"modal-type-icon\">\n @if (typeOf(selectedNotification) === 'Info') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9\"/><path d=\"M13.73 21a2 2 0 0 1-3.46 0\"/>\n </svg>\n }\n @if (typeOf(selectedNotification) === 'Success') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M22 11.08V12a10 10 0 1 1-5.93-9.14\"/><polyline points=\"22 4 12 14.01 9 11.01\"/>\n </svg>\n }\n @if (typeOf(selectedNotification) === 'Warning') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z\"/><line x1=\"12\" y1=\"9\" x2=\"12\" y2=\"13\"/>\n </svg>\n }\n @if (typeOf(selectedNotification) === 'Error') {\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/><line x1=\"15\" y1=\"9\" x2=\"9\" y2=\"15\"/><line x1=\"9\" y1=\"9\" x2=\"15\" y2=\"15\"/>\n </svg>\n }\n </div>\n <h3>{{ selectedNotification.title }}</h3>\n </div>\n <button class=\"close-btn\" (click)=\"closeDetails()\" title=\"Close\" aria-label=\"Close notification detail\">\n <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\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n <div class=\"modal-meta\">\n <span class=\"app-name\">{{ selectedNotification.sourceAppName }}</span>\n <span class=\"time\">{{ selectedNotificationDate }}</span>\n </div>\n <div class=\"modal-body\" [innerHTML]=\"selectedNotificationHtml\"></div>\n <div class=\"modal-footer\">\n @if (selectedNotification.url?.trim()) {\n <button class=\"action-btn see-details-btn\" (click)=\"openUrl()\">See Details</button>\n }\n <button class=\"action-btn\" (click)=\"closeDetails()\">Close</button>\n </div>\n </div>\n </div>\n}\n", styles: [".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;color:var(--text-muted);cursor:pointer;font-size:13px;font-weight:500;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}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;gap:12px;color:var(--text-muted)}.empty-state p{margin:0;font-size:13px}.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)}}\n", ":host{display:block;position:relative;--primary: #1976d2;--primary-hover: #1565c0;--success: #43a047;--error: #f44336;--error-hover: #d32f2f;--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)}.tab-btn:not(.active) .tab-badge{background:var(--error)}.read-badge{background:var(--text-muted)}.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}.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)}.notif-accent.type-warning{background:var(--warning-color)}.notif-accent.type-error{background:var(--error)}.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);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);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)}.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);background:#43a0471a}.delete-btn:hover{color:var(--error);background:#f443361a}.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:#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)}.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)}.modal-header.modal-type-warning{border-top-color:var(--warning-color)}.modal-header.modal-type-error{border-top-color:var(--error)}.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);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);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;gap:8px}.modal-footer .action-btn{width:auto;padding:8px 24px}.modal-footer .see-details-btn{background:var(--info-bg);color:var(--info-color);border:1px solid var(--info-color)}.modal-footer .see-details-btn:hover{opacity:.85}@media(max-width:600px){.notification-panel{width:100%;right:-100%}.modal-container{width:95%;max-height:90vh}}\n"] }]
|
|
1437
947
|
}], ctorParameters: () => [{ type: MesAuthService }, { type: ToastService }, { type: ThemeService }], propDecorators: { notificationRead: [{
|
|
1438
948
|
type: Output
|
|
1439
949
|
}], themeClass: [{
|
|
@@ -1670,219 +1180,11 @@ class MaApprovalPanelComponent {
|
|
|
1670
1180
|
this.router.navigate(['/auth/approval/my-requests'], { queryParams: { status } });
|
|
1671
1181
|
}
|
|
1672
1182
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: MaApprovalPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1673
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
1674
|
-
<div class="approval-backdrop" [class.open]="isOpen" (click)="close()"></div>
|
|
1675
|
-
<div class="approval-panel" [class.open]="isOpen">
|
|
1676
|
-
|
|
1677
|
-
<!-- Header -->
|
|
1678
|
-
<div class="panel-header">
|
|
1679
|
-
<div class="panel-header-left">
|
|
1680
|
-
<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">
|
|
1681
|
-
<path d="M9 11l3 3L22 4"/>
|
|
1682
|
-
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
|
|
1683
|
-
</svg>
|
|
1684
|
-
<h3>Approvals</h3>
|
|
1685
|
-
</div>
|
|
1686
|
-
<button class="close-btn" (click)="close()" aria-label="Close">
|
|
1687
|
-
<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">
|
|
1688
|
-
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
1689
|
-
</svg>
|
|
1690
|
-
</button>
|
|
1691
|
-
</div>
|
|
1692
|
-
|
|
1693
|
-
<!-- Tabs -->
|
|
1694
|
-
<div class="tabs">
|
|
1695
|
-
<button class="tab-btn" [class.active]="activeTab === 'processing'" (click)="switchTab('processing')">
|
|
1696
|
-
Processing
|
|
1697
|
-
<span class="tab-badge" *ngIf="processingItems.length > 0">{{ processingItems.length }}</span>
|
|
1698
|
-
</button>
|
|
1699
|
-
<button class="tab-btn" [class.active]="activeTab === 'approved'" (click)="switchTab('approved')">
|
|
1700
|
-
Approved
|
|
1701
|
-
</button>
|
|
1702
|
-
<button class="tab-btn" [class.active]="activeTab === 'rejected'" (click)="switchTab('rejected')">
|
|
1703
|
-
Rejected
|
|
1704
|
-
</button>
|
|
1705
|
-
</div>
|
|
1706
|
-
|
|
1707
|
-
<!-- Content -->
|
|
1708
|
-
<div class="panel-content">
|
|
1709
|
-
<!-- Loading -->
|
|
1710
|
-
<div class="loading-state" *ngIf="loading">
|
|
1711
|
-
<div class="spinner"></div>
|
|
1712
|
-
<span>Loading...</span>
|
|
1713
|
-
</div>
|
|
1714
|
-
|
|
1715
|
-
<!-- Processing tab -->
|
|
1716
|
-
<ng-container *ngIf="!loading && activeTab === 'processing'">
|
|
1717
|
-
<div class="empty-state" *ngIf="processingItems.length === 0">
|
|
1718
|
-
<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>
|
|
1719
|
-
<p>No pending approvals</p>
|
|
1720
|
-
</div>
|
|
1721
|
-
<div class="approval-item" *ngFor="let item of processingItems" (click)="navigateToDetail(item.id)">
|
|
1722
|
-
<div class="item-title">{{ item.title }}</div>
|
|
1723
|
-
<div class="item-meta">
|
|
1724
|
-
<span class="item-requester">By {{ item.requestedByUserName }}</span>
|
|
1725
|
-
<span class="item-step" *ngIf="item.currentStepName">· {{ item.currentStepName }}</span>
|
|
1726
|
-
</div>
|
|
1727
|
-
<div class="item-footer">
|
|
1728
|
-
<span class="item-time">{{ item.createdAt | date:'shortDate' }}</span>
|
|
1729
|
-
<span class="item-link">View →</span>
|
|
1730
|
-
</div>
|
|
1731
|
-
</div>
|
|
1732
|
-
</ng-container>
|
|
1733
|
-
|
|
1734
|
-
<!-- Approved tab -->
|
|
1735
|
-
<ng-container *ngIf="!loading && activeTab === 'approved'">
|
|
1736
|
-
<div class="empty-state" *ngIf="approvedItems.length === 0">
|
|
1737
|
-
<p>No approved documents</p>
|
|
1738
|
-
</div>
|
|
1739
|
-
<div class="approval-item approved" *ngFor="let item of approvedItems" (click)="navigateToDetail(item.id)">
|
|
1740
|
-
<div class="item-title">{{ item.title }}</div>
|
|
1741
|
-
<div class="item-meta">
|
|
1742
|
-
<span class="item-requester">By {{ item.requestedByUserName }}</span>
|
|
1743
|
-
</div>
|
|
1744
|
-
<div class="item-footer">
|
|
1745
|
-
<span class="status-badge approved-badge">Approved</span>
|
|
1746
|
-
<span class="item-time">{{ item.completedAt | date:'shortDate' }}</span>
|
|
1747
|
-
<span class="item-link">View →</span>
|
|
1748
|
-
</div>
|
|
1749
|
-
</div>
|
|
1750
|
-
<div class="show-more" *ngIf="approvedItems.length >= 10" (click)="showMore('approved')">
|
|
1751
|
-
Show more →
|
|
1752
|
-
</div>
|
|
1753
|
-
</ng-container>
|
|
1754
|
-
|
|
1755
|
-
<!-- Rejected tab -->
|
|
1756
|
-
<ng-container *ngIf="!loading && activeTab === 'rejected'">
|
|
1757
|
-
<div class="empty-state" *ngIf="rejectedItems.length === 0">
|
|
1758
|
-
<p>No rejected documents</p>
|
|
1759
|
-
</div>
|
|
1760
|
-
<div class="approval-item rejected" *ngFor="let item of rejectedItems" (click)="navigateToDetail(item.id)">
|
|
1761
|
-
<div class="item-title">{{ item.title }}</div>
|
|
1762
|
-
<div class="item-meta">
|
|
1763
|
-
<span class="item-requester">By {{ item.requestedByUserName }}</span>
|
|
1764
|
-
</div>
|
|
1765
|
-
<div class="item-footer">
|
|
1766
|
-
<span class="status-badge rejected-badge">Rejected</span>
|
|
1767
|
-
<span class="item-time">{{ item.completedAt | date:'shortDate' }}</span>
|
|
1768
|
-
<span class="item-link">View →</span>
|
|
1769
|
-
</div>
|
|
1770
|
-
</div>
|
|
1771
|
-
<div class="show-more" *ngIf="rejectedItems.length >= 10" (click)="showMore('rejected')">
|
|
1772
|
-
Show more →
|
|
1773
|
-
</div>
|
|
1774
|
-
</ng-container>
|
|
1775
|
-
</div>
|
|
1776
|
-
</div>
|
|
1777
|
-
`, isInline: true, styles: [".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;color:var(--text-muted);cursor:pointer;font-size:13px;font-weight:500;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}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;gap:12px;color:var(--text-muted)}.empty-state p{margin:0;font-size:13px}.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)}}\n", ":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-content{flex:1;overflow-y:auto;padding:8px 0}.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" }] });
|
|
1183
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: MaApprovalPanelComponent, isStandalone: true, selector: "ma-approval-panel", outputs: { approvalActioned: "approvalActioned" }, ngImport: i0, template: "<div class=\"approval-backdrop\" [class.open]=\"isOpen\" (click)=\"close()\"></div>\n<div class=\"approval-panel\" [class.open]=\"isOpen\">\n\n <!-- Header -->\n <div class=\"panel-header\">\n <div class=\"panel-header-left\">\n <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\">\n <path d=\"M9 11l3 3L22 4\"/>\n <path d=\"M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11\"/>\n </svg>\n <h3>Approvals</h3>\n </div>\n <button class=\"close-btn\" (click)=\"close()\" aria-label=\"Close\">\n <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\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n\n <!-- Tabs -->\n <div class=\"tabs\">\n <button class=\"tab-btn\" [class.active]=\"activeTab === 'processing'\" (click)=\"switchTab('processing')\">\n Processing\n @if (processingItems.length > 0) {\n <span class=\"tab-badge\">{{ processingItems.length }}</span>\n }\n </button>\n <button class=\"tab-btn\" [class.active]=\"activeTab === 'approved'\" (click)=\"switchTab('approved')\">\n Approved\n </button>\n <button class=\"tab-btn\" [class.active]=\"activeTab === 'rejected'\" (click)=\"switchTab('rejected')\">\n Rejected\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"panel-content\">\n <!-- Loading -->\n @if (loading) {\n <div class=\"loading-state\">\n <div class=\"spinner\"></div>\n <span>Loading...</span>\n </div>\n }\n\n <!-- Processing tab -->\n @if (!loading && activeTab === 'processing') {\n @if (processingItems.length === 0) {\n <div class=\"empty-state\">\n <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>\n <p>No pending approvals</p>\n </div>\n }\n @for (item of processingItems; track item.id) {\n <div class=\"approval-item\" (click)=\"navigateToDetail(item.id)\">\n <div class=\"item-title\">{{ item.title }}</div>\n <div class=\"item-meta\">\n <span class=\"item-requester\">By {{ item.requestedByUserName }}</span>\n @if (item.currentStepName) {\n <span class=\"item-step\">\u00B7 {{ item.currentStepName }}</span>\n }\n </div>\n <div class=\"item-footer\">\n <span class=\"item-time\">{{ item.createdAt | date:'shortDate' }}</span>\n <span class=\"item-link\">View →</span>\n </div>\n </div>\n }\n }\n\n <!-- Approved tab -->\n @if (!loading && activeTab === 'approved') {\n @if (approvedItems.length === 0) {\n <div class=\"empty-state\">\n <p>No approved documents</p>\n </div>\n }\n @for (item of approvedItems; track item.id) {\n <div class=\"approval-item approved\" (click)=\"navigateToDetail(item.id)\">\n <div class=\"item-title\">{{ item.title }}</div>\n <div class=\"item-meta\">\n <span class=\"item-requester\">By {{ item.requestedByUserName }}</span>\n </div>\n <div class=\"item-footer\">\n <span class=\"status-badge approved-badge\">Approved</span>\n <span class=\"item-time\">{{ item.completedAt | date:'shortDate' }}</span>\n <span class=\"item-link\">View →</span>\n </div>\n </div>\n }\n @if (approvedItems.length >= 10) {\n <div class=\"show-more\" (click)=\"showMore('approved')\">Show more →</div>\n }\n }\n\n <!-- Rejected tab -->\n @if (!loading && activeTab === 'rejected') {\n @if (rejectedItems.length === 0) {\n <div class=\"empty-state\">\n <p>No rejected documents</p>\n </div>\n }\n @for (item of rejectedItems; track item.id) {\n <div class=\"approval-item rejected\" (click)=\"navigateToDetail(item.id)\">\n <div class=\"item-title\">{{ item.title }}</div>\n <div class=\"item-meta\">\n <span class=\"item-requester\">By {{ item.requestedByUserName }}</span>\n </div>\n <div class=\"item-footer\">\n <span class=\"status-badge rejected-badge\">Rejected</span>\n <span class=\"item-time\">{{ item.completedAt | date:'shortDate' }}</span>\n <span class=\"item-link\">View →</span>\n </div>\n </div>\n }\n @if (rejectedItems.length >= 10) {\n <div class=\"show-more\" (click)=\"showMore('rejected')\">Show more →</div>\n }\n }\n </div>\n</div>\n", styles: [".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;color:var(--text-muted);cursor:pointer;font-size:13px;font-weight:500;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}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;gap:12px;color:var(--text-muted)}.empty-state p{margin:0;font-size:13px}.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)}}\n", ":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-content{flex:1;overflow-y:auto;padding:8px 0}.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: "pipe", type: DatePipe, name: "date" }] });
|
|
1778
1184
|
}
|
|
1779
1185
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: MaApprovalPanelComponent, decorators: [{
|
|
1780
1186
|
type: Component,
|
|
1781
|
-
args: [{ selector: 'ma-approval-panel', standalone: true, imports: [
|
|
1782
|
-
<div class="approval-backdrop" [class.open]="isOpen" (click)="close()"></div>
|
|
1783
|
-
<div class="approval-panel" [class.open]="isOpen">
|
|
1784
|
-
|
|
1785
|
-
<!-- Header -->
|
|
1786
|
-
<div class="panel-header">
|
|
1787
|
-
<div class="panel-header-left">
|
|
1788
|
-
<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">
|
|
1789
|
-
<path d="M9 11l3 3L22 4"/>
|
|
1790
|
-
<path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>
|
|
1791
|
-
</svg>
|
|
1792
|
-
<h3>Approvals</h3>
|
|
1793
|
-
</div>
|
|
1794
|
-
<button class="close-btn" (click)="close()" aria-label="Close">
|
|
1795
|
-
<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">
|
|
1796
|
-
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
1797
|
-
</svg>
|
|
1798
|
-
</button>
|
|
1799
|
-
</div>
|
|
1800
|
-
|
|
1801
|
-
<!-- Tabs -->
|
|
1802
|
-
<div class="tabs">
|
|
1803
|
-
<button class="tab-btn" [class.active]="activeTab === 'processing'" (click)="switchTab('processing')">
|
|
1804
|
-
Processing
|
|
1805
|
-
<span class="tab-badge" *ngIf="processingItems.length > 0">{{ processingItems.length }}</span>
|
|
1806
|
-
</button>
|
|
1807
|
-
<button class="tab-btn" [class.active]="activeTab === 'approved'" (click)="switchTab('approved')">
|
|
1808
|
-
Approved
|
|
1809
|
-
</button>
|
|
1810
|
-
<button class="tab-btn" [class.active]="activeTab === 'rejected'" (click)="switchTab('rejected')">
|
|
1811
|
-
Rejected
|
|
1812
|
-
</button>
|
|
1813
|
-
</div>
|
|
1814
|
-
|
|
1815
|
-
<!-- Content -->
|
|
1816
|
-
<div class="panel-content">
|
|
1817
|
-
<!-- Loading -->
|
|
1818
|
-
<div class="loading-state" *ngIf="loading">
|
|
1819
|
-
<div class="spinner"></div>
|
|
1820
|
-
<span>Loading...</span>
|
|
1821
|
-
</div>
|
|
1822
|
-
|
|
1823
|
-
<!-- Processing tab -->
|
|
1824
|
-
<ng-container *ngIf="!loading && activeTab === 'processing'">
|
|
1825
|
-
<div class="empty-state" *ngIf="processingItems.length === 0">
|
|
1826
|
-
<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>
|
|
1827
|
-
<p>No pending approvals</p>
|
|
1828
|
-
</div>
|
|
1829
|
-
<div class="approval-item" *ngFor="let item of processingItems" (click)="navigateToDetail(item.id)">
|
|
1830
|
-
<div class="item-title">{{ item.title }}</div>
|
|
1831
|
-
<div class="item-meta">
|
|
1832
|
-
<span class="item-requester">By {{ item.requestedByUserName }}</span>
|
|
1833
|
-
<span class="item-step" *ngIf="item.currentStepName">· {{ item.currentStepName }}</span>
|
|
1834
|
-
</div>
|
|
1835
|
-
<div class="item-footer">
|
|
1836
|
-
<span class="item-time">{{ item.createdAt | date:'shortDate' }}</span>
|
|
1837
|
-
<span class="item-link">View →</span>
|
|
1838
|
-
</div>
|
|
1839
|
-
</div>
|
|
1840
|
-
</ng-container>
|
|
1841
|
-
|
|
1842
|
-
<!-- Approved tab -->
|
|
1843
|
-
<ng-container *ngIf="!loading && activeTab === 'approved'">
|
|
1844
|
-
<div class="empty-state" *ngIf="approvedItems.length === 0">
|
|
1845
|
-
<p>No approved documents</p>
|
|
1846
|
-
</div>
|
|
1847
|
-
<div class="approval-item approved" *ngFor="let item of approvedItems" (click)="navigateToDetail(item.id)">
|
|
1848
|
-
<div class="item-title">{{ item.title }}</div>
|
|
1849
|
-
<div class="item-meta">
|
|
1850
|
-
<span class="item-requester">By {{ item.requestedByUserName }}</span>
|
|
1851
|
-
</div>
|
|
1852
|
-
<div class="item-footer">
|
|
1853
|
-
<span class="status-badge approved-badge">Approved</span>
|
|
1854
|
-
<span class="item-time">{{ item.completedAt | date:'shortDate' }}</span>
|
|
1855
|
-
<span class="item-link">View →</span>
|
|
1856
|
-
</div>
|
|
1857
|
-
</div>
|
|
1858
|
-
<div class="show-more" *ngIf="approvedItems.length >= 10" (click)="showMore('approved')">
|
|
1859
|
-
Show more →
|
|
1860
|
-
</div>
|
|
1861
|
-
</ng-container>
|
|
1862
|
-
|
|
1863
|
-
<!-- Rejected tab -->
|
|
1864
|
-
<ng-container *ngIf="!loading && activeTab === 'rejected'">
|
|
1865
|
-
<div class="empty-state" *ngIf="rejectedItems.length === 0">
|
|
1866
|
-
<p>No rejected documents</p>
|
|
1867
|
-
</div>
|
|
1868
|
-
<div class="approval-item rejected" *ngFor="let item of rejectedItems" (click)="navigateToDetail(item.id)">
|
|
1869
|
-
<div class="item-title">{{ item.title }}</div>
|
|
1870
|
-
<div class="item-meta">
|
|
1871
|
-
<span class="item-requester">By {{ item.requestedByUserName }}</span>
|
|
1872
|
-
</div>
|
|
1873
|
-
<div class="item-footer">
|
|
1874
|
-
<span class="status-badge rejected-badge">Rejected</span>
|
|
1875
|
-
<span class="item-time">{{ item.completedAt | date:'shortDate' }}</span>
|
|
1876
|
-
<span class="item-link">View →</span>
|
|
1877
|
-
</div>
|
|
1878
|
-
</div>
|
|
1879
|
-
<div class="show-more" *ngIf="rejectedItems.length >= 10" (click)="showMore('rejected')">
|
|
1880
|
-
Show more →
|
|
1881
|
-
</div>
|
|
1882
|
-
</ng-container>
|
|
1883
|
-
</div>
|
|
1884
|
-
</div>
|
|
1885
|
-
`, styles: [".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;color:var(--text-muted);cursor:pointer;font-size:13px;font-weight:500;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}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;gap:12px;color:var(--text-muted)}.empty-state p{margin:0;font-size:13px}.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)}}\n", ":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-content{flex:1;overflow-y:auto;padding:8px 0}.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"] }]
|
|
1187
|
+
args: [{ selector: 'ma-approval-panel', standalone: true, imports: [DatePipe], template: "<div class=\"approval-backdrop\" [class.open]=\"isOpen\" (click)=\"close()\"></div>\n<div class=\"approval-panel\" [class.open]=\"isOpen\">\n\n <!-- Header -->\n <div class=\"panel-header\">\n <div class=\"panel-header-left\">\n <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\">\n <path d=\"M9 11l3 3L22 4\"/>\n <path d=\"M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11\"/>\n </svg>\n <h3>Approvals</h3>\n </div>\n <button class=\"close-btn\" (click)=\"close()\" aria-label=\"Close\">\n <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\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/><line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n </div>\n\n <!-- Tabs -->\n <div class=\"tabs\">\n <button class=\"tab-btn\" [class.active]=\"activeTab === 'processing'\" (click)=\"switchTab('processing')\">\n Processing\n @if (processingItems.length > 0) {\n <span class=\"tab-badge\">{{ processingItems.length }}</span>\n }\n </button>\n <button class=\"tab-btn\" [class.active]=\"activeTab === 'approved'\" (click)=\"switchTab('approved')\">\n Approved\n </button>\n <button class=\"tab-btn\" [class.active]=\"activeTab === 'rejected'\" (click)=\"switchTab('rejected')\">\n Rejected\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"panel-content\">\n <!-- Loading -->\n @if (loading) {\n <div class=\"loading-state\">\n <div class=\"spinner\"></div>\n <span>Loading...</span>\n </div>\n }\n\n <!-- Processing tab -->\n @if (!loading && activeTab === 'processing') {\n @if (processingItems.length === 0) {\n <div class=\"empty-state\">\n <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>\n <p>No pending approvals</p>\n </div>\n }\n @for (item of processingItems; track item.id) {\n <div class=\"approval-item\" (click)=\"navigateToDetail(item.id)\">\n <div class=\"item-title\">{{ item.title }}</div>\n <div class=\"item-meta\">\n <span class=\"item-requester\">By {{ item.requestedByUserName }}</span>\n @if (item.currentStepName) {\n <span class=\"item-step\">\u00B7 {{ item.currentStepName }}</span>\n }\n </div>\n <div class=\"item-footer\">\n <span class=\"item-time\">{{ item.createdAt | date:'shortDate' }}</span>\n <span class=\"item-link\">View →</span>\n </div>\n </div>\n }\n }\n\n <!-- Approved tab -->\n @if (!loading && activeTab === 'approved') {\n @if (approvedItems.length === 0) {\n <div class=\"empty-state\">\n <p>No approved documents</p>\n </div>\n }\n @for (item of approvedItems; track item.id) {\n <div class=\"approval-item approved\" (click)=\"navigateToDetail(item.id)\">\n <div class=\"item-title\">{{ item.title }}</div>\n <div class=\"item-meta\">\n <span class=\"item-requester\">By {{ item.requestedByUserName }}</span>\n </div>\n <div class=\"item-footer\">\n <span class=\"status-badge approved-badge\">Approved</span>\n <span class=\"item-time\">{{ item.completedAt | date:'shortDate' }}</span>\n <span class=\"item-link\">View →</span>\n </div>\n </div>\n }\n @if (approvedItems.length >= 10) {\n <div class=\"show-more\" (click)=\"showMore('approved')\">Show more →</div>\n }\n }\n\n <!-- Rejected tab -->\n @if (!loading && activeTab === 'rejected') {\n @if (rejectedItems.length === 0) {\n <div class=\"empty-state\">\n <p>No rejected documents</p>\n </div>\n }\n @for (item of rejectedItems; track item.id) {\n <div class=\"approval-item rejected\" (click)=\"navigateToDetail(item.id)\">\n <div class=\"item-title\">{{ item.title }}</div>\n <div class=\"item-meta\">\n <span class=\"item-requester\">By {{ item.requestedByUserName }}</span>\n </div>\n <div class=\"item-footer\">\n <span class=\"status-badge rejected-badge\">Rejected</span>\n <span class=\"item-time\">{{ item.completedAt | date:'shortDate' }}</span>\n <span class=\"item-link\">View →</span>\n </div>\n </div>\n }\n @if (rejectedItems.length >= 10) {\n <div class=\"show-more\" (click)=\"showMore('rejected')\">Show more →</div>\n }\n }\n </div>\n</div>\n", styles: [".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;color:var(--text-muted);cursor:pointer;font-size:13px;font-weight:500;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}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;gap:12px;color:var(--text-muted)}.empty-state p{margin:0;font-size:13px}.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)}}\n", ":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-content{flex:1;overflow-y:auto;padding:8px 0}.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"] }]
|
|
1886
1188
|
}], propDecorators: { approvalActioned: [{
|
|
1887
1189
|
type: Output
|
|
1888
1190
|
}] } });
|
|
@@ -1907,31 +1209,11 @@ class MaUserComponent {
|
|
|
1907
1209
|
}
|
|
1908
1210
|
}
|
|
1909
1211
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: MaUserComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1910
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.7", 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:
|
|
1911
|
-
<ma-toast-container></ma-toast-container>
|
|
1912
|
-
<div class="user-header">
|
|
1913
|
-
<ma-user-profile
|
|
1914
|
-
(notificationClick)="notificationPanel.open()"
|
|
1915
|
-
(approvalClick)="approvalPanel.open()">
|
|
1916
|
-
</ma-user-profile>
|
|
1917
|
-
</div>
|
|
1918
|
-
<ma-notification-panel #notificationPanel (notificationRead)="onNotificationRead()"></ma-notification-panel>
|
|
1919
|
-
<ma-approval-panel #approvalPanel (approvalActioned)="onApprovalActioned()"></ma-approval-panel>
|
|
1920
|
-
`, 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"] }] });
|
|
1212
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.7", 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: "<ma-toast-container></ma-toast-container>\n<div class=\"user-header\">\n <ma-user-profile\n (notificationClick)=\"notificationPanel.open()\"\n (approvalClick)=\"approvalPanel.open()\">\n </ma-user-profile>\n</div>\n<ma-notification-panel #notificationPanel (notificationRead)=\"onNotificationRead()\"></ma-notification-panel>\n<ma-approval-panel #approvalPanel (approvalActioned)=\"onApprovalActioned()\"></ma-approval-panel>\n", 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"] }] });
|
|
1921
1213
|
}
|
|
1922
1214
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: MaUserComponent, decorators: [{
|
|
1923
1215
|
type: Component,
|
|
1924
|
-
args: [{ selector: 'ma-user', standalone: true, imports: [ToastContainerComponent, UserProfileComponent, NotificationPanelComponent, MaApprovalPanelComponent], template:
|
|
1925
|
-
<ma-toast-container></ma-toast-container>
|
|
1926
|
-
<div class="user-header">
|
|
1927
|
-
<ma-user-profile
|
|
1928
|
-
(notificationClick)="notificationPanel.open()"
|
|
1929
|
-
(approvalClick)="approvalPanel.open()">
|
|
1930
|
-
</ma-user-profile>
|
|
1931
|
-
</div>
|
|
1932
|
-
<ma-notification-panel #notificationPanel (notificationRead)="onNotificationRead()"></ma-notification-panel>
|
|
1933
|
-
<ma-approval-panel #approvalPanel (approvalActioned)="onApprovalActioned()"></ma-approval-panel>
|
|
1934
|
-
`, styles: [".user-header{display:flex;justify-content:flex-end}\n"] }]
|
|
1216
|
+
args: [{ selector: 'ma-user', standalone: true, imports: [ToastContainerComponent, UserProfileComponent, NotificationPanelComponent, MaApprovalPanelComponent], template: "<ma-toast-container></ma-toast-container>\n<div class=\"user-header\">\n <ma-user-profile\n (notificationClick)=\"notificationPanel.open()\"\n (approvalClick)=\"approvalPanel.open()\">\n </ma-user-profile>\n</div>\n<ma-notification-panel #notificationPanel (notificationRead)=\"onNotificationRead()\"></ma-notification-panel>\n<ma-approval-panel #approvalPanel (approvalActioned)=\"onApprovalActioned()\"></ma-approval-panel>\n", styles: [".user-header{display:flex;justify-content:flex-end}\n"] }]
|
|
1935
1217
|
}], propDecorators: { userProfile: [{
|
|
1936
1218
|
type: ViewChild,
|
|
1937
1219
|
args: [UserProfileComponent]
|
|
@@ -2000,21 +1282,11 @@ class NotificationBadgeComponent {
|
|
|
2000
1282
|
this.notificationClick.emit();
|
|
2001
1283
|
}
|
|
2002
1284
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: NotificationBadgeComponent, deps: [{ token: MesAuthService }, { token: ThemeService }], target: i0.ɵɵFactoryTarget.Component });
|
|
2003
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
2004
|
-
<button class="notification-btn" (click)="onNotificationClick()" title="Notifications">
|
|
2005
|
-
<span class="icon">🔔</span>
|
|
2006
|
-
<span class="badge" *ngIf="unreadCount > 0">{{ unreadCount }}</span>
|
|
2007
|
-
</button>
|
|
2008
|
-
`, isInline: true, styles: [":host{--error-color: #f44336}:host(.theme-dark){--error-color: #ef5350}.notification-btn{position:relative;background:none;border:none;font-size:24px;cursor:pointer;padding:8px;transition:opacity .2s}.notification-btn:hover{opacity:.7}.icon{display:inline-block}.badge{position:absolute;top:0;right:0;background-color:var(--error-color);color:#fff;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
1285
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: NotificationBadgeComponent, isStandalone: true, selector: "ma-notification-badge", outputs: { notificationClick: "notificationClick" }, host: { properties: { "class": "this.themeClass" } }, ngImport: i0, template: "<button class=\"notification-btn\" (click)=\"onNotificationClick()\" title=\"Notifications\">\n <span class=\"icon\">\uD83D\uDD14</span>\n @if (unreadCount > 0) {\n <span class=\"badge\">{{ unreadCount }}</span>\n }\n</button>\n", styles: [".notification-btn{position:relative;background:none;border:none;font-size:24px;cursor:pointer;padding:8px;transition:opacity .2s}.notification-btn:hover{opacity:.7}.icon{display:inline-block}.badge{position:absolute;top:0;right:0;background-color:var(--error-color);color:#fff;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700}\n"] });
|
|
2009
1286
|
}
|
|
2010
1287
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: NotificationBadgeComponent, decorators: [{
|
|
2011
1288
|
type: Component,
|
|
2012
|
-
args: [{ selector: 'ma-notification-badge', standalone: true, imports: [
|
|
2013
|
-
<button class="notification-btn" (click)="onNotificationClick()" title="Notifications">
|
|
2014
|
-
<span class="icon">🔔</span>
|
|
2015
|
-
<span class="badge" *ngIf="unreadCount > 0">{{ unreadCount }}</span>
|
|
2016
|
-
</button>
|
|
2017
|
-
`, styles: [":host{--error-color: #f44336}:host(.theme-dark){--error-color: #ef5350}.notification-btn{position:relative;background:none;border:none;font-size:24px;cursor:pointer;padding:8px;transition:opacity .2s}.notification-btn:hover{opacity:.7}.icon{display:inline-block}.badge{position:absolute;top:0;right:0;background-color:var(--error-color);color:#fff;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700}\n"] }]
|
|
1289
|
+
args: [{ selector: 'ma-notification-badge', standalone: true, imports: [], template: "<button class=\"notification-btn\" (click)=\"onNotificationClick()\" title=\"Notifications\">\n <span class=\"icon\">\uD83D\uDD14</span>\n @if (unreadCount > 0) {\n <span class=\"badge\">{{ unreadCount }}</span>\n }\n</button>\n", styles: [".notification-btn{position:relative;background:none;border:none;font-size:24px;cursor:pointer;padding:8px;transition:opacity .2s}.notification-btn:hover{opacity:.7}.icon{display:inline-block}.badge{position:absolute;top:0;right:0;background-color:var(--error-color);color:#fff;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:700}\n"] }]
|
|
2018
1290
|
}], ctorParameters: () => [{ type: MesAuthService }, { type: ThemeService }], propDecorators: { notificationClick: [{
|
|
2019
1291
|
type: Output
|
|
2020
1292
|
}], themeClass: [{
|
|
@@ -2027,6 +1299,7 @@ class MaArvContainerComponent {
|
|
|
2027
1299
|
description;
|
|
2028
1300
|
referenceId = '';
|
|
2029
1301
|
templateId;
|
|
1302
|
+
templateIds;
|
|
2030
1303
|
callbackUrl;
|
|
2031
1304
|
deadlineHours;
|
|
2032
1305
|
approvalSubmitted = new EventEmitter();
|
|
@@ -2078,6 +1351,8 @@ class MaArvContainerComponent {
|
|
|
2078
1351
|
loadTemplates() {
|
|
2079
1352
|
this.approvalSvc.getTemplates().pipe(takeUntil(this.destroy$)).subscribe({
|
|
2080
1353
|
next: (t) => {
|
|
1354
|
+
if (this.templateIds && this.templateIds.length > 0)
|
|
1355
|
+
t = t.filter(temp => this.templateIds.includes(temp.id));
|
|
2081
1356
|
this.templates = t;
|
|
2082
1357
|
if (this.templateId != null && this.selectedTemplateId == null) {
|
|
2083
1358
|
this.selectedTemplateId = this.templateId;
|
|
@@ -2232,7 +1507,6 @@ class MaArvContainerComponent {
|
|
|
2232
1507
|
.toPromise()
|
|
2233
1508
|
.then((r) => {
|
|
2234
1509
|
const items = r?.data || r?.Data || r?.items || r || [];
|
|
2235
|
-
// Normalize PascalCase to camelCase for template binding
|
|
2236
1510
|
return (Array.isArray(items) ? items : []).map((u) => ({
|
|
2237
1511
|
id: u.id || u.Id,
|
|
2238
1512
|
userName: u.userName || u.UserName,
|
|
@@ -2413,7 +1687,6 @@ class MaArvContainerComponent {
|
|
|
2413
1687
|
// Step 6: Strip scripts, Angular attributes, style/link tags, and form elements
|
|
2414
1688
|
clone.querySelectorAll('script, link, style').forEach(el => el.remove());
|
|
2415
1689
|
clone.querySelectorAll('*').forEach(el => {
|
|
2416
|
-
// Remove Angular-specific attributes
|
|
2417
1690
|
Array.from(el.attributes).forEach(attr => {
|
|
2418
1691
|
if (attr.name.startsWith('_ngcontent-') ||
|
|
2419
1692
|
attr.name.startsWith('_nghost-') ||
|
|
@@ -2425,7 +1698,6 @@ class MaArvContainerComponent {
|
|
|
2425
1698
|
el.removeAttribute(attr.name);
|
|
2426
1699
|
}
|
|
2427
1700
|
});
|
|
2428
|
-
// Remove class attribute (framework-specific classes add no value)
|
|
2429
1701
|
el.removeAttribute('class');
|
|
2430
1702
|
});
|
|
2431
1703
|
// Step 7: Wrap in a clean, print-friendly HTML document
|
|
@@ -2448,7 +1720,7 @@ class MaArvContainerComponent {
|
|
|
2448
1720
|
img { max-width: 100%; }
|
|
2449
1721
|
table { border-collapse: collapse; width: 100%; }
|
|
2450
1722
|
td, th { padding: 8px 12px; border: 1px solid #ddd; text-align: left; vertical-align: top; }
|
|
2451
|
-
input, textarea, select {
|
|
1723
|
+
input, textarea, select {
|
|
2452
1724
|
border: 1px solid #ccc; border-radius: 4px; padding: 6px 10px;
|
|
2453
1725
|
font-size: 14px; width: 100%; background: #fafafa; color: #333;
|
|
2454
1726
|
}
|
|
@@ -2471,8 +1743,6 @@ ${clone.outerHTML}
|
|
|
2471
1743
|
return canvas.toDataURL('image/png').split(',')[1];
|
|
2472
1744
|
}
|
|
2473
1745
|
getStyleProperties() {
|
|
2474
|
-
// Only capture essential layout/typography — NOT colors, widths, or cursor styles
|
|
2475
|
-
// Colors and backgrounds are handled by the clean document stylesheet instead
|
|
2476
1746
|
return [
|
|
2477
1747
|
'font-size', 'font-weight', 'font-style', 'line-height', 'text-align', 'text-decoration',
|
|
2478
1748
|
'border', 'border-top', 'border-right', 'border-bottom', 'border-left',
|
|
@@ -2485,331 +1755,11 @@ ${clone.outerHTML}
|
|
|
2485
1755
|
];
|
|
2486
1756
|
}
|
|
2487
1757
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: MaArvContainerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2488
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.7", type: MaArvContainerComponent, isStandalone: true, selector: "ma-arv-container", inputs: { title: "title", description: "description", referenceId: "referenceId", templateId: "templateId", callbackUrl: "callbackUrl", deadlineHours: "deadlineHours" }, outputs: { approvalSubmitted: "approvalSubmitted", approvalSubmitting: "approvalSubmitting", cancelled: "cancelled" }, host: { properties: { "class": "this.themeClass" } }, viewQueries: [{ propertyName: "contentBody", first: true, predicate: ["contentBody"], descendants: true, static: true }], ngImport: i0, template: `
|
|
2489
|
-
<div class="arv-container">
|
|
2490
|
-
<!-- Content Area -->
|
|
2491
|
-
<div class="arv-content-body" #contentBody>
|
|
2492
|
-
<ng-content></ng-content>
|
|
2493
|
-
</div>
|
|
2494
|
-
|
|
2495
|
-
<!-- Approval Footer -->
|
|
2496
|
-
<div class="arv-footer" *ngIf="!isSubmitted">
|
|
2497
|
-
<div class="arv-footer-inner">
|
|
2498
|
-
|
|
2499
|
-
<!-- Routing mode -->
|
|
2500
|
-
<div class="arv-routing">
|
|
2501
|
-
<div class="arv-routing-header">
|
|
2502
|
-
<h4 class="arv-section-title">Approval Routing</h4>
|
|
2503
|
-
<!-- Show routing toggle only when templateId is NOT locked -->
|
|
2504
|
-
<div *ngIf="!templateId" class="arv-routing-mode">
|
|
2505
|
-
<label class="arv-radio">
|
|
2506
|
-
<input type="radio" name="routingMode" value="template" [checked]="routingMode === 'template'" (change)="routingMode = 'template'"> Use Template
|
|
2507
|
-
</label>
|
|
2508
|
-
<label class="arv-radio">
|
|
2509
|
-
<input type="radio" name="routingMode" value="adhoc" [checked]="routingMode === 'adhoc'" (change)="routingMode = 'adhoc'"> Custom Steps
|
|
2510
|
-
</label>
|
|
2511
|
-
</div>
|
|
2512
|
-
</div>
|
|
2513
|
-
|
|
2514
|
-
<!-- Locked template: show name only, no selector -->
|
|
2515
|
-
<div *ngIf="templateId && routingMode === 'template'" class="arv-template-select">
|
|
2516
|
-
<div *ngIf="loadingTemplate" class="arv-template-loading">Loading template...</div>
|
|
2517
|
-
<div *ngIf="selectedTemplate && !loadingTemplate" class="arv-locked-template">
|
|
2518
|
-
<span class="arv-locked-label">Template</span>
|
|
2519
|
-
<span class="arv-locked-name">{{ selectedTemplate.name }}</span>
|
|
2520
|
-
</div>
|
|
2521
|
-
</div>
|
|
2522
|
-
|
|
2523
|
-
<!-- Free template selector (no locked templateId) -->
|
|
2524
|
-
<div *ngIf="!templateId && routingMode === 'template'" class="arv-template-select">
|
|
2525
|
-
<label class="arv-label">Select Template</label>
|
|
2526
|
-
<select class="arv-select" (change)="onTemplateChange($event)">
|
|
2527
|
-
<option value="">-- Select a template --</option>
|
|
2528
|
-
<option *ngFor="let t of templates" [value]="t.id" [selected]="t.id === selectedTemplateId">{{ t.name }}</option>
|
|
2529
|
-
</select>
|
|
2530
|
-
</div>
|
|
2531
|
-
|
|
2532
|
-
<!-- Step pickers (shared for both locked and free template) -->
|
|
2533
|
-
<div *ngIf="routingMode === 'template'" class="arv-template-select">
|
|
2534
|
-
<div *ngIf="!loadingTemplate && selectedTemplate" class="arv-template-steps">
|
|
2535
|
-
<div class="arv-step-card" *ngFor="let s of selectedTemplate.steps; let i = index">
|
|
2536
|
-
<div class="arv-step-card-header">
|
|
2537
|
-
<span class="arv-step-preview-num">Step {{ s.stepOrder }}</span>
|
|
2538
|
-
<span class="arv-step-preview-name">{{ s.stepName }}</span>
|
|
2539
|
-
<span *ngIf="s.roles && s.roles.length > 0" class="arv-step-role-badge">
|
|
2540
|
-
{{ s.roles[0].positionLevel }}{{ s.roles[0].orgName ? ' · ' + s.roles[0].orgName : '' }}
|
|
2541
|
-
</span>
|
|
2542
|
-
</div>
|
|
2543
|
-
<!-- Selectable step (role-based or multi-assignee): show approver picker -->
|
|
2544
|
-
<div *ngIf="stepCandidates[i]?.length > 1 || stepLoadingCandidates[i]" class="arv-step-picker">
|
|
2545
|
-
<div *ngIf="stepLoadingCandidates[i]" class="arv-template-loading">Loading candidates...</div>
|
|
2546
|
-
<select *ngIf="!stepLoadingCandidates[i]" class="arv-select arv-select-sm"
|
|
2547
|
-
(change)="onStepUserChange(i, $event)">
|
|
2548
|
-
<option value="">-- Select approver --</option>
|
|
2549
|
-
<option *ngFor="let u of stepCandidates[i]" [value]="u.userId"
|
|
2550
|
-
[selected]="u.userId === stepSelectedUsers[i]">
|
|
2551
|
-
{{ u.fullName || u.userId }}{{ u.department ? ' · ' + u.department : '' }}{{ u.position ? ' (' + u.position + ')' : '' }}
|
|
2552
|
-
</option>
|
|
2553
|
-
</select>
|
|
2554
|
-
</div>
|
|
2555
|
-
<!-- Single fixed-user step: show who is assigned -->
|
|
2556
|
-
<div *ngIf="!stepLoadingCandidates[i] && stepCandidates[i]?.length === 1" class="arv-step-fixed">
|
|
2557
|
-
{{ stepCandidates[i][0].fullName || stepCandidates[i][0].userId }}{{ stepCandidates[i][0].department ? ' · ' + stepCandidates[i][0].department : '' }}{{ stepCandidates[i][0].position ? ' (' + stepCandidates[i][0].position + ')' : '' }}
|
|
2558
|
-
</div>
|
|
2559
|
-
</div>
|
|
2560
|
-
<div *ngIf="selectedTemplate.referenceUserIds.length > 0" class="arv-step-preview arv-step-preview-cc">
|
|
2561
|
-
<span class="arv-step-preview-num">CC</span>
|
|
2562
|
-
<span class="arv-step-preview-name">{{ selectedTemplate.referenceUserIds.length }} reference user(s) from template</span>
|
|
2563
|
-
</div>
|
|
2564
|
-
</div>
|
|
2565
|
-
</div>
|
|
2566
|
-
|
|
2567
|
-
<!-- Ad-hoc steps -->
|
|
2568
|
-
<div *ngIf="routingMode === 'adhoc'" class="arv-steps">
|
|
2569
|
-
<div class="arv-step" *ngFor="let step of adHocSteps; let i = index">
|
|
2570
|
-
<div class="arv-step-header">
|
|
2571
|
-
<span class="arv-step-num">Step {{ i + 1 }}</span>
|
|
2572
|
-
<input class="arv-input" [value]="step.stepName" (input)="step.stepName = $any($event.target).value" placeholder="Step name" />
|
|
2573
|
-
<button class="arv-btn-icon arv-btn-danger" (click)="removeStep(i)" title="Remove step">
|
|
2574
|
-
<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>
|
|
2575
|
-
</button>
|
|
2576
|
-
</div>
|
|
2577
|
-
<div class="arv-approvers">
|
|
2578
|
-
<div class="arv-approver-tags">
|
|
2579
|
-
<span class="arv-tag" *ngFor="let uid of step.approverUserIds; let j = index">
|
|
2580
|
-
{{ userLabelMap[uid] || uid }}
|
|
2581
|
-
<button class="arv-tag-remove" (click)="removeApprover(i, j)">x</button>
|
|
2582
|
-
</span>
|
|
2583
|
-
</div>
|
|
2584
|
-
<div class="arv-user-search">
|
|
2585
|
-
<input class="arv-input arv-input-sm" [value]="userSearchQuery[i] || ''"
|
|
2586
|
-
(input)="onUserSearchInput(i, $any($event.target).value)"
|
|
2587
|
-
placeholder="Search approver..." />
|
|
2588
|
-
<div class="arv-search-results" *ngIf="userSearchResults[i]?.length">
|
|
2589
|
-
<div class="arv-search-item"
|
|
2590
|
-
*ngFor="let u of userSearchResults[i]"
|
|
2591
|
-
(click)="addApprover(i, u.id)">
|
|
2592
|
-
{{ u.fullName || u.userName }}
|
|
2593
|
-
</div>
|
|
2594
|
-
</div>
|
|
2595
|
-
</div>
|
|
2596
|
-
</div>
|
|
2597
|
-
</div>
|
|
2598
|
-
<button class="arv-btn arv-btn-outline" (click)="addStep()">+ Add Step</button>
|
|
2599
|
-
</div>
|
|
2600
|
-
</div>
|
|
2601
|
-
|
|
2602
|
-
<!-- Reference / CC users -->
|
|
2603
|
-
<div class="arv-references">
|
|
2604
|
-
<h4 class="arv-section-title">CC / Reference</h4>
|
|
2605
|
-
<p class="arv-hint">These users will be notified when the approval completes, but won't be asked to approve.</p>
|
|
2606
|
-
<div class="arv-approver-tags">
|
|
2607
|
-
<span class="arv-tag" *ngFor="let uid of referenceUserIds; let j = index">
|
|
2608
|
-
{{ userLabelMap[uid] || uid }}
|
|
2609
|
-
<button class="arv-tag-remove" (click)="removeReference(j)">x</button>
|
|
2610
|
-
</span>
|
|
2611
|
-
</div>
|
|
2612
|
-
<div class="arv-user-search">
|
|
2613
|
-
<input class="arv-input arv-input-sm" [value]="refSearchQuery"
|
|
2614
|
-
(input)="onRefSearchInput($any($event.target).value)"
|
|
2615
|
-
placeholder="Search CC user..." />
|
|
2616
|
-
<div class="arv-search-results" *ngIf="refSearchResults?.length">
|
|
2617
|
-
<div class="arv-search-item"
|
|
2618
|
-
*ngFor="let u of refSearchResults"
|
|
2619
|
-
(click)="addReference(u.id)">
|
|
2620
|
-
{{ u.fullName || u.userName }}
|
|
2621
|
-
</div>
|
|
2622
|
-
</div>
|
|
2623
|
-
</div>
|
|
2624
|
-
</div>
|
|
2625
|
-
|
|
2626
|
-
<!-- Actions -->
|
|
2627
|
-
<div class="arv-actions">
|
|
2628
|
-
<button class="arv-btn arv-btn-secondary" (click)="onCancel()">Cancel</button>
|
|
2629
|
-
<button class="arv-btn arv-btn-primary" [disabled]="submitting" (click)="submit()">
|
|
2630
|
-
<span *ngIf="submitting" class="arv-spinner"></span>
|
|
2631
|
-
{{ submitting ? 'Submitting...' : 'Submit for Approval' }}
|
|
2632
|
-
</button>
|
|
2633
|
-
</div>
|
|
2634
|
-
|
|
2635
|
-
<div class="arv-error" *ngIf="errorMessage">{{ errorMessage }}</div>
|
|
2636
|
-
</div>
|
|
2637
|
-
</div>
|
|
2638
|
-
|
|
2639
|
-
<!-- Success state -->
|
|
2640
|
-
<div class="arv-success" *ngIf="isSubmitted">
|
|
2641
|
-
<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">
|
|
2642
|
-
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
|
|
2643
|
-
<polyline points="22 4 12 14.01 9 11.01"/>
|
|
2644
|
-
</svg>
|
|
2645
|
-
<p>Submitted for approval successfully.</p>
|
|
2646
|
-
</div>
|
|
2647
|
-
</div>
|
|
2648
|
-
`, 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}.arv-template-loading{font-size:12px;color:var(--arv-text-muted);margin-top:8px;padding:4px 0}.arv-template-steps{margin-top:8px;display:flex;flex-direction:column;gap:6px}.arv-step-card{background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:var(--arv-radius);overflow:hidden}.arv-step-card-header{display:flex;align-items:center;gap:8px;padding:7px 10px;background:#00000008;border-bottom:1px solid var(--arv-border);flex-wrap:wrap}.arv-step-preview{display:flex;align-items:center;gap:8px;padding:5px 8px;background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:4px;font-size:12px}.arv-step-preview-num{font-weight:700;color:var(--arv-primary);min-width:44px;flex-shrink:0;font-size:12px}.arv-step-preview-name{flex:1;color:var(--arv-text);font-size:12px;font-weight:600}.arv-step-role-badge{font-size:11px;color:var(--arv-text-muted);background:#90caf91a;border:1px solid rgba(144,202,249,.25);border-radius:10px;padding:1px 7px}.arv-step-preview-cc .arv-step-preview-num{color:var(--arv-text-muted)}.arv-locked-template{display:flex;align-items:center;gap:8px;padding:7px 10px;background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:var(--arv-radius);margin-top:6px}.arv-locked-label{font-size:11px;color:var(--arv-text-muted);text-transform:uppercase;letter-spacing:.5px;flex-shrink:0}.arv-locked-name{font-size:13px;font-weight:600;color:var(--arv-text)}.arv-step-picker{padding:8px 10px}.arv-select-sm{font-size:12px;padding:6px 8px}.arv-step-fixed{padding:6px 10px;font-size:12px;color:var(--arv-text-muted)}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] });
|
|
1758
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.7", type: MaArvContainerComponent, isStandalone: true, selector: "ma-arv-container", inputs: { title: "title", description: "description", referenceId: "referenceId", templateId: "templateId", templateIds: "templateIds", callbackUrl: "callbackUrl", deadlineHours: "deadlineHours" }, outputs: { approvalSubmitted: "approvalSubmitted", approvalSubmitting: "approvalSubmitting", cancelled: "cancelled" }, host: { properties: { "class": "this.themeClass" } }, viewQueries: [{ propertyName: "contentBody", first: true, predicate: ["contentBody"], descendants: true, static: true }], ngImport: i0, template: "<div class=\"arv-container\">\n <!-- Content Area -->\n <div class=\"arv-content-body\" #contentBody>\n <ng-content></ng-content>\n </div>\n\n <!-- Approval Footer -->\n <div class=\"arv-footer\">\n @if (!isSubmitted) {\n <div class=\"arv-footer-inner\">\n\n <!-- Routing mode -->\n <div class=\"arv-routing\">\n <div class=\"arv-routing-header\">\n <h4 class=\"arv-section-title\">Approval Routing</h4>\n <!-- Show routing toggle only when templateId is NOT locked to a single template -->\n @if (!templateId || (!templateIds && templateIds.length > 0)) {\n <div class=\"arv-routing-mode\">\n <label class=\"arv-radio\">\n <input type=\"radio\" name=\"routingMode\" value=\"template\" [checked]=\"routingMode === 'template'\" (change)=\"routingMode = 'template'\"> Use Template\n </label>\n <label class=\"arv-radio\">\n <input type=\"radio\" name=\"routingMode\" value=\"adhoc\" [checked]=\"routingMode === 'adhoc'\" (change)=\"routingMode = 'adhoc'\"> Custom Steps\n </label>\n </div>\n }\n </div>\n\n <!-- Locked template: only when templateId set and no multi-choice -->\n @if (templateId && (!templateIds || templateIds.length <= 1) && routingMode === 'template') {\n <div class=\"arv-template-select\">\n @if (loadingTemplate) {\n <div class=\"arv-template-loading\">Loading template...</div>\n }\n @if (selectedTemplate && !loadingTemplate) {\n <div class=\"arv-locked-template\">\n <span class=\"arv-locked-label\">Template</span>\n <span class=\"arv-locked-name\">{{ selectedTemplate.name }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Template selector: shown when no templateId, OR when templateIds has multiple choices -->\n @if ((!templateId || (templateIds && templateIds.length > 1)) && routingMode === 'template') {\n <div class=\"arv-template-select\">\n <label class=\"arv-label\">Select Template</label>\n <select class=\"arv-select\" (change)=\"onTemplateChange($event)\">\n <option value=\"\">-- Select a template --</option>\n @for (t of templates; track t.id) {\n <option [value]=\"t.id\" [selected]=\"t.id === selectedTemplateId\">{{ t.name }}</option>\n }\n </select>\n </div>\n }\n\n <!-- Step pickers (shared for both locked and free template) -->\n @if (routingMode === 'template') {\n <div class=\"arv-template-select\">\n @if (!loadingTemplate && selectedTemplate) {\n <div class=\"arv-template-steps\">\n @for (s of selectedTemplate.steps; track s.stepOrder; let i = $index) {\n <div class=\"arv-step-card\">\n <div class=\"arv-step-card-header\">\n <span class=\"arv-step-preview-num\">Step {{ s.stepOrder }}</span>\n <span class=\"arv-step-preview-name\">{{ s.stepName }}</span>\n @if (s.roles && s.roles.length > 0) {\n <span class=\"arv-step-role-badge\">\n {{ s.roles[0].positionLevel }}{{ s.roles[0].orgName ? ' \u00B7 ' + s.roles[0].orgName : '' }}\n </span>\n }\n </div>\n <!-- Selectable step: show approver picker -->\n @if (stepCandidates[i]?.length > 1 || stepLoadingCandidates[i]) {\n <div class=\"arv-step-picker\">\n @if (stepLoadingCandidates[i]) {\n <div class=\"arv-template-loading\">Loading candidates...</div>\n }\n @if (!stepLoadingCandidates[i]) {\n <select class=\"arv-select arv-select-sm\" (change)=\"onStepUserChange(i, $event)\">\n <option value=\"\">-- Select approver --</option>\n @for (u of stepCandidates[i]; track u.userId) {\n <option [value]=\"u.userId\" [selected]=\"u.userId === stepSelectedUsers[i]\">\n {{ u.fullName || u.userId }}{{ u.department ? ' \u00B7 ' + u.department : '' }}{{ u.position ? ' (' + u.position + ')' : '' }}\n </option>\n }\n </select>\n }\n </div>\n }\n <!-- Single fixed-user step: show who is assigned -->\n @if (!stepLoadingCandidates[i] && stepCandidates[i]?.length === 1) {\n <div class=\"arv-step-fixed\">\n {{ stepCandidates[i][0].fullName || stepCandidates[i][0].userId }}{{ stepCandidates[i][0].department ? ' \u00B7 ' + stepCandidates[i][0].department : '' }}{{ stepCandidates[i][0].position ? ' (' + stepCandidates[i][0].position + ')' : '' }}\n </div>\n }\n </div>\n }\n @if (selectedTemplate.referenceUserIds.length > 0) {\n <div class=\"arv-step-preview arv-step-preview-cc\">\n <span class=\"arv-step-preview-num\">CC</span>\n <span class=\"arv-step-preview-name\">{{ selectedTemplate.referenceUserIds.length }} reference user(s) from template</span>\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Ad-hoc steps -->\n @if (routingMode === 'adhoc') {\n <div class=\"arv-steps\">\n @for (step of adHocSteps; track step.stepOrder; let i = $index) {\n <div class=\"arv-step\">\n <div class=\"arv-step-header\">\n <span class=\"arv-step-num\">Step {{ i + 1 }}</span>\n <input class=\"arv-input\" [value]=\"step.stepName\" (input)=\"step.stepName = $any($event.target).value\" placeholder=\"Step name\" />\n <button class=\"arv-btn-icon arv-btn-danger\" (click)=\"removeStep(i)\" title=\"Remove step\">\n <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>\n </button>\n </div>\n <div class=\"arv-approvers\">\n <div class=\"arv-approver-tags\">\n @for (uid of step.approverUserIds; track uid; let j = $index) {\n <span class=\"arv-tag\">\n {{ userLabelMap[uid] || uid }}\n <button class=\"arv-tag-remove\" (click)=\"removeApprover(i, j)\">x</button>\n </span>\n }\n </div>\n <div class=\"arv-user-search\">\n <input class=\"arv-input arv-input-sm\" [value]=\"userSearchQuery[i] || ''\"\n (input)=\"onUserSearchInput(i, $any($event.target).value)\"\n placeholder=\"Search approver...\" />\n @if (userSearchResults[i]?.length) {\n <div class=\"arv-search-results\">\n @for (u of userSearchResults[i]; track u.id) {\n <div class=\"arv-search-item\" (click)=\"addApprover(i, u.id)\">\n {{ u.fullName || u.userName }}\n </div>\n }\n </div>\n }\n </div>\n </div>\n </div>\n }\n <button class=\"arv-btn arv-btn-outline\" (click)=\"addStep()\">+ Add Step</button>\n </div>\n }\n </div>\n\n <!-- Reference / CC users -->\n <div class=\"arv-references\">\n <h4 class=\"arv-section-title\">CC / Reference</h4>\n <p class=\"arv-hint\">These users will be notified when the approval completes, but won't be asked to approve.</p>\n <div class=\"arv-approver-tags\">\n @for (uid of referenceUserIds; track uid; let j = $index) {\n <span class=\"arv-tag\">\n {{ userLabelMap[uid] || uid }}\n <button class=\"arv-tag-remove\" (click)=\"removeReference(j)\">x</button>\n </span>\n }\n </div>\n <div class=\"arv-user-search\">\n <input class=\"arv-input arv-input-sm\" [value]=\"refSearchQuery\"\n (input)=\"onRefSearchInput($any($event.target).value)\"\n placeholder=\"Search CC user...\" />\n @if (refSearchResults?.length) {\n <div class=\"arv-search-results\">\n @for (u of refSearchResults; track u.id) {\n <div class=\"arv-search-item\" (click)=\"addReference(u.id)\">\n {{ u.fullName || u.userName }}\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n <!-- Actions -->\n <div class=\"arv-actions\">\n <button class=\"arv-btn arv-btn-secondary\" (click)=\"onCancel()\">Cancel</button>\n <button class=\"arv-btn arv-btn-primary\" [disabled]=\"submitting\" (click)=\"submit()\">\n @if (submitting) {\n <span class=\"arv-spinner\"></span>\n }\n {{ submitting ? 'Submitting...' : 'Submit for Approval' }}\n </button>\n </div>\n\n @if (errorMessage) {\n <div class=\"arv-error\">{{ errorMessage }}</div>\n }\n </div>\n }\n </div>\n\n <!-- Success state -->\n @if (isSubmitted) {\n <div class=\"arv-success\">\n <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\">\n <path d=\"M22 11.08V12a10 10 0 1 1-5.93-9.14\"/>\n <polyline points=\"22 4 12 14.01 9 11.01\"/>\n </svg>\n <p>Submitted for approval successfully.</p>\n </div>\n }\n</div>\n", styles: [".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}.arv-template-loading{font-size:12px;color:var(--arv-text-muted);margin-top:8px;padding:4px 0}.arv-template-steps{margin-top:8px;display:flex;flex-direction:column;gap:6px}.arv-step-card{background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:var(--arv-radius);overflow:hidden}.arv-step-card-header{display:flex;align-items:center;gap:8px;padding:7px 10px;background:#00000008;border-bottom:1px solid var(--arv-border);flex-wrap:wrap}.arv-step-preview{display:flex;align-items:center;gap:8px;padding:5px 8px;background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:4px;font-size:12px}.arv-step-preview-num{font-weight:700;color:var(--arv-primary);min-width:44px;flex-shrink:0;font-size:12px}.arv-step-preview-name{flex:1;color:var(--arv-text);font-size:12px;font-weight:600}.arv-step-role-badge{font-size:11px;color:var(--arv-text-muted);background:#90caf91a;border:1px solid rgba(144,202,249,.25);border-radius:10px;padding:1px 7px}.arv-step-preview-cc .arv-step-preview-num{color:var(--arv-text-muted)}.arv-locked-template{display:flex;align-items:center;gap:8px;padding:7px 10px;background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:var(--arv-radius);margin-top:6px}.arv-locked-label{font-size:11px;color:var(--arv-text-muted);text-transform:uppercase;letter-spacing:.5px;flex-shrink:0}.arv-locked-name{font-size:13px;font-weight:600;color:var(--arv-text)}.arv-step-picker{padding:8px 10px}.arv-select-sm{font-size:12px;padding:6px 8px}.arv-step-fixed{padding:6px 10px;font-size:12px;color:var(--arv-text-muted)}\n"] });
|
|
2649
1759
|
}
|
|
2650
1760
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: MaArvContainerComponent, decorators: [{
|
|
2651
1761
|
type: Component,
|
|
2652
|
-
args: [{ selector: 'ma-arv-container', standalone: true, imports: [NgIf, NgFor], template: `
|
|
2653
|
-
<div class="arv-container">
|
|
2654
|
-
<!-- Content Area -->
|
|
2655
|
-
<div class="arv-content-body" #contentBody>
|
|
2656
|
-
<ng-content></ng-content>
|
|
2657
|
-
</div>
|
|
2658
|
-
|
|
2659
|
-
<!-- Approval Footer -->
|
|
2660
|
-
<div class="arv-footer" *ngIf="!isSubmitted">
|
|
2661
|
-
<div class="arv-footer-inner">
|
|
2662
|
-
|
|
2663
|
-
<!-- Routing mode -->
|
|
2664
|
-
<div class="arv-routing">
|
|
2665
|
-
<div class="arv-routing-header">
|
|
2666
|
-
<h4 class="arv-section-title">Approval Routing</h4>
|
|
2667
|
-
<!-- Show routing toggle only when templateId is NOT locked -->
|
|
2668
|
-
<div *ngIf="!templateId" class="arv-routing-mode">
|
|
2669
|
-
<label class="arv-radio">
|
|
2670
|
-
<input type="radio" name="routingMode" value="template" [checked]="routingMode === 'template'" (change)="routingMode = 'template'"> Use Template
|
|
2671
|
-
</label>
|
|
2672
|
-
<label class="arv-radio">
|
|
2673
|
-
<input type="radio" name="routingMode" value="adhoc" [checked]="routingMode === 'adhoc'" (change)="routingMode = 'adhoc'"> Custom Steps
|
|
2674
|
-
</label>
|
|
2675
|
-
</div>
|
|
2676
|
-
</div>
|
|
2677
|
-
|
|
2678
|
-
<!-- Locked template: show name only, no selector -->
|
|
2679
|
-
<div *ngIf="templateId && routingMode === 'template'" class="arv-template-select">
|
|
2680
|
-
<div *ngIf="loadingTemplate" class="arv-template-loading">Loading template...</div>
|
|
2681
|
-
<div *ngIf="selectedTemplate && !loadingTemplate" class="arv-locked-template">
|
|
2682
|
-
<span class="arv-locked-label">Template</span>
|
|
2683
|
-
<span class="arv-locked-name">{{ selectedTemplate.name }}</span>
|
|
2684
|
-
</div>
|
|
2685
|
-
</div>
|
|
2686
|
-
|
|
2687
|
-
<!-- Free template selector (no locked templateId) -->
|
|
2688
|
-
<div *ngIf="!templateId && routingMode === 'template'" class="arv-template-select">
|
|
2689
|
-
<label class="arv-label">Select Template</label>
|
|
2690
|
-
<select class="arv-select" (change)="onTemplateChange($event)">
|
|
2691
|
-
<option value="">-- Select a template --</option>
|
|
2692
|
-
<option *ngFor="let t of templates" [value]="t.id" [selected]="t.id === selectedTemplateId">{{ t.name }}</option>
|
|
2693
|
-
</select>
|
|
2694
|
-
</div>
|
|
2695
|
-
|
|
2696
|
-
<!-- Step pickers (shared for both locked and free template) -->
|
|
2697
|
-
<div *ngIf="routingMode === 'template'" class="arv-template-select">
|
|
2698
|
-
<div *ngIf="!loadingTemplate && selectedTemplate" class="arv-template-steps">
|
|
2699
|
-
<div class="arv-step-card" *ngFor="let s of selectedTemplate.steps; let i = index">
|
|
2700
|
-
<div class="arv-step-card-header">
|
|
2701
|
-
<span class="arv-step-preview-num">Step {{ s.stepOrder }}</span>
|
|
2702
|
-
<span class="arv-step-preview-name">{{ s.stepName }}</span>
|
|
2703
|
-
<span *ngIf="s.roles && s.roles.length > 0" class="arv-step-role-badge">
|
|
2704
|
-
{{ s.roles[0].positionLevel }}{{ s.roles[0].orgName ? ' · ' + s.roles[0].orgName : '' }}
|
|
2705
|
-
</span>
|
|
2706
|
-
</div>
|
|
2707
|
-
<!-- Selectable step (role-based or multi-assignee): show approver picker -->
|
|
2708
|
-
<div *ngIf="stepCandidates[i]?.length > 1 || stepLoadingCandidates[i]" class="arv-step-picker">
|
|
2709
|
-
<div *ngIf="stepLoadingCandidates[i]" class="arv-template-loading">Loading candidates...</div>
|
|
2710
|
-
<select *ngIf="!stepLoadingCandidates[i]" class="arv-select arv-select-sm"
|
|
2711
|
-
(change)="onStepUserChange(i, $event)">
|
|
2712
|
-
<option value="">-- Select approver --</option>
|
|
2713
|
-
<option *ngFor="let u of stepCandidates[i]" [value]="u.userId"
|
|
2714
|
-
[selected]="u.userId === stepSelectedUsers[i]">
|
|
2715
|
-
{{ u.fullName || u.userId }}{{ u.department ? ' · ' + u.department : '' }}{{ u.position ? ' (' + u.position + ')' : '' }}
|
|
2716
|
-
</option>
|
|
2717
|
-
</select>
|
|
2718
|
-
</div>
|
|
2719
|
-
<!-- Single fixed-user step: show who is assigned -->
|
|
2720
|
-
<div *ngIf="!stepLoadingCandidates[i] && stepCandidates[i]?.length === 1" class="arv-step-fixed">
|
|
2721
|
-
{{ stepCandidates[i][0].fullName || stepCandidates[i][0].userId }}{{ stepCandidates[i][0].department ? ' · ' + stepCandidates[i][0].department : '' }}{{ stepCandidates[i][0].position ? ' (' + stepCandidates[i][0].position + ')' : '' }}
|
|
2722
|
-
</div>
|
|
2723
|
-
</div>
|
|
2724
|
-
<div *ngIf="selectedTemplate.referenceUserIds.length > 0" class="arv-step-preview arv-step-preview-cc">
|
|
2725
|
-
<span class="arv-step-preview-num">CC</span>
|
|
2726
|
-
<span class="arv-step-preview-name">{{ selectedTemplate.referenceUserIds.length }} reference user(s) from template</span>
|
|
2727
|
-
</div>
|
|
2728
|
-
</div>
|
|
2729
|
-
</div>
|
|
2730
|
-
|
|
2731
|
-
<!-- Ad-hoc steps -->
|
|
2732
|
-
<div *ngIf="routingMode === 'adhoc'" class="arv-steps">
|
|
2733
|
-
<div class="arv-step" *ngFor="let step of adHocSteps; let i = index">
|
|
2734
|
-
<div class="arv-step-header">
|
|
2735
|
-
<span class="arv-step-num">Step {{ i + 1 }}</span>
|
|
2736
|
-
<input class="arv-input" [value]="step.stepName" (input)="step.stepName = $any($event.target).value" placeholder="Step name" />
|
|
2737
|
-
<button class="arv-btn-icon arv-btn-danger" (click)="removeStep(i)" title="Remove step">
|
|
2738
|
-
<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>
|
|
2739
|
-
</button>
|
|
2740
|
-
</div>
|
|
2741
|
-
<div class="arv-approvers">
|
|
2742
|
-
<div class="arv-approver-tags">
|
|
2743
|
-
<span class="arv-tag" *ngFor="let uid of step.approverUserIds; let j = index">
|
|
2744
|
-
{{ userLabelMap[uid] || uid }}
|
|
2745
|
-
<button class="arv-tag-remove" (click)="removeApprover(i, j)">x</button>
|
|
2746
|
-
</span>
|
|
2747
|
-
</div>
|
|
2748
|
-
<div class="arv-user-search">
|
|
2749
|
-
<input class="arv-input arv-input-sm" [value]="userSearchQuery[i] || ''"
|
|
2750
|
-
(input)="onUserSearchInput(i, $any($event.target).value)"
|
|
2751
|
-
placeholder="Search approver..." />
|
|
2752
|
-
<div class="arv-search-results" *ngIf="userSearchResults[i]?.length">
|
|
2753
|
-
<div class="arv-search-item"
|
|
2754
|
-
*ngFor="let u of userSearchResults[i]"
|
|
2755
|
-
(click)="addApprover(i, u.id)">
|
|
2756
|
-
{{ u.fullName || u.userName }}
|
|
2757
|
-
</div>
|
|
2758
|
-
</div>
|
|
2759
|
-
</div>
|
|
2760
|
-
</div>
|
|
2761
|
-
</div>
|
|
2762
|
-
<button class="arv-btn arv-btn-outline" (click)="addStep()">+ Add Step</button>
|
|
2763
|
-
</div>
|
|
2764
|
-
</div>
|
|
2765
|
-
|
|
2766
|
-
<!-- Reference / CC users -->
|
|
2767
|
-
<div class="arv-references">
|
|
2768
|
-
<h4 class="arv-section-title">CC / Reference</h4>
|
|
2769
|
-
<p class="arv-hint">These users will be notified when the approval completes, but won't be asked to approve.</p>
|
|
2770
|
-
<div class="arv-approver-tags">
|
|
2771
|
-
<span class="arv-tag" *ngFor="let uid of referenceUserIds; let j = index">
|
|
2772
|
-
{{ userLabelMap[uid] || uid }}
|
|
2773
|
-
<button class="arv-tag-remove" (click)="removeReference(j)">x</button>
|
|
2774
|
-
</span>
|
|
2775
|
-
</div>
|
|
2776
|
-
<div class="arv-user-search">
|
|
2777
|
-
<input class="arv-input arv-input-sm" [value]="refSearchQuery"
|
|
2778
|
-
(input)="onRefSearchInput($any($event.target).value)"
|
|
2779
|
-
placeholder="Search CC user..." />
|
|
2780
|
-
<div class="arv-search-results" *ngIf="refSearchResults?.length">
|
|
2781
|
-
<div class="arv-search-item"
|
|
2782
|
-
*ngFor="let u of refSearchResults"
|
|
2783
|
-
(click)="addReference(u.id)">
|
|
2784
|
-
{{ u.fullName || u.userName }}
|
|
2785
|
-
</div>
|
|
2786
|
-
</div>
|
|
2787
|
-
</div>
|
|
2788
|
-
</div>
|
|
2789
|
-
|
|
2790
|
-
<!-- Actions -->
|
|
2791
|
-
<div class="arv-actions">
|
|
2792
|
-
<button class="arv-btn arv-btn-secondary" (click)="onCancel()">Cancel</button>
|
|
2793
|
-
<button class="arv-btn arv-btn-primary" [disabled]="submitting" (click)="submit()">
|
|
2794
|
-
<span *ngIf="submitting" class="arv-spinner"></span>
|
|
2795
|
-
{{ submitting ? 'Submitting...' : 'Submit for Approval' }}
|
|
2796
|
-
</button>
|
|
2797
|
-
</div>
|
|
2798
|
-
|
|
2799
|
-
<div class="arv-error" *ngIf="errorMessage">{{ errorMessage }}</div>
|
|
2800
|
-
</div>
|
|
2801
|
-
</div>
|
|
2802
|
-
|
|
2803
|
-
<!-- Success state -->
|
|
2804
|
-
<div class="arv-success" *ngIf="isSubmitted">
|
|
2805
|
-
<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">
|
|
2806
|
-
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
|
|
2807
|
-
<polyline points="22 4 12 14.01 9 11.01"/>
|
|
2808
|
-
</svg>
|
|
2809
|
-
<p>Submitted for approval successfully.</p>
|
|
2810
|
-
</div>
|
|
2811
|
-
</div>
|
|
2812
|
-
`, 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}.arv-template-loading{font-size:12px;color:var(--arv-text-muted);margin-top:8px;padding:4px 0}.arv-template-steps{margin-top:8px;display:flex;flex-direction:column;gap:6px}.arv-step-card{background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:var(--arv-radius);overflow:hidden}.arv-step-card-header{display:flex;align-items:center;gap:8px;padding:7px 10px;background:#00000008;border-bottom:1px solid var(--arv-border);flex-wrap:wrap}.arv-step-preview{display:flex;align-items:center;gap:8px;padding:5px 8px;background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:4px;font-size:12px}.arv-step-preview-num{font-weight:700;color:var(--arv-primary);min-width:44px;flex-shrink:0;font-size:12px}.arv-step-preview-name{flex:1;color:var(--arv-text);font-size:12px;font-weight:600}.arv-step-role-badge{font-size:11px;color:var(--arv-text-muted);background:#90caf91a;border:1px solid rgba(144,202,249,.25);border-radius:10px;padding:1px 7px}.arv-step-preview-cc .arv-step-preview-num{color:var(--arv-text-muted)}.arv-locked-template{display:flex;align-items:center;gap:8px;padding:7px 10px;background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:var(--arv-radius);margin-top:6px}.arv-locked-label{font-size:11px;color:var(--arv-text-muted);text-transform:uppercase;letter-spacing:.5px;flex-shrink:0}.arv-locked-name{font-size:13px;font-weight:600;color:var(--arv-text)}.arv-step-picker{padding:8px 10px}.arv-select-sm{font-size:12px;padding:6px 8px}.arv-step-fixed{padding:6px 10px;font-size:12px;color:var(--arv-text-muted)}\n"] }]
|
|
1762
|
+
args: [{ selector: 'ma-arv-container', standalone: true, imports: [], template: "<div class=\"arv-container\">\n <!-- Content Area -->\n <div class=\"arv-content-body\" #contentBody>\n <ng-content></ng-content>\n </div>\n\n <!-- Approval Footer -->\n <div class=\"arv-footer\">\n @if (!isSubmitted) {\n <div class=\"arv-footer-inner\">\n\n <!-- Routing mode -->\n <div class=\"arv-routing\">\n <div class=\"arv-routing-header\">\n <h4 class=\"arv-section-title\">Approval Routing</h4>\n <!-- Show routing toggle only when templateId is NOT locked to a single template -->\n @if (!templateId || (!templateIds && templateIds.length > 0)) {\n <div class=\"arv-routing-mode\">\n <label class=\"arv-radio\">\n <input type=\"radio\" name=\"routingMode\" value=\"template\" [checked]=\"routingMode === 'template'\" (change)=\"routingMode = 'template'\"> Use Template\n </label>\n <label class=\"arv-radio\">\n <input type=\"radio\" name=\"routingMode\" value=\"adhoc\" [checked]=\"routingMode === 'adhoc'\" (change)=\"routingMode = 'adhoc'\"> Custom Steps\n </label>\n </div>\n }\n </div>\n\n <!-- Locked template: only when templateId set and no multi-choice -->\n @if (templateId && (!templateIds || templateIds.length <= 1) && routingMode === 'template') {\n <div class=\"arv-template-select\">\n @if (loadingTemplate) {\n <div class=\"arv-template-loading\">Loading template...</div>\n }\n @if (selectedTemplate && !loadingTemplate) {\n <div class=\"arv-locked-template\">\n <span class=\"arv-locked-label\">Template</span>\n <span class=\"arv-locked-name\">{{ selectedTemplate.name }}</span>\n </div>\n }\n </div>\n }\n\n <!-- Template selector: shown when no templateId, OR when templateIds has multiple choices -->\n @if ((!templateId || (templateIds && templateIds.length > 1)) && routingMode === 'template') {\n <div class=\"arv-template-select\">\n <label class=\"arv-label\">Select Template</label>\n <select class=\"arv-select\" (change)=\"onTemplateChange($event)\">\n <option value=\"\">-- Select a template --</option>\n @for (t of templates; track t.id) {\n <option [value]=\"t.id\" [selected]=\"t.id === selectedTemplateId\">{{ t.name }}</option>\n }\n </select>\n </div>\n }\n\n <!-- Step pickers (shared for both locked and free template) -->\n @if (routingMode === 'template') {\n <div class=\"arv-template-select\">\n @if (!loadingTemplate && selectedTemplate) {\n <div class=\"arv-template-steps\">\n @for (s of selectedTemplate.steps; track s.stepOrder; let i = $index) {\n <div class=\"arv-step-card\">\n <div class=\"arv-step-card-header\">\n <span class=\"arv-step-preview-num\">Step {{ s.stepOrder }}</span>\n <span class=\"arv-step-preview-name\">{{ s.stepName }}</span>\n @if (s.roles && s.roles.length > 0) {\n <span class=\"arv-step-role-badge\">\n {{ s.roles[0].positionLevel }}{{ s.roles[0].orgName ? ' \u00B7 ' + s.roles[0].orgName : '' }}\n </span>\n }\n </div>\n <!-- Selectable step: show approver picker -->\n @if (stepCandidates[i]?.length > 1 || stepLoadingCandidates[i]) {\n <div class=\"arv-step-picker\">\n @if (stepLoadingCandidates[i]) {\n <div class=\"arv-template-loading\">Loading candidates...</div>\n }\n @if (!stepLoadingCandidates[i]) {\n <select class=\"arv-select arv-select-sm\" (change)=\"onStepUserChange(i, $event)\">\n <option value=\"\">-- Select approver --</option>\n @for (u of stepCandidates[i]; track u.userId) {\n <option [value]=\"u.userId\" [selected]=\"u.userId === stepSelectedUsers[i]\">\n {{ u.fullName || u.userId }}{{ u.department ? ' \u00B7 ' + u.department : '' }}{{ u.position ? ' (' + u.position + ')' : '' }}\n </option>\n }\n </select>\n }\n </div>\n }\n <!-- Single fixed-user step: show who is assigned -->\n @if (!stepLoadingCandidates[i] && stepCandidates[i]?.length === 1) {\n <div class=\"arv-step-fixed\">\n {{ stepCandidates[i][0].fullName || stepCandidates[i][0].userId }}{{ stepCandidates[i][0].department ? ' \u00B7 ' + stepCandidates[i][0].department : '' }}{{ stepCandidates[i][0].position ? ' (' + stepCandidates[i][0].position + ')' : '' }}\n </div>\n }\n </div>\n }\n @if (selectedTemplate.referenceUserIds.length > 0) {\n <div class=\"arv-step-preview arv-step-preview-cc\">\n <span class=\"arv-step-preview-num\">CC</span>\n <span class=\"arv-step-preview-name\">{{ selectedTemplate.referenceUserIds.length }} reference user(s) from template</span>\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Ad-hoc steps -->\n @if (routingMode === 'adhoc') {\n <div class=\"arv-steps\">\n @for (step of adHocSteps; track step.stepOrder; let i = $index) {\n <div class=\"arv-step\">\n <div class=\"arv-step-header\">\n <span class=\"arv-step-num\">Step {{ i + 1 }}</span>\n <input class=\"arv-input\" [value]=\"step.stepName\" (input)=\"step.stepName = $any($event.target).value\" placeholder=\"Step name\" />\n <button class=\"arv-btn-icon arv-btn-danger\" (click)=\"removeStep(i)\" title=\"Remove step\">\n <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>\n </button>\n </div>\n <div class=\"arv-approvers\">\n <div class=\"arv-approver-tags\">\n @for (uid of step.approverUserIds; track uid; let j = $index) {\n <span class=\"arv-tag\">\n {{ userLabelMap[uid] || uid }}\n <button class=\"arv-tag-remove\" (click)=\"removeApprover(i, j)\">x</button>\n </span>\n }\n </div>\n <div class=\"arv-user-search\">\n <input class=\"arv-input arv-input-sm\" [value]=\"userSearchQuery[i] || ''\"\n (input)=\"onUserSearchInput(i, $any($event.target).value)\"\n placeholder=\"Search approver...\" />\n @if (userSearchResults[i]?.length) {\n <div class=\"arv-search-results\">\n @for (u of userSearchResults[i]; track u.id) {\n <div class=\"arv-search-item\" (click)=\"addApprover(i, u.id)\">\n {{ u.fullName || u.userName }}\n </div>\n }\n </div>\n }\n </div>\n </div>\n </div>\n }\n <button class=\"arv-btn arv-btn-outline\" (click)=\"addStep()\">+ Add Step</button>\n </div>\n }\n </div>\n\n <!-- Reference / CC users -->\n <div class=\"arv-references\">\n <h4 class=\"arv-section-title\">CC / Reference</h4>\n <p class=\"arv-hint\">These users will be notified when the approval completes, but won't be asked to approve.</p>\n <div class=\"arv-approver-tags\">\n @for (uid of referenceUserIds; track uid; let j = $index) {\n <span class=\"arv-tag\">\n {{ userLabelMap[uid] || uid }}\n <button class=\"arv-tag-remove\" (click)=\"removeReference(j)\">x</button>\n </span>\n }\n </div>\n <div class=\"arv-user-search\">\n <input class=\"arv-input arv-input-sm\" [value]=\"refSearchQuery\"\n (input)=\"onRefSearchInput($any($event.target).value)\"\n placeholder=\"Search CC user...\" />\n @if (refSearchResults?.length) {\n <div class=\"arv-search-results\">\n @for (u of refSearchResults; track u.id) {\n <div class=\"arv-search-item\" (click)=\"addReference(u.id)\">\n {{ u.fullName || u.userName }}\n </div>\n }\n </div>\n }\n </div>\n </div>\n\n <!-- Actions -->\n <div class=\"arv-actions\">\n <button class=\"arv-btn arv-btn-secondary\" (click)=\"onCancel()\">Cancel</button>\n <button class=\"arv-btn arv-btn-primary\" [disabled]=\"submitting\" (click)=\"submit()\">\n @if (submitting) {\n <span class=\"arv-spinner\"></span>\n }\n {{ submitting ? 'Submitting...' : 'Submit for Approval' }}\n </button>\n </div>\n\n @if (errorMessage) {\n <div class=\"arv-error\">{{ errorMessage }}</div>\n }\n </div>\n }\n </div>\n\n <!-- Success state -->\n @if (isSubmitted) {\n <div class=\"arv-success\">\n <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\">\n <path d=\"M22 11.08V12a10 10 0 1 1-5.93-9.14\"/>\n <polyline points=\"22 4 12 14.01 9 11.01\"/>\n </svg>\n <p>Submitted for approval successfully.</p>\n </div>\n }\n</div>\n", styles: [".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}.arv-template-loading{font-size:12px;color:var(--arv-text-muted);margin-top:8px;padding:4px 0}.arv-template-steps{margin-top:8px;display:flex;flex-direction:column;gap:6px}.arv-step-card{background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:var(--arv-radius);overflow:hidden}.arv-step-card-header{display:flex;align-items:center;gap:8px;padding:7px 10px;background:#00000008;border-bottom:1px solid var(--arv-border);flex-wrap:wrap}.arv-step-preview{display:flex;align-items:center;gap:8px;padding:5px 8px;background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:4px;font-size:12px}.arv-step-preview-num{font-weight:700;color:var(--arv-primary);min-width:44px;flex-shrink:0;font-size:12px}.arv-step-preview-name{flex:1;color:var(--arv-text);font-size:12px;font-weight:600}.arv-step-role-badge{font-size:11px;color:var(--arv-text-muted);background:#90caf91a;border:1px solid rgba(144,202,249,.25);border-radius:10px;padding:1px 7px}.arv-step-preview-cc .arv-step-preview-num{color:var(--arv-text-muted)}.arv-locked-template{display:flex;align-items:center;gap:8px;padding:7px 10px;background:var(--arv-bg);border:1px solid var(--arv-border);border-radius:var(--arv-radius);margin-top:6px}.arv-locked-label{font-size:11px;color:var(--arv-text-muted);text-transform:uppercase;letter-spacing:.5px;flex-shrink:0}.arv-locked-name{font-size:13px;font-weight:600;color:var(--arv-text)}.arv-step-picker{padding:8px 10px}.arv-select-sm{font-size:12px;padding:6px 8px}.arv-step-fixed{padding:6px 10px;font-size:12px;color:var(--arv-text-muted)}\n"] }]
|
|
2813
1763
|
}], propDecorators: { title: [{
|
|
2814
1764
|
type: Input
|
|
2815
1765
|
}], description: [{
|
|
@@ -2818,6 +1768,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImpor
|
|
|
2818
1768
|
type: Input
|
|
2819
1769
|
}], templateId: [{
|
|
2820
1770
|
type: Input
|
|
1771
|
+
}], templateIds: [{
|
|
1772
|
+
type: Input
|
|
2821
1773
|
}], callbackUrl: [{
|
|
2822
1774
|
type: Input
|
|
2823
1775
|
}], deadlineHours: [{
|
|
@@ -2872,9 +1824,130 @@ function xMaResource(request) {
|
|
|
2872
1824
|
});
|
|
2873
1825
|
}
|
|
2874
1826
|
|
|
1827
|
+
/**
|
|
1828
|
+
* MaIconComponent
|
|
1829
|
+
* Renders an SVG icon from the server-hosted sprite.
|
|
1830
|
+
* The sprite URL is derived from the stylesheet link injected by MesAuthService,
|
|
1831
|
+
* so changing the sprite on the server updates icons without a package rebuild.
|
|
1832
|
+
*
|
|
1833
|
+
* Selector: <ma-icon name="bell" [size]="20" />
|
|
1834
|
+
*
|
|
1835
|
+
* When the sprite is not available, falls back to inline SVG definitions
|
|
1836
|
+
* for the icons used internally by the library (bell, check, close, etc.).
|
|
1837
|
+
*
|
|
1838
|
+
* This component is intentionally lightweight — it is the single place where
|
|
1839
|
+
* icon SVG is defined, replacing the repeated inline <svg> blocks that were
|
|
1840
|
+
* scattered across every component template.
|
|
1841
|
+
*/
|
|
1842
|
+
class MaIconComponent {
|
|
1843
|
+
name = '';
|
|
1844
|
+
size = 18;
|
|
1845
|
+
stroke = 'currentColor';
|
|
1846
|
+
strokeWidth = 2;
|
|
1847
|
+
svgContent = '';
|
|
1848
|
+
sanitizer = inject(DomSanitizer);
|
|
1849
|
+
ngOnChanges(changes) {
|
|
1850
|
+
if (changes['name'] || changes['size'] || changes['stroke'] || changes['strokeWidth']) {
|
|
1851
|
+
this.svgContent = this.sanitizer.bypassSecurityTrustHtml(this.buildSvg());
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
buildSvg() {
|
|
1855
|
+
const w = this.size;
|
|
1856
|
+
const h = this.size;
|
|
1857
|
+
const s = this.stroke;
|
|
1858
|
+
const sw = this.strokeWidth;
|
|
1859
|
+
const base = `xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 24 24" fill="none" stroke="${s}" stroke-width="${sw}" stroke-linecap="round" stroke-linejoin="round"`;
|
|
1860
|
+
switch (this.name) {
|
|
1861
|
+
case 'bell':
|
|
1862
|
+
return `<svg ${base}><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>`;
|
|
1863
|
+
case 'check':
|
|
1864
|
+
return `<svg ${base}><polyline points="20 6 9 17 4 12"/></svg>`;
|
|
1865
|
+
case 'check-circle':
|
|
1866
|
+
return `<svg ${base}><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>`;
|
|
1867
|
+
case 'close':
|
|
1868
|
+
return `<svg ${base}><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>`;
|
|
1869
|
+
case 'alert-triangle':
|
|
1870
|
+
return `<svg ${base}><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>`;
|
|
1871
|
+
case 'x-circle':
|
|
1872
|
+
return `<svg ${base}><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>`;
|
|
1873
|
+
case 'clipboard-check':
|
|
1874
|
+
return `<svg ${base}><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>`;
|
|
1875
|
+
case 'trash':
|
|
1876
|
+
return `<svg ${base}><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2"/></svg>`;
|
|
1877
|
+
case 'user':
|
|
1878
|
+
return `<svg ${base}><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>`;
|
|
1879
|
+
case 'log-out':
|
|
1880
|
+
return `<svg ${base}><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><polyline points="16 17 21 12 16 7"/><line x1="21" y1="12" x2="9" y2="12"/></svg>`;
|
|
1881
|
+
default:
|
|
1882
|
+
return `<svg ${base}><circle cx="12" cy="12" r="10"/></svg>`;
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: MaIconComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1886
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.7", type: MaIconComponent, isStandalone: true, selector: "ma-icon", inputs: { name: "name", size: "size", stroke: "stroke", strokeWidth: "strokeWidth" }, usesOnChanges: true, ngImport: i0, template: `<span class="ma-icon" [style.width.px]="size" [style.height.px]="size" [innerHTML]="svgContent"></span>`, isInline: true, styles: [":host{display:inline-flex;align-items:center;justify-content:center}.ma-icon{display:flex;align-items:center;justify-content:center}.ma-icon svg{display:block}\n"] });
|
|
1887
|
+
}
|
|
1888
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: MaIconComponent, decorators: [{
|
|
1889
|
+
type: Component,
|
|
1890
|
+
args: [{ selector: 'ma-icon', standalone: true, template: `<span class="ma-icon" [style.width.px]="size" [style.height.px]="size" [innerHTML]="svgContent"></span>`, styles: [":host{display:inline-flex;align-items:center;justify-content:center}.ma-icon{display:flex;align-items:center;justify-content:center}.ma-icon svg{display:block}\n"] }]
|
|
1891
|
+
}], propDecorators: { name: [{
|
|
1892
|
+
type: Input
|
|
1893
|
+
}], size: [{
|
|
1894
|
+
type: Input
|
|
1895
|
+
}], stroke: [{
|
|
1896
|
+
type: Input
|
|
1897
|
+
}], strokeWidth: [{
|
|
1898
|
+
type: Input
|
|
1899
|
+
}] } });
|
|
1900
|
+
|
|
1901
|
+
/**
|
|
1902
|
+
* MaThemeDirective
|
|
1903
|
+
* Apply `maTheme` to any element or component that needs automatic theme-class binding.
|
|
1904
|
+
* Adds `theme-light` or `theme-dark` to the host element's class list and keeps it
|
|
1905
|
+
* in sync with ThemeService, replacing the duplicated @HostBinding + subscription
|
|
1906
|
+
* pattern that was repeated in every component.
|
|
1907
|
+
*
|
|
1908
|
+
* Usage:
|
|
1909
|
+
* <div maTheme> ... </div>
|
|
1910
|
+
*
|
|
1911
|
+
* Or as a hostDirective in a component:
|
|
1912
|
+
* @Component({ hostDirectives: [MaThemeDirective], ... })
|
|
1913
|
+
*/
|
|
1914
|
+
class MaThemeDirective {
|
|
1915
|
+
themeClass = 'theme-light';
|
|
1916
|
+
themeService = inject(ThemeService);
|
|
1917
|
+
destroy$ = new Subject();
|
|
1918
|
+
ngOnInit() {
|
|
1919
|
+
this.themeService.currentTheme$
|
|
1920
|
+
.pipe(takeUntil(this.destroy$))
|
|
1921
|
+
.subscribe((theme) => {
|
|
1922
|
+
this.themeClass = `theme-${theme}`;
|
|
1923
|
+
});
|
|
1924
|
+
}
|
|
1925
|
+
ngOnDestroy() {
|
|
1926
|
+
this.destroy$.next();
|
|
1927
|
+
this.destroy$.complete();
|
|
1928
|
+
}
|
|
1929
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: MaThemeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
|
1930
|
+
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.7", type: MaThemeDirective, isStandalone: true, selector: "[maTheme]", host: { properties: { "class": "this.themeClass" } }, ngImport: i0 });
|
|
1931
|
+
}
|
|
1932
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.7", ngImport: i0, type: MaThemeDirective, decorators: [{
|
|
1933
|
+
type: Directive,
|
|
1934
|
+
args: [{
|
|
1935
|
+
selector: '[maTheme]',
|
|
1936
|
+
standalone: true
|
|
1937
|
+
}]
|
|
1938
|
+
}], propDecorators: { themeClass: [{
|
|
1939
|
+
type: HostBinding,
|
|
1940
|
+
args: ['class']
|
|
1941
|
+
}] } });
|
|
1942
|
+
|
|
1943
|
+
// mesauth-angular — primary entry point (backward compatible)
|
|
1944
|
+
// Re-exports everything so existing imports from 'mesauth-angular' continue to work.
|
|
1945
|
+
// Prefer importing from 'mesauth-angular/core' or 'mesauth-angular/ui' for
|
|
1946
|
+
// better tree-shaking and to express which layer you depend on.
|
|
1947
|
+
|
|
2875
1948
|
/**
|
|
2876
1949
|
* Generated bundle index. Do not edit.
|
|
2877
1950
|
*/
|
|
2878
1951
|
|
|
2879
|
-
export { ALL_ACTIONS, ApprovalActionType, ApprovalDocumentStatus, ApprovalStepMode, ApprovalStepStatus, MES_AUTH_CONFIG, MaApprovalPanelComponent, MaApprovalService, MaArvContainerComponent, MaUserComponent, MesAuthModule, MesAuthService, NotificationBadgeComponent, NotificationPanelComponent, NotificationType, ThemeService, ToastContainerComponent, ToastService, UserProfileComponent, extractXMaPerm, mesAuthInterceptor, provideMesAuth, withXMaPerm, xMaResource };
|
|
1952
|
+
export { ALL_ACTIONS, ApprovalActionType, ApprovalDocumentStatus, ApprovalStepMode, ApprovalStepStatus, MES_AUTH_CONFIG, MaApprovalPanelComponent, MaApprovalService, MaArvContainerComponent, MaIconComponent, MaThemeDirective, MaUiConfigService, MaUserComponent, MesAuthModule, MesAuthService, NotificationBadgeComponent, NotificationPanelComponent, NotificationType, ThemeService, ToastContainerComponent, ToastService, UserProfileComponent, extractXMaPerm, mesAuthInterceptor, provideMesAuth, withXMaPerm, xMaResource };
|
|
2880
1953
|
//# sourceMappingURL=mesauth-angular.mjs.map
|