line-gestao-frontend/src/app/pages/chips-controle-recebidos/chips-controle-recebidos.ts

797 lines
24 KiB
TypeScript

import { Component, Inject, PLATFORM_ID, OnInit, OnDestroy } from '@angular/core';
import { CommonModule, isPlatformBrowser } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { HttpClient } from '@angular/common/http';
import { ChipsControleService, ChipVirgemListDto, ControleRecebidoListDto, SortDir, UpdateChipVirgemRequest, UpdateControleRecebidoRequest, CreateChipVirgemRequest, CreateControleRecebidoRequest } from '../../services/chips-controle.service';
import { CustomSelectComponent } from '../../components/custom-select/custom-select';
import { AuthService } from '../../services/auth.service';
// Interface para o Agrupamento
interface ChipGroup {
observacao: string;
total: number;
items: ChipVirgemListDto[];
}
interface ControleGroup {
conteudo: string;
total: number;
items: ControleRecebidoListDto[];
}
interface ChipVirgemCreateModel {
id: string;
item: number | null;
numeroDoChip: string | null;
observacoes: string | null;
}
type ChipsSortKey = 'item' | 'numeroDoChip' | 'observacoes';
type ControleSortKey =
| 'ano' | 'item' | 'notaFiscal' | 'chip' | 'serial' | 'conteudoDaNf' | 'numeroDaLinha'
| 'valorUnit' | 'valorDaNf' | 'dataDaNf' | 'dataDoRecebimento' | 'quantidade' | 'isResumo';
@Component({
selector: 'app-chips-controle-recebidos',
standalone: true,
imports: [CommonModule, FormsModule, CustomSelectComponent],
templateUrl: './chips-controle-recebidos.html',
styleUrls: ['./chips-controle-recebidos.scss']
})
export class ChipsControleRecebidos implements OnInit, OnDestroy {
activeTab: 'chips' | 'controle' = 'chips';
// --- Chips ---
chipsRows: ChipVirgemListDto[] = [];
chipsGroups: ChipGroup[] = [];
pagedChipsGroups: ChipGroup[] = [];
expandedGroupObservacao: string | null = null;
chipsLoading = false;
chipsSearch = '';
chipsPage = 1;
chipsPageSize = 10;
chipsTotal = 0;
chipsSortBy: ChipsSortKey = 'item';
chipsSortDir: SortDir = 'asc';
private chipsSearchTimer: any = null;
// --- Controle ---
controleRows: ControleRecebidoListDto[] = [];
controleGroups: ControleGroup[] = [];
pagedControleGroups: ControleGroup[] = [];
expandedControleConteudo: string | null = null;
controleLoading = false;
controleSearch = '';
controlePage = 1;
controlePageSize = 10;
controleTotal = 0;
controleSortBy: ControleSortKey = 'ano';
controleSortDir: SortDir = 'desc';
controleAno: number | '' = '';
controleResumo: '' | 'true' | 'false' = '';
private controleSearchTimer: any = null;
// --- Opções ---
pageSizeOptions = [10, 20, 50, 100];
anoOptions = [
{ label: 'Todos', value: '' },
{ label: '2022', value: 2022 },
{ label: '2023', value: 2023 },
{ label: '2024', value: 2024 },
{ label: '2025', value: 2025 }
];
toastOpen = false;
toastMessage = '';
toastType: 'success' | 'danger' = 'success';
private toastTimer: any = null;
chipDetailOpen = false;
chipDetailLoading = false;
chipDetailData: ChipVirgemListDto | null = null;
chipCreateOpen = false;
chipCreateSaving = false;
chipCreateModel: ChipVirgemCreateModel | null = null;
chipEditOpen = false;
chipEditSaving = false;
chipEditModel: ChipVirgemListDto | null = null;
chipEditingId: string | null = null;
chipDeleteOpen = false;
chipDeleteTarget: ChipVirgemListDto | null = null;
controleDetailOpen = false;
controleDetailLoading = false;
controleDetailData: ControleRecebidoListDto | null = null;
controleCreateOpen = false;
controleCreateSaving = false;
controleCreateModel: ControleRecebidoListDto | null = null;
controleCreateDataNf = '';
controleCreateRecebimento = '';
controleEditOpen = false;
controleEditSaving = false;
controleEditModel: ControleRecebidoListDto | null = null;
controleEditDataNf = '';
controleEditRecebimento = '';
controleEditingId: string | null = null;
controleDeleteOpen = false;
controleDeleteTarget: ControleRecebidoListDto | null = null;
isAdmin = false;
constructor(
@Inject(PLATFORM_ID) private platformId: object,
private service: ChipsControleService,
private http: HttpClient,
private authService: AuthService
) {}
ngOnInit(): void {
if (!isPlatformBrowser(this.platformId)) return;
this.isAdmin = this.authService.hasRole('admin');
this.fetchChips();
this.fetchControle();
}
ngOnDestroy(): void {
if (this.chipsSearchTimer) clearTimeout(this.chipsSearchTimer);
if (this.controleSearchTimer) clearTimeout(this.controleSearchTimer);
if (this.toastTimer) clearTimeout(this.toastTimer);
}
setTab(tab: 'chips' | 'controle') {
this.activeTab = tab;
if (tab === 'chips') {
this.expandedGroupObservacao = null;
this.applyChipsPagination();
this.closeControleDetail();
} else {
this.expandedControleConteudo = null;
this.applyControlePagination();
this.closeChipDetail();
}
}
// =====================
// Chips Virgens
// =====================
fetchChips() {
this.chipsLoading = true;
this.service.getChipsVirgens({
search: this.chipsSearch,
page: 1,
pageSize: 5000,
sortBy: this.chipsSortBy,
sortDir: this.chipsSortDir
}).subscribe({
next: (res) => {
const items = (res as any)?.items ?? [];
this.chipsRows = items.map((x: any, idx: number) => this.normalizeChip(x, idx));
this.buildChipsGroups();
this.chipsTotal = this.chipsGroups.length;
this.applyChipsPagination();
this.chipsLoading = false;
},
error: () => {
this.chipsLoading = false;
this.showToast('Erro ao carregar Chips Virgens.', 'danger');
}
});
}
private buildChipsGroups() {
const groupsMap = new Map<string, ChipVirgemListDto[]>();
this.chipsRows.forEach(row => {
const key = row.observacoes && row.observacoes.trim() !== ''
? row.observacoes.trim()
: '(Sem Observações)';
if (!groupsMap.has(key)) groupsMap.set(key, []);
groupsMap.get(key)?.push(row);
});
this.chipsGroups = [];
groupsMap.forEach((items, key) => {
this.chipsGroups.push({ observacao: key, total: items.length, items });
});
this.chipsGroups.sort((a, b) => a.observacao.localeCompare(b.observacao));
this.expandedGroupObservacao = null;
}
private applyChipsPagination() {
const start = (this.chipsPage - 1) * this.chipsPageSize;
const end = start + this.chipsPageSize;
this.pagedChipsGroups = this.chipsGroups.slice(start, end);
if (this.expandedGroupObservacao && !this.pagedChipsGroups.some(g => g.observacao === this.expandedGroupObservacao)) {
this.expandedGroupObservacao = null;
}
}
toggleGroup(obs: string) {
this.expandedGroupObservacao = this.expandedGroupObservacao === obs ? null : obs;
}
openChipDetail(row: ChipVirgemListDto) {
if (!row?.id) return;
this.chipDetailOpen = true;
this.chipDetailLoading = true;
this.chipDetailData = null;
this.service.getChipVirgemById(row.id).subscribe({
next: (data) => {
this.chipDetailData = data ?? row;
this.chipDetailLoading = false;
},
error: () => {
this.chipDetailLoading = false;
this.chipDetailData = row;
}
});
}
openChipCreate() {
if (!this.isAdmin) return;
this.chipCreateModel = {
id: '',
item: null,
numeroDoChip: '',
observacoes: ''
};
this.chipCreateOpen = true;
this.chipCreateSaving = false;
}
closeChipCreate() {
this.chipCreateOpen = false;
this.chipCreateSaving = false;
this.chipCreateModel = null;
}
saveChipCreate() {
if (!this.chipCreateModel) return;
this.chipCreateSaving = true;
const payload: CreateChipVirgemRequest = {
item: this.toNullableNumber(this.chipCreateModel.item),
numeroDoChip: this.chipCreateModel.numeroDoChip,
observacoes: this.chipCreateModel.observacoes
};
this.service.createChipVirgem(payload).subscribe({
next: () => {
this.chipCreateSaving = false;
this.closeChipCreate();
this.fetchChips();
this.showToast('Chip criado com sucesso!', 'success');
},
error: () => {
this.chipCreateSaving = false;
this.showToast('Erro ao criar chip.', 'danger');
}
});
}
openChipEdit(row: ChipVirgemListDto) {
if (!this.isAdmin) return;
this.service.getChipVirgemById(row.id).subscribe({
next: (data) => {
this.chipEditingId = data.id;
this.chipEditModel = { ...data };
this.chipEditOpen = true;
},
error: () => this.showToast('Erro ao abrir edição.', 'danger')
});
}
closeChipEdit() {
this.chipEditOpen = false;
this.chipEditSaving = false;
this.chipEditModel = null;
this.chipEditingId = null;
}
saveChipEdit() {
if (!this.chipEditModel || !this.chipEditingId) return;
this.chipEditSaving = true;
const payload: UpdateChipVirgemRequest = {
item: this.toNullableNumber(this.chipEditModel.item),
numeroDoChip: this.chipEditModel.numeroDoChip,
observacoes: this.chipEditModel.observacoes
};
this.service.updateChipVirgem(this.chipEditingId, payload).subscribe({
next: () => {
this.chipEditSaving = false;
this.closeChipEdit();
this.fetchChips();
this.showToast('Chip atualizado!', 'success');
},
error: () => {
this.chipEditSaving = false;
this.showToast('Erro ao salvar.', 'danger');
}
});
}
openChipDelete(row: ChipVirgemListDto) {
if (!this.isAdmin) return;
this.chipDeleteTarget = row;
this.chipDeleteOpen = true;
}
cancelChipDelete() {
this.chipDeleteOpen = false;
this.chipDeleteTarget = null;
}
confirmChipDelete() {
if (!this.chipDeleteTarget) return;
const id = this.chipDeleteTarget.id;
this.service.removeChipVirgem(id).subscribe({
next: () => {
this.chipDeleteOpen = false;
this.chipDeleteTarget = null;
this.fetchChips();
this.showToast('Chip removido.', 'success');
},
error: () => {
this.chipDeleteOpen = false;
this.chipDeleteTarget = null;
this.showToast('Erro ao remover.', 'danger');
}
});
}
closeChipDetail() {
this.chipDetailOpen = false;
this.chipDetailLoading = false;
this.chipDetailData = null;
}
onChipsSearch() {
if (this.chipsSearchTimer) clearTimeout(this.chipsSearchTimer);
this.chipsSearchTimer = setTimeout(() => {
this.chipsPage = 1;
this.fetchChips();
}, 300);
}
clearChipsSearch() {
this.chipsSearch = '';
this.chipsPage = 1;
this.fetchChips();
}
onChipsPageSizeChange() {
this.chipsPage = 1;
this.applyChipsPagination();
}
// =====================
// Controle Recebidos
// =====================
fetchControle() {
this.controleLoading = true;
this.service.getControleRecebidos({
search: this.controleSearch,
page: 1,
pageSize: 5000,
sortBy: this.controleSortBy,
sortDir: this.controleSortDir,
ano: this.controleAno,
isResumo: this.controleResumo
}).subscribe({
next: (res) => {
const items = (res as any)?.items ?? [];
this.controleRows = items.map((x: any, idx: number) => this.normalizeControle(x, idx));
this.buildControleGroups();
this.controleTotal = this.controleGroups.length;
this.applyControlePagination();
this.controleLoading = false;
},
error: () => {
this.controleLoading = false;
this.showToast('Erro ao carregar Controle.', 'danger');
}
});
}
onControleSearch() {
if (this.controleSearchTimer) clearTimeout(this.controleSearchTimer);
this.controleSearchTimer = setTimeout(() => {
this.controlePage = 1;
this.fetchControle();
}, 300);
}
clearControleSearch() {
this.controleSearch = '';
this.controlePage = 1;
this.fetchControle();
}
setControleSort(key: ControleSortKey) {
if (this.controleSortBy === key) {
this.controleSortDir = this.controleSortDir === 'asc' ? 'desc' : 'asc';
} else {
this.controleSortBy = key;
this.controleSortDir = 'asc';
}
this.controlePage = 1;
this.fetchControle();
}
onControlePageSizeChange() {
this.controlePage = 1;
this.applyControlePagination();
}
onControleAnoChange() {
this.controlePage = 1;
this.fetchControle();
}
setControleResumo(val: '' | 'true' | 'false') {
this.controleResumo = val;
this.controlePage = 1;
this.fetchControle();
}
private buildControleGroups() {
const groupsMap = new Map<string, ControleRecebidoListDto[]>();
this.controleRows.forEach(row => {
const key = row.conteudoDaNf && row.conteudoDaNf.trim() !== ''
? row.conteudoDaNf.trim()
: '(Sem Conteúdo)';
if (!groupsMap.has(key)) groupsMap.set(key, []);
groupsMap.get(key)?.push(row);
});
this.controleGroups = [];
groupsMap.forEach((items, key) => {
this.controleGroups.push({ conteudo: key, total: items.length, items });
});
this.controleGroups.sort((a, b) => a.conteudo.localeCompare(b.conteudo));
this.expandedControleConteudo = null;
}
private applyControlePagination() {
const start = (this.controlePage - 1) * this.controlePageSize;
const end = start + this.controlePageSize;
this.pagedControleGroups = this.controleGroups.slice(start, end);
if (this.expandedControleConteudo && !this.pagedControleGroups.some(g => g.conteudo === this.expandedControleConteudo)) {
this.expandedControleConteudo = null;
}
}
toggleControleGroup(conteudo: string) {
this.expandedControleConteudo = this.expandedControleConteudo === conteudo ? null : conteudo;
}
openControleDetail(row: ControleRecebidoListDto) {
if (!row?.id) return;
this.controleDetailOpen = true;
this.controleDetailLoading = true;
this.controleDetailData = null;
this.service.getControleRecebidoById(row.id).subscribe({
next: (data) => {
this.controleDetailData = data ?? row;
this.controleDetailLoading = false;
},
error: () => {
this.controleDetailLoading = false;
this.controleDetailData = row;
}
});
}
openControleCreate() {
if (!this.isAdmin) return;
this.controleCreateModel = {
id: '',
ano: new Date().getFullYear(),
item: null,
notaFiscal: '',
chip: '',
serial: '',
conteudoDaNf: '',
numeroDaLinha: '',
valorUnit: null,
valorDaNf: null,
dataDaNf: null,
dataDoRecebimento: null,
quantidade: null,
isResumo: false
} as ControleRecebidoListDto;
this.controleCreateDataNf = '';
this.controleCreateRecebimento = '';
this.controleCreateOpen = true;
this.controleCreateSaving = false;
}
closeControleCreate() {
this.controleCreateOpen = false;
this.controleCreateSaving = false;
this.controleCreateModel = null;
this.controleCreateDataNf = '';
this.controleCreateRecebimento = '';
}
onControleCreateValueChange() {
if (!this.controleCreateModel) return;
this.recalculateControleTotals(this.controleCreateModel);
}
onControleEditValueChange() {
if (!this.controleEditModel) return;
this.recalculateControleTotals(this.controleEditModel);
}
onControleCreateDateChange() {
if (!this.controleCreateModel) return;
if (!this.controleCreateModel.ano && this.controleCreateDataNf) {
const year = new Date(this.controleCreateDataNf).getFullYear();
if (Number.isFinite(year)) this.controleCreateModel.ano = year;
}
}
onControleEditDateChange() {
if (!this.controleEditModel) return;
if (!this.controleEditModel.ano && this.controleEditDataNf) {
const year = new Date(this.controleEditDataNf).getFullYear();
if (Number.isFinite(year)) this.controleEditModel.ano = year;
}
}
private recalculateControleTotals(model: ControleRecebidoListDto) {
const quantidade = this.toNullableNumber(model.quantidade);
const valorUnit = this.toNullableNumber(model.valorUnit);
const valorDaNf = this.toNullableNumber(model.valorDaNf);
if (quantidade != null && valorUnit != null && (valorDaNf == null || valorDaNf === 0)) {
model.valorDaNf = Number((valorUnit * quantidade).toFixed(2));
} else if (quantidade != null && valorDaNf != null && (valorUnit == null || valorUnit === 0)) {
model.valorUnit = Number((valorDaNf / quantidade).toFixed(2));
}
}
saveControleCreate() {
if (!this.controleCreateModel) return;
this.recalculateControleTotals(this.controleCreateModel);
this.controleCreateSaving = true;
const payload: CreateControleRecebidoRequest = {
ano: this.toNullableNumber(this.controleCreateModel.ano),
item: this.toNullableNumber(this.controleCreateModel.item),
notaFiscal: this.controleCreateModel.notaFiscal,
chip: this.controleCreateModel.chip,
serial: this.controleCreateModel.serial,
conteudoDaNf: this.controleCreateModel.conteudoDaNf,
numeroDaLinha: this.controleCreateModel.numeroDaLinha,
valorUnit: this.toNullableNumber(this.controleCreateModel.valorUnit),
valorDaNf: this.toNullableNumber(this.controleCreateModel.valorDaNf),
dataDaNf: this.dateInputToIso(this.controleCreateDataNf),
dataDoRecebimento: this.dateInputToIso(this.controleCreateRecebimento),
quantidade: this.toNullableNumber(this.controleCreateModel.quantidade),
isResumo: this.controleCreateModel.isResumo ?? false
};
this.service.createControleRecebido(payload).subscribe({
next: () => {
this.controleCreateSaving = false;
this.closeControleCreate();
this.fetchControle();
this.showToast('Recebimento criado com sucesso!', 'success');
},
error: () => {
this.controleCreateSaving = false;
this.showToast('Erro ao criar recebimento.', 'danger');
}
});
}
openControleEdit(row: ControleRecebidoListDto) {
if (!this.isAdmin) return;
this.service.getControleRecebidoById(row.id).subscribe({
next: (data) => {
this.controleEditingId = data.id;
this.controleEditModel = { ...data };
this.controleEditDataNf = this.toDateInput(data.dataDaNf);
this.controleEditRecebimento = this.toDateInput(data.dataDoRecebimento);
this.controleEditOpen = true;
},
error: () => this.showToast('Erro ao abrir edição.', 'danger')
});
}
closeControleEdit() {
this.controleEditOpen = false;
this.controleEditSaving = false;
this.controleEditModel = null;
this.controleEditDataNf = '';
this.controleEditRecebimento = '';
this.controleEditingId = null;
}
saveControleEdit() {
if (!this.controleEditModel || !this.controleEditingId) return;
this.recalculateControleTotals(this.controleEditModel);
this.controleEditSaving = true;
const payload: UpdateControleRecebidoRequest = {
ano: this.toNullableNumber(this.controleEditModel.ano),
item: this.toNullableNumber(this.controleEditModel.item),
notaFiscal: this.controleEditModel.notaFiscal,
chip: this.controleEditModel.chip,
serial: this.controleEditModel.serial,
conteudoDaNf: this.controleEditModel.conteudoDaNf,
numeroDaLinha: this.controleEditModel.numeroDaLinha,
valorUnit: this.toNullableNumber(this.controleEditModel.valorUnit),
valorDaNf: this.toNullableNumber(this.controleEditModel.valorDaNf),
dataDaNf: this.dateInputToIso(this.controleEditDataNf),
dataDoRecebimento: this.dateInputToIso(this.controleEditRecebimento),
quantidade: this.toNullableNumber(this.controleEditModel.quantidade),
isResumo: this.controleEditModel.isResumo ?? false
};
this.service.updateControleRecebido(this.controleEditingId, payload).subscribe({
next: () => {
this.controleEditSaving = false;
this.closeControleEdit();
this.fetchControle();
this.showToast('Registro atualizado!', 'success');
},
error: () => {
this.controleEditSaving = false;
this.showToast('Erro ao salvar.', 'danger');
}
});
}
openControleDelete(row: ControleRecebidoListDto) {
if (!this.isAdmin) return;
this.controleDeleteTarget = row;
this.controleDeleteOpen = true;
}
cancelControleDelete() {
this.controleDeleteOpen = false;
this.controleDeleteTarget = null;
}
confirmControleDelete() {
if (!this.controleDeleteTarget) return;
const id = this.controleDeleteTarget.id;
this.service.removeControleRecebido(id).subscribe({
next: () => {
this.controleDeleteOpen = false;
this.controleDeleteTarget = null;
this.fetchControle();
this.showToast('Registro removido.', 'success');
},
error: () => {
this.controleDeleteOpen = false;
this.controleDeleteTarget = null;
this.showToast('Erro ao remover.', 'danger');
}
});
}
private toDateInput(value: string | null): string {
if (!value) return '';
const d = new Date(value);
if (isNaN(d.getTime())) return '';
return d.toISOString().slice(0, 10);
}
private dateInputToIso(value: string): string | null {
if (!value) return null;
const d = new Date(`${value}T00:00:00`);
if (isNaN(d.getTime())) return null;
return d.toISOString();
}
private toNullableNumber(value: any): number | null {
if (value === undefined || value === null || value === '') return null;
const n = Number(value);
return Number.isNaN(n) ? null : n;
}
closeControleDetail() {
this.controleDetailOpen = false;
this.controleDetailLoading = false;
this.controleDetailData = null;
}
// =====================
// Paginação e Helpers
// =====================
get activePage() { return this.activeTab === 'chips' ? this.chipsPage : this.controlePage; }
get activeTotal() { return this.activeTab === 'chips' ? this.chipsTotal : this.controleTotal; }
get activePageSize() { return this.activeTab === 'chips' ? this.chipsPageSize : this.controlePageSize; }
get activeTotalPages() { return Math.max(1, Math.ceil((this.activeTotal || 0) / (this.activePageSize || 10))); }
get activePageStart() { return this.activeTotal === 0 ? 0 : (this.activePage - 1) * this.activePageSize + 1; }
get activePageEnd() { return this.activeTotal === 0 ? 0 : Math.min(this.activePage * this.activePageSize, this.activeTotal); }
get activeLoading() { return this.activeTab === 'chips' ? this.chipsLoading : this.controleLoading; } // ✅ novo
get activePageNumbers() {
const total = this.activeTotalPages;
const current = this.activePage;
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 = [];
for (let i = start; i <= end; i++) pages.push(i);
return pages;
}
goToPage(p: number) {
const target = Math.max(1, Math.min(this.activeTotalPages, p));
if (this.activeTab === 'chips') {
this.chipsPage = target;
this.applyChipsPagination();
} else {
this.controlePage = target;
this.applyControlePagination();
}
}
normalizeChip(x: any, idx: number): ChipVirgemListDto {
return {
id: String(x.id || idx),
item: Number(x.item || 0),
numeroDoChip: x.numeroDoChip || x.NumeroDoChip,
observacoes: x.observacoes || x.Observacoes
};
}
normalizeControle(x: any, idx: number): ControleRecebidoListDto {
return {
id: String(x.id || idx),
ano: x.ano,
item: x.item,
notaFiscal: x.notaFiscal,
chip: x.chip,
serial: x.serial,
conteudoDaNf: x.conteudoDaNf,
numeroDaLinha: x.numeroDaLinha,
valorUnit: x.valorUnit,
valorDaNf: x.valorDaNf,
dataDaNf: x.dataDaNf,
dataDoRecebimento: x.dataDoRecebimento,
quantidade: x.quantidade,
isResumo: x.isResumo
};
}
display(val: any) { return val ? String(val) : '-'; }
formatMoney(val: any) {
if (!val) return '-';
return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(val);
}
formatDate(val: any) { if (!val) return '-'; return new Date(val).toLocaleDateString('pt-BR'); }
isResumo(r: any) { return !!r.isResumo; }
getResumoItems(items: ControleRecebidoListDto[]) { return (items || []).filter(r => this.isResumo(r)); }
getDetalheItems(items: ControleRecebidoListDto[]) { return (items || []).filter(r => !this.isResumo(r)); }
trackById(idx: number, item: any) { return item.id; }
showToast(msg: string, type: 'success' | 'danger') {
this.toastMessage = msg;
this.toastType = type;
this.toastOpen = true;
setTimeout(() => this.toastOpen = false, 3000);
}
}