Renomeia Relatórios para Dashboard e adiciona menu de opções
This commit is contained in:
parent
e1744bb202
commit
c9c2f2fffb
|
|
@ -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: '' },
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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()">
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ========================= */
|
/* ========================= */
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -24,7 +24,7 @@
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.relatorios-page {
|
.dashboard-page {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
@ -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 = [
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue