Adiciona notificações no header e página dedicada
This commit is contained in:
parent
1eac19177c
commit
29348e54ae
|
|
@ -12,6 +12,7 @@ import { DadosUsuarios } from './pages/dados-usuarios/dados-usuarios';
|
||||||
import { VigenciaComponent } from './pages/vigencia/vigencia';
|
import { VigenciaComponent } from './pages/vigencia/vigencia';
|
||||||
import { TrocaNumero } from './pages/troca-numero/troca-numero';
|
import { TrocaNumero } from './pages/troca-numero/troca-numero';
|
||||||
import { Dashboard } from './pages/dashboard/dashboard';
|
import { Dashboard } from './pages/dashboard/dashboard';
|
||||||
|
import { Notificacoes } from './pages/notificacoes/notificacoes';
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
{ path: '', component: Home },
|
{ path: '', component: Home },
|
||||||
|
|
@ -24,6 +25,7 @@ export const routes: Routes = [
|
||||||
{ path: 'dadosusuarios', component: DadosUsuarios, canActivate: [authGuard] },
|
{ path: 'dadosusuarios', component: DadosUsuarios, canActivate: [authGuard] },
|
||||||
{ path: 'vigencia', component: VigenciaComponent, canActivate: [authGuard] },
|
{ path: 'vigencia', component: VigenciaComponent, canActivate: [authGuard] },
|
||||||
{ path: 'trocanumero', component: TrocaNumero, canActivate: [authGuard] },
|
{ path: 'trocanumero', component: TrocaNumero, canActivate: [authGuard] },
|
||||||
|
{ path: 'notificacoes', component: Notificacoes, canActivate: [authGuard] },
|
||||||
|
|
||||||
// ✅ rota correta
|
// ✅ rota correta
|
||||||
{ path: 'dashboard', component: Dashboard, canActivate: [authGuard] },
|
{ path: 'dashboard', component: Dashboard, canActivate: [authGuard] },
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ export class AppComponent {
|
||||||
'/vigencia',
|
'/vigencia',
|
||||||
'/trocanumero',
|
'/trocanumero',
|
||||||
'/dashboard', // ✅ ADICIONADO: esconde footer na página de dashboard
|
'/dashboard', // ✅ ADICIONADO: esconde footer na página de dashboard
|
||||||
|
'/notificacoes',
|
||||||
];
|
];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,49 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="logged-actions">
|
<div class="logged-actions">
|
||||||
<button type="button" class="btn-icon btn-bell" aria-label="Notificações">
|
<div class="notifications-menu" [class.open]="notificationsOpen" (click)="$event.stopPropagation()">
|
||||||
<i class="bi bi-bell"></i>
|
<button
|
||||||
</button>
|
type="button"
|
||||||
|
class="btn-icon btn-bell"
|
||||||
|
aria-label="Notificações"
|
||||||
|
(click)="toggleNotifications()"
|
||||||
|
[attr.aria-expanded]="notificationsOpen"
|
||||||
|
>
|
||||||
|
<i class="bi bi-bell"></i>
|
||||||
|
<span class="badge-dot" *ngIf="unreadCount > 0">{{ unreadCount }}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="notifications-dropdown" *ngIf="notificationsOpen">
|
||||||
|
<div class="notifications-head">
|
||||||
|
<span>Notificações</span>
|
||||||
|
<a routerLink="/notificacoes" class="see-all" (click)="closeNotifications()">Ver todas</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="notifications-body">
|
||||||
|
<div class="notifications-state" *ngIf="notificationsLoading">
|
||||||
|
Carregando...
|
||||||
|
</div>
|
||||||
|
<div class="notifications-state warn" *ngIf="notificationsError">
|
||||||
|
Falha ao carregar notificações.
|
||||||
|
</div>
|
||||||
|
<div class="notifications-state" *ngIf="!notificationsLoading && !notificationsError && notifications.length === 0">
|
||||||
|
Nenhuma notificação por aqui.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="notification-item" *ngFor="let n of notifications">
|
||||||
|
<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>
|
||||||
|
<button type="button" class="mark-read" (click)="markNotificationRead(n)">
|
||||||
|
{{ n.lida ? 'Lida' : 'Marcar como lida' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="options-menu" [class.open]="optionsOpen" (click)="$event.stopPropagation()">
|
<div class="options-menu" [class.open]="optionsOpen" (click)="$event.stopPropagation()">
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -149,6 +149,126 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.notifications-menu {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-dot {
|
||||||
|
position: absolute;
|
||||||
|
top: -4px;
|
||||||
|
right: -4px;
|
||||||
|
min-width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
padding: 0 5px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #ef4444;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 800;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
box-shadow: 0 0 0 3px #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-dropdown {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: calc(100% + 8px);
|
||||||
|
width: min(360px, 82vw);
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid rgba(0,0,0,0.08);
|
||||||
|
box-shadow: 0 18px 40px rgba(0,0,0,0.12);
|
||||||
|
z-index: 1200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-head {
|
||||||
|
padding: 12px 14px 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-weight: 800;
|
||||||
|
color: rgba(17, 18, 20, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.see-all {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--brand-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-body {
|
||||||
|
max-height: 320px;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 6px 8px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-state {
|
||||||
|
padding: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: rgba(17, 18, 20, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-state.warn {
|
||||||
|
color: #b45309;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-item {
|
||||||
|
background: rgba(248, 249, 255, 0.9);
|
||||||
|
border: 1px solid rgba(0,0,0,0.06);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #1f2937;
|
||||||
|
background: rgba(3, 15, 170, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-tag.warn {
|
||||||
|
background: rgba(227, 61, 207, 0.16);
|
||||||
|
color: #8b2a7d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-tag.danger {
|
||||||
|
background: rgba(239, 68, 68, 0.16);
|
||||||
|
color: #b91c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-title {
|
||||||
|
margin-top: 6px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: rgba(17, 18, 20, 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-message {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(17, 18, 20, 0.68);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mark-read {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid rgba(0,0,0,0.08);
|
||||||
|
background: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.options-menu {
|
.options-menu {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { CommonModule, isPlatformBrowser } from '@angular/common';
|
||||||
import { PLATFORM_ID } from '@angular/core';
|
import { PLATFORM_ID } from '@angular/core';
|
||||||
import { filter } from 'rxjs/operators';
|
import { filter } from 'rxjs/operators';
|
||||||
import { AuthService } from '../../services/auth.service';
|
import { AuthService } from '../../services/auth.service';
|
||||||
|
import { NotificationsService, NotificationDto } from '../../services/notifications.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-header',
|
selector: 'app-header',
|
||||||
|
|
@ -17,8 +18,12 @@ export class Header {
|
||||||
|
|
||||||
menuOpen = false;
|
menuOpen = false;
|
||||||
optionsOpen = false;
|
optionsOpen = false;
|
||||||
|
notificationsOpen = false;
|
||||||
isLoggedHeader = false;
|
isLoggedHeader = false;
|
||||||
isHome = false;
|
isHome = false;
|
||||||
|
notifications: NotificationDto[] = [];
|
||||||
|
notificationsLoading = false;
|
||||||
|
notificationsError = false;
|
||||||
|
|
||||||
private readonly loggedPrefixes = [
|
private readonly loggedPrefixes = [
|
||||||
'/geral',
|
'/geral',
|
||||||
|
|
@ -28,11 +33,13 @@ export class Header {
|
||||||
'/vigencia',
|
'/vigencia',
|
||||||
'/trocanumero',
|
'/trocanumero',
|
||||||
'/dashboard', // ✅ ADICIONADO
|
'/dashboard', // ✅ ADICIONADO
|
||||||
|
'/notificacoes',
|
||||||
];
|
];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
|
private notificationsService: NotificationsService,
|
||||||
@Inject(PLATFORM_ID) private platformId: object
|
@Inject(PLATFORM_ID) private platformId: object
|
||||||
) {
|
) {
|
||||||
// ✅ resolve no carregamento inicial
|
// ✅ resolve no carregamento inicial
|
||||||
|
|
@ -46,6 +53,7 @@ export class Header {
|
||||||
this.syncHeaderState(rawUrl);
|
this.syncHeaderState(rawUrl);
|
||||||
this.menuOpen = false;
|
this.menuOpen = false;
|
||||||
this.optionsOpen = false;
|
this.optionsOpen = false;
|
||||||
|
this.notificationsOpen = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,15 +77,43 @@ export class Header {
|
||||||
|
|
||||||
toggleOptions() {
|
toggleOptions() {
|
||||||
this.optionsOpen = !this.optionsOpen;
|
this.optionsOpen = !this.optionsOpen;
|
||||||
|
if (this.optionsOpen) this.notificationsOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
closeOptions() {
|
closeOptions() {
|
||||||
this.optionsOpen = false;
|
this.optionsOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toggleNotifications() {
|
||||||
|
this.notificationsOpen = !this.notificationsOpen;
|
||||||
|
if (this.notificationsOpen) {
|
||||||
|
this.optionsOpen = false;
|
||||||
|
this.loadNotifications();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closeNotifications() {
|
||||||
|
this.notificationsOpen = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
markNotificationRead(notification: NotificationDto) {
|
||||||
|
if (notification.lida) return;
|
||||||
|
this.notificationsService.markAsRead(notification.id).subscribe({
|
||||||
|
next: () => {
|
||||||
|
notification.lida = true;
|
||||||
|
notification.lidaEm = new Date().toISOString();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get unreadCount() {
|
||||||
|
return this.notifications.filter(n => !n.lida).length;
|
||||||
|
}
|
||||||
|
|
||||||
logout() {
|
logout() {
|
||||||
this.authService.logout();
|
this.authService.logout();
|
||||||
this.optionsOpen = false;
|
this.optionsOpen = false;
|
||||||
|
this.notificationsOpen = false;
|
||||||
this.router.navigate(['/']);
|
this.router.navigate(['/']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,6 +126,7 @@ export class Header {
|
||||||
@HostListener('document:click', [])
|
@HostListener('document:click', [])
|
||||||
onDocumentClick() {
|
onDocumentClick() {
|
||||||
this.optionsOpen = false;
|
this.optionsOpen = false;
|
||||||
|
this.notificationsOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('document:keydown.escape', [])
|
@HostListener('document:keydown.escape', [])
|
||||||
|
|
@ -97,5 +134,22 @@ export class Header {
|
||||||
if (!isPlatformBrowser(this.platformId)) return;
|
if (!isPlatformBrowser(this.platformId)) return;
|
||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
this.closeOptions();
|
this.closeOptions();
|
||||||
|
this.closeNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadNotifications() {
|
||||||
|
if (!isPlatformBrowser(this.platformId)) return;
|
||||||
|
this.notificationsLoading = true;
|
||||||
|
this.notificationsError = false;
|
||||||
|
this.notificationsService.list().subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.notifications = data || [];
|
||||||
|
this.notificationsLoading = false;
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.notificationsError = true;
|
||||||
|
this.notificationsLoading = false;
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
<section class="notificacoes-page">
|
||||||
|
<div class="wrap">
|
||||||
|
<div class="container">
|
||||||
|
<div class="page-head">
|
||||||
|
<div>
|
||||||
|
<h2>Notificações</h2>
|
||||||
|
<p>Acompanhe vencimentos e avisos recentes.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="state" *ngIf="loading">Carregando notificações...</div>
|
||||||
|
<div class="state warn" *ngIf="!loading && error">Falha ao carregar notificações.</div>
|
||||||
|
<div class="state" *ngIf="!loading && !error && notifications.length === 0">
|
||||||
|
Nenhuma notificação encontrada.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="notifications-grid" *ngIf="!loading && !error && notifications.length > 0">
|
||||||
|
<article class="notification-card" *ngFor="let n of notifications">
|
||||||
|
<div class="card-head">
|
||||||
|
<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>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<button type="button" class="mark-read" (click)="markAsRead(n)">
|
||||||
|
{{ n.lida ? 'Lida' : 'Marcar como lida' }}
|
||||||
|
</button>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notificacoes-page {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrap {
|
||||||
|
padding: 24px 0 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 1100px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 800;
|
||||||
|
margin: 0 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
color: rgba(17, 18, 20, 0.6);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.state {
|
||||||
|
padding: 12px 14px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: rgba(255, 255, 255, 0.7);
|
||||||
|
border: 1px solid rgba(0,0,0,0.08);
|
||||||
|
font-weight: 700;
|
||||||
|
color: rgba(17, 18, 20, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
.state.warn {
|
||||||
|
color: #b45309;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid rgba(0,0,0,0.08);
|
||||||
|
padding: 16px;
|
||||||
|
box-shadow: 0 18px 36px rgba(0,0,0,0.08);
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin: 12px 0 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: rgba(17, 18, 20, 0.92);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: rgba(17, 18, 20, 0.68);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 800;
|
||||||
|
background: rgba(3, 15, 170, 0.12);
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag.warn {
|
||||||
|
background: rgba(227, 61, 207, 0.16);
|
||||||
|
color: #8b2a7d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag.danger {
|
||||||
|
background: rgba(239, 68, 68, 0.16);
|
||||||
|
color: #b91c1c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date {
|
||||||
|
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;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid rgba(0,0,0,0.08);
|
||||||
|
background: #fff;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
|
import { NotificationsService, NotificationDto } from '../../services/notifications.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-notificacoes',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule],
|
||||||
|
templateUrl: './notificacoes.html',
|
||||||
|
styleUrls: ['./notificacoes.scss'],
|
||||||
|
})
|
||||||
|
export class Notificacoes implements OnInit {
|
||||||
|
notifications: NotificationDto[] = [];
|
||||||
|
loading = false;
|
||||||
|
error = false;
|
||||||
|
|
||||||
|
constructor(private notificationsService: NotificationsService) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.loadNotifications();
|
||||||
|
}
|
||||||
|
|
||||||
|
markAsRead(notification: NotificationDto) {
|
||||||
|
if (notification.lida) return;
|
||||||
|
this.notificationsService.markAsRead(notification.id).subscribe({
|
||||||
|
next: () => {
|
||||||
|
notification.lida = true;
|
||||||
|
notification.lidaEm = new Date().toISOString();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadNotifications() {
|
||||||
|
this.loading = true;
|
||||||
|
this.error = false;
|
||||||
|
this.notificationsService.list().subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
this.notifications = data || [];
|
||||||
|
this.loading = false;
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.error = true;
|
||||||
|
this.loading = false;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { environment } from '../../environments/environment';
|
||||||
|
|
||||||
|
export type NotificationTipo = 'AVencer' | 'Vencido';
|
||||||
|
|
||||||
|
export type NotificationDto = {
|
||||||
|
id: string;
|
||||||
|
tipo: NotificationTipo;
|
||||||
|
titulo: string;
|
||||||
|
mensagem: string;
|
||||||
|
data: string;
|
||||||
|
referenciaData?: string | null;
|
||||||
|
diasParaVencer?: number | null;
|
||||||
|
lida: boolean;
|
||||||
|
lidaEm?: string | null;
|
||||||
|
vigenciaLineId?: string | null;
|
||||||
|
cliente?: string | null;
|
||||||
|
linha?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class NotificationsService {
|
||||||
|
private readonly baseUrl = `${environment.apiUrl}/notifications`;
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) {}
|
||||||
|
|
||||||
|
list(): Observable<NotificationDto[]> {
|
||||||
|
return this.http.get<NotificationDto[]>(this.baseUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
markAsRead(id: string): Observable<void> {
|
||||||
|
return this.http.patch<void>(`${this.baseUrl}/${id}/read`, {});
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue