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(); 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(); 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); } }