Renomeia Relatórios para Dashboard e adiciona menu de opções

This commit is contained in:
Eduardo Lopes 2026-01-22 14:03:58 -03:00
parent e1744bb202
commit c9c2f2fffb
9 changed files with 200 additions and 50 deletions

View File

@ -11,7 +11,7 @@ import { authGuard } from './guards/auth.guard';
import { DadosUsuarios } from './pages/dados-usuarios/dados-usuarios'; import { DadosUsuarios } from './pages/dados-usuarios/dados-usuarios';
import { VigenciaComponent } from './pages/vigencia/vigencia'; import { VigenciaComponent } from './pages/vigencia/vigencia';
import { TrocaNumero } from './pages/troca-numero/troca-numero'; import { TrocaNumero } from './pages/troca-numero/troca-numero';
import { Relatorios } from './pages/relatorios/relatorios'; import { Dashboard } from './pages/dashboard/dashboard';
export const routes: Routes = [ export const routes: Routes = [
{ path: '', component: Home }, { path: '', component: Home },
@ -26,10 +26,10 @@ export const routes: Routes = [
{ path: 'trocanumero', component: TrocaNumero, canActivate: [authGuard] }, { path: 'trocanumero', component: TrocaNumero, canActivate: [authGuard] },
// ✅ rota correta // ✅ rota correta
{ path: 'relatorios', component: Relatorios, canActivate: [authGuard] }, { path: 'dashboard', component: Dashboard, canActivate: [authGuard] },
// ✅ compatibilidade: se alguém acessar /portal/relatorios, manda pra /relatorios // ✅ compatibilidade: se alguém acessar /portal/dashboard, manda pra /dashboard
{ path: 'portal/relatorios', redirectTo: 'relatorios', pathMatch: 'full' }, { path: 'portal/dashboard', redirectTo: 'dashboard', pathMatch: 'full' },
{ path: '**', redirectTo: '' }, { path: '**', redirectTo: '' },
]; ];

View File

@ -33,7 +33,7 @@ export class AppComponent {
'/dadosusuarios', '/dadosusuarios',
'/vigencia', '/vigencia',
'/trocanumero', '/trocanumero',
'/relatorios', // ✅ ADICIONADO: esconde footer na página de relatórios '/dashboard', // ✅ ADICIONADO: esconde footer na página de dashboard
]; ];
constructor( constructor(

View File

@ -3,19 +3,54 @@
<!-- ✅ LOGADO: hambúrguer + logo JUNTOS --> <!-- ✅ LOGADO: hambúrguer + logo JUNTOS -->
<ng-container *ngIf="isLoggedHeader; else publicHeader"> <ng-container *ngIf="isLoggedHeader; else publicHeader">
<div class="left-logged"> <div class="logged-header">
<button class="btn-icon" type="button" (click)="toggleMenu()" aria-label="Abrir menu"> <div class="left-logged">
<i class="bi bi-list"></i> <button class="btn-icon" type="button" (click)="toggleMenu()" aria-label="Abrir menu">
</button> <i class="bi bi-list"></i>
</button>
<a routerLink="/relatorios" class="logo-area" (click)="closeMenu()"> <a routerLink="/dashboard" class="logo-area" (click)="closeMenu()">
<div class="logo-icon"> <div class="logo-icon">
<i class="bi bi-layers-fill"></i> <i class="bi bi-layers-fill"></i>
</div>
<div class="logo-text">
Line<span class="highlight">Gestão</span>
</div>
</a>
</div>
<div class="logged-actions">
<span class="status-pill">
<span class="status-dot"></span>
LIVE
</span>
<button type="button" class="btn-icon btn-bell" aria-label="Notificações">
<i class="bi bi-bell"></i>
</button>
<div class="options-menu" [class.open]="optionsOpen" (click)="$event.stopPropagation()">
<button
type="button"
class="options-trigger"
(click)="toggleOptions()"
aria-haspopup="true"
[attr.aria-expanded]="optionsOpen"
>
Opções
<i class="bi bi-chevron-down"></i>
</button>
<div class="options-dropdown" *ngIf="optionsOpen">
<a routerLink="/dadosusuarios" class="options-item" (click)="closeOptions()">
Perfil
</a>
<button type="button" class="options-item danger" (click)="logout()">
Sair
</button>
</div>
</div> </div>
<div class="logo-text"> </div>
Line<span class="highlight">Gestão</span>
</div>
</a>
</div> </div>
</ng-container> </ng-container>
@ -58,7 +93,7 @@
(click)="$event.stopPropagation()" (click)="$event.stopPropagation()"
> >
<div class="side-menu-header"> <div class="side-menu-header">
<a class="side-logo" routerLink="/relatorios" (click)="closeMenu()"> <a class="side-logo" routerLink="/dashboard" (click)="closeMenu()">
<span class="side-logo-icon"><i class="bi bi-layers-fill"></i></span> <span class="side-logo-icon"><i class="bi bi-layers-fill"></i></span>
<span class="side-logo-text">Line<span class="highlight">Gestão</span></span> <span class="side-logo-text">Line<span class="highlight">Gestão</span></span>
</a> </a>
@ -69,8 +104,8 @@
</div> </div>
<div class="side-menu-body"> <div class="side-menu-body">
<a routerLink="/relatorios" routerLinkActive="active" class="side-item" (click)="closeMenu()"> <a routerLink="/dashboard" routerLinkActive="active" class="side-item" (click)="closeMenu()">
<i class="bi bi-bar-chart-fill"></i> <span>Relatórios</span> <i class="bi bi-bar-chart-fill"></i> <span>Dashboard</span>
</a> </a>
<a routerLink="/geral" routerLinkActive="active" class="side-item" (click)="closeMenu()"> <a routerLink="/geral" routerLinkActive="active" class="side-item" (click)="closeMenu()">

View File

@ -30,6 +30,20 @@
gap: 12px; gap: 12px;
} }
.logged-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
width: 100%;
}
.logged-actions {
display: flex;
align-items: center;
gap: 10px;
}
/* Logo */ /* Logo */
.logo-area { .logo-area {
display: flex; display: flex;
@ -123,22 +137,99 @@
} }
} }
/* Faixa home */ /* ✅ Status e opções (logado) */
.header-bar { .status-pill {
margin-top: 10px; display: inline-flex;
width: 100%;
height: 34px;
display: flex;
align-items: center; align-items: center;
justify-content: center; gap: 8px;
background: linear-gradient(90deg, #0B2BD6 0%, #6A55FF 40%, #E33DCF 100%); padding: 6px 12px;
border-radius: 999px;
background: rgba(16, 185, 129, 0.12);
color: #0f766e;
font-weight: 800;
font-size: 12px;
letter-spacing: 0.06em;
} }
.header-bar-text { .status-dot {
color: #ffffff; width: 10px;
font-size: 15px; height: 10px;
font-weight: 800; border-radius: 50%;
font-family: 'Poppins', sans-serif; background: #22c55e;
box-shadow: 0 0 0 4px rgba(34, 197, 94, 0.18);
}
.btn-bell {
width: 42px;
height: 42px;
border-radius: 12px;
i {
font-size: 18px;
}
}
.options-menu {
position: relative;
display: flex;
align-items: center;
}
.options-trigger {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 8px 14px;
border-radius: 12px;
border: 1px solid rgba(0,0,0,0.1);
background: #fff;
font-weight: 700;
color: var(--text-main);
cursor: pointer;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
i {
font-size: 12px;
}
&:hover {
border-color: rgba(227, 61, 207, 0.35);
box-shadow: 0 12px 22px rgba(0,0,0,0.08);
}
}
.options-dropdown {
position: absolute;
right: 0;
top: calc(100% + 8px);
min-width: 200px;
padding: 8px 0;
border-radius: 14px;
border: 1px solid rgba(0,0,0,0.08);
background: #fff;
box-shadow: 0 18px 40px rgba(0,0,0,0.12);
z-index: 1200;
}
.options-item {
display: flex;
align-items: center;
width: 100%;
padding: 10px 16px;
font-weight: 700;
color: rgba(17, 18, 20, 0.85);
text-decoration: none;
background: transparent;
border: none;
cursor: pointer;
&:hover {
background: rgba(227, 61, 207, 0.08);
}
&.danger {
color: #c2410c;
}
} }
/* ========================= */ /* ========================= */

View File

@ -3,6 +3,7 @@ import { RouterLink, Router, NavigationEnd } from '@angular/router';
import { CommonModule, isPlatformBrowser } from '@angular/common'; import { CommonModule, isPlatformBrowser } from '@angular/common';
import { PLATFORM_ID } from '@angular/core'; import { PLATFORM_ID } from '@angular/core';
import { filter } from 'rxjs/operators'; import { filter } from 'rxjs/operators';
import { AuthService } from '../../services/auth.service';
@Component({ @Component({
selector: 'app-header', selector: 'app-header',
@ -15,6 +16,7 @@ export class Header {
isScrolled = false; isScrolled = false;
menuOpen = false; menuOpen = false;
optionsOpen = false;
isLoggedHeader = false; isLoggedHeader = false;
isHome = false; isHome = false;
@ -25,11 +27,12 @@ export class Header {
'/dadosusuarios', '/dadosusuarios',
'/vigencia', '/vigencia',
'/trocanumero', '/trocanumero',
'/relatorios', // ✅ ADICIONADO '/dashboard', // ✅ ADICIONADO
]; ];
constructor( constructor(
private router: Router, private router: Router,
private authService: AuthService,
@Inject(PLATFORM_ID) private platformId: object @Inject(PLATFORM_ID) private platformId: object
) { ) {
// ✅ resolve no carregamento inicial // ✅ resolve no carregamento inicial
@ -42,6 +45,7 @@ export class Header {
const rawUrl = event.urlAfterRedirects || event.url; const rawUrl = event.urlAfterRedirects || event.url;
this.syncHeaderState(rawUrl); this.syncHeaderState(rawUrl);
this.menuOpen = false; this.menuOpen = false;
this.optionsOpen = false;
}); });
} }
@ -63,15 +67,35 @@ export class Header {
this.menuOpen = false; this.menuOpen = false;
} }
toggleOptions() {
this.optionsOpen = !this.optionsOpen;
}
closeOptions() {
this.optionsOpen = false;
}
logout() {
this.authService.logout();
this.optionsOpen = false;
this.router.navigate(['/']);
}
@HostListener('window:scroll', []) @HostListener('window:scroll', [])
onWindowScroll() { onWindowScroll() {
if (!isPlatformBrowser(this.platformId)) return; if (!isPlatformBrowser(this.platformId)) return;
this.isScrolled = window.scrollY > 10; this.isScrolled = window.scrollY > 10;
} }
@HostListener('document:click', [])
onDocumentClick() {
this.optionsOpen = false;
}
@HostListener('document:keydown.escape', []) @HostListener('document:keydown.escape', [])
onEsc() { onEsc() {
if (!isPlatformBrowser(this.platformId)) return; if (!isPlatformBrowser(this.platformId)) return;
this.closeMenu(); this.closeMenu();
this.closeOptions();
} }
} }

