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

433 lines
12 KiB
TypeScript

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<T> {
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<string, MuregRow[]>();
// 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<HTMLElement>('[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<ApiPagedResult<any> | 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);
}
}
}