import { Component, ElementRef, ViewChild, Inject, PLATFORM_ID, AfterViewInit, ChangeDetectorRef } from '@angular/core'; import { isPlatformBrowser, CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { HttpClient, HttpClientModule, HttpParams } from '@angular/common/http'; type MuregKey = 'item' | 'linhaAntiga' | 'linhaNova' | 'iccid' | 'dataDaMureg' | 'cliente'; interface MuregRow { id: string; item: string; linhaAntiga: string; linhaNova: string; iccid: string; dataDaMureg: string; cliente: string; raw: any; } interface ApiPagedResult { page?: number; pageSize?: number; total?: number; items?: T[]; } interface ClientGroup { cliente: string; total: number; trocas: number; comIccid: number; semIccid: number; } @Component({ standalone: true, imports: [CommonModule, FormsModule, HttpClientModule], templateUrl: './mureg.html', styleUrls: ['./mureg.scss'] }) export class Mureg implements AfterViewInit { toastMessage = ''; loading = false; @ViewChild('successToast', { static: false }) successToast!: ElementRef; constructor( @Inject(PLATFORM_ID) private platformId: object, private http: HttpClient, private cdr: ChangeDetectorRef ) {} private readonly apiBase = 'https://localhost:7205/api/mureg'; // ====== DATA ====== clientGroups: ClientGroup[] = []; pagedClientGroups: ClientGroup[] = []; expandedGroup: string | null = null; groupRows: MuregRow[] = []; private rowsByClient = new Map(); // KPIs groupLoadedRecords = 0; groupTotalTrocas = 0; groupTotalIccids = 0; // ====== FILTERS & PAGINATION ====== searchTerm = ''; private searchTimer: any = null; page = 1; pageSize = 10; total = 0; // ====== EDIT MODAL ====== editOpen = false; editSaving = false; editModel: any = null; // ====== CREATE MODAL ====== createOpen = false; createSaving = false; createModel: any = { cliente: '', item: '', linhaAntiga: '', linhaNova: '', iccid: '', dataDaMureg: '' }; async ngAfterViewInit() { if (!isPlatformBrowser(this.platformId)) return; this.initAnimations(); setTimeout(() => { this.refresh(); }); } private initAnimations() { document.documentElement.classList.add('js-animate'); setTimeout(() => { const items = document.querySelectorAll('[data-animate]'); items.forEach((el) => el.classList.add('is-visible')); }, 100); } refresh() { this.page = 1; this.loadForGroups(); } onSearch() { if (this.searchTimer) clearTimeout(this.searchTimer); this.searchTimer = setTimeout(() => { this.page = 1; this.expandedGroup = null; this.groupRows = []; this.loadForGroups(); }, 300); } clearSearch() { this.searchTerm = ''; this.page = 1; this.expandedGroup = null; this.groupRows = []; this.loadForGroups(); } onPageSizeChange() { this.page = 1; this.applyPagination(); } goToPage(p: number) { this.page = Math.max(1, Math.min(this.totalPages, p)); this.applyPagination(); } get totalPages() { return Math.ceil((this.total || 0) / this.pageSize) || 1; } get pageNumbers() { 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() { return this.total === 0 ? 0 : (this.page - 1) * this.pageSize + 1; } get pageEnd() { if (this.total === 0) return 0; return Math.min(this.page * this.pageSize, this.total); } trackById(_: number, row: MuregRow) { return row.id; } // ======================================================================= // LOAD LOGIC // ======================================================================= private loadForGroups() { this.loading = true; const MAX_FETCH = 5000; let params = new HttpParams() .set('page', '1') .set('pageSize', String(MAX_FETCH)) .set('search', (this.searchTerm ?? '').trim()) .set('sortBy', 'cliente') .set('sortDir', 'asc'); this.http.get | any[]>(this.apiBase, { params }).subscribe({ next: (res: any) => { const items = Array.isArray(res) ? res : (res.items ?? []); const normalized = (items ?? []).map((x: any, idx: number) => this.normalizeRow(x, idx)); this.buildGroups(normalized); this.applyPagination(); this.loading = false; this.cdr.detectChanges(); }, error: async () => { this.loading = false; await this.showToast('Erro ao carregar MUREG.'); } }); } private buildGroups(all: MuregRow[]) { this.rowsByClient.clear(); const safeClient = (c: any) => (String(c ?? '').trim() || 'SEM CLIENTE'); for (const r of all) { const key = safeClient(r.cliente); r.cliente = key; const arr = this.rowsByClient.get(key) ?? []; arr.push(r); this.rowsByClient.set(key, arr); } const groups: ClientGroup[] = []; let trocasTotal = 0; let iccidsTotal = 0; this.rowsByClient.forEach((arr, cliente) => { const total = arr.length; const trocas = arr.filter(x => this.isTroca(x)).length; const comIccid = arr.filter(x => String(x.iccid ?? '').trim() !== '').length; const semIccid = total - comIccid; trocasTotal += trocas; iccidsTotal += comIccid; groups.push({ cliente, total, trocas, comIccid, semIccid }); }); groups.sort((a, b) => a.cliente.localeCompare(b.cliente, 'pt-BR', { sensitivity: 'base' })); this.clientGroups = groups; this.total = groups.length; this.groupLoadedRecords = all.length; this.groupTotalTrocas = trocasTotal; this.groupTotalIccids = iccidsTotal; } private applyPagination() { const start = (this.page - 1) * this.pageSize; const end = start + this.pageSize; this.pagedClientGroups = this.clientGroups.slice(start, end); if (this.expandedGroup && !this.pagedClientGroups.some(g => g.cliente === this.expandedGroup)) { this.expandedGroup = null; this.groupRows = []; } } toggleGroup(cliente: string) { if (this.expandedGroup === cliente) { this.expandedGroup = null; this.groupRows = []; return; } this.expandedGroup = cliente; const rows = this.rowsByClient.get(cliente) ?? []; this.groupRows = [...rows].sort((a, b) => { const ai = parseInt(String(a.item ?? '0'), 10); const bi = parseInt(String(b.item ?? '0'), 10); if (Number.isFinite(ai) && Number.isFinite(bi) && ai !== bi) return ai - bi; return String(a.linhaNova ?? '').localeCompare(String(b.linhaNova ?? ''), 'pt-BR', { sensitivity: 'base' }); }); } isTroca(r: MuregRow): boolean { const a = String(r.linhaAntiga ?? '').trim(); const b = String(r.linhaNova ?? '').trim(); if (!a || !b) return false; return a !== b; } private normalizeRow(x: any, idx: number): MuregRow { const pick = (obj: any, keys: string[]): any => { for (const k of keys) { if (obj && obj[k] !== undefined && obj[k] !== null && String(obj[k]).trim() !== '') return obj[k]; } return ''; }; const item = pick(x, ['item', 'ITEM', 'ITÉM']); const linhaAntiga = pick(x, ['linhaAntiga', 'linha_antiga', 'LINHA ANTIGA']); const linhaNova = pick(x, ['linhaNova', 'linha_nova', 'LINHA NOVA']); const iccid = pick(x, ['iccid', 'ICCID']); const dataDaMureg = pick(x, ['dataDaMureg', 'data_da_mureg', 'DATA DA MUREG']); const cliente = pick(x, ['cliente', 'CLIENTE']); const id = String(pick(x, ['id', 'ID']) || `${idx}-${item}-${linhaNova}-${iccid}`); return { id, item: String(item ?? ''), linhaAntiga: String(linhaAntiga ?? ''), linhaNova: String(linhaNova ?? ''), iccid: String(iccid ?? ''), dataDaMureg: String(dataDaMureg ?? ''), cliente: String(cliente ?? ''), raw: x }; } // ====== MODAL EDIÇÃO ====== // 1. Abrir modal onEditar(r: MuregRow) { this.editOpen = true; this.editSaving = false; this.editModel = { id: r.id, item: r.item, linhaAntiga: r.linhaAntiga, linhaNova: r.linhaNova, iccid: r.iccid, cliente: r.cliente, dataDaMureg: this.isoToDateInput(r.dataDaMureg) }; } // 2. Fechar modal closeEdit() { this.editOpen = false; this.editModel = null; this.editSaving = false; } // 3. Salvar (PUT) saveEdit() { if(!this.editModel || !this.editModel.id) return; this.editSaving = true; const payload = { ...this.editModel, dataDaMureg: this.dateInputToIso(this.editModel.dataDaMureg) }; this.http.put(`${this.apiBase}/${this.editModel.id}`, payload).subscribe({ next: async () => { this.editSaving = false; await this.showToast('Registro atualizado com sucesso!'); this.closeEdit(); const currentGroup = this.expandedGroup; this.loadForGroups(); if(currentGroup) setTimeout(() => this.expandedGroup = currentGroup, 400); }, error: async () => { this.editSaving = false; await this.showToast('Erro ao salvar edição.'); } }); } // ====== MODAL CRIAÇÃO ====== onCreate() { this.createOpen = true; this.createSaving = false; this.createModel = { cliente: '', item: '', linhaAntiga: '', linhaNova: '', iccid: '', dataDaMureg: '' }; } closeCreate() { this.createOpen = false; } saveCreate() { if(!this.createModel.cliente || !this.createModel.linhaNova) { this.showToast('Preencha Cliente e Linha Nova.'); return; } this.createSaving = true; const payload = { ...this.createModel, dataDaMureg: this.dateInputToIso(this.createModel.dataDaMureg) }; this.http.post(this.apiBase, payload).subscribe({ next: async () => { this.createSaving = false; await this.showToast('Mureg criada com sucesso!'); this.closeCreate(); this.loadForGroups(); }, error: async () => { this.createSaving = false; await this.showToast('Erro ao criar Mureg.'); } }); } // Helpers de Data private isoToDateInput(iso: string | null | undefined): string { if(!iso) return ''; const dt = new Date(iso); if(Number.isNaN(dt.getTime())) return ''; return dt.toISOString().slice(0,10); } private dateInputToIso(val: string | null | undefined): string | null { if(!val) return null; const dt = new Date(val); if(Number.isNaN(dt.getTime())) return null; return dt.toISOString(); } displayValue(key: MuregKey, v: any): string { if (v === null || v === undefined || String(v).trim() === '') return '-'; if (key === 'dataDaMureg') { const s = String(v).trim(); const d = new Date(s); if (!Number.isNaN(d.getTime())) { return new Intl.DateTimeFormat('pt-BR').format(d); } return s; } return String(v); } 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); } } }