804 lines
23 KiB
TypeScript
804 lines
23 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, HttpParams } from '@angular/common/http';
|
|
import { LinesService } from '../../services/lines.service';
|
|
import { CustomSelectComponent } from '../../components/custom-select/custom-select';
|
|
import { environment } from '../../../environments/environment';
|
|
import { confirmDeletionWithTyping } from '../../utils/destructive-confirmation';
|
|
|
|
type MuregKey = 'item' | 'linhaAntiga' | 'linhaNova' | 'iccid' | 'dataDaMureg' | 'cliente';
|
|
|
|
interface ApiPagedResult<T> {
|
|
page?: number;
|
|
pageSize?: number;
|
|
total?: number;
|
|
items?: T[];
|
|
}
|
|
|
|
interface ClientGroup {
|
|
cliente: string;
|
|
total: number;
|
|
trocas: number;
|
|
comIccid: number;
|
|
semIccid: number;
|
|
}
|
|
|
|
interface MuregRow {
|
|
id: string;
|
|
item: string;
|
|
linhaAntiga: string;
|
|
linhaNova: string;
|
|
iccid: string;
|
|
dataDaMureg: string;
|
|
cliente: string;
|
|
mobileLineId: string;
|
|
raw: any;
|
|
}
|
|
|
|
/** ✅ AGORA COM item/usuario/chip (igual Troca de Número) */
|
|
interface LineOptionDto {
|
|
id: string;
|
|
item: number;
|
|
linha: string | null;
|
|
chip: string | null; // => ICCID
|
|
usuario: string | null;
|
|
cliente?: string | null;
|
|
skil?: string | null;
|
|
label?: string;
|
|
}
|
|
|
|
interface MuregDetailDto {
|
|
id: string;
|
|
item: number;
|
|
linhaAntiga: string | null;
|
|
linhaNova: string | null;
|
|
iccid: string | null;
|
|
dataDaMureg: string | null;
|
|
mobileLineId: string;
|
|
|
|
cliente: string | null;
|
|
usuario: string | null;
|
|
skil: string | null;
|
|
|
|
linhaAtualNaGeral: string | null;
|
|
chipNaGeral: string | null;
|
|
contaNaGeral: string | null;
|
|
statusNaGeral: string | null;
|
|
}
|
|
|
|
@Component({
|
|
standalone: true,
|
|
imports: [CommonModule, FormsModule, CustomSelectComponent],
|
|
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 linesService: LinesService
|
|
) {}
|
|
|
|
private readonly apiBase = (() => {
|
|
const raw = (environment.apiUrl || '').replace(/\/+$/, '');
|
|
const apiBase = raw.toLowerCase().endsWith('/api') ? raw : `${raw}/api`;
|
|
return `${apiBase}/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;
|
|
pageSizeOptions = [10, 20, 50, 100];
|
|
total = 0;
|
|
|
|
// ====== OPTIONS (GERAL) ======
|
|
clientOptions: string[] = [];
|
|
|
|
// create options
|
|
lineOptionsCreate: LineOptionDto[] = [];
|
|
createClientsLoading = false;
|
|
createLinesLoading = false;
|
|
|
|
// edit options
|
|
lineOptionsEdit: LineOptionDto[] = [];
|
|
editClientsLoading = false;
|
|
editLinesLoading = false;
|
|
|
|
// ====== EDIT MODAL ======
|
|
editOpen = false;
|
|
editSaving = false;
|
|
editModel: any = null;
|
|
|
|
// ====== DETAIL MODAL ======
|
|
detailOpen = false;
|
|
detailLoading = false;
|
|
detailData: MuregDetailDto | null = null;
|
|
|
|
// ====== DELETE MODAL ======
|
|
deleteOpen = false;
|
|
deleteSaving = false;
|
|
deleteTarget: MuregRow | null = null;
|
|
|
|
// ====== CREATE MODAL ======
|
|
createOpen = false;
|
|
createSaving = false;
|
|
createModel: any = {
|
|
selectedClient: '',
|
|
mobileLineId: '',
|
|
item: '',
|
|
linhaAntiga: '',
|
|
linhaNova: '',
|
|
iccid: '',
|
|
dataDaMureg: '',
|
|
clienteInfo: ''
|
|
};
|
|
|
|
async ngAfterViewInit() {
|
|
if (!isPlatformBrowser(this.platformId)) return;
|
|
this.initAnimations();
|
|
setTimeout(() => {
|
|
this.preloadClients(); // ✅ já deixa o select pronto
|
|
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 (lista e grupos)
|
|
// =======================================================================
|
|
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 mobileLineId = String(pick(x, ['mobileLineId', 'MobileLineId', 'mobile_line_id']) ?? '');
|
|
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 ?? ''),
|
|
mobileLineId,
|
|
raw: x
|
|
};
|
|
}
|
|
|
|
// =======================================================================
|
|
// CLIENTS / LINES OPTIONS (GERAL)
|
|
// =======================================================================
|
|
private preloadClients() {
|
|
if (this.clientOptions.length > 0) return;
|
|
|
|
this.createClientsLoading = true;
|
|
this.editClientsLoading = true;
|
|
|
|
this.linesService.getClients().subscribe({
|
|
next: (list) => {
|
|
this.clientOptions = (list ?? []).filter(x => !!String(x ?? '').trim());
|
|
this.createClientsLoading = false;
|
|
this.editClientsLoading = false;
|
|
this.cdr.detectChanges();
|
|
},
|
|
error: async () => {
|
|
this.createClientsLoading = false;
|
|
this.editClientsLoading = false;
|
|
await this.showToast('Erro ao carregar clientes da GERAL.');
|
|
}
|
|
});
|
|
}
|
|
|
|
private loadLinesForClient(cliente: string, target: 'create' | 'edit') {
|
|
const c = (cliente ?? '').trim();
|
|
|
|
if (!c) {
|
|
if (target === 'create') this.lineOptionsCreate = [];
|
|
else this.lineOptionsEdit = [];
|
|
return;
|
|
}
|
|
|
|
if (target === 'create') {
|
|
this.createLinesLoading = true;
|
|
this.lineOptionsCreate = [];
|
|
} else {
|
|
this.editLinesLoading = true;
|
|
this.lineOptionsEdit = [];
|
|
}
|
|
|
|
// ✅ aqui assumimos que o getLinesByClient retorna (id,item,linha,chip,usuario...)
|
|
this.linesService.getLinesByClient(c).subscribe({
|
|
next: (items: any[]) => {
|
|
const mapped: LineOptionDto[] = (items ?? [])
|
|
.filter(x => !!String(x?.id ?? '').trim())
|
|
.map(x => ({
|
|
id: String(x.id),
|
|
item: Number(x.item ?? 0),
|
|
linha: x.linha ?? null,
|
|
chip: x.chip ?? null,
|
|
usuario: x.usuario ?? null,
|
|
cliente: x.cliente ?? null,
|
|
skil: x.skil ?? null,
|
|
label: `${x.item ?? ''} • ${x.linha ?? '-'} • ${x.usuario ?? 'SEM USUÁRIO'}`
|
|
}))
|
|
.filter(x => !!String(x.linha ?? '').trim());
|
|
|
|
if (target === 'create') {
|
|
this.lineOptionsCreate = mapped;
|
|
this.createLinesLoading = false;
|
|
} else {
|
|
this.lineOptionsEdit = mapped;
|
|
this.editLinesLoading = false;
|
|
}
|
|
|
|
this.cdr.detectChanges();
|
|
},
|
|
error: async () => {
|
|
if (target === 'create') this.createLinesLoading = false;
|
|
else this.editLinesLoading = false;
|
|
|
|
await this.showToast('Erro ao carregar linhas do cliente (GERAL).');
|
|
}
|
|
});
|
|
}
|
|
|
|
private applySelectedLineToModel(model: any, selectedId: string, options: LineOptionDto[]) {
|
|
const id = String(selectedId ?? '').trim();
|
|
const opt = options.find(x => x.id === id);
|
|
|
|
if (!opt) return;
|
|
|
|
// ✅ snapshot automático (linha antiga)
|
|
model.linhaAntiga = opt.linha ?? '';
|
|
|
|
// ✅ ICCID automático (chip do GERAL)
|
|
model.iccid = opt.chip ?? '';
|
|
|
|
// ✅ se item estiver vazio, preenche com item da GERAL (igual Troca)
|
|
if (!String(model.item ?? '').trim() && opt.item) {
|
|
model.item = String(opt.item);
|
|
}
|
|
|
|
model.clienteInfo = `Vínculo (GERAL): ${model.selectedClient} • ${opt.item} • ${opt.linha ?? '-'} • ${opt.usuario ?? 'SEM USUÁRIO'}`;
|
|
}
|
|
|
|
// =======================================================================
|
|
// CREATE MODAL
|
|
// =======================================================================
|
|
onCreate() {
|
|
this.preloadClients();
|
|
|
|
this.createOpen = true;
|
|
this.createSaving = false;
|
|
|
|
this.createModel = {
|
|
selectedClient: '',
|
|
mobileLineId: '',
|
|
item: '',
|
|
linhaAntiga: '',
|
|
linhaNova: '',
|
|
iccid: '',
|
|
dataDaMureg: '',
|
|
clienteInfo: ''
|
|
};
|
|
|
|
this.lineOptionsCreate = [];
|
|
}
|
|
|
|
closeCreate() {
|
|
this.createOpen = false;
|
|
}
|
|
|
|
onCreateClientChange() {
|
|
const c = (this.createModel.selectedClient ?? '').trim();
|
|
|
|
this.createModel.mobileLineId = '';
|
|
this.createModel.linhaAntiga = '';
|
|
this.createModel.iccid = '';
|
|
this.createModel.clienteInfo = c ? `Cliente selecionado: ${c}` : '';
|
|
|
|
this.loadLinesForClient(c, 'create');
|
|
}
|
|
|
|
onCreateLineChange() {
|
|
this.applySelectedLineToModel(this.createModel, this.createModel.mobileLineId, this.lineOptionsCreate);
|
|
}
|
|
|
|
saveCreate() {
|
|
const mobileLineId = String(this.createModel.mobileLineId ?? '').trim();
|
|
const linhaNova = String(this.createModel.linhaNova ?? '').trim();
|
|
|
|
if (!mobileLineId || !linhaNova) {
|
|
this.showToast('Selecione Cliente + Linha Antiga (GERAL) e preencha Linha Nova.');
|
|
return;
|
|
}
|
|
|
|
this.createSaving = true;
|
|
|
|
const payload: any = {
|
|
item: this.toIntOrZero(this.createModel.item),
|
|
mobileLineId,
|
|
linhaAntiga: (this.createModel.linhaAntiga ?? '') || null,
|
|
linhaNova: (this.createModel.linhaNova ?? '') || null,
|
|
iccid: (this.createModel.iccid ?? '') || null,
|
|
dataDaMureg: this.dateInputToIso(this.createModel.dataDaMureg)
|
|
};
|
|
|
|
if (!payload.item || payload.item <= 0) delete payload.item;
|
|
|
|
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 (err) => {
|
|
this.createSaving = false;
|
|
const msg = this.extractApiMessage(err) ?? 'Erro ao criar Mureg.';
|
|
await this.showToast(msg);
|
|
}
|
|
});
|
|
}
|
|
|
|
// =======================================================================
|
|
// EDIT MODAL
|
|
// =======================================================================
|
|
onEditar(r: MuregRow) {
|
|
this.preloadClients();
|
|
|
|
this.editOpen = true;
|
|
this.editSaving = false;
|
|
this.editModel = null;
|
|
this.lineOptionsEdit = [];
|
|
|
|
this.http.get<MuregDetailDto>(`${this.apiBase}/${r.id}`).subscribe({
|
|
next: (d) => {
|
|
const selectedClient = String(d?.cliente ?? '').trim();
|
|
|
|
this.editModel = {
|
|
id: d.id,
|
|
item: String(d.item ?? ''),
|
|
dataDaMureg: this.isoToDateInput(d.dataDaMureg),
|
|
|
|
// snapshot atual
|
|
linhaAntiga: String(d.linhaAntiga ?? d.linhaAtualNaGeral ?? ''),
|
|
linhaNova: String(d.linhaNova ?? ''),
|
|
|
|
// ✅ se não tiver iccid salvo, usa chip da geral
|
|
iccid: String(d.iccid ?? d.chipNaGeral ?? ''),
|
|
|
|
mobileLineId: String(d.mobileLineId ?? ''),
|
|
selectedClient,
|
|
|
|
clienteInfo: selectedClient
|
|
? `GERAL: ${selectedClient} • Linha atual: ${d.linhaAtualNaGeral ?? '-'} • Usuário: ${d.usuario ?? '-'} • Chip: ${d.chipNaGeral ?? '-'}`
|
|
: ''
|
|
};
|
|
|
|
if (selectedClient) {
|
|
this.loadLinesForClient(selectedClient, 'edit');
|
|
}
|
|
|
|
this.cdr.detectChanges();
|
|
},
|
|
error: async () => {
|
|
this.editOpen = false;
|
|
await this.showToast('Erro ao abrir detalhes do registro.');
|
|
}
|
|
});
|
|
}
|
|
|
|
closeEdit() {
|
|
this.editOpen = false;
|
|
this.editModel = null;
|
|
this.editSaving = false;
|
|
}
|
|
|
|
onEditClientChange() {
|
|
const c = (this.editModel?.selectedClient ?? '').trim();
|
|
|
|
this.editModel.mobileLineId = '';
|
|
this.editModel.linhaAntiga = '';
|
|
this.editModel.iccid = '';
|
|
this.editModel.clienteInfo = c ? `Cliente selecionado: ${c}` : '';
|
|
|
|
this.loadLinesForClient(c, 'edit');
|
|
}
|
|
|
|
onEditLineChange() {
|
|
if (!this.editModel) return;
|
|
this.applySelectedLineToModel(this.editModel, this.editModel.mobileLineId, this.lineOptionsEdit);
|
|
}
|
|
|
|
saveEdit() {
|
|
if (!this.editModel || !this.editModel.id) return;
|
|
|
|
const mobileLineId = String(this.editModel.mobileLineId ?? '').trim();
|
|
if (!mobileLineId) {
|
|
this.showToast('Selecione Cliente e Linha Antiga (GERAL).');
|
|
return;
|
|
}
|
|
|
|
this.editSaving = true;
|
|
|
|
const payload: any = {
|
|
item: this.toIntOrNull(this.editModel.item),
|
|
mobileLineId,
|
|
linhaAntiga: (this.editModel.linhaAntiga ?? '') || null,
|
|
linhaNova: (this.editModel.linhaNova ?? '') || null,
|
|
iccid: (this.editModel.iccid ?? '') || null,
|
|
dataDaMureg: this.dateInputToIso(this.editModel.dataDaMureg)
|
|
};
|
|
|
|
if (payload.item == null) delete payload.item;
|
|
|
|
this.http.put(`${this.apiBase}/${this.editModel.id}`, payload).subscribe({
|
|
next: async () => {
|
|
this.editSaving = false;
|
|
await this.showToast('Registro atualizado com sucesso!');
|
|
const currentGroup = this.expandedGroup;
|
|
this.closeEdit();
|
|
this.loadForGroups();
|
|
|
|
if (currentGroup) {
|
|
setTimeout(() => {
|
|
this.expandedGroup = currentGroup;
|
|
this.toggleGroup(currentGroup);
|
|
}, 400);
|
|
}
|
|
},
|
|
error: async (err) => {
|
|
this.editSaving = false;
|
|
const msg = this.extractApiMessage(err) ?? 'Erro ao salvar edição.';
|
|
await this.showToast(msg);
|
|
}
|
|
});
|
|
}
|
|
|
|
// =======================================================================
|
|
// DETAIL MODAL
|
|
// =======================================================================
|
|
onView(row: MuregRow) {
|
|
this.detailOpen = true;
|
|
this.detailLoading = true;
|
|
this.detailData = null;
|
|
|
|
this.http.get<MuregDetailDto>(`${this.apiBase}/${row.id}`).subscribe({
|
|
next: (data) => {
|
|
this.detailData = data;
|
|
this.detailLoading = false;
|
|
},
|
|
error: async () => {
|
|
this.detailLoading = false;
|
|
await this.showToast('Erro ao carregar detalhes da Mureg.');
|
|
}
|
|
});
|
|
}
|
|
|
|
closeDetail() {
|
|
this.detailOpen = false;
|
|
this.detailLoading = false;
|
|
this.detailData = null;
|
|
}
|
|
|
|
// =======================================================================
|
|
// DELETE MODAL
|
|
// =======================================================================
|
|
onDelete(row: MuregRow) {
|
|
this.deleteTarget = row;
|
|
this.deleteOpen = true;
|
|
this.deleteSaving = false;
|
|
}
|
|
|
|
closeDelete() {
|
|
this.deleteOpen = false;
|
|
this.deleteTarget = null;
|
|
this.deleteSaving = false;
|
|
}
|
|
|
|
async confirmDelete() {
|
|
if (!this.deleteTarget?.id) return;
|
|
if (!(await confirmDeletionWithTyping('esta Mureg'))) return;
|
|
|
|
this.deleteSaving = true;
|
|
const targetId = this.deleteTarget.id;
|
|
const currentGroup = this.expandedGroup;
|
|
|
|
this.http.delete(`${this.apiBase}/${targetId}`).subscribe({
|
|
next: async () => {
|
|
this.deleteSaving = false;
|
|
await this.showToast('Mureg excluída com sucesso!');
|
|
this.closeDelete();
|
|
this.loadForGroups();
|
|
|
|
if (currentGroup) {
|
|
setTimeout(() => {
|
|
this.expandedGroup = currentGroup;
|
|
this.toggleGroup(currentGroup);
|
|
}, 400);
|
|
}
|
|
},
|
|
error: async (err) => {
|
|
this.deleteSaving = false;
|
|
const msg = this.extractApiMessage(err) ?? 'Erro ao excluir Mureg.';
|
|
await this.showToast(msg);
|
|
}
|
|
});
|
|
}
|
|
|
|
// =======================================================================
|
|
// Helpers
|
|
// =======================================================================
|
|
private toIntOrZero(val: any): number {
|
|
const n = parseInt(String(val ?? '').trim(), 10);
|
|
return Number.isFinite(n) ? n : 0;
|
|
}
|
|
|
|
private toIntOrNull(val: any): number | null {
|
|
const s = String(val ?? '').trim();
|
|
if (!s) return null;
|
|
const n = parseInt(s, 10);
|
|
return Number.isFinite(n) ? n : null;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
private extractApiMessage(err: any): string | null {
|
|
try {
|
|
const m1 = err?.error?.message;
|
|
if (m1) return String(m1);
|
|
const m2 = err?.error?.title;
|
|
if (m2) return String(m2);
|
|
return null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|