mesauth-angular 1.3.1 → 1.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import * as i0 from '@angular/core';
2
- import { InjectionToken, makeEnvironmentProviders, provideAppInitializer, inject, Injectable, NgModule, EventEmitter, signal, HostListener, HostBinding, Output, Component, ViewChild } from '@angular/core';
2
+ import { InjectionToken, makeEnvironmentProviders, provideAppInitializer, inject, NgZone, Injectable, NgModule, EventEmitter, signal, HostListener, HostBinding, Output, Component, ViewChild } from '@angular/core';
3
3
  import { HttpClient } from '@angular/common/http';
4
4
  import { HubConnectionBuilder, LogLevel } from '@microsoft/signalr';
5
5
  import { BehaviorSubject, Subject, EMPTY, of, throwError } from 'rxjs';
@@ -38,7 +38,8 @@ function provideMesAuth(config) {
38
38
  const mesAuthService = inject(MesAuthService);
39
39
  const httpClient = inject(HttpClient);
40
40
  const router = inject(Router);
41
- mesAuthService.init(config, httpClient, router);
41
+ const ngZone = inject(NgZone);
42
+ mesAuthService.init(config, httpClient, router, ngZone);
42
43
  })
43
44
  ]);
44
45
  }
@@ -59,13 +60,15 @@ class MesAuthService {
59
60
  config = null;
60
61
  http;
61
62
  router;
63
+ ngZone = null;
62
64
  constructor() {
63
65
  // Empty constructor - all dependencies passed to init()
64
66
  }
65
- init(config, httpClient, router) {
67
+ init(config, httpClient, router, ngZone) {
66
68
  this.config = config;
67
69
  this.http = httpClient;
68
70
  this.router = router;
71
+ this.ngZone = ngZone ?? null;
69
72
  this.apiBase = config.apiBaseUrl.replace(/\/$/, '');
70
73
  // Fetch user once on init. Route changes do NOT re-fetch the user.
71
74
  // Auth state is maintained via cookies; 401 errors are handled by HTTP interceptors.
@@ -225,7 +228,12 @@ class MesAuthService {
225
228
  .configureLogging(LogLevel.Warning);
226
229
  this.hubConnection = builder.build();
227
230
  this.hubConnection.on('ReceiveNotification', (n) => {
228
- this._notifications.next(n);
231
+ if (this.ngZone) {
232
+ this.ngZone.run(() => this._notifications.next(n));
233
+ }
234
+ else {
235
+ this._notifications.next(n);
236
+ }
229
237
  });
230
238
  this.hubConnection.start().then(() => { }).catch((err) => { });
231
239
  this.hubConnection.onclose(() => { });
@@ -259,10 +267,10 @@ class MesAuthService {
259
267
  refreshUser() {
260
268
  return this.fetchCurrentUser();
261
269
  }
262
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MesAuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
263
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MesAuthService });
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 });
264
272
  }
265
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MesAuthService, decorators: [{
273
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MesAuthService, decorators: [{
266
274
  type: Injectable
267
275
  }], ctorParameters: () => [] });
268
276
 
@@ -317,13 +325,13 @@ const mesAuthInterceptor = (req, next) => {
317
325
  };
318
326
 
319
327
  class MesAuthModule {
320
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MesAuthModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
321
- static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.16", ngImport: i0, type: MesAuthModule });
322
- static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MesAuthModule, providers: [
328
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MesAuthModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
329
+ static ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.18", ngImport: i0, type: MesAuthModule });
330
+ static ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MesAuthModule, providers: [
323
331
  MesAuthService
324
332
  ] });
325
333
  }
