line-gestao-frontend/src/app/pages/historico/historico.ts

276 lines
7.6 KiB
TypeScript

import { Component, OnInit, ElementRef, ViewChild, ChangeDetectorRef, Inject, PLATFORM_ID } from '@angular/core';
import { CommonModule, isPlatformBrowser } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { HttpErrorResponse } from '@angular/common/http';
import { CustomSelectComponent } from '../../components/custom-select/custom-select';
import { HistoricoService, AuditLogDto, AuditChangeType, HistoricoQuery } from '../../services/historico.service';
interface SelectOption {
value: string;
label: string;
}
@Component({
selector: 'app-historico',
standalone: true,
imports: [CommonModule, FormsModule, CustomSelectComponent],
templateUrl: './historico.html',
styleUrls: ['./historico.scss'],
})
export class Historico implements OnInit {
@ViewChild('successToast', { static: false }) successToast!: ElementRef;
logs: AuditLogDto[] = [];
loading = false;
error = false;
errorMsg = '';
toastMessage = '';
expandedLogId: string | null = null;
page = 1;
pageSize = 10;
pageSizeOptions = [10, 20, 50, 100];
total = 0;
filterPageName = '';
filterAction = '';
filterUserId = '';
filterSearch = '';
dateFrom = '';
dateTo = '';
readonly pageOptions: SelectOption[] = [
{ value: '', label: 'Todas as páginas' },
{ value: 'Geral', label: 'Geral' },
{ value: 'Mureg', label: 'Mureg' },
{ value: 'Faturamento', label: 'Faturamento' },
{ value: 'Parcelamentos', label: 'Parcelamentos' },
{ value: 'Dados e Usuários', label: 'Dados PF/PJ' },
{ value: 'Vigência', label: 'Vigência' },
{ value: 'Chips Virgens e Recebidos', label: 'Chips Virgens e Recebidos' },
{ value: 'Troca de número', label: 'Troca de número' },
];
readonly actionOptions: SelectOption[] = [
{ value: '', label: 'Todas as ações' },
{ value: 'CREATE', label: 'Criação' },
{ value: 'UPDATE', label: 'Atualização' },
{ value: 'DELETE', label: 'Exclusão' },
];
private searchTimer: any = null;
constructor(
private historicoService: HistoricoService,
private cdr: ChangeDetectorRef,
@Inject(PLATFORM_ID) private platformId: object
) {}
ngOnInit(): void {
this.fetch(1);
}
refresh(): void {
this.fetch(1);
}
applyFilters(): void {
this.page = 1;
this.fetch();
}
clearFilters(): void {
this.filterPageName = '';
this.filterAction = '';
this.filterUserId = '';
this.filterSearch = '';
this.dateFrom = '';
this.dateTo = '';
this.page = 1;
this.fetch();
}
clearSearch(): void {
this.filterSearch = '';
this.page = 1;
this.fetch();
}
onSearchChange(): void {
if (this.searchTimer) clearTimeout(this.searchTimer);
this.searchTimer = setTimeout(() => {
this.page = 1;
this.fetch();
}, 300);
}
onPageSizeChange(): void {
this.page = 1;
this.fetch();
}
goToPage(p: number): void {
this.page = Math.max(1, Math.min(this.totalPages, p));
this.fetch();
}
get totalPages(): number {
return Math.ceil((this.total || 0) / this.pageSize) || 1;
}
get pageNumbers(): number[] {
const total = this.totalPages;
const current = this.page;
const max = 5;
let start = Math.max(1, current - 2);
let end = Math.min(total, start + (max - 1));
start = Math.max(1, end - (max - 1));
const pages: number[] = [];
for (let i = start; i <= end; i++) pages.push(i);
return pages;
}
get pageStart(): number {
return this.total === 0 ? 0 : (this.page - 1) * this.pageSize + 1;
}
get pageEnd(): number {
if (this.total === 0) return 0;
return Math.min(this.page * this.pageSize, this.total);
}
toggleDetails(log: AuditLogDto, event?: Event): void {
if (event) event.stopPropagation();
this.expandedLogId = this.expandedLogId === log.id ? null : log.id;
}
formatDateTime(value?: string | null): string {
if (!value) return '-';
const d = new Date(value);
if (isNaN(d.getTime())) return '-';
return d.toLocaleString('pt-BR');
}
displayUserName(log: AuditLogDto): string {
const name = (log.userName || '').trim();
return name ? name : 'SISTEMA';
}
displayEntity(log: AuditLogDto): string {
const label = (log.entityLabel || '').trim();
if (label) return label;
return log.entityName || '-';
}
formatAction(action?: string | null): string {
const value = (action || '').toUpperCase();
if (!value) return '-';
if (value === 'CREATE') return 'Criação';
if (value === 'UPDATE') return 'Atualização';
if (value === 'DELETE') return 'Exclusão';
return 'Outro';
}
actionClass(action?: string | null): string {
const value = (action || '').toUpperCase();
if (value === 'CREATE') return 'action-create';
if (value === 'UPDATE') return 'action-update';
if (value === 'DELETE') return 'action-delete';
return 'action-default';
}
changeTypeLabel(type?: AuditChangeType | string | null): string {
if (!type) return 'Alterado';
if (type === 'added') return 'Adicionado';
if (type === 'removed') return 'Removido';
return 'Alterado';
}
changeTypeClass(type?: AuditChangeType | string | null): string {
if (type === 'added') return 'change-added';
if (type === 'removed') return 'change-removed';
if (type === 'modified') return 'change-modified';
return 'change-modified';
}
formatChangeValue(value?: string | null): string {
if (value === undefined || value === null || value === '') return '-';
return String(value);
}
trackByLog(_: number, log: AuditLogDto): string {
return log.id;
}
trackByField(_: number, change: { field: string }): string {
return change.field;
}
private fetch(goToPage?: number): void {
if (goToPage) this.page = goToPage;
this.loading = true;
this.error = false;
this.errorMsg = '';
this.expandedLogId = null;
const query: HistoricoQuery = {
page: this.page,
pageSize: this.pageSize,
pageName: this.filterPageName || undefined,
action: this.filterAction || undefined,
userId: this.filterUserId?.trim() || undefined,
search: this.filterSearch?.trim() || undefined,
dateFrom: this.toIsoDate(this.dateFrom, false) || undefined,
dateTo: this.toIsoDate(this.dateTo, true) || undefined,
};
this.historicoService.list(query).subscribe({
next: (res) => {
this.logs = res.items || [];
this.total = res.total || 0;
this.page = res.page || this.page;
this.pageSize = res.pageSize || this.pageSize;
this.loading = false;
},
error: (err: HttpErrorResponse) => {
this.error = true;
if (err?.status === 403) {
this.errorMsg = 'Acesso restrito.';
} else {
this.errorMsg = 'Erro ao carregar histórico. Tente novamente.';
}
this.loading = false;
},
});
}
private toIsoDate(value: string, endOfDay: boolean): string | null {
if (!value) return null;
const time = endOfDay ? '23:59:59' : '00:00:00';
const date = new Date(`${value}T${time}`);
if (isNaN(date.getTime())) return null;
return date.toISOString();
}
private async showToast(message: string) {
if (!isPlatformBrowser(this.platformId)) return;
this.toastMessage = message;
this.cdr.detectChanges();
if (!this.successToast?.nativeElement) return;
try {
const bs = await import('bootstrap');
const toastInstance = bs.Toast.getOrCreateInstance(this.successToast.nativeElement, {
autohide: true,
delay: 3000
});
toastInstance.show();
} catch (error) {
console.error(error);
}
}
}