Refonte export PDF, colonne commentaire et UX résumés

- Export PDF: nouveau nommage AAAAMM/AAAA_Trajets_MATRICULE_PLAQUE.pdf,
  logo carré embarqué en en-tête, km formatés selon Precision_Distance,
  restructuration en-têtes (Domicile déplacé, période/année fusionnées
  au titre)
- Correction bug: le nom de fichier suggéré au téléchargement ne
  remontait pas au frontend (CORS WithExposedHeaders manquant +
  composants résumé ne lisaient pas Content-Disposition)
- Résumé mensuel: colonne Commentaire, sélecteur temporel unifié
  (auto-chargement), km selon précision
- Résumé annuel: bouton Export PDF repositionné, km selon précision
- Panneau totaux: renommage "Remboursement indicatif" + infobulles
  d'aide (icône) au lieu du texte permanent
- Ajout de PdfServiceIntegrationTests (3 tests), 65/65 tests backend OK
- Mise à jour requirements.md et design.md en conséquence
This commit is contained in:
2026-09-28 08:58:23 +02:00
parent 9db3c91471
commit 61298d6384
17 changed files with 614 additions and 266 deletions
@@ -1,4 +1,4 @@
import { HttpClient, HttpParams } from '@angular/common/http';
import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
import { Observable } from 'rxjs';
@@ -77,22 +77,46 @@ export class TrajetService {
});
}
/** Export PDF mensuel (GET /api/exports/mensuel/{annee}/{mois}). */
exportPdfMensuel(annee: number, mois: number, vehiculeId?: number): Observable<Blob> {
/**
* Export PDF mensuel (GET /api/exports/mensuel/{annee}/{mois}). Réponse complète
* (et non le seul Blob) afin que l'appelant puisse lire le nom de fichier suggéré
* par le backend depuis l'en-tête `Content-Disposition`.
*/
exportPdfMensuel(annee: number, mois: number, vehiculeId?: number): Observable<HttpResponse<Blob>> {
return this.http.get(`${this.baseUrl}/exports/mensuel/${annee}/${mois}`, {
params: this.vehiculeParams(vehiculeId),
responseType: 'blob'
responseType: 'blob',
observe: 'response'
});
}
/** Export PDF annuel (GET /api/exports/annuel/{annee}). */
exportPdfAnnuel(annee: number, vehiculeId?: number): Observable<Blob> {
/**
* Export PDF annuel (GET /api/exports/annuel/{annee}). Réponse complète (et non le
* seul Blob) afin que l'appelant puisse lire le nom de fichier suggéré par le backend
* depuis l'en-tête `Content-Disposition`.
*/
exportPdfAnnuel(annee: number, vehiculeId?: number): Observable<HttpResponse<Blob>> {
return this.http.get(`${this.baseUrl}/exports/annuel/${annee}`, {
params: this.vehiculeParams(vehiculeId),
responseType: 'blob'
responseType: 'blob',
observe: 'response'
});
}
/**
* Extrait le nom de fichier suggéré par le backend depuis l'en-tête
* `Content-Disposition: attachment; filename="..."` d'une réponse HTTP.
* Retourne `null` si l'en-tête est absent ou ne contient pas de nom de fichier.
*/
extraireNomFichier(reponse: HttpResponse<Blob>): string | null {
const entete = reponse.headers.get('Content-Disposition');
if (!entete) {
return null;
}
const correspondance = /filename\*?=(?:UTF-8'')?"?([^;"]+)"?/i.exec(entete);
return correspondance ? decodeURIComponent(correspondance[1]) : null;
}
/**
* Trajets d'une journée avec géométries pour la carte
* (GET /api/trajets/journee/{date}). La date est au format ISO (yyyy-MM-dd).