Refina layout dos cards de notificações
This commit is contained in:
parent
8a12a2e0d0
commit
72a79479a8
|
|
@ -50,11 +50,17 @@
|
|||
</div>
|
||||
|
||||
<div class="notification-item" *ngFor="let n of notifications">
|
||||
<div class="notification-top">
|
||||
<span class="notification-tag" [class.danger]="n.tipo === 'Vencido'" [class.warn]="n.tipo === 'AVencer'">
|
||||
{{ n.tipo === 'Vencido' ? 'Vencido' : 'A vencer' }}
|
||||
</span>
|
||||
<div class="notification-title">{{ n.titulo }}</div>
|
||||
<div class="notification-message">{{ n.mensagem }}</div>
|
||||
<span class="notification-line">{{ n.linha || '-' }}</span>
|
||||
</div>
|
||||
<div class="notification-info">
|
||||
<div><strong>Linha:</strong> {{ n.linha || '-' }}</div>
|
||||
<div><strong>Cliente:</strong> {{ n.cliente || '-' }}</div>
|
||||
<div><strong>{{ n.tipo === 'Vencido' ? 'Venceu em' : 'Vence em' }}:</strong> {{ n.referenciaData ? (n.referenciaData | date:'dd/MM/yyyy') : '-' }}</div>
|
||||
</div>
|
||||
<button type="button" class="mark-read" (click)="markNotificationRead(n)">
|
||||
{{ n.lida ? 'Lida' : 'Marcar como lida' }}
|
||||
</button>
|
||||
|
|
@ -117,6 +123,21 @@
|
|||
|
||||
</header>
|
||||
|
||||
<div class="toast-container position-fixed top-0 end-0 p-3">
|
||||
<div class="toast notification-toast" #notifToast>
|
||||
<div class="toast-header">
|
||||
<strong class="me-auto">Vigência próxima</strong>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="toast" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="toast-body" *ngIf="toastNotification as toastItem">
|
||||
A linha {{ toastItem.linha || '-' }} vence em 5 dias.
|
||||
<button type="button" class="btn-aware" (click)="acknowledgeNotification(toastItem)" data-bs-dismiss="toast">
|
||||
Estou ciente
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ OVERLAY (logado) -->
|
||||
<div class="menu-overlay" *ngIf="isLoggedHeader && menuOpen" (click)="closeMenu()"></div>
|
||||
|
||||
|
|
|
|||
|
|
@ -242,6 +242,31 @@
|
|||
background: rgba(3, 15, 170, 0.12);
|
||||
}
|
||||
|
||||
.notification-top {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.notification-line {
|
||||
font-weight: 800;
|
||||
font-size: 12px;
|
||||
color: rgba(17, 18, 20, 0.65);
|
||||
}
|
||||
|
||||
.notification-info {
|
||||
margin-top: 8px;
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
color: rgba(17, 18, 20, 0.75);
|
||||
|
||||
strong {
|
||||
color: rgba(17, 18, 20, 0.9);
|
||||
}
|
||||
}
|
||||
|
||||
.notification-tag.warn {
|
||||
background: rgba(227, 61, 207, 0.16);
|
||||
color: #8b2a7d;
|
||||
|
|
@ -282,6 +307,30 @@
|
|||
}
|
||||
}
|
||||
|
||||
.notification-toast {
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(0,0,0,0.08);
|
||||
box-shadow: 0 18px 36px rgba(0,0,0,0.16);
|
||||
}
|
||||
|
||||
.notification-toast .toast-header {
|
||||
border-bottom: 1px solid rgba(0,0,0,0.06);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.btn-aware {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(0,0,0,0.1);
|
||||
background: #fff;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.options-menu {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, HostListener, Inject } from '@angular/core';
|
||||
import { Component, HostListener, Inject, ElementRef, ViewChild } from '@angular/core';
|
||||
import { RouterLink, Router, NavigationEnd } from '@angular/router';
|
||||
import { CommonModule, isPlatformBrowser } from '@angular/common';
|
||||
import { PLATFORM_ID } from '@angular/core';
|
||||
|
|
@ -24,6 +24,8 @@ export class Header {
|
|||
notifications: NotificationDto[] = [];
|
||||
notificationsLoading = false;
|
||||
notificationsError = false;
|
||||
private notificationsLoaded = false;
|
||||
@ViewChild('notifToast') notifToast?: ElementRef;
|
||||
|
||||
private readonly loggedPrefixes = [
|
||||
'/geral',
|
||||
|
|
@ -54,7 +56,13 @@ export class Header {
|
|||
this.menuOpen = false;
|
||||
this.optionsOpen = false;
|
||||
this.notificationsOpen = false;
|
||||
if (this.isLoggedHeader) {
|
||||
this.ensureNotificationsLoaded();
|
||||
}
|
||||
});
|
||||
if (this.isLoggedHeader) {
|
||||
this.ensureNotificationsLoaded();
|
||||
}
|
||||
}
|
||||
|
||||
private syncHeaderState(rawUrl: string) {
|
||||
|
|
@ -137,6 +145,18 @@ export class Header {
|
|||
this.closeNotifications();
|
||||
}
|
||||
|
||||
acknowledgeNotification(notification: NotificationDto) {
|
||||
if (!isPlatformBrowser(this.platformId)) return;
|
||||
const acknowledged = this.getAcknowledgedIds();
|
||||
acknowledged.add(notification.id);
|
||||
localStorage.setItem('vigenciaAcknowledgedIds', JSON.stringify(Array.from(acknowledged)));
|
||||
}
|
||||
|
||||
private ensureNotificationsLoaded() {
|
||||
if (this.notificationsLoaded || this.notificationsLoading) return;
|
||||
this.loadNotifications();
|
||||
}
|
||||
|
||||
private loadNotifications() {
|
||||
if (!isPlatformBrowser(this.platformId)) return;
|
||||
this.notificationsLoading = true;
|
||||
|
|
@ -144,7 +164,9 @@ export class Header {
|
|||
this.notificationsService.list().subscribe({
|
||||
next: (data) => {
|
||||
this.notifications = data || [];
|
||||
this.notificationsLoaded = true;
|
||||
this.notificationsLoading = false;
|
||||
this.maybeShowVigenciaToast();
|
||||
},
|
||||
error: () => {
|
||||
this.notificationsError = true;
|
||||
|
|
@ -152,4 +174,36 @@ export class Header {
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
private async maybeShowVigenciaToast() {
|
||||
if (!this.notifToast || !isPlatformBrowser(this.platformId)) return;
|
||||
const pending = this.getPendingVigenciaToast();
|
||||
if (!pending) return;
|
||||
|
||||
const bs = await import('bootstrap');
|
||||
const toast = new bs.Toast(this.notifToast.nativeElement, { autohide: false });
|
||||
toast.show();
|
||||
}
|
||||
|
||||
get toastNotification() {
|
||||
return this.getPendingVigenciaToast();
|
||||
}
|
||||
|
||||
private getPendingVigenciaToast() {
|
||||
const acknowledged = this.getAcknowledgedIds();
|
||||
return this.notifications.find(
|
||||
n => n.tipo === 'AVencer' && n.diasParaVencer === 5 && !acknowledged.has(n.id)
|
||||
);
|
||||
}
|
||||
|
||||
private getAcknowledgedIds() {
|
||||
if (!isPlatformBrowser(this.platformId)) return new Set<string>();
|
||||
try {
|
||||
const raw = localStorage.getItem('vigenciaAcknowledgedIds');
|
||||
const ids = raw ? (JSON.parse(raw) as string[]) : [];
|
||||
return new Set(ids);
|
||||
} catch {
|
||||
return new Set<string>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,15 +49,13 @@
|
|||
<span class="tag" [class.danger]="n.tipo === 'Vencido'" [class.warn]="n.tipo === 'AVencer'">
|
||||
{{ n.tipo === 'Vencido' ? 'Vencido' : 'A vencer' }}
|
||||
</span>
|
||||
<span class="date">{{ n.data | date:'dd/MM/yyyy' }}</span>
|
||||
<span class="line-number">{{ n.linha || '-' }}</span>
|
||||
</div>
|
||||
|
||||
<h3>{{ n.titulo }}</h3>
|
||||
<p>{{ n.mensagem }}</p>
|
||||
|
||||
<div class="card-meta">
|
||||
<span *ngIf="n.cliente">Cliente: {{ n.cliente }}</span>
|
||||
<span *ngIf="n.linha">Linha: {{ n.linha }}</span>
|
||||
<div class="card-info">
|
||||
<div><strong>Linha:</strong> {{ n.linha || '-' }}</div>
|
||||
<div><strong>Cliente:</strong> {{ n.cliente || '-' }}</div>
|
||||
<div><strong>{{ n.tipo === 'Vencido' ? 'Venceu em' : 'Vence em' }}:</strong> {{ n.referenciaData ? (n.referenciaData | date:'dd/MM/yyyy') : '-' }}</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="mark-read" (click)="markAsRead(n)">
|
||||
|
|
|
|||
|
|
@ -106,17 +106,16 @@
|
|||
box-shadow: 0 22px 44px rgba(0,0,0,0.12);
|
||||
}
|
||||
|
||||
h3 {
|
||||
margin: 12px 0 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 800;
|
||||
.card-info {
|
||||
margin-top: 12px;
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
font-size: 13px;
|
||||
color: rgba(17, 18, 20, 0.72);
|
||||
|
||||
strong {
|
||||
color: rgba(17, 18, 20, 0.92);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0 0 12px;
|
||||
font-size: 13px;
|
||||
color: rgba(17, 18, 20, 0.68);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -148,19 +147,12 @@
|
|||
color: #b91c1c;
|
||||
}
|
||||
|
||||
.date {
|
||||
.line-number {
|
||||
font-size: 12px;
|
||||
color: rgba(17, 18, 20, 0.55);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.card-meta {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
color: rgba(17, 18, 20, 0.7);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.mark-read {
|
||||
margin-top: 12px;
|
||||
|
|
|
|||
Loading…
Reference in New Issue