import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { CustomSelectComponent } from '../../../../components/custom-select/custom-select'; export type MonthOption = { value: number; label: string }; export type ParcelamentoCreateModel = { anoRef: number | null; linha: string; cliente: string; item: number | null; qtParcelas: string; parcelaAtual: number | null; totalParcelas: number | null; valorCheio: string; desconto: string; valorComDesconto: string; competenciaAno: number | null; competenciaMes: number | null; monthValues: Array<{ competencia: string; valor: string }>; }; type PreviewRow = { competencia: string; label: string; parcela: number; valor: string; }; @Component({ selector: 'app-parcelamento-create-modal', standalone: true, imports: [CommonModule, FormsModule, CustomSelectComponent], templateUrl: './parcelamento-create-modal.html', styleUrls: ['./parcelamento-create-modal.scss'], }) export class ParcelamentoCreateModalComponent implements OnChanges { @Input() open = false; @Input() monthOptions: MonthOption[] = []; @Input() model!: ParcelamentoCreateModel; @Input() title = 'Novo Parcelamento'; @Input() submitLabel = 'Salvar'; @Input() loading = false; @Input() errorMessage = ''; @Output() close = new EventEmitter(); @Output() save = new EventEmitter(); touched = false; previewRows: PreviewRow[] = []; ngOnChanges(changes: SimpleChanges): void { if (changes['model'] && this.model) { this.syncMonthValues(); return; } if (changes['open'] && this.model) { this.rebuildPreviewRows(); } } onValueChange(): void { const cheio = this.toNumber(this.model.valorCheio); const desconto = this.toNumber(this.model.desconto); if (cheio === null) { this.model.valorComDesconto = ''; this.syncMonthValues(); return; } const calc = Math.max(0, cheio - (desconto ?? 0)); this.model.valorComDesconto = this.formatInput(calc); this.syncMonthValues(); } onCompetenciaChange(): void { this.syncMonthValues(); } onValorComDescontoChange(): void { this.syncMonthValues(); } onParcelaChange(): void { this.syncQtParcelas(); this.syncMonthValues(); } onQtParcelasChange(): void { const parsed = this.parseQtParcelas(this.model.qtParcelas); if (parsed) { this.model.parcelaAtual = parsed.atual; this.model.totalParcelas = parsed.total; } this.syncMonthValues(); } get competenciaFinalLabel(): string { if (this.model.monthValues?.length) { const last = this.model.monthValues[this.model.monthValues.length - 1]; return this.formatCompetenciaLabel(last.competencia); } const total = this.model.totalParcelas ?? 0; const ano = this.model.competenciaAno ?? 0; const mes = this.model.competenciaMes ?? 0; if (!total || !ano || !mes) return '-'; const index = (mes - 1) + (total - 1); const finalAno = ano + Math.floor(index / 12); const finalMes = (index % 12) + 1; return `${String(finalMes).padStart(2, '0')}/${finalAno}`; } onPreviewValueChange(competencia: string, value: string): void { const list = this.model.monthValues ?? []; const item = list.find((entry) => entry.competencia === competencia); if (item) item.valor = value ?? ''; const row = this.previewRows.find((entry) => entry.competencia === competencia); if (row) row.valor = value ?? ''; } trackByPreview(_: number, row: PreviewRow): string { return row.competencia; } get isValid(): boolean { return !!( this.model.anoRef && this.model.item && this.model.linha?.trim() && this.model.cliente?.trim() && this.model.totalParcelas && this.model.totalParcelas > 0 && this.model.valorCheio && this.model.competenciaAno && this.model.competenciaMes ); } onSave(): void { this.touched = true; if (!this.isValid) return; this.save.emit(this.model); } private syncQtParcelas(): void { const atual = this.model.parcelaAtual; const total = this.model.totalParcelas; if (atual && total) { this.model.qtParcelas = `${atual}/${total}`; } } private syncMonthValues(): void { const total = this.model.totalParcelas ?? 0; const ano = this.model.competenciaAno ?? 0; const mes = this.model.competenciaMes ?? 0; if (!total || !ano || !mes) { this.model.monthValues = []; this.previewRows = []; return; } const existing = new Map(); (this.model.monthValues ?? []).forEach((m) => { if (m?.competencia) existing.set(m.competencia, m.valor ?? ''); }); const valorTotal = this.toNumber(this.model.valorComDesconto) ?? this.toNumber(this.model.valorCheio); const valorParcela = valorTotal !== null ? valorTotal / total : null; const defaultValor = valorParcela !== null ? this.formatInput(valorParcela) : ''; const list: Array<{ competencia: string; valor: string }> = []; for (let i = 0; i < total; i++) { const index = (mes - 1) + i; const y = ano + Math.floor(index / 12); const m = (index % 12) + 1; const competencia = `${y}-${String(m).padStart(2, '0')}-01`; list.push({ competencia, valor: existing.get(competencia) ?? defaultValor, }); } this.model.monthValues = list; this.rebuildPreviewRows(); } private rebuildPreviewRows(): void { const list = this.model?.monthValues ?? []; if (!list.length) { this.previewRows = []; return; } this.previewRows = list.slice(0, 36).map((item, idx) => ({ competencia: item.competencia, label: this.formatCompetenciaLabel(item.competencia), parcela: idx + 1, valor: item.valor ?? '', })); } private formatCompetenciaLabel(value: string): string { const match = value.match(/^(\d{4})-(\d{2})/); if (!match) return value || '-'; return `${match[2]}/${match[1]}`; } private parseQtParcelas(raw: string | null | undefined): { atual: number; total: number } | null { if (!raw) return null; const parts = raw.split('/'); if (parts.length < 2) return null; const atualStr = this.onlyDigits(parts[0]); const totalStr = this.onlyDigits(parts[1]); if (!atualStr || !totalStr) return null; return { atual: Number(atualStr), total: Number(totalStr) }; } private toNumber(value: any): number | null { if (value === null || value === undefined || value === '') return null; if (typeof value === 'number') return Number.isFinite(value) ? value : null; const raw = String(value).trim(); if (!raw) return null; let cleaned = raw.replace(/[^\d,.-]/g, ''); if (cleaned.includes(',') && cleaned.includes('.')) { if (cleaned.lastIndexOf(',') > cleaned.lastIndexOf('.')) { cleaned = cleaned.replace(/\./g, '').replace(',', '.'); } else { cleaned = cleaned.replace(/,/g, ''); } } else if (cleaned.includes(',')) { cleaned = cleaned.replace(/\./g, '').replace(',', '.'); } else { cleaned = cleaned.replace(/,/g, ''); } const n = Number(cleaned); return Number.isNaN(n) ? null : n; } private onlyDigits(value: string): string { let out = ''; for (const ch of value ?? '') { if (ch >= '0' && ch <= '9') out += ch; } return out; } private formatInput(value: number): string { return new Intl.NumberFormat('pt-BR', { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(value); } }