196 lines
5.0 KiB
TypeScript
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, '');
|
|
}
|
|
}
|