View File

@ -1,10 +1,10 @@
<section class="relatorios-page"> <section class="dashboard-page">
<div class="wrap"> <div class="wrap">
<div class="container"> <div class="container">
<div class="page-head fade-in-up"> <div class="page-head fade-in-up">
<div class="title"> <div class="title">
<span class="badge"> <span class="badge">
<i class="bi bi-bar-chart-fill"></i> Relatórios <i class="bi bi-bar-chart-fill"></i> Dashboard
</span> </span>
<p class="subtitle">Resumo e indicadores do ambiente.</p> <p class="subtitle">Resumo e indicadores do ambiente.</p>
</div> </div>

View File

@ -24,7 +24,7 @@
display: none !important; display: none !important;
} }
.relatorios-page { .dashboard-page {
width: 100%; width: 100%;
overflow-x: hidden; overflow-x: hidden;
} }

View File

@ -88,7 +88,7 @@ type DashboardKpisDto = {
userDataComEmail: number; userDataComEmail: number;
}; };
type RelatoriosDashboardDto = { type DashboardDto = {
kpis: DashboardKpisDto; kpis: DashboardKpisDto;
topClientes: TopClienteDto[]; topClientes: TopClienteDto[];
@ -105,13 +105,13 @@ type RelatoriosDashboardDto = {
}; };
@Component({ @Component({
selector: 'app-relatorios', selector: 'app-dashboard',
standalone: true, standalone: true,
imports: [CommonModule], imports: [CommonModule],
templateUrl: './relatorios.html', templateUrl: './dashboard.html',
styleUrls: ['./relatorios.scss'], styleUrls: ['./dashboard.scss'],
}) })
export class Relatorios implements OnInit, AfterViewInit, OnDestroy { export class Dashboard implements OnInit, AfterViewInit, OnDestroy {
@ViewChild('chartMureg12') chartMureg12?: ElementRef<HTMLCanvasElement>; @ViewChild('chartMureg12') chartMureg12?: ElementRef<HTMLCanvasElement>;
@ViewChild('chartTroca12') chartTroca12?: ElementRef<HTMLCanvasElement>; @ViewChild('chartTroca12') chartTroca12?: ElementRef<HTMLCanvasElement>;
@ViewChild('chartStatusPie') chartStatusPie?: ElementRef<HTMLCanvasElement>; @ViewChild('chartStatusPie') chartStatusPie?: ElementRef<HTMLCanvasElement>;
@ -202,17 +202,17 @@ export class Relatorios implements OnInit, AfterViewInit, OnDestroy {
} catch { } catch {
this.loading = false; this.loading = false;
this.errorMsg = this.errorMsg =
'Falha ao carregar Relatórios. Verifique se a API está rodando e o endpoint /api/relatorios/dashboard está acessível.'; 'Falha ao carregar Dashboard. Verifique se a API está rodando e o endpoint /api/dashboard está acessível.';
} }
} }
private async fetchDashboardReal(): Promise<RelatoriosDashboardDto> { private async fetchDashboardReal(): Promise<DashboardDto> {
if (!isPlatformBrowser(this.platformId)) throw new Error('SSR não suportado para charts'); if (!isPlatformBrowser(this.platformId)) throw new Error('SSR não suportado para charts');
const url = `${this.baseApi}/relatorios/dashboard`; const url = `${this.baseApi}/dashboard`;
return await firstValueFrom(this.http.get<RelatoriosDashboardDto>(url)); return await firstValueFrom(this.http.get<DashboardDto>(url));
} }
private applyDto(dto: RelatoriosDashboardDto) { private applyDto(dto: DashboardDto) {
const k = dto.kpis; const k = dto.kpis;
this.kpis = [ this.kpis = [

View File

@ -117,18 +117,18 @@ export class LoginComponent {
const nome = this.getNameFromToken(token); const nome = this.getNameFromToken(token);
console.log('👤 Nome extraído:', nome); console.log('👤 Nome extraído:', nome);
console.log('🔄 Tentando ir para /relatorios...'); console.log('🔄 Tentando ir para /dashboard...');
this.router.navigate(['/relatorios'], { this.router.navigate(['/dashboard'], {
state: { toastMessage: `Bem-vindo, ${nome}!` } state: { toastMessage: `Bem-vindo, ${nome}!` }
}).then(sucesso => { }).then(sucesso => {
if (sucesso) console.log('✅ Navegação funcionou!'); if (sucesso) console.log('✅ Navegação funcionou!');
else console.error('❌ Navegação falhou! A rota "/relatorios" existe?'); else console.error('❌ Navegação falhou! A rota "/dashboard" existe?');
}); });
} catch (e) { } catch (e) {
console.error('❌ Erro ao processar token ou navegar:', e); console.error('❌ Erro ao processar token ou navegar:', e);
// Força a ida mesmo se o nome falhar // Força a ida mesmo se o nome falhar
this.router.navigate(['/relatorios']); this.router.navigate(['/dashboard']);
} }
}, },
error: (err) => { error: (err) => {