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

196 lines
5.0 KiB
TypeScript

import { CommonModule, isPlatformBrowser } from '@angular/common';
import { Component, ElementRef, Inject, PLATFORM_ID, ViewChild, AfterViewInit, OnInit } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { firstValueFrom } from 'rxjs';
import Chart from 'chart.js/auto';
import {
ParcelamentoService,
ParcelamentoKpis,
ParcelamentoMonthlyPoint,
ParcelamentoTopLine
} from '../../services/parcelamento.service';
@Component({
selector: 'app-parcelamento',
standalone: true,
imports: [CommonModule, FormsModule, HttpClientModule],
templateUrl: './parcelamento.html',
styleUrl: './parcelamento.scss'
})
export class Parcelamento implements OnInit, AfterViewInit {
@ViewChild('monthlyCanvas') monthlyCanvas!: ElementRef<HTMLCanvasElement>;
@ViewChild('topLinesCanvas') topLinesCanvas!: ElementRef<HTMLCanvasElement>;
private monthlyChart?: Chart;
private topLinesChart?: Chart;
loading = false;
clients: string[] = [];
selectedClient: string = '';
lineSearch: string = '';
kpis: ParcelamentoKpis = {
linhas: 0,
clientes: 0,
totalValorCheio: 0,
totalDesconto: 0,
totalComDesconto: 0,
meses: 0
};
monthlySeries: ParcelamentoMonthlyPoint[] = [];
topLines: ParcelamentoTopLine[] = [];
constructor(
private parcelamentoService: ParcelamentoService,
@Inject(PLATFORM_ID) private platformId: Object
) {}
async ngOnInit() {
await this.loadClients();
await this.refreshAll();
}
ngAfterViewInit() {
if (isPlatformBrowser(this.platformId)) {
this.renderCharts();
}
}
private buildOpts() {
const cliente = this.selectedClient?.trim() || undefined;
const linha = this.onlyDigits(this.lineSearch) || undefined;
return { cliente, linha };
}
async loadClients() {
try {
this.clients = await firstValueFrom(this.parcelamentoService.getClients());
} catch {
this.clients = [];
}
}
async refreshAll() {
this.loading = true;
try {
const opts = this.buildOpts();
this.kpis = await firstValueFrom(this.parcelamentoService.getKpis(opts));
this.monthlySeries = await firstValueFrom(this.parcelamentoService.getMonthlySeries(opts));
this.topLines = await firstValueFrom(
this.parcelamentoService.getTopLines({ cliente: opts.cliente, take: 10 })
);
this.renderCharts();
} finally {
this.loading = false;
}
}
onClearFilters() {
this.selectedClient = '';
this.lineSearch = '';
this.refreshAll();
}
onApplyFilters() {
this.refreshAll();
}
private renderCharts() {
if (!isPlatformBrowser(this.platformId)) return;
if (!this.monthlyCanvas || !this.topLinesCanvas) return;
this.renderMonthlyChart();
this.renderTopLinesChart();
}
private renderMonthlyChart() {
const labels = this.monthlySeries.map(x => x.label);
const values = this.monthlySeries.map(x => x.total ?? 0);
if (this.monthlyChart) this.monthlyChart.destroy();
this.monthlyChart = new Chart(this.monthlyCanvas.nativeElement.getContext('2d')!, {
type: 'bar',
data: {
labels,
datasets: [{ label: 'Valor por mês (R$)', data: values }]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: true },
tooltip: {
callbacks: {
label: (ctx) => {
const y = (ctx.parsed as any)?.y;
return ` ${this.money(typeof y === 'number' ? y : 0)}`;
}
}
}
},
scales: {
y: {
ticks: {
callback: (v: any) => this.money(Number(v) || 0)
}
}
}
}
});
}
private renderTopLinesChart() {
const labels = this.topLines.map(x => (x.linha ?? '').toString());
const values = this.topLines.map(x => x.total ?? 0);
if (this.topLinesChart) this.topLinesChart.destroy();
this.topLinesChart = new Chart(this.topLinesCanvas.nativeElement.getContext('2d')!, {
type: 'bar',
data: {
labels,
datasets: [{ label: 'Top 10 linhas (Total R$)', data: values }]
},
options: {
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: true },
tooltip: {
callbacks: {
label: (ctx) => {
const x = (ctx.parsed as any)?.x;
return ` ${this.money(typeof x === 'number' ? x : 0)}`;
}
}
}
},
scales: {
x: {
ticks: {
callback: (v: any) => this.money(Number(v) || 0)
}
}
}
}
});
}
money(v: number) {
return new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' }).format(v ?? 0);
}
private onlyDigits(s: string) {
return (s ?? '').replace(/\D/g, '');
}
}