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

View File

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

View File

@ -3,12 +3,13 @@
<!-- ✅ LOGADO: hambúrguer + logo JUNTOS -->
<ng-container *ngIf="isLoggedHeader; else publicHeader">
<div class="logged-header">
<div class="left-logged">
<button class="btn-icon" type="button" (click)="toggleMenu()" aria-label="Abrir menu">
<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">
<i class="bi bi-layers-fill"></i>
</div>
@ -17,6 +18,40 @@
</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>
</ng-container>
<!-- ✅ PÚBLICO (HOME): menu + botão -->
@ -58,7 +93,7 @@
(click)="$event.stopPropagation()"
>
<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-text">Line<span class="highlight">Gestão</span></span>
</a>
@ -69,8 +104,8 @@
</div>
<div class="side-menu-body">
<a routerLink="/relatorios" routerLinkActive="active" class="side-item" (click)="closeMenu()">
<i class="bi bi-bar-chart-fill"></i> <span>Relatórios</span>
<a routerLink="/dashboard" routerLinkActive="active" class="side-item" (click)="closeMenu()">
<i class="bi bi-bar-chart-fill"></i> <span>Dashboard</span>
</a>
<a routerLink="/geral" routerLinkActive="active" class="side-item" (click)="closeMenu()">

View File

@ -30,6 +30,20 @@
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-area {
display: flex;
@ -123,22 +137,99 @@
}
}
/* Faixa home */
.header-bar {
margin-top: 10px;
width: 100%;
height: 34px;
display: flex;
/* ✅ Status e opções (logado) */
.status-pill {
display: inline-flex;
align-items: center;
justify-content: center;
background: linear-gradient(90deg, #0B2BD6 0%, #6A55FF 40%, #E33DCF 100%);
gap: 8px;
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 {
color: #ffffff;
font-size: 15px;
font-weight: 800;
font-family: 'Poppins', sans-serif;
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
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 { PLATFORM_ID } from '@angular/core';
import { filter } from 'rxjs/operators';
import { AuthService } from '../../services/auth.service';
@Component({
selector: 'app-header',
@ -15,6 +16,7 @@ export class Header {
isScrolled = false;
menuOpen = false;
optionsOpen = false;
isLoggedHeader = false;
isHome = false;
@ -25,11 +27,12 @@ export class Header {
'/dadosusuarios',
'/vigencia',
'/trocanumero',
'/relatorios', // ✅ ADICIONADO
'/dashboard', // ✅ ADICIONADO
];
constructor(
private router: Router,
private authService: AuthService,
@Inject(PLATFORM_ID) private platformId: object
) {
// ✅ resolve no carregamento inicial
@ -42,6 +45,7 @@ export class Header {
const rawUrl = event.urlAfterRedirects || event.url;
this.syncHeaderState(rawUrl);
this.menuOpen = false;
this.optionsOpen = false;
});
}
@ -63,15 +67,35 @@ export class Header {
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', [])
onWindowScroll() {
if (!isPlatformBrowser(this.platformId)) return;
this.isScrolled = window.scrollY > 10;
}
@HostListener('document:click', [])
onDocumentClick() {
this.optionsOpen = false;
}
@HostListener('document:keydown.escape', [])
onEsc() {
if (!isPlatformBrowser(this.platformId)) return;
this.closeMenu();
this.closeOptions();
}
}

View File

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

View File

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

View File

@ -88,7 +88,7 @@ type DashboardKpisDto = {
userDataComEmail: number;
};
type RelatoriosDashboardDto = {
type DashboardDto = {
kpis: DashboardKpisDto;
topClientes: TopClienteDto[];
@ -105,13 +105,13 @@ type RelatoriosDashboardDto = {
};
@Component({
selector: 'app-relatorios',
selector: 'app-dashboard',
standalone: true,
imports: [CommonModule],
templateUrl: './relatorios.html',
styleUrls: ['./relatorios.scss'],
templateUrl: './dashboard.html',
styleUrls: ['./dashboard.scss'],
})
export class Relatorios implements OnInit, AfterViewInit, OnDestroy {
export class Dashboard implements OnInit, AfterViewInit, OnDestroy {
@ViewChild('chartMureg12') chartMureg12?: ElementRef<HTMLCanvasElement>;
@ViewChild('chartTroca12') chartTroca12?: ElementRef<HTMLCanvasElement>;
@ViewChild('chartStatusPie') chartStatusPie?: ElementRef<HTMLCanvasElement>;
@ -202,17 +202,17 @@ export class Relatorios implements OnInit, AfterViewInit, OnDestroy {
} catch {
this.loading = false;
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');
const url = `${this.baseApi}/relatorios/dashboard`;
return await firstValueFrom(this.http.get<RelatoriosDashboardDto>(url));
const url = `${this.baseApi}/dashboard`;
return await firstValueFrom(this.http.get<DashboardDto>(url));
}
private applyDto(dto: RelatoriosDashboardDto) {
private applyDto(dto: DashboardDto) {
const k = dto.kpis;
this.kpis = [

View File

@ -117,18 +117,18 @@ export class LoginComponent {
const nome = this.getNameFromToken(token);
console.log('👤 Nome extraído:', nome);
console.log('🔄 Tentando ir para /relatorios...');
this.router.navigate(['/relatorios'], {
console.log('🔄 Tentando ir para /dashboard...');
this.router.navigate(['/dashboard'], {
state: { toastMessage: `Bem-vindo, ${nome}!` }
}).then(sucesso => {
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) {
console.error('❌ Erro ao processar token ou navegar:', e);
// Força a ida mesmo se o nome falhar
this.router.navigate(['/relatorios']);
this.router.navigate(['/dashboard']);
}
},
error: (err) => {