import { Component, OnInit, OnDestroy } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { Subscription } from 'rxjs'; import { NotificationsService, NotificationDto } from '../../services/notifications.service'; @Component({ selector: 'app-notificacoes', standalone: true, imports: [CommonModule, FormsModule], templateUrl: './notificacoes.html', styleUrls: ['./notificacoes.scss'], }) export class Notificacoes implements OnInit, OnDestroy { notifications: NotificationDto[] = []; filter: 'todas' | 'vencidas' | 'aVencer' | 'lidas' = 'todas'; search = ''; loading = false; error = false; bulkLoading = false; bulkUnreadLoading = false; exportLoading = false; selectedIds = new Set(); private readonly subs = new Subscription(); constructor(private notificationsService: NotificationsService) {} ngOnInit(): void { this.loadNotifications(); this.subs.add( this.notificationsService.events$.subscribe((ev) => { if (ev.type === 'read') { const ids = new Set(ev.ids); this.notifications.forEach((n) => { if (ids.has(n.id)) { n.lida = true; n.lidaEm = ev.readAtIso; } }); return; } if (ev.type === 'readAll') { this.notifications.forEach((n) => { if (!n.lida) { n.lida = true; n.lidaEm = ev.readAtIso; } }); return; } if (ev.type === 'unread') { const ids = new Set(ev.ids); this.notifications.forEach((n) => { if (ids.has(n.id)) { n.lida = false; n.lidaEm = null; } }); return; } if (ev.type === 'unreadAll') { this.notifications.forEach((n) => { if (n.lida) { n.lida = false; n.lidaEm = null; } }); return; } if (ev.type === 'reload') { this.loadNotifications(); } }) ); } ngOnDestroy(): void { this.subs.unsubscribe(); } markAsRead(notification: NotificationDto) { if (notification.lida) return; this.notificationsService.markAsRead(notification.id).subscribe({ next: () => { notification.lida = true; notification.lidaEm = new Date().toISOString(); }, }); } markAsUnread(notification: NotificationDto) { if (!notification.lida) return; this.notificationsService.markAsUnread(notification.id).subscribe({ next: () => { notification.lida = false; notification.lidaEm = null; }, }); } setFilter(value: 'todas' | 'vencidas' | 'aVencer' | 'lidas') { this.filter = value; this.clearSelection(); } get filteredNotifications() { const base = this.getBaseFilteredNotifications(); const q = (this.search || '').trim().toLowerCase(); if (!q) return base; return base.filter(n => this.buildSearchText(n).includes(q)); } clearSearch() { this.search = ''; this.clearSelection(); } formatDateLabel(date?: string | null): string { if (!date) return '-'; const parsed = this.parseDateOnly(date); if (!parsed) return '-'; return parsed.toLocaleDateString('pt-BR'); } getNotificationTipo(notification: NotificationDto): 'Vencido' | 'AVencer' { const reference = notification.dtTerminoFidelizacao ?? notification.referenciaData; const parsed = this.parseDateOnly(reference); if (!parsed) return notification.tipo; const today = this.startOfDay(new Date()); return parsed < today ? 'Vencido' : 'AVencer'; } 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; }, }); } countByType(tipo: 'Vencido' | 'AVencer'): number { return this.notifications.filter(n => this.getNotificationTipo(n) === tipo && !n.lida).length; } markAllAsRead() { if (this.filter === 'lidas' || this.bulkLoading) return; this.bulkLoading = true; const selectedIds = Array.from(this.selectedIds); const scopedIds = selectedIds.length ? selectedIds : (this.filter !== 'todas' ? this.filteredNotifications.map(n => n.id) : []); const filterParam = scopedIds.length ? undefined : this.getFilterParam(); this.notificationsService.markAllAsRead(filterParam, scopedIds.length ? scopedIds : undefined).subscribe({ next: () => { const now = new Date().toISOString(); this.notifications = this.notifications.map((n) => { if (scopedIds.length ? scopedIds.includes(n.id) : this.shouldMarkRead(n)) { return { ...n, lida: true, lidaEm: now }; } return n; }); this.clearSelection(); this.bulkLoading = false; }, error: () => { this.bulkLoading = false; } }); } markAllAsUnread() { if (this.filter !== 'lidas' || this.bulkUnreadLoading) return; this.bulkUnreadLoading = true; const selectedIds = Array.from(this.selectedIds); const scopedIds = selectedIds.length ? selectedIds : this.filteredNotifications.map(n => n.id); this.notificationsService.markAllAsUnread(undefined, scopedIds.length ? scopedIds : undefined).subscribe({ next: () => { this.notifications = this.notifications.map((n) => { if (scopedIds.length ? scopedIds.includes(n.id) : n.lida) { return { ...n, lida: false, lidaEm: null }; } return n; }); this.clearSelection(); this.bulkUnreadLoading = false; }, error: () => { this.bulkUnreadLoading = false; } }); } exportNotifications() { if (this.filter === 'lidas' || this.exportLoading) return; this.exportLoading = true; const selectedIds = Array.from(this.selectedIds); const scopedIds = selectedIds.length ? selectedIds : (this.filter !== 'todas' ? this.filteredNotifications.map(n => n.id) : []); const filterParam = scopedIds.length ? undefined : this.getFilterParam(); this.notificationsService.export(filterParam, scopedIds.length ? scopedIds : undefined).subscribe({ next: (res) => { const blob = res.body; if (!blob) { this.exportLoading = false; return; } const filename = this.extractFilename(res.headers.get('content-disposition')) || this.buildDefaultFilename(); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); a.remove(); URL.revokeObjectURL(url); this.clearSelection(); this.exportLoading = false; }, error: () => { this.exportLoading = false; } }); } isSelected(notification: NotificationDto): boolean { return this.selectedIds.has(notification.id); } toggleSelection(notification: NotificationDto) { if (this.selectedIds.has(notification.id)) { this.selectedIds.delete(notification.id); } else { this.selectedIds.add(notification.id); } } get isAllSelected(): boolean { const list = this.filteredNotifications; return list.length > 0 && list.every(n => this.selectedIds.has(n.id)); } toggleSelectAll() { const list = this.filteredNotifications; if (this.isAllSelected) { this.clearSelection(); return; } list.forEach(n => this.selectedIds.add(n.id)); } clearSelection() { this.selectedIds.clear(); } private getFilterParam(): string | undefined { if (this.filter === 'aVencer') return 'a-vencer'; if (this.filter === 'vencidas') return 'vencidas'; if (this.filter === 'todas') return undefined; return undefined; } private shouldMarkRead(n: NotificationDto): boolean { if (this.filter === 'todas') return true; if (this.filter === 'aVencer') return this.getNotificationTipo(n) === 'AVencer'; if (this.filter === 'vencidas') return this.getNotificationTipo(n) === 'Vencido'; return false; } private getBaseFilteredNotifications(): NotificationDto[] { if (this.filter === 'lidas') { return this.notifications.filter(n => n.lida); } if (this.filter === 'vencidas') { return this.notifications.filter(n => !n.lida && this.getNotificationTipo(n) === 'Vencido'); } if (this.filter === 'aVencer') { return this.notifications.filter(n => !n.lida && this.getNotificationTipo(n) === 'AVencer'); } // "todas" aqui representa o inbox: pendentes (não lidas). return this.notifications.filter(n => !n.lida); } private buildSearchText(n: NotificationDto): string { const parts: string[] = []; const push = (v?: string | null) => { const t = (v ?? '').toString().trim(); if (t) parts.push(t); }; push(n.cliente); push(n.conta); push(n.linha); push(n.usuario); push(n.planoContrato); push(n.titulo); push(n.mensagem); push(n.data); push(n.referenciaData ?? null); push(n.dtEfetivacaoServico ?? null); push(n.dtTerminoFidelizacao ?? null); const efetivacao = this.formatDateSearch(n.dtEfetivacaoServico); const termino = this.formatDateSearch(n.dtTerminoFidelizacao); push(efetivacao); push(termino); return parts.join(' ').toLowerCase(); } private formatDateSearch(raw?: string | null): string { if (!raw) return ''; const parsed = this.parseDateOnly(raw); if (!parsed) return ''; // Ex.: 12/02/2026 (facilita busca por padrão BR). return parsed.toLocaleDateString('pt-BR'); } private parseDateOnly(raw?: string | null): Date | null { if (!raw) return null; const datePart = raw.split('T')[0]; const parts = datePart.split('-'); if (parts.length === 3) { const year = Number(parts[0]); const month = Number(parts[1]); const day = Number(parts[2]); if (Number.isFinite(year) && Number.isFinite(month) && Number.isFinite(day)) { return new Date(year, month - 1, day); } } const fallback = new Date(raw); if (Number.isNaN(fallback.getTime())) return null; return this.startOfDay(fallback); } private startOfDay(date: Date): Date { return new Date(date.getFullYear(), date.getMonth(), date.getDate()); } private extractFilename(contentDisposition: string | null): string | null { if (!contentDisposition) return null; const utf8Match = contentDisposition.match(/filename\*=UTF-8''([^;]+)/i); if (utf8Match?.[1]) return decodeURIComponent(utf8Match[1]); const normalMatch = contentDisposition.match(/filename=\"?([^\";]+)\"?/i); return normalMatch?.[1] ?? null; } private buildDefaultFilename(): string { const stamp = new Date().toISOString().replace(/[-:]/g, '').replace('T', '').slice(0, 14); return `notificacoes-${stamp}.xlsx`; } }