326
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MesAuthModule, decorators: [{
334
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MesAuthModule, decorators: [{
327
335
  type: NgModule,
328
336
  args: [{
329
337
  providers: [
@@ -382,10 +390,10 @@ class ThemeService {
382
390
  refreshTheme() {
383
391
  this.detectTheme();
384
392
  }
385
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
386
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ThemeService, providedIn: 'root' });
393
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ThemeService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
394
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ThemeService, providedIn: 'root' });
387
395
  }
388
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ThemeService, decorators: [{
396
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ThemeService, decorators: [{
389
397
  type: Injectable,
390
398
  args: [{
391
399
  providedIn: 'root'
@@ -532,101 +540,101 @@ class UserProfileComponent {
532
540
  onNotificationClick() {
533
541
  this.notificationClick.emit();
534
542
  }
535
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: UserProfileComponent, deps: [{ token: MesAuthService }, { token: i2.Router }, { token: ThemeService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
536
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: UserProfileComponent, isStandalone: true, selector: "ma-user-profile", outputs: { notificationClick: "notificationClick" }, host: { listeners: { "document:click": "onDocumentClick($event)" }, properties: { "class": "this.themeClass" } }, ngImport: i0, template: `
537
- <div class="user-profile-container">
538
- <!-- Not logged in -->
539
- <ng-container *ngIf="!currentUser()">
540
- <button class="login-btn" (click)="onLogin()">
541
- Login
542
- </button>
543
- </ng-container>
544
-
545
- <!-- Logged in -->
546
- <ng-container *ngIf="currentUser()">
547
- <div class="user-header">
548
- <button class="notification-btn" (click)="onNotificationClick()" title="Notifications">
549
- <span class="icon">🔔</span>
550
- <span class="badge" *ngIf="unreadCount > 0">{{ unreadCount }}</span>
551
- </button>
552
-
553
- <div class="user-menu-wrapper">
554
- <button class="user-menu-btn" (click)="toggleDropdown()">
555
- <img
556
- *ngIf="currentUser().fullName || currentUser().userName"
557
- [src]="getAvatarUrl(currentUser())"
558
- [alt]="currentUser().fullName || currentUser().userName"
559
- class="avatar"
560
- />
561
- <span *ngIf="!(currentUser().fullName || currentUser().userName)" class="avatar-initial">
562
- {{ getLastNameInitial(currentUser()) }}
563
- </span>
564
- </button>
565
-
566
- <div class="mes-dropdown-menu" *ngIf="dropdownOpen">
567
- <div class="mes-dropdown-header">
568
- {{ currentUser().fullName || currentUser().userName }}
569
- </div>
570
- <button class="mes-dropdown-item profile-link" (click)="onViewProfile()">
571
- View Profile
572
- </button>
573
- <button class="mes-dropdown-item logout-item" (click)="onLogout()">
574
- Logout
575
- </button>
576
- </div>
577
- </div>
578
- </div>
579
- </ng-container>
580
- </div>
543
+ 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 });
544
+ 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: `
545
+ <div class="user-profile-container">
546
+ <!-- Not logged in -->
547
+ <ng-container *ngIf="!currentUser()">
548
+ <button class="login-btn" (click)="onLogin()">
549
+ Login
550
+ </button>
551
+ </ng-container>
552
+
553
+ <!-- Logged in -->
554
+ <ng-container *ngIf="currentUser()">
555
+ <div class="user-header">
556
+ <button class="notification-btn" (click)="onNotificationClick()" title="Notifications">
557
+ <span class="icon">🔔</span>
558
+ <span class="badge" *ngIf="unreadCount > 0">{{ unreadCount }}</span>
559
+ </button>
560
+
561
+ <div class="user-menu-wrapper">
562
+ <button class="user-menu-btn" (click)="toggleDropdown()">
563
+ <img
564
+ *ngIf="currentUser().fullName || currentUser().userName"
565
+ [src]="getAvatarUrl(currentUser())"
566
+ [alt]="currentUser().fullName || currentUser().userName"
567
+ class="avatar"
568
+ />
569
+ <span *ngIf="!(currentUser().fullName || currentUser().userName)" class="avatar-initial">
570
+ {{ getLastNameInitial(currentUser()) }}
571
+ </span>
572
+ </button>
573
+
574
+ <div class="mes-dropdown-menu" *ngIf="dropdownOpen">
575
+ <div class="mes-dropdown-header">
576
+ {{ currentUser().fullName || currentUser().userName }}
577
+ </div>
578
+ <button class="mes-dropdown-item profile-link" (click)="onViewProfile()">
579
+ View Profile
580
+ </button>
581
+ <button class="mes-dropdown-item logout-item" (click)="onLogout()">
582
+ Logout
583
+ </button>
584
+ </div>
585
+ </div>
586
+ </div>
587
+ </ng-container>
588
+ </div>
581
589
  `, isInline: true, styles: [":host{--primary-color: #1976d2;--primary-hover: #1565c0;--primary-light: rgba(25, 118, 210, .1);--error-color: #f44336;--error-light: #ffebee;--text-primary: #333;--text-secondary: #666;--text-muted: #999;--bg-primary: white;--bg-secondary: #f5f5f5;--bg-tertiary: #fafafa;--bg-hover: #f5f5f5;--border-color: #e0e0e0;--border-light: #f0f0f0;--shadow: rgba(0, 0, 0, .15);--shadow-light: rgba(0, 0, 0, .1)}:host(.theme-dark){--primary-color: #90caf9;--primary-hover: #64b5f6;--primary-light: rgba(144, 202, 249, .1);--error-color: #ef5350;--error-light: rgba(239, 83, 80, .1);--text-primary: #e0e0e0;--text-secondary: #b0b0b0;--text-muted: #888;--bg-primary: #1e1e1e;--bg-secondary: #2d2d2d;--bg-tertiary: #252525;--bg-hover: #333;--border-color: #404040;--border-light: #333;--shadow: rgba(0, 0, 0, .3);--shadow-light: rgba(0, 0, 0, .2)}.user-profile-container{display:flex;align-items:center;gap:16px;padding:0 16px}.login-btn{padding:8px 16px;background-color:var(--primary-color);color:#fff;border:none;border-radius:4px;cursor:pointer;font-weight:500;transition:background-color .3s}.login-btn:hover{background-color:var(--primary-hover)}.user-header{display:flex;align-items:center;gap:16px}.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}.user-menu-wrapper{position:relative}.user-menu-btn{background:none;border:none;cursor:pointer;padding:4px;border-radius:50%;transition:background-color .2s;display:flex;align-items:center;justify-content:center}.user-menu-btn:hover{background-color:var(--primary-light)}.avatar{width:40px;height:40px;border-radius:50%;object-fit:cover;background-color:#e0e0e0}.avatar-initial{width:40px;height:40px;border-radius:50%;background-color:var(--primary-color);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:16px}.mes-dropdown-menu{position:absolute;top:calc(100% + 8px);right:0;background:var(--bg-primary);border:1px solid var(--border-color);border-radius:4px;box-shadow:0 2px 8px var(--shadow);min-width:200px;z-index:1000;overflow:hidden}.mes-dropdown-header{padding:12px 16px;border-bottom:1px solid var(--border-light);font-weight:600;color:var(--text-primary);font-size:14px}.mes-dropdown-item{display:block;width:100%;padding:12px 16px;border:none;background:none;text-align:left;cursor:pointer;font-size:14px;color:var(--text-primary);text-decoration:none;transition:background-color .2s}.mes-dropdown-item:hover{background-color:var(--bg-hover)}.profile-link{color:var(--primary-color)}.logout-item{border-top:1px solid var(--border-light);color:var(--error-color)}.logout-item:hover{background-color:var(--error-light)}.user-info{display:flex;flex-direction:column;gap:2px}.user-name{font-weight:500;font-size:14px;color:var(--text-primary)}.user-position{font-size:12px;color:var(--text-secondary)}.logout-btn{background:none;border:none;font-size:20px;cursor:pointer;color:var(--text-secondary);padding:4px 8px;transition:color .2s}.logout-btn:hover{color:var(--primary-color)}@media(max-width:768px){.user-info{display:none}.avatar{width:32px;height:32px}}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
582
590
  }
583
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: UserProfileComponent, decorators: [{
591
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: UserProfileComponent, decorators: [{
584
592
  type: Component,
585
- args: [{ selector: 'ma-user-profile', standalone: true, imports: [NgIf], template: `
586
- <div class="user-profile-container">
587
- <!-- Not logged in -->
588
- <ng-container *ngIf="!currentUser()">
589
- <button class="login-btn" (click)="onLogin()">
590
- Login
591
- </button>
592
- </ng-container>
593
-
594
- <!-- Logged in -->
595
- <ng-container *ngIf="currentUser()">
596
- <div class="user-header">
597
- <button class="notification-btn" (click)="onNotificationClick()" title="Notifications">
598
- <span class="icon">🔔</span>
599
- <span class="badge" *ngIf="unreadCount > 0">{{ unreadCount }}</span>
600
- </button>
601
-
602
- <div class="user-menu-wrapper">
603
- <button class="user-menu-btn" (click)="toggleDropdown()">
604
- <img
605
- *ngIf="currentUser().fullName || currentUser().userName"
606
- [src]="getAvatarUrl(currentUser())"
607
- [alt]="currentUser().fullName || currentUser().userName"
608
- class="avatar"
609
- />
610
- <span *ngIf="!(currentUser().fullName || currentUser().userName)" class="avatar-initial">
611
- {{ getLastNameInitial(currentUser()) }}
612
- </span>
613
- </button>
614
-
615
- <div class="mes-dropdown-menu" *ngIf="dropdownOpen">
616
- <div class="mes-dropdown-header">
617
- {{ currentUser().fullName || currentUser().userName }}
618
- </div>
619
- <button class="mes-dropdown-item profile-link" (click)="onViewProfile()">
620
- View Profile
621
- </button>
622
- <button class="mes-dropdown-item logout-item" (click)="onLogout()">
623
- Logout
624
- </button>
625
- </div>
626
- </div>
627
- </div>
628
- </ng-container>
629
- </div>
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
+ Login
599
+ </button>
600
+ </ng-container>
601
+
602
+ <!-- Logged in -->
603
+ <ng-container *ngIf="currentUser()">
604
+ <div class="user-header">
605
+ <button class="notification-btn" (click)="onNotificationClick()" title="Notifications">
606
+ <span class="icon">🔔</span>
607
+ <span class="badge" *ngIf="unreadCount > 0">{{ unreadCount }}</span>
608
+ </button>
609
+
610
+ <div class="user-menu-wrapper">
611
+ <button class="user-menu-btn" (click)="toggleDropdown()">
612
+ <img
613
+ *ngIf="currentUser().fullName || currentUser().userName"
614
+ [src]="getAvatarUrl(currentUser())"
615
+ [alt]="currentUser().fullName || currentUser().userName"
616
+ class="avatar"
617
+ />
618
+ <span *ngIf="!(currentUser().fullName || currentUser().userName)" class="avatar-initial">
619
+ {{ getLastNameInitial(currentUser()) }}
620
+ </span>
621
+ </button>
622
+
623
+ <div class="mes-dropdown-menu" *ngIf="dropdownOpen">
624
+ <div class="mes-dropdown-header">
625
+ {{ currentUser().fullName || currentUser().userName }}
626
+ </div>
627
+ <button class="mes-dropdown-item profile-link" (click)="onViewProfile()">
628
+ View Profile
629
+ </button>
630
+ <button class="mes-dropdown-item logout-item" (click)="onLogout()">
631
+ Logout
632
+ </button>
633
+ </div>
634
+ </div>
635
+ </div>
636
+ </ng-container>
637
+ </div>
630
638
  `, styles: [":host{--primary-color: #1976d2;--primary-hover: #1565c0;--primary-light: rgba(25, 118, 210, .1);--error-color: #f44336;--error-light: #ffebee;--text-primary: #333;--text-secondary: #666;--text-muted: #999;--bg-primary: white;--bg-secondary: #f5f5f5;--bg-tertiary: #fafafa;--bg-hover: #f5f5f5;--border-color: #e0e0e0;--border-light: #f0f0f0;--shadow: rgba(0, 0, 0, .15);--shadow-light: rgba(0, 0, 0, .1)}:host(.theme-dark){--primary-color: #90caf9;--primary-hover: #64b5f6;--primary-light: rgba(144, 202, 249, .1);--error-color: #ef5350;--error-light: rgba(239, 83, 80, .1);--text-primary: #e0e0e0;--text-secondary: #b0b0b0;--text-muted: #888;--bg-primary: #1e1e1e;--bg-secondary: #2d2d2d;--bg-tertiary: #252525;--bg-hover: #333;--border-color: #404040;--border-light: #333;--shadow: rgba(0, 0, 0, .3);--shadow-light: rgba(0, 0, 0, .2)}.user-profile-container{display:flex;align-items:center;gap:16px;padding:0 16px}.login-btn{padding:8px 16px;background-color:var(--primary-color);color:#fff;border:none;border-radius:4px;cursor:pointer;font-weight:500;transition:background-color .3s}.login-btn:hover{background-color:var(--primary-hover)}.user-header{display:flex;align-items:center;gap:16px}.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}.user-menu-wrapper{position:relative}.user-menu-btn{background:none;border:none;cursor:pointer;padding:4px;border-radius:50%;transition:background-color .2s;display:flex;align-items:center;justify-content:center}.user-menu-btn:hover{background-color:var(--primary-light)}.avatar{width:40px;height:40px;border-radius:50%;object-fit:cover;background-color:#e0e0e0}.avatar-initial{width:40px;height:40px;border-radius:50%;background-color:var(--primary-color);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:16px}.mes-dropdown-menu{position:absolute;top:calc(100% + 8px);right:0;background:var(--bg-primary);border:1px solid var(--border-color);border-radius:4px;box-shadow:0 2px 8px var(--shadow);min-width:200px;z-index:1000;overflow:hidden}.mes-dropdown-header{padding:12px 16px;border-bottom:1px solid var(--border-light);font-weight:600;color:var(--text-primary);font-size:14px}.mes-dropdown-item{display:block;width:100%;padding:12px 16px;border:none;background:none;text-align:left;cursor:pointer;font-size:14px;color:var(--text-primary);text-decoration:none;transition:background-color .2s}.mes-dropdown-item:hover{background-color:var(--bg-hover)}.profile-link{color:var(--primary-color)}.logout-item{border-top:1px solid var(--border-light);color:var(--error-color)}.logout-item:hover{background-color:var(--error-light)}.user-info{display:flex;flex-direction:column;gap:2px}.user-name{font-weight:500;font-size:14px;color:var(--text-primary)}.user-position{font-size:12px;color:var(--text-secondary)}.logout-btn{background:none;border:none;font-size:20px;cursor:pointer;color:var(--text-secondary);padding:4px 8px;transition:color .2s}.logout-btn:hover{color:var(--primary-color)}@media(max-width:768px){.user-info{display:none}.avatar{width:32px;height:32px}}\n"] }]
631
639
  }], ctorParameters: () => [{ type: MesAuthService }, { type: i2.Router }, { type: ThemeService }, { type: i0.ChangeDetectorRef }], propDecorators: { notificationClick: [{
632
640
  type: Output
@@ -666,10 +674,10 @@ class ToastService {
666
674
  clear() {
667
675
  this.toasts$.next([]);
668
676
  }
669
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ToastService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
670
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ToastService, providedIn: 'root' });
677
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ToastService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
678
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ToastService, providedIn: 'root' });
671
679
  }
672
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ToastService, decorators: [{
680
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ToastService, decorators: [{
673
681
  type: Injectable,
674
682
  args: [{ providedIn: 'root' }]
675
683
  }] });
@@ -706,45 +714,45 @@ class ToastContainerComponent {
706
714
  close(id) {
707
715
  this.toastService.remove(id);
708
716
  }
709
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ToastContainerComponent, deps: [{ token: ToastService }, { token: ThemeService }], target: i0.ɵɵFactoryTarget.Component });
710
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: ToastContainerComponent, isStandalone: true, selector: "ma-toast-container", host: { properties: { "class": "this.themeClass" } }, ngImport: i0, template: `
711
- <div class="toast-container">
712
- <div
713
- *ngFor="let toast of toasts"
714
- class="toast"
715
- [class]="'toast-' + toast.type"
716
- [@slideIn]
717
- >
718
- <div class="toast-content">
719
- <div *ngIf="toast.title" class="toast-title">{{ toast.title }}</div>
720
- <div class="toast-message" [innerHTML]="toast.message"></div>
721
- </div>
722
- <button class="toast-close" (click)="close(toast.id)" aria-label="Close">
723
-
724
- </button>
725
- </div>
726
- </div>
717
+ 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 });
718
+ 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: `
719
+ <div class="toast-container">
720
+ <div
721
+ *ngFor="let toast of toasts"
722
+ class="toast"
723
+ [class]="'toast-' + toast.type"
724
+ [@slideIn]
725
+ >
726
+ <div class="toast-content">
727
+ <div *ngIf="toast.title" class="toast-title">{{ toast.title }}</div>
728
+ <div class="toast-message" [innerHTML]="toast.message"></div>
729
+ </div>
730
+ <button class="toast-close" (click)="close(toast.id)" aria-label="Close">
731
+
732
+ </button>
733
+ </div>
734
+ </div>
727
735
  `, isInline: true, styles: [":host{--info-color: #2196f3;--success-color: #4caf50;--warning-color: #ff9800;--error-color: #f44336;--text-primary: #333;--bg-primary: white;--shadow: rgba(0, 0, 0, .15);--text-secondary: #999;--border-color: rgba(0, 0, 0, .1)}:host(.theme-dark){--info-color: #64b5f6;--success-color: #81c784;--warning-color: #ffb74d;--error-color: #ef5350;--text-primary: #e0e0e0;--bg-primary: #1e1e1e;--shadow: rgba(0, 0, 0, .3);--text-secondary: #888;--border-color: rgba(255, 255, 255, .1)}.toast-container{position:fixed;top:20px;right:20px;z-index:9999;pointer-events:none}.toast{display:flex;align-items:flex-start;gap:12px;padding:12px 16px;margin-bottom:12px;border-radius:4px;background:var(--bg-primary);border:1px solid var(--border-color);box-shadow:0 4px 12px var(--shadow);pointer-events:auto;min-width:280px;max-width:400px;animation:slideIn .3s ease-out}.toast-content{flex:1}.toast-title{font-weight:600;font-size:14px;margin-bottom:4px}.toast-message{font-size:13px;line-height:1.4}.toast-close{background:none;border:none;cursor:pointer;font-size:18px;color:var(--text-secondary);padding:0;width:24px;height:24px;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:color .2s}.toast-close:hover{color:var(--text-primary)}.toast-info{border-left:4px solid var(--info-color)}.toast-info .toast-title{color:var(--info-color)}.toast-info .toast-message{color:var(--text-primary)}.toast-success{border-left:4px solid var(--success-color)}.toast-success .toast-title{color:var(--success-color)}.toast-success .toast-message{color:var(--text-primary)}.toast-warning{border-left:4px solid var(--warning-color)}.toast-warning .toast-title{color:var(--warning-color)}.toast-warning .toast-message{color:var(--text-primary)}.toast-error{border-left:4px solid var(--error-color)}.toast-error .toast-title{color:var(--error-color)}.toast-error .toast-message{color:var(--text-primary)}@keyframes slideIn{0%{transform:translate(400px);opacity:0}to{transform:translate(0);opacity:1}}@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"] }] });
728
736
  }
729
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: ToastContainerComponent, decorators: [{
737
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ToastContainerComponent, decorators: [{
730
738
  type: Component,
731
- args: [{ selector: 'ma-toast-container', standalone: true, imports: [CommonModule], template: `
732
- <div class="toast-container">
733
- <div
734
- *ngFor="let toast of toasts"
735
- class="toast"
736
- [class]="'toast-' + toast.type"
737
- [@slideIn]
738
- >
739
- <div class="toast-content">
740
- <div *ngIf="toast.title" class="toast-title">{{ toast.title }}</div>
741
- <div class="toast-message" [innerHTML]="toast.message"></div>
742
- </div>
743
- <button class="toast-close" (click)="close(toast.id)" aria-label="Close">
744
-
745
- </button>
746
- </div>
747
- </div>
739
+ args: [{ selector: 'ma-toast-container', standalone: true, imports: [CommonModule], template: `
740
+ <div class="toast-container">
741
+ <div
742
+ *ngFor="let toast of toasts"
743
+ class="toast"
744
+ [class]="'toast-' + toast.type"
745
+ [@slideIn]
746
+ >
747
+ <div class="toast-content">
748
+ <div *ngIf="toast.title" class="toast-title">{{ toast.title }}</div>
749
+ <div class="toast-message" [innerHTML]="toast.message"></div>
750
+ </div>
751
+ <button class="toast-close" (click)="close(toast.id)" aria-label="Close">
752
+
753
+ </button>
754
+ </div>
755
+ </div>
748
756
  `, styles: [":host{--info-color: #2196f3;--success-color: #4caf50;--warning-color: #ff9800;--error-color: #f44336;--text-primary: #333;--bg-primary: white;--shadow: rgba(0, 0, 0, .15);--text-secondary: #999;--border-color: rgba(0, 0, 0, .1)}:host(.theme-dark){--info-color: #64b5f6;--success-color: #81c784;--warning-color: #ffb74d;--error-color: #ef5350;--text-primary: #e0e0e0;--bg-primary: #1e1e1e;--shadow: rgba(0, 0, 0, .3);--text-secondary: #888;--border-color: rgba(255, 255, 255, .1)}.toast-container{position:fixed;top:20px;right:20px;z-index:9999;pointer-events:none}.toast{display:flex;align-items:flex-start;gap:12px;padding:12px 16px;margin-bottom:12px;border-radius:4px;background:var(--bg-primary);border:1px solid var(--border-color);box-shadow:0 4px 12px var(--shadow);pointer-events:auto;min-width:280px;max-width:400px;animation:slideIn .3s ease-out}.toast-content{flex:1}.toast-title{font-weight:600;font-size:14px;margin-bottom:4px}.toast-message{font-size:13px;line-height:1.4}.toast-close{background:none;border:none;cursor:pointer;font-size:18px;color:var(--text-secondary);padding:0;width:24px;height:24px;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:color .2s}.toast-close:hover{color:var(--text-primary)}.toast-info{border-left:4px solid var(--info-color)}.toast-info .toast-title{color:var(--info-color)}.toast-info .toast-message{color:var(--text-primary)}.toast-success{border-left:4px solid var(--success-color)}.toast-success .toast-title{color:var(--success-color)}.toast-success .toast-message{color:var(--text-primary)}.toast-warning{border-left:4px solid var(--warning-color)}.toast-warning .toast-title{color:var(--warning-color)}.toast-warning .toast-message{color:var(--text-primary)}.toast-error{border-left:4px solid var(--error-color)}.toast-error .toast-title{color:var(--error-color)}.toast-error .toast-message{color:var(--text-primary)}@keyframes slideIn{0%{transform:translate(400px);opacity:0}to{transform:translate(0);opacity:1}}@media(max-width:600px){.toast-container{top:10px;right:10px;left:10px}.toast{min-width:auto;max-width:100%}}\n"] }]
749
757
  }], ctorParameters: () => [{ type: ToastService }, { type: ThemeService }], propDecorators: { themeClass: [{
750
758
  type: HostBinding,
@@ -764,14 +772,20 @@ class NotificationPanelComponent {
764
772
  currentTheme = 'light';
765
773
  activeTab = 'unread'; // Default to unread tab
766
774
  destroy$ = new Subject();
775
+ // Cached filtered lists — updated explicitly to avoid re-filtering on every CD cycle
776
+ _unreadNotifications = [];
777
+ _readNotifications = [];
778
+ // Stable time-ago strings keyed by notification id — refreshed every 30s
779
+ dateLabels = new Map();
780
+ dateTimer = null;
767
781
  get unreadNotifications() {
768
- return this.notifications.filter(n => !n.isRead);
782
+ return this._unreadNotifications;
769
783
  }
770
784
  get readNotifications() {
771
- return this.notifications.filter(n => n.isRead);
785
+ return this._readNotifications;
772
786
  }
773
787
  get currentNotifications() {
774
- return this.activeTab === 'unread' ? this.unreadNotifications : this.readNotifications;
788
+ return this.activeTab === 'unread' ? this._unreadNotifications : this._readNotifications;
775
789
  }
776
790
  selectedNotification = null;
777
791
  selectedNotificationHtml = null;
@@ -793,6 +807,8 @@ class NotificationPanelComponent {
793
807
  this.currentTheme = theme;
794
808
  });
795
809
  this.loadNotifications();
810
+ // Refresh time-ago labels every 30s to avoid NG0100 from live new Date() in template
811
+ this.dateTimer = setInterval(() => this.refreshDateLabels(), 30000);
796
812
  // Listen for new real-time notifications
797
813
  this.authService.notifications$
798
814
  .pipe(takeUntil(this.destroy$))
@@ -804,6 +820,10 @@ class NotificationPanelComponent {
804
820
  });
805
821
  }
806
822
  ngOnDestroy() {
823
+ if (this.dateTimer !== null) {
824
+ clearInterval(this.dateTimer);
825
+ this.dateTimer = null;
826
+ }
807
827
  this.destroy$.next();
808
828
  this.destroy$.complete();
809
829
  }
@@ -811,6 +831,7 @@ class NotificationPanelComponent {
811
831
  this.authService.getNotifications(1, 50, true).subscribe({
812
832
  next: (response) => {
813
833
  this.notifications = response.items || [];
834
+ this.onNotificationsChanged();
814
835
  },
815
836
  error: (err) => { }
816
837
  });
@@ -837,6 +858,7 @@ class NotificationPanelComponent {
837
858
  next: () => {
838
859
  notification.isRead = true;
839
860
  this.notificationRead.emit();
861
+ this.onNotificationsChanged();
840
862
  },
841
863
  error: () => { }
842
864
  });
@@ -857,6 +879,7 @@ class NotificationPanelComponent {
857
879
  if (notification) {
858
880
  notification.isRead = true;
859
881
  this.notificationRead.emit();
882
+ this.onNotificationsChanged();
860
883
  }
861
884
  },
862
885
  error: (err) => { }
@@ -867,6 +890,7 @@ class NotificationPanelComponent {
867
890
  next: () => {
868
891
  this.notifications.forEach(n => n.isRead = true);
869
892
  this.notificationRead.emit();
893
+ this.onNotificationsChanged();
870
894
  },
871
895
  error: (err) => { }
872
896
  });
@@ -880,6 +904,7 @@ class NotificationPanelComponent {
880
904
  Promise.all(deletePromises).then(() => {
881
905
  // Remove all read notifications from the local array
882
906
  this.notifications = this.notifications.filter(n => !n.isRead);
907
+ this.onNotificationsChanged();
883
908
  }).catch((err) => {
884
909
  // If bulk delete fails, reload notifications to get current state
885
910
  this.loadNotifications();
@@ -894,6 +919,8 @@ class NotificationPanelComponent {
894
919
  Promise.all(deletePromises).then(() => {
895
920
  // Remove all unread notifications from the local array
896
921
  this.notifications = this.notifications.filter(n => n.isRead);
922
+ this.notificationRead.emit();
923
+ this.onNotificationsChanged();
897
924
  }).catch((err) => {
898
925
  // If bulk delete fails, reload notifications to get current state
899
926
  this.loadNotifications();
@@ -901,22 +928,28 @@ class NotificationPanelComponent {
901
928
  }
902
929
  delete(notificationId, event) {
903
930
  event.stopPropagation();
931
+ const wasUnread = this.notifications.find(n => n.id === notificationId && !n.isRead) !== undefined;
904
932
  this.authService.deleteNotification(notificationId).subscribe({
905
933
  next: () => {
906
934
  this.notifications = this.notifications.filter(n => n.id !== notificationId);
935
+ if (wasUnread) {
936
+ this.notificationRead.emit();
937
+ }
938
+ this.onNotificationsChanged();
907
939
  },
908
940
  error: (err) => { }
909
941
  });
910
942
  }
911
943
  formatDate(dateString) {
912
- // Parse date string from server (stored in UTC but without 'Z' suffix or 'T' separator)
944
+ return this.computeTimeAgo(dateString, new Date());
945
+ }
946
+ // Pure computation — takes now as param so it never calls new Date() internally
947
+ computeTimeAgo(dateString, now) {
913
948
  const normalizedDateString = this.parseUtcDate(dateString);
914
949
  const date = new Date(normalizedDateString);
915
- // Check if the date is valid
916
950
  if (isNaN(date.getTime())) {
917
951
  return 'Invalid date';
918
952
  }
919
- const now = new Date();
920
953
  const diffMs = now.getTime() - date.getTime();
921
954
  const diffMins = Math.floor(diffMs / 60000);
922
955
  const diffHours = Math.floor(diffMs / 3600000);
@@ -931,6 +964,23 @@ class NotificationPanelComponent {
931
964
  return `${diffDays}d ago`;
932
965
  return date.toLocaleDateString();
933
966
  }
967
+ // Rebuild dateLabels map using a single shared now — prevents mid-loop clock drift
968
+ refreshDateLabels() {
969
+ const now = new Date();
970
+ for (const n of this.notifications) {
971
+ this.dateLabels.set(n.id, this.computeTimeAgo(n.createdAt, now));
972
+ }
973
+ }
974
+ // Re-run filter once and store results in stable arrays
975
+ recomputeFilteredLists() {
976
+ this._unreadNotifications = this.notifications.filter(n => !n.isRead);
977
+ this._readNotifications = this.notifications.filter(n => n.isRead);
978
+ }
979
+ // Single call-site after every notification mutation
980
+ onNotificationsChanged() {
981
+ this.recomputeFilteredLists();
982
+ this.refreshDateLabels();
983
+ }
934
984
  // Parse date string from server (stored in UTC but without 'Z' suffix or 'T' separator)
935
985
  parseUtcDate(dateStr) {
936
986
  // Handle date strings that might be missing the 'T' separator
@@ -942,215 +992,215 @@ class NotificationPanelComponent {
942
992
  }
943
993
  return normalized;
944
994
  }
945
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationPanelComponent, deps: [{ token: MesAuthService }, { token: ToastService }, { token: ThemeService }], target: i0.ɵɵFactoryTarget.Component });
946
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: NotificationPanelComponent, isStandalone: true, selector: "ma-notification-panel", outputs: { notificationRead: "notificationRead" }, host: { properties: { "class": "this.themeClass" } }, ngImport: i0, template: `
947
- <div class="notification-panel" [class.open]="isOpen">
948
- <!-- Header -->
949
- <div class="panel-header">
950
- <h3>Notifications</h3>
951
- <button class="close-btn" (click)="close()" title="Close">✕</button>
952
- </div>
953
-
954
- <!-- Tabs -->
955
- <div class="tabs">
956
- <button
957
- class="tab-btn"
958
- [class.active]="activeTab === 'unread'"
959
- (click)="switchTab('unread')"
960
- >
961
- Unread ({{ unreadNotifications.length }})
962
- </button>
963
- <button
964
- class="tab-btn"
965
- [class.active]="activeTab === 'read'"
966
- (click)="switchTab('read')"
967
- >
968
- Read ({{ readNotifications.length }})
969
- </button>
970
- </div>
971
-
972
- <!-- Notifications List -->
973
- <div class="notifications-list">
974
- <ng-container *ngIf="currentNotifications.length > 0">
975
- <div
976
- *ngFor="let notification of currentNotifications"
977
- class="notification-item"
978
- [class.unread]="!notification.isRead"
979
- (click)="openDetails(notification)"
980
- >
981
- <div class="notification-content">
982
- <div class="notification-title">{{ notification.title }}</div>
983
- <div class="notification-message">{{ getNotificationMessage(notification) }}</div>
984
- <div class="notification-meta">
985
- <span class="app-name">{{ notification.sourceAppName }}</span>
986
- <span class="time">{{ formatDate(notification.createdAt) }}</span>
987
- </div>
988
- </div>
989
- <button
990
- class="read-btn"
991
- (click)="markAsRead(notification.id, $event)"
992
- title="Mark as read"
993
- *ngIf="!notification.isRead"
994
- >
995
-
996
- </button>
997
- <button
998
- class="delete-btn"
999
- (click)="delete(notification.id, $event)"
1000
- title="Delete notification"
1001
- *ngIf="notification.isRead"
1002
- >
1003
- 🗑
1004
- </button>
1005
- </div>
1006
- </ng-container>
1007
-
1008
- <ng-container *ngIf="currentNotifications.length === 0">
1009
- <div class="empty-state">
1010
- No {{ activeTab }} notifications
1011
- </div>
1012
- </ng-container>
1013
- </div>
1014
-
1015
- <!-- Footer Actions -->
1016
- <div class="panel-footer" *ngIf="currentNotifications.length > 0">
1017
- <div class="footer-actions" *ngIf="activeTab === 'unread'">
1018
- <button class="action-btn" (click)="markAllAsRead()" *ngIf="unreadNotifications.length > 0">
1019
- Mark all as read
1020
- </button>
1021
- <button class="action-btn delete-all-btn" (click)="deleteAllUnread()" *ngIf="unreadNotifications.length > 0">
1022
- Delete all
1023
- </button>
1024
- </div>
1025
- <button class="action-btn delete-all-btn" (click)="deleteAllRead()" *ngIf="activeTab === 'read' && readNotifications.length > 0">
1026
- Delete all
1027
- </button>
1028
- </div>
1029
- </div>
1030
-
1031
- <!-- Details Modal -->
1032
- <div class="modal-overlay" *ngIf="selectedNotification" (click)="closeDetails()">
1033
- <div class="modal-container" (click)="$event.stopPropagation()">
1034
- <div class="modal-header">
1035
- <h3>{{ selectedNotification.title }}</h3>
1036
- <button class="close-btn" (click)="closeDetails()" title="Close">✕</button>
1037
- </div>
1038
- <div class="modal-meta">
1039
- <span class="app-name">{{ selectedNotification.sourceAppName }}</span>
1040
- <span class="time">{{ selectedNotificationDate }}</span>
1041
- </div>
1042
- <div class="modal-body" [innerHTML]="selectedNotificationHtml"></div>
1043
- <div class="modal-footer">
1044
- <button class="action-btn" (click)="closeDetails()">Close</button>
1045
- </div>
1046
- </div>
1047
- </div>
995
+ 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 });
996
+ 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
+ <div class="notification-panel" [class.open]="isOpen">
998
+ <!-- Header -->
999
+ <div class="panel-header">
1000
+ <h3>Notifications</h3>
1001
+ <button class="close-btn" (click)="close()" title="Close">✕</button>
1002
+ </div>
1003
+
1004
+ <!-- Tabs -->
1005
+ <div class="tabs">
1006
+ <button
1007
+ class="tab-btn"
1008
+ [class.active]="activeTab === 'unread'"
1009
+ (click)="switchTab('unread')"
1010
+ >
1011
+ Unread ({{ unreadNotifications.length }})
1012
+ </button>
1013
+ <button
1014
+ class="tab-btn"
1015
+ [class.active]="activeTab === 'read'"
1016
+ (click)="switchTab('read')"
1017
+ >
1018
+ Read ({{ readNotifications.length }})
1019
+ </button>
1020
+ </div>
1021
+
1022
+ <!-- Notifications List -->
1023
+ <div class="notifications-list">
1024
+ <ng-container *ngIf="currentNotifications.length > 0">
1025
+ <div
1026
+ *ngFor="let notification of currentNotifications"
1027
+ class="notification-item"
1028
+ [class.unread]="!notification.isRead"
1029
+ (click)="openDetails(notification)"
1030
+ >
1031
+ <div class="notification-content">
1032
+ <div class="notification-title">{{ notification.title }}</div>
1033
+ <div class="notification-message">{{ getNotificationMessage(notification) }}</div>
1034
+ <div class="notification-meta">
1035
+ <span class="app-name">{{ notification.sourceAppName }}</span>
1036
+ <span class="time">{{ dateLabels.get(notification.id) }}</span>
1037
+ </div>
1038
+ </div>
1039
+ <button
1040
+ class="read-btn"
1041
+ (click)="markAsRead(notification.id, $event)"
1042
+ title="Mark as read"
1043
+ *ngIf="!notification.isRead"
1044
+ >
1045
+
1046
+ </button>
1047
+ <button
1048
+ class="delete-btn"
1049
+ (click)="delete(notification.id, $event)"
1050
+ title="Delete notification"
1051
+ *ngIf="notification.isRead"
1052
+ >
1053
+ 🗑
1054
+ </button>
1055
+ </div>
1056
+ </ng-container>
1057
+
1058
+ <ng-container *ngIf="currentNotifications.length === 0">
1059
+ <div class="empty-state">
1060
+ No {{ activeTab }} notifications
1061
+ </div>
1062
+ </ng-container>
1063
+ </div>
1064
+
1065
+ <!-- Footer Actions -->
1066
+ <div class="panel-footer" *ngIf="currentNotifications.length > 0">
1067
+ <div class="footer-actions" *ngIf="activeTab === 'unread'">
1068
+ <button class="action-btn" (click)="markAllAsRead()" *ngIf="unreadNotifications.length > 0">
1069
+ Mark all as read
1070
+ </button>
1071
+ <button class="action-btn delete-all-btn" (click)="deleteAllUnread()" *ngIf="unreadNotifications.length > 0">
1072
+ Delete all
1073
+ </button>
1074
+ </div>
1075
+ <button class="action-btn delete-all-btn" (click)="deleteAllRead()" *ngIf="activeTab === 'read' && readNotifications.length > 0">
1076
+ Delete all
1077
+ </button>
1078
+ </div>
1079
+ </div>
1080
+
1081
+ <!-- Details Modal -->
1082
+ <div class="modal-overlay" *ngIf="selectedNotification" (click)="closeDetails()">
1083
+ <div class="modal-container" (click)="$event.stopPropagation()">
1084
+ <div class="modal-header">
1085
+ <h3>{{ selectedNotification.title }}</h3>
1086
+ <button class="close-btn" (click)="closeDetails()" title="Close">✕</button>
1087
+ </div>
1088
+ <div class="modal-meta">
1089
+ <span class="app-name">{{ selectedNotification.sourceAppName }}</span>
1090
+ <span class="time">{{ selectedNotificationDate }}</span>
1091
+ </div>
1092
+ <div class="modal-body" [innerHTML]="selectedNotificationHtml"></div>
1093
+ <div class="modal-footer">
1094
+ <button class="action-btn" (click)="closeDetails()">Close</button>
1095
+ </div>
1096
+ </div>
1097
+ </div>
1048
1098
  `, isInline: true, styles: [":host{display:block;position:relative;--primary-color: #1976d2;--primary-hover: #1565c0;--success-color: #4caf50;--error-color: #f44336;--text-primary: #333;--text-secondary: #666;--text-muted: #999;--bg-primary: white;--bg-secondary: #f5f5f5;--bg-tertiary: #fafafa;--bg-hover: #f5f5f5;--bg-unread: #e3f2fd;--border-color: #e0e0e0;--border-light: #f0f0f0;--shadow: rgba(0, 0, 0, .1)}:host(.theme-dark){display:block;position:relative;--primary-color: #90caf9;--primary-hover: #64b5f6;--success-color: #81c784;--error-color: #ef5350;--text-primary: #e0e0e0;--text-secondary: #b0b0b0;--text-muted: #888;--bg-primary: #1e1e1e;--bg-secondary: #2d2d2d;--bg-tertiary: #252525;--bg-hover: #333;--bg-unread: rgba(144, 202, 249, .1);--border-color: #404040;--border-light: #333;--shadow: rgba(0, 0, 0, .3)}.notification-panel{position:fixed;top:0;right:-350px;width:350px;height:100vh;background:var(--bg-primary);box-shadow:-2px 0 8px var(--shadow);display:flex;flex-direction:column;z-index:1030;transition:right .3s ease}.notification-panel.open{right:0}.panel-header{display:flex;justify-content:space-between;align-items:center;padding:16px;border-bottom:1px solid var(--border-color);background-color:var(--bg-secondary)}.panel-header h3{margin:0;font-size:18px;color:var(--text-primary)}.close-btn{background:none;border:none;font-size:20px;cursor:pointer;color:var(--text-secondary);padding:0;width:32px;height:32px;display:flex;align-items:center;justify-content:center;transition:color .2s}.close-btn:hover{color:var(--text-primary)}.tabs{display:flex;border-bottom:1px solid var(--border-color);background-color:var(--bg-secondary)}.tab-btn{flex:1;padding:12px 16px;background:none;border:none;color:var(--text-secondary);cursor:pointer;font-size:14px;font-weight:500;transition:all .2s;border-bottom:2px solid transparent}.tab-btn:hover{background-color:var(--bg-hover);color:var(--text-primary)}.tab-btn.active{color:var(--primary-color);border-bottom-color:var(--primary-color);background-color:var(--bg-primary)}.notifications-list{flex:1;overflow-y:auto}.notification-item{display:flex;gap:12px;padding:12px 16px;border-bottom:1px solid var(--border-light);cursor:pointer;background-color:var(--bg-tertiary);transition:background-color .2s}.notification-item:hover{background-color:var(--bg-hover)}.notification-item.unread{background-color:var(--bg-unread)}.notification-content{flex:1;min-width:0}.notification-title{font-weight:600;color:var(--text-primary);font-size:14px;margin-bottom:4px}.notification-message{color:var(--text-secondary);font-size:12px;line-height:1.4;margin-bottom:6px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.notification-meta{display:flex;justify-content:space-between;font-size:12px;color:var(--text-muted)}.app-name{font-weight:500;color:var(--primary-color)}.read-btn{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:14px;padding:0;width:24px;height:24px;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:color .2s}.read-btn:hover{color:var(--success-color)}.delete-btn{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:14px;padding:0;width:24px;height:24px;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:color .2s}.delete-btn:hover{color:var(--error-color)}.empty-state{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);font-size:14px}.panel-footer{padding:12px 16px;border-top:1px solid var(--border-color);background-color:var(--bg-secondary)}.footer-actions{display:flex;gap:8px}.footer-actions .action-btn{flex:1}.action-btn{width:100%;padding:8px;background-color:var(--primary-color);color:#fff;border:none;border-radius:4px;cursor:pointer;font-weight:500;transition:background-color .2s}.action-btn:hover{background-color:var(--primary-hover)}.delete-all-btn{background-color:var(--error-color);color:#fff}.delete-all-btn:hover{background-color:#d32f2f}.modal-overlay{position:fixed;inset:0;width:100vw;height:100vh;background-color:#00000080;display:flex;align-items:center;justify-content:center;z-index:1060}.modal-container{background:var(--bg-primary);border-radius:8px;width:90%;max-width:600px;max-height:80vh;display:flex;flex-direction:column;box-shadow:0 4px 20px var(--shadow)}.modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--border-color);background-color:var(--bg-secondary);border-radius:8px 8px 0 0}.modal-header h3{margin:0;font-size:18px;color:var(--text-primary)}.modal-meta{display:flex;justify-content:space-between;padding:8px 20px;font-size:12px;color:var(--text-muted);background-color:var(--bg-tertiary);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.6}.modal-footer{padding:12px 20px;border-top:1px solid var(--border-color);background-color:var(--bg-secondary);border-radius:0 0 8px 8px;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"] }] });
1049
1099
  }
1050
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationPanelComponent, decorators: [{
1100
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: NotificationPanelComponent, decorators: [{
1051
1101
  type: Component,
1052
- args: [{ selector: 'ma-notification-panel', standalone: true, imports: [NgIf, NgFor], template: `
1053
- <div class="notification-panel" [class.open]="isOpen">
1054
- <!-- Header -->
1055
- <div class="panel-header">
1056
- <h3>Notifications</h3>
1057
- <button class="close-btn" (click)="close()" title="Close">✕</button>
1058
- </div>
1059
-
1060
- <!-- Tabs -->
1061
- <div class="tabs">
1062
- <button
1063
- class="tab-btn"
1064
- [class.active]="activeTab === 'unread'"
1065
- (click)="switchTab('unread')"
1066
- >
1067
- Unread ({{ unreadNotifications.length }})
1068
- </button>
1069
- <button
1070
- class="tab-btn"
1071
- [class.active]="activeTab === 'read'"
1072
- (click)="switchTab('read')"
1073
- >
1074
- Read ({{ readNotifications.length }})
1075
- </button>
1076
- </div>
1077
-
1078
- <!-- Notifications List -->
1079
- <div class="notifications-list">
1080
- <ng-container *ngIf="currentNotifications.length > 0">
1081
- <div
1082
- *ngFor="let notification of currentNotifications"
1083
- class="notification-item"
1084
- [class.unread]="!notification.isRead"
1085
- (click)="openDetails(notification)"
1086
- >
1087
- <div class="notification-content">
1088
- <div class="notification-title">{{ notification.title }}</div>
1089
- <div class="notification-message">{{ getNotificationMessage(notification) }}</div>
1090
- <div class="notification-meta">
1091
- <span class="app-name">{{ notification.sourceAppName }}</span>
1092
- <span class="time">{{ formatDate(notification.createdAt) }}</span>
1093
- </div>
1094
- </div>
1095
- <button
1096
- class="read-btn"
1097
- (click)="markAsRead(notification.id, $event)"
1098
- title="Mark as read"
1099
- *ngIf="!notification.isRead"
1100
- >
1101
-
1102
- </button>
1103
- <button
1104
- class="delete-btn"
1105
- (click)="delete(notification.id, $event)"
1106
- title="Delete notification"
1107
- *ngIf="notification.isRead"
1108
- >
1109
- 🗑
1110
- </button>
1111
- </div>
1112
- </ng-container>
1113
-
1114
- <ng-container *ngIf="currentNotifications.length === 0">
1115
- <div class="empty-state">
1116
- No {{ activeTab }} notifications
1117
- </div>
1118
- </ng-container>
1119
- </div>
1120
-
1121
- <!-- Footer Actions -->
1122
- <div class="panel-footer" *ngIf="currentNotifications.length > 0">
1123
- <div class="footer-actions" *ngIf="activeTab === 'unread'">
1124
- <button class="action-btn" (click)="markAllAsRead()" *ngIf="unreadNotifications.length > 0">
1125
- Mark all as read
1126
- </button>
1127
- <button class="action-btn delete-all-btn" (click)="deleteAllUnread()" *ngIf="unreadNotifications.length > 0">
1128
- Delete all
1129
- </button>
1130
- </div>
1131
- <button class="action-btn delete-all-btn" (click)="deleteAllRead()" *ngIf="activeTab === 'read' && readNotifications.length > 0">
1132
- Delete all
1133
- </button>
1134
- </div>
1135
- </div>
1136
-
1137
- <!-- Details Modal -->
1138
- <div class="modal-overlay" *ngIf="selectedNotification" (click)="closeDetails()">
1139
- <div class="modal-container" (click)="$event.stopPropagation()">
1140
- <div class="modal-header">
1141
- <h3>{{ selectedNotification.title }}</h3>
1142
- <button class="close-btn" (click)="closeDetails()" title="Close">✕</button>
1143
- </div>
1144
- <div class="modal-meta">
1145
- <span class="app-name">{{ selectedNotification.sourceAppName }}</span>
1146
- <span class="time">{{ selectedNotificationDate }}</span>
1147
- </div>
1148
- <div class="modal-body" [innerHTML]="selectedNotificationHtml"></div>
1149
- <div class="modal-footer">
1150
- <button class="action-btn" (click)="closeDetails()">Close</button>
1151
- </div>
1152
- </div>
1153
- </div>
1102
+ args: [{ selector: 'ma-notification-panel', standalone: true, imports: [NgIf, NgFor], template: `
1103
+ <div class="notification-panel" [class.open]="isOpen">
1104
+ <!-- Header -->
1105
+ <div class="panel-header">
1106
+ <h3>Notifications</h3>
1107
+ <button class="close-btn" (click)="close()" title="Close">✕</button>
1108
+ </div>
1109
+
1110
+ <!-- Tabs -->
1111
+ <div class="tabs">
1112
+ <button
1113
+ class="tab-btn"
1114
+ [class.active]="activeTab === 'unread'"
1115
+ (click)="switchTab('unread')"
1116
+ >
1117
+ Unread ({{ unreadNotifications.length }})
1118
+ </button>
1119
+ <button
1120
+ class="tab-btn"
1121
+ [class.active]="activeTab === 'read'"
1122
+ (click)="switchTab('read')"
1123
+ >
1124
+ Read ({{ readNotifications.length }})
1125
+ </button>
1126
+ </div>
1127
+
1128
+ <!-- Notifications List -->
1129
+ <div class="notifications-list">
1130
+ <ng-container *ngIf="currentNotifications.length > 0">
1131
+ <div
1132
+ *ngFor="let notification of currentNotifications"
1133
+ class="notification-item"
1134
+ [class.unread]="!notification.isRead"
1135
+ (click)="openDetails(notification)"
1136
+ >
1137
+ <div class="notification-content">
1138
+ <div class="notification-title">{{ notification.title }}</div>
1139
+ <div class="notification-message">{{ getNotificationMessage(notification) }}</div>
1140
+ <div class="notification-meta">
1141
+ <span class="app-name">{{ notification.sourceAppName }}</span>
1142
+ <span class="time">{{ dateLabels.get(notification.id) }}</span>
1143
+ </div>
1144
+ </div>
1145
+ <button
1146
+ class="read-btn"
1147
+ (click)="markAsRead(notification.id, $event)"
1148
+ title="Mark as read"
1149
+ *ngIf="!notification.isRead"
1150
+ >
1151
+
1152
+ </button>
1153
+ <button
1154
+ class="delete-btn"
1155
+ (click)="delete(notification.id, $event)"
1156
+ title="Delete notification"
1157
+ *ngIf="notification.isRead"
1158
+ >
1159
+ 🗑
1160
+ </button>
1161
+ </div>
1162
+ </ng-container>
1163
+
1164
+ <ng-container *ngIf="currentNotifications.length === 0">
1165
+ <div class="empty-state">
1166
+ No {{ activeTab }} notifications
1167
+ </div>
1168
+ </ng-container>
1169
+ </div>
1170
+
1171
+ <!-- Footer Actions -->
1172
+ <div class="panel-footer" *ngIf="currentNotifications.length > 0">
1173
+ <div class="footer-actions" *ngIf="activeTab === 'unread'">
1174
+ <button class="action-btn" (click)="markAllAsRead()" *ngIf="unreadNotifications.length > 0">
1175
+ Mark all as read
1176
+ </button>
1177
+ <button class="action-btn delete-all-btn" (click)="deleteAllUnread()" *ngIf="unreadNotifications.length > 0">
1178
+ Delete all
1179
+ </button>
1180
+ </div>
1181
+ <button class="action-btn delete-all-btn" (click)="deleteAllRead()" *ngIf="activeTab === 'read' && readNotifications.length > 0">
1182
+ Delete all
1183
+ </button>
1184
+ </div>
1185
+ </div>
1186
+
1187
+ <!-- Details Modal -->
1188
+ <div class="modal-overlay" *ngIf="selectedNotification" (click)="closeDetails()">
1189
+ <div class="modal-container" (click)="$event.stopPropagation()">
1190
+ <div class="modal-header">
1191
+ <h3>{{ selectedNotification.title }}</h3>
1192
+ <button class="close-btn" (click)="closeDetails()" title="Close">✕</button>
1193
+ </div>
1194
+ <div class="modal-meta">
1195
+ <span class="app-name">{{ selectedNotification.sourceAppName }}</span>
1196
+ <span class="time">{{ selectedNotificationDate }}</span>
1197
+ </div>
1198
+ <div class="modal-body" [innerHTML]="selectedNotificationHtml"></div>
1199
+ <div class="modal-footer">
1200
+ <button class="action-btn" (click)="closeDetails()">Close</button>
1201
+ </div>
1202
+ </div>
1203
+ </div>
1154
1204
  `, styles: [":host{display:block;position:relative;--primary-color: #1976d2;--primary-hover: #1565c0;--success-color: #4caf50;--error-color: #f44336;--text-primary: #333;--text-secondary: #666;--text-muted: #999;--bg-primary: white;--bg-secondary: #f5f5f5;--bg-tertiary: #fafafa;--bg-hover: #f5f5f5;--bg-unread: #e3f2fd;--border-color: #e0e0e0;--border-light: #f0f0f0;--shadow: rgba(0, 0, 0, .1)}:host(.theme-dark){display:block;position:relative;--primary-color: #90caf9;--primary-hover: #64b5f6;--success-color: #81c784;--error-color: #ef5350;--text-primary: #e0e0e0;--text-secondary: #b0b0b0;--text-muted: #888;--bg-primary: #1e1e1e;--bg-secondary: #2d2d2d;--bg-tertiary: #252525;--bg-hover: #333;--bg-unread: rgba(144, 202, 249, .1);--border-color: #404040;--border-light: #333;--shadow: rgba(0, 0, 0, .3)}.notification-panel{position:fixed;top:0;right:-350px;width:350px;height:100vh;background:var(--bg-primary);box-shadow:-2px 0 8px var(--shadow);display:flex;flex-direction:column;z-index:1030;transition:right .3s ease}.notification-panel.open{right:0}.panel-header{display:flex;justify-content:space-between;align-items:center;padding:16px;border-bottom:1px solid var(--border-color);background-color:var(--bg-secondary)}.panel-header h3{margin:0;font-size:18px;color:var(--text-primary)}.close-btn{background:none;border:none;font-size:20px;cursor:pointer;color:var(--text-secondary);padding:0;width:32px;height:32px;display:flex;align-items:center;justify-content:center;transition:color .2s}.close-btn:hover{color:var(--text-primary)}.tabs{display:flex;border-bottom:1px solid var(--border-color);background-color:var(--bg-secondary)}.tab-btn{flex:1;padding:12px 16px;background:none;border:none;color:var(--text-secondary);cursor:pointer;font-size:14px;font-weight:500;transition:all .2s;border-bottom:2px solid transparent}.tab-btn:hover{background-color:var(--bg-hover);color:var(--text-primary)}.tab-btn.active{color:var(--primary-color);border-bottom-color:var(--primary-color);background-color:var(--bg-primary)}.notifications-list{flex:1;overflow-y:auto}.notification-item{display:flex;gap:12px;padding:12px 16px;border-bottom:1px solid var(--border-light);cursor:pointer;background-color:var(--bg-tertiary);transition:background-color .2s}.notification-item:hover{background-color:var(--bg-hover)}.notification-item.unread{background-color:var(--bg-unread)}.notification-content{flex:1;min-width:0}.notification-title{font-weight:600;color:var(--text-primary);font-size:14px;margin-bottom:4px}.notification-message{color:var(--text-secondary);font-size:12px;line-height:1.4;margin-bottom:6px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.notification-meta{display:flex;justify-content:space-between;font-size:12px;color:var(--text-muted)}.app-name{font-weight:500;color:var(--primary-color)}.read-btn{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:14px;padding:0;width:24px;height:24px;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:color .2s}.read-btn:hover{color:var(--success-color)}.delete-btn{background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:14px;padding:0;width:24px;height:24px;display:flex;align-items:center;justify-content:center;flex-shrink:0;transition:color .2s}.delete-btn:hover{color:var(--error-color)}.empty-state{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);font-size:14px}.panel-footer{padding:12px 16px;border-top:1px solid var(--border-color);background-color:var(--bg-secondary)}.footer-actions{display:flex;gap:8px}.footer-actions .action-btn{flex:1}.action-btn{width:100%;padding:8px;background-color:var(--primary-color);color:#fff;border:none;border-radius:4px;cursor:pointer;font-weight:500;transition:background-color .2s}.action-btn:hover{background-color:var(--primary-hover)}.delete-all-btn{background-color:var(--error-color);color:#fff}.delete-all-btn:hover{background-color:#d32f2f}.modal-overlay{position:fixed;inset:0;width:100vw;height:100vh;background-color:#00000080;display:flex;align-items:center;justify-content:center;z-index:1060}.modal-container{background:var(--bg-primary);border-radius:8px;width:90%;max-width:600px;max-height:80vh;display:flex;flex-direction:column;box-shadow:0 4px 20px var(--shadow)}.modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--border-color);background-color:var(--bg-secondary);border-radius:8px 8px 0 0}.modal-header h3{margin:0;font-size:18px;color:var(--text-primary)}.modal-meta{display:flex;justify-content:space-between;padding:8px 20px;font-size:12px;color:var(--text-muted);background-color:var(--bg-tertiary);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.6}.modal-footer{padding:12px 20px;border-top:1px solid var(--border-color);background-color:var(--bg-secondary);border-radius:0 0 8px 8px;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"] }]
1155
1205
  }], ctorParameters: () => [{ type: MesAuthService }, { type: ToastService }, { type: ThemeService }], propDecorators: { notificationRead: [{
1156
1206
  type: Output
@@ -1172,23 +1222,23 @@ class MaUserComponent {
1172
1222
  this.userProfile.loadUnreadCount();
1173
1223
  }
1174
1224
  }
1175
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MaUserComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1176
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: MaUserComponent, isStandalone: true, selector: "ma-user", viewQueries: [{ propertyName: "userProfile", first: true, predicate: UserProfileComponent, descendants: true }], ngImport: i0, template: `
1177
- <ma-toast-container></ma-toast-container>
1178
- <div class="user-header">
1179
- <ma-user-profile (notificationClick)="notificationPanel.open()"></ma-user-profile>
1180
- </div>
1181
- <ma-notification-panel #notificationPanel (notificationRead)="onNotificationRead()"></ma-notification-panel>
1225
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MaUserComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
1226
+ 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: `
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>
1182
1232
  `, 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"] }] });
1183
1233
  }
1184
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: MaUserComponent, decorators: [{
1234
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: MaUserComponent, decorators: [{
1185
1235
  type: Component,
1186
- args: [{ selector: 'ma-user', standalone: true, imports: [ToastContainerComponent, UserProfileComponent, NotificationPanelComponent], template: `
1187
- <ma-toast-container></ma-toast-container>
1188
- <div class="user-header">
1189
- <ma-user-profile (notificationClick)="notificationPanel.open()"></ma-user-profile>
1190
- </div>
1191
- <ma-notification-panel #notificationPanel (notificationRead)="onNotificationRead()"></ma-notification-panel>
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>
1192
1242
  `, styles: [".user-header{display:flex;justify-content:flex-end}\n"] }]
1193
1243
  }], propDecorators: { userProfile: [{
1194
1244
  type: ViewChild,
@@ -1254,21 +1304,21 @@ class NotificationBadgeComponent {
1254
1304
  onNotificationClick() {
1255
1305
  this.notificationClick.emit();
1256
1306
  }
1257
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationBadgeComponent, deps: [{ token: MesAuthService }, { token: ThemeService }], target: i0.ɵɵFactoryTarget.Component });
1258
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.16", type: NotificationBadgeComponent, isStandalone: true, selector: "ma-notification-badge", outputs: { notificationClick: "notificationClick" }, host: { properties: { "class": "this.themeClass" } }, ngImport: i0, template: `
1259
- <button class="notification-btn" (click)="onNotificationClick()" title="Notifications">
1260
- <span class="icon">🔔</span>
1261
- <span class="badge" *ngIf="unreadCount > 0">{{ unreadCount }}</span>
1262
- </button>
1307
+ 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 });
1308
+ 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: `
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>
1263
1313
  `, 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"] }] });
1264
1314
  }
1265
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: NotificationBadgeComponent, decorators: [{
1315
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: NotificationBadgeComponent, decorators: [{
1266
1316
  type: Component,
1267
- args: [{ selector: 'ma-notification-badge', standalone: true, imports: [NgIf], template: `
1268
- <button class="notification-btn" (click)="onNotificationClick()" title="Notifications">
1269
- <span class="icon">🔔</span>
1270
- <span class="badge" *ngIf="unreadCount > 0">{{ unreadCount }}</span>
1271
- </button>
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>
1272
1322
  `, 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"] }]
1273
1323
  }], ctorParameters: () => [{ type: MesAuthService }, { type: ThemeService }], propDecorators: { notificationClick: [{
1274
1324
  type: Output