feat(frontend): vue liste des sites
Nouveau SitesService (GET /sites) et page SiteList consommant le design système (ev-card, ev-badge, ev-alert, ev-brand). Ajoute la route /sites, un lien depuis le dashboard, et une route détail /sites/:siteId pointant vers un placeholder minimal en attendant l'issue #51. Closes #49
This commit is contained in:
@@ -7,9 +7,16 @@
|
||||
<p class="dashboard__subtitle">Consommation instantanée du parc</p>
|
||||
</div>
|
||||
</div>
|
||||
<ev-button class="logout-button" variant="secondary" [fullWidth]="false" (click)="onLogout()"
|
||||
>Déconnexion</ev-button
|
||||
>
|
||||
<div class="dashboard__actions">
|
||||
<a routerLink="/sites" class="dashboard__sites-link">Voir les sites</a>
|
||||
<ev-button
|
||||
class="logout-button"
|
||||
variant="secondary"
|
||||
[fullWidth]="false"
|
||||
(click)="onLogout()"
|
||||
>Déconnexion</ev-button
|
||||
>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@if (error(); as message) {
|
||||
|
||||
@@ -34,6 +34,22 @@
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.dashboard__actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.dashboard__sites-link {
|
||||
color: var(--color-primary);
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Dashboard } from './dashboard';
|
||||
import { StatsService } from '../../core/services/stats.service';
|
||||
import { AlertsService } from '../../core/services/alerts.service';
|
||||
import {AuthService} from '../../core/services/auth.service';
|
||||
import {Router} from '@angular/router';
|
||||
import {Router, provideRouter} from '@angular/router';
|
||||
|
||||
vi.mock('chart.js', () => {
|
||||
class ChartMock {
|
||||
@@ -29,6 +29,7 @@ describe('Dashboard', () => {
|
||||
providers: [
|
||||
{ provide: StatsService, useValue: statsMock },
|
||||
{ provide: AlertsService, useValue: alertsMock },
|
||||
provideRouter([]),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -60,6 +61,7 @@ describe('Dashboard', () => {
|
||||
providers: [
|
||||
{ provide: StatsService, useValue: statsMock },
|
||||
{ provide: AlertsService, useValue: alertsMock },
|
||||
provideRouter([]),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -86,6 +88,7 @@ describe('Dashboard', () => {
|
||||
providers: [
|
||||
{ provide: StatsService, useValue: statsMock },
|
||||
{ provide: AlertsService, useValue: alertsMock },
|
||||
provideRouter([]),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -99,7 +102,6 @@ describe('Dashboard', () => {
|
||||
const statsMock = { getSummary: vi.fn().mockReturnValue(of({ total_sites: 7, sites: [] })) };
|
||||
const alertsMock = { getAlerts: vi.fn().mockReturnValue(of([])) };
|
||||
const authMock = { logout: vi.fn().mockReturnValue(of(undefined)), clearSession: vi.fn() };
|
||||
const routerMock = { navigate: vi.fn() };
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [Dashboard],
|
||||
@@ -107,18 +109,21 @@ describe('Dashboard', () => {
|
||||
{ provide: StatsService, useValue: statsMock },
|
||||
{ provide: AlertsService, useValue: alertsMock },
|
||||
{ provide: AuthService, useValue: authMock },
|
||||
{ provide: Router, useValue: routerMock },
|
||||
provideRouter([]),
|
||||
],
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(Dashboard);
|
||||
fixture.detectChanges();
|
||||
|
||||
const router = TestBed.inject(Router);
|
||||
const navigateSpy = vi.spyOn(router, 'navigate').mockResolvedValue(true);
|
||||
|
||||
const button = fixture.nativeElement.querySelector('.logout-button');
|
||||
button.click();
|
||||
|
||||
expect(authMock.logout).toHaveBeenCalled();
|
||||
expect(routerMock.navigate).toHaveBeenCalledWith(['/login']);
|
||||
expect(navigateSpy).toHaveBeenCalledWith(['/login']);
|
||||
});
|
||||
it('déconnecte localement et redirige vers /login même si logout échoue côté réseau', () => {
|
||||
const statsMock = { getSummary: vi.fn().mockReturnValue(of({ total_sites: 7, sites: [] })) };
|
||||
@@ -127,26 +132,27 @@ describe('Dashboard', () => {
|
||||
logout: vi.fn().mockReturnValue(throwError(() => new Error('réseau indisponible'))),
|
||||
clearSession: vi.fn(),
|
||||
};
|
||||
const routerMock = { navigate: vi.fn() };
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
imports: [Dashboard],
|
||||
providers: [
|
||||
{ provide: StatsService, useValue: statsMock },
|
||||
{ provide: AlertsService, useValue: alertsMock },
|
||||
{ provide: AuthService, useValue: authMock },
|
||||
{ provide: Router, useValue: routerMock },
|
||||
provideRouter([]),
|
||||
],
|
||||
});
|
||||
|
||||
const fixture = TestBed.createComponent(Dashboard);
|
||||
fixture.detectChanges();
|
||||
|
||||
const router = TestBed.inject(Router);
|
||||
const navigateSpy = vi.spyOn(router, 'navigate').mockResolvedValue(true);
|
||||
|
||||
const button = fixture.nativeElement.querySelector('.logout-button');
|
||||
button.click();
|
||||
|
||||
expect(authMock.clearSession).toHaveBeenCalled();
|
||||
expect(routerMock.navigate).toHaveBeenCalledWith(['/login']);
|
||||
expect(navigateSpy).toHaveBeenCalledWith(['/login']);
|
||||
});
|
||||
|
||||
it('distingue le ton des sévérités high et critical', () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Component, OnInit, inject, signal, DestroyRef } from '@angular/core';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { timer, switchMap, catchError, EMPTY, Observable } from 'rxjs';
|
||||
import { DecimalPipe } from '@angular/common';
|
||||
import { Router } from '@angular/router';
|
||||
import { Router, RouterLink } from '@angular/router';
|
||||
import { StatsService } from '../../core/services/stats.service';
|
||||
import { ConsumptionGauge } from '../../shared/components/consumption-gauge/consumption-gauge';
|
||||
import { SiteLoadChart } from '../../shared/components/site-load-chart/site-load-chart';
|
||||
@@ -30,7 +30,17 @@ const TON_PAR_SEVERITE: Record<AlertSeverity, BadgeTone> = {
|
||||
@Component({
|
||||
selector: 'app-dashboard',
|
||||
standalone: true,
|
||||
imports: [DecimalPipe, ConsumptionGauge, SiteLoadChart, Card, EvAlert, Badge, Brand, Button],
|
||||
imports: [
|
||||
DecimalPipe,
|
||||
RouterLink,
|
||||
ConsumptionGauge,
|
||||
SiteLoadChart,
|
||||
Card,
|
||||
EvAlert,
|
||||
Badge,
|
||||
Brand,
|
||||
Button,
|
||||
],
|
||||
templateUrl: './dashboard.html',
|
||||
styleUrl: './dashboard.scss',
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user