diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 179b507..e794487 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -11,7 +11,6 @@ import { authGuard } from './guards/auth.guard'; import { DadosUsuarios } from './pages/dados-usuarios/dados-usuarios'; import { VigenciaComponent } from './pages/vigencia/vigencia'; import { TrocaNumero } from './pages/troca-numero/troca-numero'; -import { Parcelamento } from './pages/parcelamento/parcelamento'; // ✅ NOVO: TROCA DE NÚMERO @@ -30,7 +29,5 @@ export const routes: Routes = [ // ✅ NOVO: rota da página Troca de Número { path: 'trocanumero', component: TrocaNumero, canActivate: [authGuard] }, - { path: 'parcelamento', component: Parcelamento, canActivate: [authGuard] }, - { path: '**', redirectTo: '' }, ]; diff --git a/src/app/components/header/header.html b/src/app/components/header/header.html index c3693c2..e4ca643 100644 --- a/src/app/components/header/header.html +++ b/src/app/components/header/header.html @@ -110,11 +110,6 @@ Troca de Número - - - Parcelamento - - Controle de Contratos diff --git a/src/app/components/header/header.ts b/src/app/components/header/header.ts index 2739aca..5d0c534 100644 --- a/src/app/components/header/header.ts +++ b/src/app/components/header/header.ts @@ -1,3 +1,4 @@ +// header.ts import { Component, HostListener, Inject } from '@angular/core'; import { RouterLink, Router, NavigationEnd } from '@angular/router'; import { CommonModule, isPlatformBrowser } from '@angular/common'; @@ -14,10 +15,7 @@ export class Header { isScrolled = false; isHome = true; - // ✅ menu hamburguer menuOpen = false; - - // ✅ define quando mostrar header “logado” isLoggedHeader = false; // ✅ rotas internas que usam menu lateral @@ -28,7 +26,6 @@ export class Header { '/dadosusuarios', '/vigencia', '/trocanumero', - '/parcelamento', // ✅ ADICIONADO: Parcelamento ]; constructor( @@ -38,19 +35,14 @@ export class Header { this.router.events.subscribe((event) => { if (event instanceof NavigationEnd) { const rawUrl = event.urlAfterRedirects || event.url; - - // normaliza (remove query/hash) const url = rawUrl.split('?')[0].split('#')[0]; this.isHome = (url === '/' || url === ''); - // ✅ considera "logado" se a rota começa com qualquer prefixo interno - // aceita também subrotas, ex: /parcelamento/detalhes/123 this.isLoggedHeader = this.loggedPrefixes.some((p) => url === p || url.startsWith(p + '/') ); - // ✅ ao trocar de rota, fecha o menu this.menuOpen = false; } }); diff --git a/src/app/pages/parcelamento/parcelamento.html b/src/app/pages/parcelamento/parcelamento.html deleted file mode 100644 index 75cb928..0000000 --- a/src/app/pages/parcelamento/parcelamento.html +++ /dev/null @@ -1,90 +0,0 @@ -
- - - - - - - -
-
- Total (c/ desconto) - {{ money(kpis.totalComDesconto) }} -
- -
- Total (valor cheio) - {{ money(kpis.totalValorCheio) }} -
- -
- Desconto total - {{ money(kpis.totalDesconto) }} -
- -
- Linhas / Clientes - {{ kpis.linhas }} / {{ kpis.clientes }} - {{ kpis.meses }} meses mapeados -
-
- - -
-
-
-
Valor por mês
- Soma mensal do parcelamento -
-
- -
-
- -
-
-
Top 10 linhas
- Linhas com maior soma total -
-
- -
-
-
- -
-
- Carregando... -
-
diff --git a/src/app/pages/parcelamento/parcelamento.scss b/src/app/pages/parcelamento/parcelamento.scss deleted file mode 100644 index 86834cd..0000000 --- a/src/app/pages/parcelamento/parcelamento.scss +++ /dev/null @@ -1,151 +0,0 @@ -:host { - --brand: #E33DCF; - --blue: #030FAA; - --text: #111214; - --muted: rgba(17, 18, 20, 0.65); - - --radius-xl: 22px; - --shadow-card: 0 22px 46px rgba(17, 18, 20, 0.10); - --glass-bg: rgba(255, 255, 255, 0.82); - --glass-border: 1px solid rgba(227, 61, 207, 0.16); -} - -.parcelamento-page { - position: relative; - padding: clamp(14px, 3vw, 26px); - min-height: calc(100vh - 70px); - color: var(--text); -} - -.page-blob { - position: absolute; - border-radius: 999px; - filter: blur(0.2px); - opacity: 0.20; - pointer-events: none; -} - -.blob-1 { width: 240px; height: 240px; top: 16px; left: -50px; background: var(--brand); } -.blob-2 { width: 280px; height: 280px; top: 140px; right: -80px; background: var(--blue); opacity: .14; } -.blob-3 { width: 220px; height: 220px; bottom: 30px; left: 20%; background: var(--brand); opacity: .10; } - -.glass { - background: var(--glass-bg); - border: var(--glass-border); - border-radius: var(--radius-xl); - box-shadow: var(--shadow-card); - backdrop-filter: blur(10px); -} - -.page-header { - display: grid; - gap: 14px; - margin-bottom: 14px; - - .title { - h2 { margin: 0; font-weight: 800; letter-spacing: -0.3px; } - p { margin: 2px 0 0; color: var(--muted); } - } - - .filters { - padding: 14px; - .form-label { font-weight: 700; color: rgba(17,18,20,.78); } - } -} - -.kpis { - display: grid; - gap: 12px; - grid-template-columns: repeat(4, minmax(0, 1fr)); - margin-bottom: 14px; - - @media (max-width: 992px) { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - @media (max-width: 520px) { - grid-template-columns: 1fr; - } - - .kpi-card { - padding: 14px 16px; - - .kpi-label { - display: block; - font-size: 0.9rem; - font-weight: 700; - color: rgba(17,18,20,.70); - } - - .kpi-value { - display: block; - font-size: 1.55rem; - font-weight: 900; - margin-top: 6px; - - small { - font-size: 1rem; - font-weight: 800; - color: rgba(17,18,20,.70); - } - } - - .kpi-sub { - display: block; - margin-top: 6px; - font-size: .86rem; - color: var(--muted); - } - } -} - -.charts { - display: grid; - gap: 14px; - grid-template-columns: 1.2fr 1fr; - - @media (max-width: 992px) { - grid-template-columns: 1fr; - } - - .chart-card { - padding: 14px 16px; - overflow: hidden; - - .chart-head { - display: flex; - align-items: baseline; - justify-content: space-between; - gap: 12px; - margin-bottom: 10px; - - h5 { - margin: 0; - font-weight: 900; - letter-spacing: -0.2px; - } - - .muted { - font-size: .9rem; - color: var(--muted); - white-space: nowrap; - } - } - - .chart-area { - height: 360px; - - @media (max-width: 520px) { - height: 320px; - } - } - } -} - -.loading { - margin-top: 14px; - display: flex; - gap: 10px; - align-items: center; - color: var(--muted); -} diff --git a/src/app/pages/parcelamento/parcelamento.ts b/src/app/pages/parcelamento/parcelamento.ts deleted file mode 100644 index 88c44c7..0000000 --- a/src/app/pages/parcelamento/parcelamento.ts +++ /dev/null @@ -1,195 +0,0 @@ -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; - @ViewChild('topLinesCanvas') topLinesCanvas!: ElementRef; - - 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, ''); - } -} diff --git a/src/app/services/parcelamento.service.ts b/src/app/services/parcelamento.service.ts deleted file mode 100644 index fcfb8cf..0000000 --- a/src/app/services/parcelamento.service.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Injectable } from '@angular/core'; -import { HttpClient, HttpParams } from '@angular/common/http'; -import { Observable } from 'rxjs'; -import { environment } from '../../environments/environment'; - -export interface ParcelamentoKpis { - linhas: number; - clientes: number; - totalValorCheio: number; - totalDesconto: number; - totalComDesconto: number; - meses: number; -} - -export interface ParcelamentoMonthlyPoint { - month: string; // ex: "2026-01" - label: string; // ex: "JAN/2026" - total: number; -} - -export interface ParcelamentoTopLine { - linha: string | null; - cliente: string | null; - total: number; -} - -@Injectable({ providedIn: 'root' }) -export class ParcelamentoService { - private readonly baseApi: string; - - constructor(private http: HttpClient) { - // ✅ igual ao seu VigenciaService - const raw = (environment.apiUrl || '').replace(/\/+$/, ''); - this.baseApi = raw.toLowerCase().endsWith('/api') ? raw : `${raw}/api`; - } - - // ✅ /api/parcelamento/clients - getClients(): Observable { - return this.http.get(`${this.baseApi}/parcelamento/clients`); - } - - // ✅ /api/parcelamento/kpis?cliente=...&linha=... - getKpis(opts?: { cliente?: string; linha?: string }): Observable { - let params = new HttpParams(); - if (opts?.cliente && opts.cliente.trim()) params = params.set('cliente', opts.cliente.trim()); - if (opts?.linha && opts.linha.trim()) params = params.set('linha', opts.linha.trim()); - return this.http.get(`${this.baseApi}/parcelamento/kpis`, { params }); - } - - // ✅ /api/parcelamento/series/monthly?cliente=...&linha=... - getMonthlySeries(opts?: { cliente?: string; linha?: string }): Observable { - let params = new HttpParams(); - if (opts?.cliente && opts.cliente.trim()) params = params.set('cliente', opts.cliente.trim()); - if (opts?.linha && opts.linha.trim()) params = params.set('linha', opts.linha.trim()); - return this.http.get(`${this.baseApi}/parcelamento/series/monthly`, { params }); - } - - // ✅ /api/parcelamento/top-lines?cliente=...&take=10 - getTopLines(opts?: { cliente?: string; take?: number }): Observable { - let params = new HttpParams(); - params = params.set('take', String(opts?.take ?? 10)); - if (opts?.cliente && opts.cliente.trim()) params = params.set('cliente', opts.cliente.trim()); - return this.http.get(`${this.baseApi}/parcelamento/top-lines`, { params }); - } -}