mesauth-angular 1.3.2 → 1.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +305 -305
- package/fesm2022/mesauth-angular.mjs +484 -251
- package/fesm2022/mesauth-angular.mjs.map +1 -1
- package/index.d.ts +4 -0
- package/package.json +1 -1
|
@@ -2,8 +2,8 @@ import * as i0 from '@angular/core';
|
|
|
2
2
|
import { InjectionToken, makeEnvironmentProviders, provideAppInitializer, inject, NgZone, Injectable, NgModule, EventEmitter, signal, HostListener, HostBinding, Output, Component, ViewChild } from '@angular/core';
|
|
3
3
|
import { HttpClient } from '@angular/common/http';
|
|
4
4
|
import { HubConnectionBuilder, LogLevel } from '@microsoft/signalr';
|
|
5
|
-
import { BehaviorSubject, Subject, EMPTY, of, throwError } from 'rxjs';
|
|
6
|
-
import { tap, catchError, takeUntil } from 'rxjs/operators';
|
|
5
|
+
import { BehaviorSubject, Subject, EMPTY, of, timer, throwError } from 'rxjs';
|
|
6
|
+
import { tap, catchError, switchMap, takeUntil } from 'rxjs/operators';
|
|
7
7
|
import * as i2 from '@angular/router';
|
|
8
8
|
import { Router } from '@angular/router';
|
|
9
9
|
import * as i3 from '@angular/common';
|
|
@@ -267,10 +267,10 @@ class MesAuthService {
|
|
|
267
267
|
refreshUser() {
|
|
268
268
|
return this.fetchCurrentUser();
|
|
269
269
|
}
|
|
270
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
271
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
270
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MesAuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
271
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MesAuthService });
|
|
272
272
|
}
|
|
273
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
273
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MesAuthService, decorators: [{
|
|
274
274
|
type: Injectable
|
|
275
275
|
}], ctorParameters: () => [] });
|
|
276
276
|
|
|
@@ -303,12 +303,16 @@ const mesAuthInterceptor = (req, next) => {
|
|
|
303
303
|
// Skip redirect for the initial /auth/me check (app startup when not logged in)
|
|
304
304
|
const isMeAuthPage = req.url.includes('/auth/me');
|
|
305
305
|
if (status === 401 && !isLoginPage && !isAuthPage && !isMeAuthPage && !isPublicPage) {
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
306
|
+
// Wait 1.5s for the concurrent refresh's Set-Cookie to be processed, then retry once.
|
|
307
|
+
// If retry also gets 401, redirect to login.
|
|
308
|
+
return timer(1500).pipe(switchMap(() => next(req)), catchError((retryError) => {
|
|
309
|
+
if (retryError.status === 401) {
|
|
310
|
+
isRedirecting = true;
|
|
311
|
+
setTimeout(() => { isRedirecting = false; }, 5000);
|
|
312
|
+
window.location.href = `${baseUrl}/login?returnUrl=${returnUrl}`;
|
|
313
|
+
}
|
|
314
|
+
return throwError(() => retryError);
|
|
315
|
+
}));
|
|
312
316
|
}
|
|
313
317
|
else if (status === 403 && !is403Page) {
|
|
314
318
|
isRedirecting = true;
|
|
@@ -325,13 +329,13 @@ const mesAuthInterceptor = (req, next) => {
|
|
|
325
329
|
};
|
|
326
330
|
|
|
327
331
|
class MesAuthModule {
|
|
328
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
329
|
-
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.
|
|
330
|
-
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.
|
|
332
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MesAuthModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
333
|
+
static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.18", ngImport: i0, type: MesAuthModule });
|
|
334
|
+
static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MesAuthModule, providers: [
|
|
331
335
|
MesAuthService
|
|
332
336
|
] });
|
|
333
337
|
}
|
|
334
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
338
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MesAuthModule, decorators: [{
|
|
335
339
|
type: NgModule,
|
|
336
340
|
args: [{
|
|
337
341
|
providers: [
|
|
@@ -390,10 +394,10 @@ class ThemeService {
|
|
|
390
394
|
refreshTheme() {
|
|
391
395
|
this.detectTheme();
|
|
392
396
|
}
|
|
393
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
394
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
397
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
398
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ThemeService, providedIn: 'root' });
|
|
395
399
|
}
|
|
396
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
400
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ThemeService, decorators: [{
|
|
397
401
|
type: Injectable,
|
|
398
402
|
args: [{
|
|
399
403
|
providedIn: 'root'
|
|
@@ -540,102 +544,170 @@ class UserProfileComponent {
|
|
|
540
544
|
onNotificationClick() {
|
|
541
545
|
this.notificationClick.emit();
|
|
542
546
|
}
|
|
543
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
544
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.
|
|
545
|
-
<div class="user-profile-container">
|
|
546
|
-
<!-- Not logged in -->
|
|
547
|
-
<ng-container *ngIf="!currentUser()">
|
|
548
|
-
<button class="login-btn" (click)="onLogin()">
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
<
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
547
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: UserProfileComponent, deps: [{ token: MesAuthService }, { token: i2.Router }, { token: ThemeService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
548
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: UserProfileComponent, isStandalone: true, selector: "ma-user-profile", outputs: { notificationClick: "notificationClick" }, host: { listeners: { "document:click": "onDocumentClick($event)" }, properties: { "class": "this.themeClass" } }, ngImport: i0, template: `
|
|
549
|
+
<div class="user-profile-container">
|
|
550
|
+
<!-- Not logged in -->
|
|
551
|
+
<ng-container *ngIf="!currentUser()">
|
|
552
|
+
<button class="login-btn" (click)="onLogin()">Login</button>
|
|
553
|
+
</ng-container>
|
|
554
|
+
|
|
555
|
+
<!-- Logged in -->
|
|
556
|
+
<ng-container *ngIf="currentUser()">
|
|
557
|
+
<div class="user-header">
|
|
558
|
+
<!-- Notification Bell -->
|
|
559
|
+
<button class="notification-btn" [class.has-unread]="unreadCount > 0" (click)="onNotificationClick()" title="Notifications">
|
|
560
|
+
<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">
|
|
561
|
+
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
|
|
562
|
+
<path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
|
563
|
+
</svg>
|
|
564
|
+
<span class="badge" *ngIf="unreadCount > 0">{{ unreadCount > 99 ? '99+' : unreadCount }}</span>
|
|
565
|
+
</button>
|
|
566
|
+
|
|
567
|
+
<!-- User Avatar + Dropdown -->
|
|
568
|
+
<div class="user-menu-wrapper">
|
|
569
|
+
<button class="user-menu-btn" (click)="toggleDropdown()">
|
|
570
|
+
<div class="avatar-ring" [class.active]="dropdownOpen">
|
|
571
|
+
<img
|
|
572
|
+
*ngIf="currentUser().fullName || currentUser().userName"
|
|
573
|
+
[src]="getAvatarUrl(currentUser())"
|
|
574
|
+
[alt]="currentUser().fullName || currentUser().userName"
|
|
575
|
+
class="avatar"
|
|
576
|
+
/>
|
|
577
|
+
<span *ngIf="!(currentUser().fullName || currentUser().userName)" class="avatar-initial">
|
|
578
|
+
{{ getLastNameInitial(currentUser()) }}
|
|
579
|
+
</span>
|
|
580
|
+
</div>
|
|
581
|
+
</button>
|
|
582
|
+
|
|
583
|
+
<div class="mes-dropdown-menu" *ngIf="dropdownOpen">
|
|
584
|
+
<!-- User info header -->
|
|
585
|
+
<div class="mes-dropdown-header">
|
|
586
|
+
<div class="dropdown-avatar-wrap">
|
|
587
|
+
<img
|
|
588
|
+
*ngIf="currentUser().fullName || currentUser().userName"
|
|
589
|
+
[src]="getAvatarUrl(currentUser())"
|
|
590
|
+
[alt]="currentUser().fullName || currentUser().userName"
|
|
591
|
+
class="dropdown-avatar"
|
|
592
|
+
/>
|
|
593
|
+
<span *ngIf="!(currentUser().fullName || currentUser().userName)" class="dropdown-avatar-initial">
|
|
594
|
+
{{ getLastNameInitial(currentUser()) }}
|
|
595
|
+
</span>
|
|
596
|
+
</div>
|
|
597
|
+
<div class="dropdown-user-info">
|
|
598
|
+
<span class="dropdown-user-name">{{ currentUser().fullName || currentUser().userName }}</span>
|
|
599
|
+
<span class="dropdown-user-sub" *ngIf="currentUser().position || currentUser().department">
|
|
600
|
+
{{ currentUser().position || currentUser().department }}
|
|
601
|
+
</span>
|
|
602
|
+
</div>
|
|
603
|
+
</div>
|
|
604
|
+
|
|
605
|
+
<div class="mes-dropdown-divider"></div>
|
|
606
|
+
|
|
607
|
+
<button class="mes-dropdown-item profile-link" (click)="onViewProfile()">
|
|
608
|
+
<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">
|
|
609
|
+
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>
|
|
610
|
+
</svg>
|
|
611
|
+
View Profile
|
|
612
|
+
</button>
|
|
613
|
+
|
|
614
|
+
<div class="mes-dropdown-divider"></div>
|
|
615
|
+
|
|
616
|
+
<button class="mes-dropdown-item logout-item" (click)="onLogout()">
|
|
617
|
+
<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">
|
|
618
|
+
<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"/>
|
|
619
|
+
</svg>
|
|
620
|
+
Logout
|
|
621
|
+
</button>
|
|
622
|
+
</div>
|
|
623
|
+
</div>
|
|
624
|
+
</div>
|
|
625
|
+
</ng-container>
|
|
626
|
+
</div>
|
|
627
|
+
`, 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"] }] });
|
|
590
628
|
}
|
|
591
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
629
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: UserProfileComponent, decorators: [{
|
|
592
630
|
type: Component,
|
|
593
|
-
args: [{ selector: 'ma-user-profile', standalone: true, imports: [NgIf], template: `
|
|
594
|
-
<div class="user-profile-container">
|
|
595
|
-
<!-- Not logged in -->
|
|
596
|
-
<ng-container *ngIf="!currentUser()">
|
|
597
|
-
<button class="login-btn" (click)="onLogin()">
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
<
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
631
|
+
args: [{ selector: 'ma-user-profile', standalone: true, imports: [NgIf], template: `
|
|
632
|
+
<div class="user-profile-container">
|
|
633
|
+
<!-- Not logged in -->
|
|
634
|
+
<ng-container *ngIf="!currentUser()">
|
|
635
|
+
<button class="login-btn" (click)="onLogin()">Login</button>
|
|
636
|
+
</ng-container>
|
|
637
|
+
|
|
638
|
+
<!-- Logged in -->
|
|
639
|
+
<ng-container *ngIf="currentUser()">
|
|
640
|
+
<div class="user-header">
|
|
641
|
+
<!-- Notification Bell -->
|
|
642
|
+
<button class="notification-btn" [class.has-unread]="unreadCount > 0" (click)="onNotificationClick()" title="Notifications">
|
|
643
|
+
<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">
|
|
644
|
+
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
|
|
645
|
+
<path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
|
646
|
+
</svg>
|
|
647
|
+
<span class="badge" *ngIf="unreadCount > 0">{{ unreadCount > 99 ? '99+' : unreadCount }}</span>
|
|
648
|
+
</button>
|
|
649
|
+
|
|
650
|
+
<!-- User Avatar + Dropdown -->
|
|
651
|
+
<div class="user-menu-wrapper">
|
|
652
|
+
<button class="user-menu-btn" (click)="toggleDropdown()">
|
|
653
|
+
<div class="avatar-ring" [class.active]="dropdownOpen">
|
|
654
|
+
<img
|
|
655
|
+
*ngIf="currentUser().fullName || currentUser().userName"
|
|
656
|
+
[src]="getAvatarUrl(currentUser())"
|
|
657
|
+
[alt]="currentUser().fullName || currentUser().userName"
|
|
658
|
+
class="avatar"
|
|
659
|
+
/>
|
|
660
|
+
<span *ngIf="!(currentUser().fullName || currentUser().userName)" class="avatar-initial">
|
|
661
|
+
{{ getLastNameInitial(currentUser()) }}
|
|
662
|
+
</span>
|
|
663
|
+
</div>
|
|
664
|
+
</button>
|
|
665
|
+
|
|
666
|
+
<div class="mes-dropdown-menu" *ngIf="dropdownOpen">
|
|
667
|
+
<!-- User info header -->
|
|
668
|
+
<div class="mes-dropdown-header">
|
|
669
|
+
<div class="dropdown-avatar-wrap">
|
|
670
|
+
<img
|
|
671
|
+
*ngIf="currentUser().fullName || currentUser().userName"
|
|
672
|
+
[src]="getAvatarUrl(currentUser())"
|
|
673
|
+
[alt]="currentUser().fullName || currentUser().userName"
|
|
674
|
+
class="dropdown-avatar"
|
|
675
|
+
/>
|
|
676
|
+
<span *ngIf="!(currentUser().fullName || currentUser().userName)" class="dropdown-avatar-initial">
|
|
677
|
+
{{ getLastNameInitial(currentUser()) }}
|
|
678
|
+
</span>
|
|
679
|
+
</div>
|
|
680
|
+
<div class="dropdown-user-info">
|
|
681
|
+
<span class="dropdown-user-name">{{ currentUser().fullName || currentUser().userName }}</span>
|
|
682
|
+
<span class="dropdown-user-sub" *ngIf="currentUser().position || currentUser().department">
|
|
683
|
+
{{ currentUser().position || currentUser().department }}
|
|
684
|
+
</span>
|
|
685
|
+
</div>
|
|
686
|
+
</div>
|
|
687
|
+
|
|
688
|
+
<div class="mes-dropdown-divider"></div>
|
|
689
|
+
|
|
690
|
+
<button class="mes-dropdown-item profile-link" (click)="onViewProfile()">
|
|
691
|
+
<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">
|
|
692
|
+
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>
|
|
693
|
+
</svg>
|
|
694
|
+
View Profile
|
|
695
|
+
</button>
|
|
696
|
+
|
|
697
|
+
<div class="mes-dropdown-divider"></div>
|
|
698
|
+
|
|
699
|
+
<button class="mes-dropdown-item logout-item" (click)="onLogout()">
|
|
700
|
+
<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">
|
|
701
|
+
<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"/>
|
|
702
|
+
</svg>
|
|
703
|
+
Logout
|
|
704
|
+
</button>
|
|
705
|
+
</div>
|
|
706
|
+
</div>
|
|
707
|
+
</div>
|
|
708
|
+
</ng-container>
|
|
709
|
+
</div>
|
|
710
|
+
`, 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"] }]
|
|
639
711
|
}], ctorParameters: () => [{ type: MesAuthService }, { type: i2.Router }, { type: ThemeService }, { type: i0.ChangeDetectorRef }], propDecorators: { notificationClick: [{
|
|
640
712
|
type: Output
|
|
641
713
|
}], themeClass: [{
|
|
@@ -674,10 +746,10 @@ class ToastService {
|
|
|
674
746
|
clear() {
|
|
675
747
|
this.toasts$.next([]);
|
|
676
748
|
}
|
|
677
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
678
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.
|
|
749
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ToastService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
750
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ToastService, providedIn: 'root' });
|
|
679
751
|
}
|
|
680
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
752
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ToastService, decorators: [{
|
|
681
753
|
type: Injectable,
|
|
682
754
|
args: [{ providedIn: 'root' }]
|
|
683
755
|
}] });
|
|
@@ -714,46 +786,86 @@ class ToastContainerComponent {
|
|
|
714
786
|
close(id) {
|
|
715
787
|
this.toastService.remove(id);
|
|
716
788
|
}
|
|
717
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
718
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.
|
|
719
|
-
<div class="toast-container">
|
|
720
|
-
<div
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
<
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
789
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ToastContainerComponent, deps: [{ token: ToastService }, { token: ThemeService }], target: i0.ɵɵFactoryTarget.Component });
|
|
790
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: ToastContainerComponent, isStandalone: true, selector: "ma-toast-container", host: { properties: { "class": "this.themeClass" } }, ngImport: i0, template: `
|
|
791
|
+
<div class="toast-container">
|
|
792
|
+
<div *ngFor="let toast of toasts" class="toast toast-{{ toast.type }}">
|
|
793
|
+
|
|
794
|
+
<!-- Type icon -->
|
|
795
|
+
<div class="toast-icon">
|
|
796
|
+
<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">
|
|
797
|
+
<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"/>
|
|
798
|
+
</svg>
|
|
799
|
+
<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">
|
|
800
|
+
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>
|
|
801
|
+
</svg>
|
|
802
|
+
<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">
|
|
803
|
+
<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"/>
|
|
804
|
+
</svg>
|
|
805
|
+
<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">
|
|
806
|
+
<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"/>
|
|
807
|
+
</svg>
|
|
808
|
+
</div>
|
|
809
|
+
|
|
810
|
+
<!-- Content -->
|
|
811
|
+
<div class="toast-content">
|
|
812
|
+
<div *ngIf="toast.title" class="toast-title">{{ toast.title }}</div>
|
|
813
|
+
<div class="toast-message" [innerHTML]="toast.message"></div>
|
|
814
|
+
</div>
|
|
815
|
+
|
|
816
|
+
<!-- Close -->
|
|
817
|
+
<button class="toast-close" (click)="close(toast.id)" aria-label="Close">
|
|
818
|
+
<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">
|
|
819
|
+
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
820
|
+
</svg>
|
|
821
|
+
</button>
|
|
822
|
+
|
|
823
|
+
<!-- Auto-dismiss progress bar -->
|
|
824
|
+
<div class="toast-progress" [style.animation-duration]="(toast.duration || 5000) + 'ms'"></div>
|
|
825
|
+
</div>
|
|
826
|
+
</div>
|
|
827
|
+
`, 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"] }] });
|
|
736
828
|
}
|
|
737
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
829
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ToastContainerComponent, decorators: [{
|
|
738
830
|
type: Component,
|
|
739
|
-
args: [{ selector: 'ma-toast-container', standalone: true, imports: [CommonModule], template: `
|
|
740
|
-
<div class="toast-container">
|
|
741
|
-
<div
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
<
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
831
|
+
args: [{ selector: 'ma-toast-container', standalone: true, imports: [CommonModule], template: `
|
|
832
|
+
<div class="toast-container">
|
|
833
|
+
<div *ngFor="let toast of toasts" class="toast toast-{{ toast.type }}">
|
|
834
|
+
|
|
835
|
+
<!-- Type icon -->
|
|
836
|
+
<div class="toast-icon">
|
|
837
|
+
<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">
|
|
838
|
+
<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"/>
|
|
839
|
+
</svg>
|
|
840
|
+
<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">
|
|
841
|
+
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>
|
|
842
|
+
</svg>
|
|
843
|
+
<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">
|
|
844
|
+
<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"/>
|
|
845
|
+
</svg>
|
|
846
|
+
<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">
|
|
847
|
+
<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"/>
|
|
848
|
+
</svg>
|
|
849
|
+
</div>
|
|
850
|
+
|
|
851
|
+
<!-- Content -->
|
|
852
|
+
<div class="toast-content">
|
|
853
|
+
<div *ngIf="toast.title" class="toast-title">{{ toast.title }}</div>
|
|
854
|
+
<div class="toast-message" [innerHTML]="toast.message"></div>
|
|
855
|
+
</div>
|
|
856
|
+
|
|
857
|
+
<!-- Close -->
|
|
858
|
+
<button class="toast-close" (click)="close(toast.id)" aria-label="Close">
|
|
859
|
+
<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">
|
|
860
|
+
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
861
|
+
</svg>
|
|
862
|
+
</button>
|
|
863
|
+
|
|
864
|
+
<!-- Auto-dismiss progress bar -->
|
|
865
|
+
<div class="toast-progress" [style.animation-duration]="(toast.duration || 5000) + 'ms'"></div>
|
|
866
|
+
</div>
|
|
867
|
+
</div>
|
|
868
|
+
`, 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"] }]
|
|
757
869
|
}], ctorParameters: () => [{ type: ToastService }, { type: ThemeService }], propDecorators: { themeClass: [{
|
|
758
870
|
type: HostBinding,
|
|
759
871
|
args: ['class']
|
|
@@ -794,6 +906,23 @@ class NotificationPanelComponent {
|
|
|
794
906
|
getNotificationMessage(notification) {
|
|
795
907
|
return notification.message || '';
|
|
796
908
|
}
|
|
909
|
+
// Normalize type to string — API may return integer (0/1/2/3) or string ('Info'/'Warning'/'Error'/'Success')
|
|
910
|
+
typeOf(notification) {
|
|
911
|
+
const t = notification.type;
|
|
912
|
+
if (t === 0 || t === 'Info')
|
|
913
|
+
return 'Info';
|
|
914
|
+
if (t === 1 || t === 'Warning')
|
|
915
|
+
return 'Warning';
|
|
916
|
+
if (t === 2 || t === 'Error')
|
|
917
|
+
return 'Error';
|
|
918
|
+
if (t === 3 || t === 'Success')
|
|
919
|
+
return 'Success';
|
|
920
|
+
return 'Info';
|
|
921
|
+
}
|
|
922
|
+
toastType(type) {
|
|
923
|
+
const t = this.typeOf({ type });
|
|
924
|
+
return t.toLowerCase();
|
|
925
|
+
}
|
|
797
926
|
sanitizer = inject(DomSanitizer);
|
|
798
927
|
constructor(authService, toastService, themeService) {
|
|
799
928
|
this.authService = authService;
|
|
@@ -814,7 +943,7 @@ class NotificationPanelComponent {
|
|
|
814
943
|
.pipe(takeUntil(this.destroy$))
|
|
815
944
|
.subscribe((notification) => {
|
|
816
945
|
// Show toast for new notification
|
|
817
|
-
this.toastService.show(notification.messageHtml || notification.message || '', notification.title,
|
|
946
|
+
this.toastService.show(notification.messageHtml || notification.message || '', notification.title, this.toastType(notification.type), 5000);
|
|
818
947
|
// Reload notifications list
|
|
819
948
|
this.loadNotifications();
|
|
820
949
|
});
|
|
@@ -992,30 +1121,34 @@ class NotificationPanelComponent {
|
|
|
992
1121
|
}
|
|
993
1122
|
return normalized;
|
|
994
1123
|
}
|
|
995
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
996
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.
|
|
1124
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: NotificationPanelComponent, deps: [{ token: MesAuthService }, { token: ToastService }, { token: ThemeService }], target: i0.ɵɵFactoryTarget.Component });
|
|
1125
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: NotificationPanelComponent, isStandalone: true, selector: "ma-notification-panel", outputs: { notificationRead: "notificationRead" }, host: { properties: { "class": "this.themeClass" } }, ngImport: i0, template: `
|
|
997
1126
|
<div class="notification-panel" [class.open]="isOpen">
|
|
998
1127
|
<!-- Header -->
|
|
999
1128
|
<div class="panel-header">
|
|
1000
|
-
<
|
|
1001
|
-
|
|
1129
|
+
<div class="panel-header-left">
|
|
1130
|
+
<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">
|
|
1131
|
+
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
|
|
1132
|
+
<path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
|
1133
|
+
</svg>
|
|
1134
|
+
<h3>Notifications</h3>
|
|
1135
|
+
</div>
|
|
1136
|
+
<button class="close-btn" (click)="close()" title="Close">
|
|
1137
|
+
<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">
|
|
1138
|
+
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
1139
|
+
</svg>
|
|
1140
|
+
</button>
|
|
1002
1141
|
</div>
|
|
1003
1142
|
|
|
1004
1143
|
<!-- Tabs -->
|
|
1005
1144
|
<div class="tabs">
|
|
1006
|
-
<button
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
(click)="switchTab('unread')"
|
|
1010
|
-
>
|
|
1011
|
-
Unread ({{ unreadNotifications.length }})
|
|
1145
|
+
<button class="tab-btn" [class.active]="activeTab === 'unread'" (click)="switchTab('unread')">
|
|
1146
|
+
Unread
|
|
1147
|
+
<span class="tab-count" *ngIf="unreadNotifications.length > 0">{{ unreadNotifications.length }}</span>
|
|
1012
1148
|
</button>
|
|
1013
|
-
<button
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
(click)="switchTab('read')"
|
|
1017
|
-
>
|
|
1018
|
-
Read ({{ readNotifications.length }})
|
|
1149
|
+
<button class="tab-btn" [class.active]="activeTab === 'read'" (click)="switchTab('read')">
|
|
1150
|
+
Read
|
|
1151
|
+
<span class="tab-count read-count" *ngIf="readNotifications.length > 0">{{ readNotifications.length }}</span>
|
|
1019
1152
|
</button>
|
|
1020
1153
|
</div>
|
|
1021
1154
|
|
|
@@ -1028,6 +1161,29 @@ class NotificationPanelComponent {
|
|
|
1028
1161
|
[class.unread]="!notification.isRead"
|
|
1029
1162
|
(click)="openDetails(notification)"
|
|
1030
1163
|
>
|
|
1164
|
+
<div class="notif-accent"
|
|
1165
|
+
[class.type-info]="typeOf(notification) === 'Info'"
|
|
1166
|
+
[class.type-success]="typeOf(notification) === 'Success'"
|
|
1167
|
+
[class.type-warning]="typeOf(notification) === 'Warning'"
|
|
1168
|
+
[class.type-error]="typeOf(notification) === 'Error'"></div>
|
|
1169
|
+
<div class="notif-type-icon"
|
|
1170
|
+
[class.type-info]="typeOf(notification) === 'Info'"
|
|
1171
|
+
[class.type-success]="typeOf(notification) === 'Success'"
|
|
1172
|
+
[class.type-warning]="typeOf(notification) === 'Warning'"
|
|
1173
|
+
[class.type-error]="typeOf(notification) === 'Error'">
|
|
1174
|
+
<svg *ngIf="typeOf(notification) === '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">
|
|
1175
|
+
<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"/>
|
|
1176
|
+
</svg>
|
|
1177
|
+
<svg *ngIf="typeOf(notification) === '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">
|
|
1178
|
+
<polyline points="20 6 9 17 4 12"/>
|
|
1179
|
+
</svg>
|
|
1180
|
+
<svg *ngIf="typeOf(notification) === '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">
|
|
1181
|
+
<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"/>
|
|
1182
|
+
</svg>
|
|
1183
|
+
<svg *ngIf="typeOf(notification) === '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">
|
|
1184
|
+
<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"/>
|
|
1185
|
+
</svg>
|
|
1186
|
+
</div>
|
|
1031
1187
|
<div class="notification-content">
|
|
1032
1188
|
<div class="notification-title">{{ notification.title }}</div>
|
|
1033
1189
|
<div class="notification-message">{{ getNotificationMessage(notification) }}</div>
|
|
@@ -1036,28 +1192,26 @@ class NotificationPanelComponent {
|
|
|
1036
1192
|
<span class="time">{{ dateLabels.get(notification.id) }}</span>
|
|
1037
1193
|
</div>
|
|
1038
1194
|
</div>
|
|
1039
|
-
<button
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
*ngIf="!notification.isRead"
|
|
1044
|
-
>
|
|
1045
|
-
✓
|
|
1195
|
+
<button class="icon-btn read-btn" (click)="markAsRead(notification.id, $event)" title="Mark as read" *ngIf="!notification.isRead">
|
|
1196
|
+
<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">
|
|
1197
|
+
<polyline points="20 6 9 17 4 12"/>
|
|
1198
|
+
</svg>
|
|
1046
1199
|
</button>
|
|
1047
|
-
<button
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
*ngIf="notification.isRead"
|
|
1052
|
-
>
|
|
1053
|
-
🗑
|
|
1200
|
+
<button class="icon-btn delete-btn" (click)="delete(notification.id, $event)" title="Delete" *ngIf="notification.isRead">
|
|
1201
|
+
<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">
|
|
1202
|
+
<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"/>
|
|
1203
|
+
</svg>
|
|
1054
1204
|
</button>
|
|
1055
1205
|
</div>
|
|
1056
1206
|
</ng-container>
|
|
1057
1207
|
|
|
1058
1208
|
<ng-container *ngIf="currentNotifications.length === 0">
|
|
1059
1209
|
<div class="empty-state">
|
|
1060
|
-
|
|
1210
|
+
<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">
|
|
1211
|
+
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
|
|
1212
|
+
<path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
|
1213
|
+
</svg>
|
|
1214
|
+
<p>No {{ activeTab }} notifications</p>
|
|
1061
1215
|
</div>
|
|
1062
1216
|
</ng-container>
|
|
1063
1217
|
</div>
|
|
@@ -1066,13 +1220,16 @@ class NotificationPanelComponent {
|
|
|
1066
1220
|
<div class="panel-footer" *ngIf="currentNotifications.length > 0">
|
|
1067
1221
|
<div class="footer-actions" *ngIf="activeTab === 'unread'">
|
|
1068
1222
|
<button class="action-btn" (click)="markAllAsRead()" *ngIf="unreadNotifications.length > 0">
|
|
1069
|
-
|
|
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.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
|
|
1224
|
+
Mark all read
|
|
1070
1225
|
</button>
|
|
1071
1226
|
<button class="action-btn delete-all-btn" (click)="deleteAllUnread()" *ngIf="unreadNotifications.length > 0">
|
|
1227
|
+
<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>
|
|
1072
1228
|
Delete all
|
|
1073
1229
|
</button>
|
|
1074
1230
|
</div>
|
|
1075
1231
|
<button class="action-btn delete-all-btn" (click)="deleteAllRead()" *ngIf="activeTab === 'read' && readNotifications.length > 0">
|
|
1232
|
+
<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>
|
|
1076
1233
|
Delete all
|
|
1077
1234
|
</button>
|
|
1078
1235
|
</div>
|
|
@@ -1081,9 +1238,33 @@ class NotificationPanelComponent {
|
|
|
1081
1238
|
<!-- Details Modal -->
|
|
1082
1239
|
<div class="modal-overlay" *ngIf="selectedNotification" (click)="closeDetails()">
|
|
1083
1240
|
<div class="modal-container" (click)="$event.stopPropagation()">
|
|
1084
|
-
<div class="modal-header"
|
|
1085
|
-
|
|
1086
|
-
|
|
1241
|
+
<div class="modal-header"
|
|
1242
|
+
[class.modal-type-info]="typeOf(selectedNotification) === 'Info'"
|
|
1243
|
+
[class.modal-type-success]="typeOf(selectedNotification) === 'Success'"
|
|
1244
|
+
[class.modal-type-warning]="typeOf(selectedNotification) === 'Warning'"
|
|
1245
|
+
[class.modal-type-error]="typeOf(selectedNotification) === 'Error'">
|
|
1246
|
+
<div class="modal-header-left">
|
|
1247
|
+
<div class="modal-type-icon">
|
|
1248
|
+
<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">
|
|
1249
|
+
<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"/>
|
|
1250
|
+
</svg>
|
|
1251
|
+
<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">
|
|
1252
|
+
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>
|
|
1253
|
+
</svg>
|
|
1254
|
+
<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">
|
|
1255
|
+
<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"/>
|
|
1256
|
+
</svg>
|
|
1257
|
+
<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">
|
|
1258
|
+
<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"/>
|
|
1259
|
+
</svg>
|
|
1260
|
+
</div>
|
|
1261
|
+
<h3>{{ selectedNotification.title }}</h3>
|
|
1262
|
+
</div>
|
|
1263
|
+
<button class="close-btn" (click)="closeDetails()" title="Close">
|
|
1264
|
+
<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">
|
|
1265
|
+
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
1266
|
+
</svg>
|
|
1267
|
+
</button>
|
|
1087
1268
|
</div>
|
|
1088
1269
|
<div class="modal-meta">
|
|
1089
1270
|
<span class="app-name">{{ selectedNotification.sourceAppName }}</span>
|
|
@@ -1095,33 +1276,37 @@ class NotificationPanelComponent {
|
|
|
1095
1276
|
</div>
|
|
1096
1277
|
</div>
|
|
1097
1278
|
</div>
|
|
1098
|
-
`, isInline: true, styles: [":host{display:block;position:relative;--primary-color: #1976d2;--primary-hover: #1565c0;--success-color: #
|
|
1279
|
+
`, isInline: true, styles: [":host{display:block;position:relative;--primary-color: #1976d2;--primary-hover: #1565c0;--primary-light: rgba(25, 118, 210, .1);--success-color: #43a047;--error-color: #f44336;--error-hover: #d32f2f;--unread-accent: #1976d2;--info-color: #2196f3;--info-bg: rgba(33, 150, 243, .1);--success-bg: rgba(67, 160, 71, .1);--warning-color: #f57c00;--warning-bg: rgba(245, 124, 0, .1);--error-bg: rgba(244, 67, 54, .1);--text-primary: #212121;--text-secondary: #616161;--text-muted: #9e9e9e;--bg-primary: #ffffff;--bg-secondary: #f8f9fa;--bg-hover: #f0f4ff;--bg-unread: rgba(25, 118, 210, .06);--border-color: #e0e0e0;--border-light: #eeeeee;--shadow: rgba(0, 0, 0, .15)}:host(.theme-dark){display:block;position:relative;--primary-color: #90caf9;--primary-hover: #64b5f6;--primary-light: rgba(144, 202, 249, .1);--success-color: #66bb6a;--error-color: #ef5350;--error-hover: #c62828;--unread-accent: #90caf9;--info-color: #64b5f6;--info-bg: rgba(100, 181, 246, .12);--success-bg: rgba(102, 187, 106, .12);--warning-color: #ffb74d;--warning-bg: rgba(255, 183, 77, .12);--error-bg: rgba(239, 83, 80, .12);--text-primary: #e0e0e0;--text-secondary: #b0b0b0;--text-muted: #757575;--bg-primary: #1e1e2e;--bg-secondary: #27273a;--bg-hover: #2a2d4a;--bg-unread: rgba(144, 202, 249, .08);--border-color: #383850;--border-light: #2e2e42;--shadow: rgba(0, 0, 0, .4)}.notification-panel{position:fixed;top:0;right:-360px;width:360px;height:100vh;background:var(--bg-primary);box-shadow:-4px 0 24px var(--shadow);display:flex;flex-direction:column;z-index:1030;transition:right .3s cubic-bezier(.16,1,.3,1)}.notification-panel.open{right:0}.panel-header{display:flex;justify-content:space-between;align-items:center;padding:16px 18px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.panel-header-left{display:flex;align-items:center;gap:9px;color:var(--primary-color)}.panel-header h3{margin:0;font-size:16px;font-weight:700;color:var(--text-primary);letter-spacing:.1px}.close-btn{background:none;border:none;cursor:pointer;color:var(--text-muted);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:color .2s,background-color .2s}.close-btn:hover{color:var(--text-primary);background-color:var(--bg-hover)}.tabs{display:flex;gap:6px;padding:10px 14px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.tab-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:7px 12px;background:none;border:1px solid var(--border-color);border-radius:20px;color:var(--text-secondary);cursor:pointer;font-size:13px;font-weight:500;transition:all .18s}.tab-btn:hover{background:var(--bg-hover);color:var(--primary-color);border-color:var(--primary-color)}.tab-btn.active{background:var(--primary-color);border-color:var(--primary-color);color:#fff}.tab-count{background:#ffffff40;border-radius:10px;min-width:18px;height:18px;padding:0 5px;font-size:10px;font-weight:700;display:flex;align-items:center;justify-content:center}.tab-btn:not(.active) .tab-count{background:var(--error-color);color:#fff}.read-count{background:#ffffff40}.tab-btn:not(.active) .read-count{background:var(--text-muted)}.notifications-list{flex:1;overflow-y:auto}.notification-item{display:flex;align-items:flex-start;gap:0;border-bottom:1px solid var(--border-light);cursor:pointer;background:var(--bg-primary);transition:background-color .15s;position:relative}.notification-item:hover{background:var(--bg-hover)}.notification-item.unread{background:var(--bg-unread)}.notif-accent{width:3px;align-self:stretch;flex-shrink:0;background:transparent;border-radius:0 2px 2px 0;opacity:.3}.notification-item.unread .notif-accent{opacity:1}.notif-accent.type-info{background:var(--info-color)}.notif-accent.type-success{background:var(--success-color)}.notif-accent.type-warning{background:var(--warning-color)}.notif-accent.type-error{background:var(--error-color)}.notif-type-icon{flex-shrink:0;width:26px;height:26px;border-radius:7px;display:flex;align-items:center;justify-content:center;align-self:center;margin-left:10px}.notif-type-icon.type-info{color:var(--info-color);background:var(--info-bg)}.notif-type-icon.type-success{color:var(--success-color);background:var(--success-bg)}.notif-type-icon.type-warning{color:var(--warning-color);background:var(--warning-bg)}.notif-type-icon.type-error{color:var(--error-color);background:var(--error-bg)}.notification-content{flex:1;min-width:0;padding:12px 8px 12px 12px}.notification-title{font-weight:600;color:var(--text-primary);font-size:13.5px;margin-bottom:3px;line-height:1.35}.notification-message{color:var(--text-secondary);font-size:12px;line-height:1.45;margin-bottom:7px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.notification-meta{display:flex;justify-content:space-between;font-size:11px;color:var(--text-muted)}.app-name{font-weight:600;color:var(--primary-color)}.icon-btn{background:none;border:none;cursor:pointer;width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0;align-self:center;margin-right:8px;transition:color .15s,background-color .15s;color:var(--text-muted)}.read-btn:hover{color:var(--success-color);background:#43a0471a}.delete-btn:hover{color:var(--error-color);background:#f443361a}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;height:100%;padding:40px 20px;color:var(--text-muted)}.empty-icon{opacity:.35}.empty-state p{margin:0;font-size:13px}.panel-footer{padding:10px 14px;border-top:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.footer-actions{display:flex;gap:8px}.footer-actions .action-btn{flex:1}.action-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px 12px;background:var(--primary-color);color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:12.5px;font-weight:600;transition:background-color .18s,transform .12s}.action-btn:hover{background:var(--primary-hover);transform:translateY(-1px)}.delete-all-btn{background:var(--error-color)}.delete-all-btn:hover{background:var(--error-hover)}.modal-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1060;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.modal-container{background:var(--bg-primary);border-radius:14px;width:90%;max-width:580px;max-height:80vh;display:flex;flex-direction:column;box-shadow:0 16px 48px #00000040;animation:modal-in .2s cubic-bezier(.16,1,.3,1)}@keyframes modal-in{0%{opacity:0;transform:scale(.94) translateY(8px)}to{opacity:1;transform:scale(1) translateY(0)}}.modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);border-radius:14px 14px 0 0;border-top:3px solid transparent}.modal-header.modal-type-info{border-top-color:var(--info-color)}.modal-header.modal-type-success{border-top-color:var(--success-color)}.modal-header.modal-type-warning{border-top-color:var(--warning-color)}.modal-header.modal-type-error{border-top-color:var(--error-color)}.modal-header-left{display:flex;align-items:center;gap:10px;min-width:0}.modal-type-icon{flex-shrink:0;width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center}.modal-type-info .modal-type-icon{color:var(--info-color);background:var(--info-bg)}.modal-type-success .modal-type-icon{color:var(--success-color);background:var(--success-bg)}.modal-type-warning .modal-type-icon{color:var(--warning-color);background:var(--warning-bg)}.modal-type-error .modal-type-icon{color:var(--error-color);background:var(--error-bg)}.modal-header h3{margin:0;font-size:15px;font-weight:700;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.modal-meta{display:flex;justify-content:space-between;padding:8px 20px;font-size:11.5px;color:var(--text-muted);border-bottom:1px solid var(--border-light)}.modal-body{padding:20px;overflow-y:auto;flex:1;color:var(--text-primary);font-size:14px;line-height:1.65}.modal-footer{padding:12px 20px;border-top:1px solid var(--border-color);background:var(--bg-secondary);border-radius:0 0 14px 14px;display:flex;justify-content:flex-end}.modal-footer .action-btn{width:auto;padding:8px 24px}@media(max-width:600px){.notification-panel{width:100%;right:-100%}.modal-container{width:95%;max-height:90vh}}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }] });
|
|
1099
1280
|
}
|
|
1100
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1281
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: NotificationPanelComponent, decorators: [{
|
|
1101
1282
|
type: Component,
|
|
1102
1283
|
args: [{ selector: 'ma-notification-panel', standalone: true, imports: [NgIf, NgFor], template: `
|
|
1103
1284
|
<div class="notification-panel" [class.open]="isOpen">
|
|
1104
1285
|
<!-- Header -->
|
|
1105
1286
|
<div class="panel-header">
|
|
1106
|
-
<
|
|
1107
|
-
|
|
1287
|
+
<div class="panel-header-left">
|
|
1288
|
+
<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">
|
|
1289
|
+
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
|
|
1290
|
+
<path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
|
1291
|
+
</svg>
|
|
1292
|
+
<h3>Notifications</h3>
|
|
1293
|
+
</div>
|
|
1294
|
+
<button class="close-btn" (click)="close()" title="Close">
|
|
1295
|
+
<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">
|
|
1296
|
+
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
1297
|
+
</svg>
|
|
1298
|
+
</button>
|
|
1108
1299
|
</div>
|
|
1109
1300
|
|
|
1110
1301
|
<!-- Tabs -->
|
|
1111
1302
|
<div class="tabs">
|
|
1112
|
-
<button
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
(click)="switchTab('unread')"
|
|
1116
|
-
>
|
|
1117
|
-
Unread ({{ unreadNotifications.length }})
|
|
1303
|
+
<button class="tab-btn" [class.active]="activeTab === 'unread'" (click)="switchTab('unread')">
|
|
1304
|
+
Unread
|
|
1305
|
+
<span class="tab-count" *ngIf="unreadNotifications.length > 0">{{ unreadNotifications.length }}</span>
|
|
1118
1306
|
</button>
|
|
1119
|
-
<button
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
(click)="switchTab('read')"
|
|
1123
|
-
>
|
|
1124
|
-
Read ({{ readNotifications.length }})
|
|
1307
|
+
<button class="tab-btn" [class.active]="activeTab === 'read'" (click)="switchTab('read')">
|
|
1308
|
+
Read
|
|
1309
|
+
<span class="tab-count read-count" *ngIf="readNotifications.length > 0">{{ readNotifications.length }}</span>
|
|
1125
1310
|
</button>
|
|
1126
1311
|
</div>
|
|
1127
1312
|
|
|
@@ -1134,6 +1319,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
1134
1319
|
[class.unread]="!notification.isRead"
|
|
1135
1320
|
(click)="openDetails(notification)"
|
|
1136
1321
|
>
|
|
1322
|
+
<div class="notif-accent"
|
|
1323
|
+
[class.type-info]="typeOf(notification) === 'Info'"
|
|
1324
|
+
[class.type-success]="typeOf(notification) === 'Success'"
|
|
1325
|
+
[class.type-warning]="typeOf(notification) === 'Warning'"
|
|
1326
|
+
[class.type-error]="typeOf(notification) === 'Error'"></div>
|
|
1327
|
+
<div class="notif-type-icon"
|
|
1328
|
+
[class.type-info]="typeOf(notification) === 'Info'"
|
|
1329
|
+
[class.type-success]="typeOf(notification) === 'Success'"
|
|
1330
|
+
[class.type-warning]="typeOf(notification) === 'Warning'"
|
|
1331
|
+
[class.type-error]="typeOf(notification) === 'Error'">
|
|
1332
|
+
<svg *ngIf="typeOf(notification) === '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">
|
|
1333
|
+
<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"/>
|
|
1334
|
+
</svg>
|
|
1335
|
+
<svg *ngIf="typeOf(notification) === '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">
|
|
1336
|
+
<polyline points="20 6 9 17 4 12"/>
|
|
1337
|
+
</svg>
|
|
1338
|
+
<svg *ngIf="typeOf(notification) === '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">
|
|
1339
|
+
<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"/>
|
|
1340
|
+
</svg>
|
|
1341
|
+
<svg *ngIf="typeOf(notification) === '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">
|
|
1342
|
+
<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"/>
|
|
1343
|
+
</svg>
|
|
1344
|
+
</div>
|
|
1137
1345
|
<div class="notification-content">
|
|
1138
1346
|
<div class="notification-title">{{ notification.title }}</div>
|
|
1139
1347
|
<div class="notification-message">{{ getNotificationMessage(notification) }}</div>
|
|
@@ -1142,28 +1350,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
1142
1350
|
<span class="time">{{ dateLabels.get(notification.id) }}</span>
|
|
1143
1351
|
</div>
|
|
1144
1352
|
</div>
|
|
1145
|
-
<button
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
*ngIf="!notification.isRead"
|
|
1150
|
-
>
|
|
1151
|
-
✓
|
|
1353
|
+
<button class="icon-btn read-btn" (click)="markAsRead(notification.id, $event)" title="Mark as read" *ngIf="!notification.isRead">
|
|
1354
|
+
<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">
|
|
1355
|
+
<polyline points="20 6 9 17 4 12"/>
|
|
1356
|
+
</svg>
|
|
1152
1357
|
</button>
|
|
1153
|
-
<button
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
*ngIf="notification.isRead"
|
|
1158
|
-
>
|
|
1159
|
-
🗑
|
|
1358
|
+
<button class="icon-btn delete-btn" (click)="delete(notification.id, $event)" title="Delete" *ngIf="notification.isRead">
|
|
1359
|
+
<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">
|
|
1360
|
+
<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"/>
|
|
1361
|
+
</svg>
|
|
1160
1362
|
</button>
|
|
1161
1363
|
</div>
|
|
1162
1364
|
</ng-container>
|
|
1163
1365
|
|
|
1164
1366
|
<ng-container *ngIf="currentNotifications.length === 0">
|
|
1165
1367
|
<div class="empty-state">
|
|
1166
|
-
|
|
1368
|
+
<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">
|
|
1369
|
+
<path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/>
|
|
1370
|
+
<path d="M13.73 21a2 2 0 0 1-3.46 0"/>
|
|
1371
|
+
</svg>
|
|
1372
|
+
<p>No {{ activeTab }} notifications</p>
|
|
1167
1373
|
</div>
|
|
1168
1374
|
</ng-container>
|
|
1169
1375
|
</div>
|
|
@@ -1172,13 +1378,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
1172
1378
|
<div class="panel-footer" *ngIf="currentNotifications.length > 0">
|
|
1173
1379
|
<div class="footer-actions" *ngIf="activeTab === 'unread'">
|
|
1174
1380
|
<button class="action-btn" (click)="markAllAsRead()" *ngIf="unreadNotifications.length > 0">
|
|
1175
|
-
|
|
1381
|
+
<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>
|
|
1382
|
+
Mark all read
|
|
1176
1383
|
</button>
|
|
1177
1384
|
<button class="action-btn delete-all-btn" (click)="deleteAllUnread()" *ngIf="unreadNotifications.length > 0">
|
|
1385
|
+
<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>
|
|
1178
1386
|
Delete all
|
|
1179
1387
|
</button>
|
|
1180
1388
|
</div>
|
|
1181
1389
|
<button class="action-btn delete-all-btn" (click)="deleteAllRead()" *ngIf="activeTab === 'read' && readNotifications.length > 0">
|
|
1390
|
+
<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>
|
|
1182
1391
|
Delete all
|
|
1183
1392
|
</button>
|
|
1184
1393
|
</div>
|
|
@@ -1187,9 +1396,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
1187
1396
|
<!-- Details Modal -->
|
|
1188
1397
|
<div class="modal-overlay" *ngIf="selectedNotification" (click)="closeDetails()">
|
|
1189
1398
|
<div class="modal-container" (click)="$event.stopPropagation()">
|
|
1190
|
-
<div class="modal-header"
|
|
1191
|
-
|
|
1192
|
-
|
|
1399
|
+
<div class="modal-header"
|
|
1400
|
+
[class.modal-type-info]="typeOf(selectedNotification) === 'Info'"
|
|
1401
|
+
[class.modal-type-success]="typeOf(selectedNotification) === 'Success'"
|
|
1402
|
+
[class.modal-type-warning]="typeOf(selectedNotification) === 'Warning'"
|
|
1403
|
+
[class.modal-type-error]="typeOf(selectedNotification) === 'Error'">
|
|
1404
|
+
<div class="modal-header-left">
|
|
1405
|
+
<div class="modal-type-icon">
|
|
1406
|
+
<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">
|
|
1407
|
+
<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"/>
|
|
1408
|
+
</svg>
|
|
1409
|
+
<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">
|
|
1410
|
+
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>
|
|
1411
|
+
</svg>
|
|
1412
|
+
<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">
|
|
1413
|
+
<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"/>
|
|
1414
|
+
</svg>
|
|
1415
|
+
<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">
|
|
1416
|
+
<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"/>
|
|
1417
|
+
</svg>
|
|
1418
|
+
</div>
|
|
1419
|
+
<h3>{{ selectedNotification.title }}</h3>
|
|
1420
|
+
</div>
|
|
1421
|
+
<button class="close-btn" (click)="closeDetails()" title="Close">
|
|
1422
|
+
<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">
|
|
1423
|
+
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
|
1424
|
+
</svg>
|
|
1425
|
+
</button>
|
|
1193
1426
|
</div>
|
|
1194
1427
|
<div class="modal-meta">
|
|
1195
1428
|
<span class="app-name">{{ selectedNotification.sourceAppName }}</span>
|
|
@@ -1201,7 +1434,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
|
|
|
1201
1434
|
</div>
|
|
1202
1435
|
</div>
|
|
1203
1436
|
</div>
|
|
1204
|
-
`, styles: [":host{display:block;position:relative;--primary-color: #1976d2;--primary-hover: #1565c0;--success-color: #
|
|
1437
|
+
`, styles: [":host{display:block;position:relative;--primary-color: #1976d2;--primary-hover: #1565c0;--primary-light: rgba(25, 118, 210, .1);--success-color: #43a047;--error-color: #f44336;--error-hover: #d32f2f;--unread-accent: #1976d2;--info-color: #2196f3;--info-bg: rgba(33, 150, 243, .1);--success-bg: rgba(67, 160, 71, .1);--warning-color: #f57c00;--warning-bg: rgba(245, 124, 0, .1);--error-bg: rgba(244, 67, 54, .1);--text-primary: #212121;--text-secondary: #616161;--text-muted: #9e9e9e;--bg-primary: #ffffff;--bg-secondary: #f8f9fa;--bg-hover: #f0f4ff;--bg-unread: rgba(25, 118, 210, .06);--border-color: #e0e0e0;--border-light: #eeeeee;--shadow: rgba(0, 0, 0, .15)}:host(.theme-dark){display:block;position:relative;--primary-color: #90caf9;--primary-hover: #64b5f6;--primary-light: rgba(144, 202, 249, .1);--success-color: #66bb6a;--error-color: #ef5350;--error-hover: #c62828;--unread-accent: #90caf9;--info-color: #64b5f6;--info-bg: rgba(100, 181, 246, .12);--success-bg: rgba(102, 187, 106, .12);--warning-color: #ffb74d;--warning-bg: rgba(255, 183, 77, .12);--error-bg: rgba(239, 83, 80, .12);--text-primary: #e0e0e0;--text-secondary: #b0b0b0;--text-muted: #757575;--bg-primary: #1e1e2e;--bg-secondary: #27273a;--bg-hover: #2a2d4a;--bg-unread: rgba(144, 202, 249, .08);--border-color: #383850;--border-light: #2e2e42;--shadow: rgba(0, 0, 0, .4)}.notification-panel{position:fixed;top:0;right:-360px;width:360px;height:100vh;background:var(--bg-primary);box-shadow:-4px 0 24px var(--shadow);display:flex;flex-direction:column;z-index:1030;transition:right .3s cubic-bezier(.16,1,.3,1)}.notification-panel.open{right:0}.panel-header{display:flex;justify-content:space-between;align-items:center;padding:16px 18px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.panel-header-left{display:flex;align-items:center;gap:9px;color:var(--primary-color)}.panel-header h3{margin:0;font-size:16px;font-weight:700;color:var(--text-primary);letter-spacing:.1px}.close-btn{background:none;border:none;cursor:pointer;color:var(--text-muted);width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;transition:color .2s,background-color .2s}.close-btn:hover{color:var(--text-primary);background-color:var(--bg-hover)}.tabs{display:flex;gap:6px;padding:10px 14px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.tab-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:6px;padding:7px 12px;background:none;border:1px solid var(--border-color);border-radius:20px;color:var(--text-secondary);cursor:pointer;font-size:13px;font-weight:500;transition:all .18s}.tab-btn:hover{background:var(--bg-hover);color:var(--primary-color);border-color:var(--primary-color)}.tab-btn.active{background:var(--primary-color);border-color:var(--primary-color);color:#fff}.tab-count{background:#ffffff40;border-radius:10px;min-width:18px;height:18px;padding:0 5px;font-size:10px;font-weight:700;display:flex;align-items:center;justify-content:center}.tab-btn:not(.active) .tab-count{background:var(--error-color);color:#fff}.read-count{background:#ffffff40}.tab-btn:not(.active) .read-count{background:var(--text-muted)}.notifications-list{flex:1;overflow-y:auto}.notification-item{display:flex;align-items:flex-start;gap:0;border-bottom:1px solid var(--border-light);cursor:pointer;background:var(--bg-primary);transition:background-color .15s;position:relative}.notification-item:hover{background:var(--bg-hover)}.notification-item.unread{background:var(--bg-unread)}.notif-accent{width:3px;align-self:stretch;flex-shrink:0;background:transparent;border-radius:0 2px 2px 0;opacity:.3}.notification-item.unread .notif-accent{opacity:1}.notif-accent.type-info{background:var(--info-color)}.notif-accent.type-success{background:var(--success-color)}.notif-accent.type-warning{background:var(--warning-color)}.notif-accent.type-error{background:var(--error-color)}.notif-type-icon{flex-shrink:0;width:26px;height:26px;border-radius:7px;display:flex;align-items:center;justify-content:center;align-self:center;margin-left:10px}.notif-type-icon.type-info{color:var(--info-color);background:var(--info-bg)}.notif-type-icon.type-success{color:var(--success-color);background:var(--success-bg)}.notif-type-icon.type-warning{color:var(--warning-color);background:var(--warning-bg)}.notif-type-icon.type-error{color:var(--error-color);background:var(--error-bg)}.notification-content{flex:1;min-width:0;padding:12px 8px 12px 12px}.notification-title{font-weight:600;color:var(--text-primary);font-size:13.5px;margin-bottom:3px;line-height:1.35}.notification-message{color:var(--text-secondary);font-size:12px;line-height:1.45;margin-bottom:7px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.notification-meta{display:flex;justify-content:space-between;font-size:11px;color:var(--text-muted)}.app-name{font-weight:600;color:var(--primary-color)}.icon-btn{background:none;border:none;cursor:pointer;width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0;align-self:center;margin-right:8px;transition:color .15s,background-color .15s;color:var(--text-muted)}.read-btn:hover{color:var(--success-color);background:#43a0471a}.delete-btn:hover{color:var(--error-color);background:#f443361a}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;height:100%;padding:40px 20px;color:var(--text-muted)}.empty-icon{opacity:.35}.empty-state p{margin:0;font-size:13px}.panel-footer{padding:10px 14px;border-top:1px solid var(--border-color);background:var(--bg-secondary);flex-shrink:0}.footer-actions{display:flex;gap:8px}.footer-actions .action-btn{flex:1}.action-btn{display:flex;align-items:center;justify-content:center;gap:6px;width:100%;padding:8px 12px;background:var(--primary-color);color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:12.5px;font-weight:600;transition:background-color .18s,transform .12s}.action-btn:hover{background:var(--primary-hover);transform:translateY(-1px)}.delete-all-btn{background:var(--error-color)}.delete-all-btn:hover{background:var(--error-hover)}.modal-overlay{position:fixed;inset:0;background:#00000080;display:flex;align-items:center;justify-content:center;z-index:1060;-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.modal-container{background:var(--bg-primary);border-radius:14px;width:90%;max-width:580px;max-height:80vh;display:flex;flex-direction:column;box-shadow:0 16px 48px #00000040;animation:modal-in .2s cubic-bezier(.16,1,.3,1)}@keyframes modal-in{0%{opacity:0;transform:scale(.94) translateY(8px)}to{opacity:1;transform:scale(1) translateY(0)}}.modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--border-color);background:var(--bg-secondary);border-radius:14px 14px 0 0;border-top:3px solid transparent}.modal-header.modal-type-info{border-top-color:var(--info-color)}.modal-header.modal-type-success{border-top-color:var(--success-color)}.modal-header.modal-type-warning{border-top-color:var(--warning-color)}.modal-header.modal-type-error{border-top-color:var(--error-color)}.modal-header-left{display:flex;align-items:center;gap:10px;min-width:0}.modal-type-icon{flex-shrink:0;width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center}.modal-type-info .modal-type-icon{color:var(--info-color);background:var(--info-bg)}.modal-type-success .modal-type-icon{color:var(--success-color);background:var(--success-bg)}.modal-type-warning .modal-type-icon{color:var(--warning-color);background:var(--warning-bg)}.modal-type-error .modal-type-icon{color:var(--error-color);background:var(--error-bg)}.modal-header h3{margin:0;font-size:15px;font-weight:700;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.modal-meta{display:flex;justify-content:space-between;padding:8px 20px;font-size:11.5px;color:var(--text-muted);border-bottom:1px solid var(--border-light)}.modal-body{padding:20px;overflow-y:auto;flex:1;color:var(--text-primary);font-size:14px;line-height:1.65}.modal-footer{padding:12px 20px;border-top:1px solid var(--border-color);background:var(--bg-secondary);border-radius:0 0 14px 14px;display:flex;justify-content:flex-end}.modal-footer .action-btn{width:auto;padding:8px 24px}@media(max-width:600px){.notification-panel{width:100%;right:-100%}.modal-container{width:95%;max-height:90vh}}\n"] }]
|
|
1205
1438
|
}], ctorParameters: () => [{ type: MesAuthService }, { type: ToastService }, { type: ThemeService }], propDecorators: { notificationRead: [{
|
|
1206
1439
|
type: Output
|
|
1207
1440
|
}], themeClass: [{
|
|
@@ -1222,23 +1455,23 @@ class MaUserComponent {
|
|
|
1222
1455
|
this.userProfile.loadUnreadCount();
|
|
1223
1456
|
}
|
|
1224
1457
|
}
|
|
1225
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1226
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.
|
|
1227
|
-
<ma-toast-container></ma-toast-container>
|
|
1228
|
-
<div class="user-header">
|
|
1229
|
-
<ma-user-profile (notificationClick)="notificationPanel.open()"></ma-user-profile>
|
|
1230
|
-
</div>
|
|
1231
|
-
<ma-notification-panel #notificationPanel (notificationRead)="onNotificationRead()"></ma-notification-panel>
|
|
1458
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MaUserComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1459
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: MaUserComponent, isStandalone: true, selector: "ma-user", viewQueries: [{ propertyName: "userProfile", first: true, predicate: UserProfileComponent, descendants: true }], ngImport: i0, template: `
|
|
1460
|
+
<ma-toast-container></ma-toast-container>
|
|
1461
|
+
<div class="user-header">
|
|
1462
|
+
<ma-user-profile (notificationClick)="notificationPanel.open()"></ma-user-profile>
|
|
1463
|
+
</div>
|
|
1464
|
+
<ma-notification-panel #notificationPanel (notificationRead)="onNotificationRead()"></ma-notification-panel>
|
|
1232
1465
|
`, isInline: true, styles: [".user-header{display:flex;justify-content:flex-end}\n"], dependencies: [{ kind: "component", type: ToastContainerComponent, selector: "ma-toast-container" }, { kind: "component", type: UserProfileComponent, selector: "ma-user-profile", outputs: ["notificationClick"] }, { kind: "component", type: NotificationPanelComponent, selector: "ma-notification-panel", outputs: ["notificationRead"] }] });
|
|
1233
1466
|
}
|
|
1234
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1467
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MaUserComponent, decorators: [{
|
|
1235
1468
|
type: Component,
|
|
1236
|
-
args: [{ selector: 'ma-user', standalone: true, imports: [ToastContainerComponent, UserProfileComponent, NotificationPanelComponent], template: `
|
|
1237
|
-
<ma-toast-container></ma-toast-container>
|
|
1238
|
-
<div class="user-header">
|
|
1239
|
-
<ma-user-profile (notificationClick)="notificationPanel.open()"></ma-user-profile>
|
|
1240
|
-
</div>
|
|
1241
|
-
<ma-notification-panel #notificationPanel (notificationRead)="onNotificationRead()"></ma-notification-panel>
|
|
1469
|
+
args: [{ selector: 'ma-user', standalone: true, imports: [ToastContainerComponent, UserProfileComponent, NotificationPanelComponent], template: `
|
|
1470
|
+
<ma-toast-container></ma-toast-container>
|
|
1471
|
+
<div class="user-header">
|
|
1472
|
+
<ma-user-profile (notificationClick)="notificationPanel.open()"></ma-user-profile>
|
|
1473
|
+
</div>
|
|
1474
|
+
<ma-notification-panel #notificationPanel (notificationRead)="onNotificationRead()"></ma-notification-panel>
|
|
1242
1475
|
`, styles: [".user-header{display:flex;justify-content:flex-end}\n"] }]
|
|
1243
1476
|
}], propDecorators: { userProfile: [{
|
|
1244
1477
|
type: ViewChild,
|
|
@@ -1304,21 +1537,21 @@ class NotificationBadgeComponent {
|
|
|
1304
1537
|
onNotificationClick() {
|
|
1305
1538
|
this.notificationClick.emit();
|
|
1306
1539
|
}
|
|
1307
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.
|
|
1308
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.
|
|
1309
|
-
<button class="notification-btn" (click)="onNotificationClick()" title="Notifications">
|
|
1310
|
-
<span class="icon">🔔</span>
|
|
1311
|
-
<span class="badge" *ngIf="unreadCount > 0">{{ unreadCount }}</span>
|
|
1312
|
-
</button>
|
|
1540
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: NotificationBadgeComponent, deps: [{ token: MesAuthService }, { token: ThemeService }], target: i0.ɵɵFactoryTarget.Component });
|
|
1541
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.18", type: NotificationBadgeComponent, isStandalone: true, selector: "ma-notification-badge", outputs: { notificationClick: "notificationClick" }, host: { properties: { "class": "this.themeClass" } }, ngImport: i0, template: `
|
|
1542
|
+
<button class="notification-btn" (click)="onNotificationClick()" title="Notifications">
|
|
1543
|
+
<span class="icon">🔔</span>
|
|
1544
|
+
<span class="badge" *ngIf="unreadCount > 0">{{ unreadCount }}</span>
|
|
1545
|
+
</button>
|
|
1313
1546
|
`, 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"] }] });
|
|
1314
1547
|
}
|
|
1315
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.
|
|
1548
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: NotificationBadgeComponent, decorators: [{
|
|
1316
1549
|
type: Component,
|
|
1317
|
-
args: [{ selector: 'ma-notification-badge', standalone: true, imports: [NgIf], template: `
|
|
1318
|
-
<button class="notification-btn" (click)="onNotificationClick()" title="Notifications">
|
|
1319
|
-
<span class="icon">🔔</span>
|
|
1320
|
-
<span class="badge" *ngIf="unreadCount > 0">{{ unreadCount }}</span>
|
|
1321
|
-
</button>
|
|
1550
|
+
args: [{ selector: 'ma-notification-badge', standalone: true, imports: [NgIf], template: `
|
|
1551
|
+
<button class="notification-btn" (click)="onNotificationClick()" title="Notifications">
|
|
1552
|
+
<span class="icon">🔔</span>
|
|
1553
|
+
<span class="badge" *ngIf="unreadCount > 0">{{ unreadCount }}</span>
|
|
1554
|
+
</button>
|
|
1322
1555
|
`, 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"] }]
|
|
1323
1556
|
}], ctorParameters: () => [{ type: MesAuthService }, { type: ThemeService }], propDecorators: { notificationClick: [{
|
|
1324
1557
|
type: Output
|