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
@@ -2,22 +2,25 @@
<h1>Résumé annuel</h1>
<form class="selecteurs" (ngSubmit)="charger()">
<div class="champ">
<label for="annee">Année</label>
<input
id="annee"
name="annee"
type="number"
[(ngModel)]="annee"
[min]="anneeMin"
[max]="anneeMax"
/>
</div>
<div class="selecteurs__groupe">
<div class="champ">
<label for="annee">Année</label>
<input
id="annee"
name="annee"
type="number"
[(ngModel)]="annee"
[min]="anneeMin"
[max]="anneeMax"
/>
</div>
<div class="actions">
<button type="submit" class="btn-principal" [disabled]="chargement()">
Afficher
</button>
</div>
<div class="actions">
<button
type="button"
class="btn-secondaire"
@@ -43,14 +46,14 @@
@for (ligne of lignes(); track ligne.mois) {
<tr>
<td>{{ ligne.libelle }}</td>
<td class="num">{{ ligne.totalKm | number: '1.2-2' }}</td>
<td class="num">{{ ligne.totalKm | number: formatKm() }}</td>
</tr>
}
</tbody>
<tfoot>
<tr>
<td>Total annuel</td>
<td class="num total">{{ totalAnnuel() | number: '1.2-2' }} km</td>
<td class="num total">{{ totalAnnuel() | number: formatKm() }} km</td>
</tr>
</tfoot>
</table>
@@ -13,10 +13,18 @@ h1 {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1.5rem;
}
.selecteurs__groupe {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
gap: 1rem;
}
.champ {
display: flex;
flex-direction: column;
@@ -3,6 +3,7 @@ import { Component, OnInit, inject, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { NotificationService } from '../../core/services/notification.service';
import { ParametresApplicationService } from '../../core/services/parametres-application.service';
import { TrajetService } from '../../core/services/trajet.service';
import { ResumeAnnuel } from '../../core/models';
@@ -49,6 +50,7 @@ export interface LigneAnnuelle {
export class ResumeAnnuelComponent implements OnInit {
private readonly trajetService = inject(TrajetService);
private readonly notification = inject(NotificationService);
readonly parametresService = inject(ParametresApplicationService);
/** Bornes de saisie de l'année (cohérentes avec la validation backend). */
readonly anneeMin = 2000;
@@ -104,7 +106,11 @@ export class ResumeAnnuelComponent implements OnInit {
});
}
/** Déclenche l'export PDF annuel et le téléchargement navigateur (Exigences 4.6, 7.3). */
/**
* Déclenche l'export PDF annuel et le téléchargement navigateur (Exigences 4.6, 7.3). Le nom
* de fichier utilisé est celui suggéré par le backend (en-tête `Content-Disposition`, format
* `AAAA_Trajets_MATRICULE_PLAQUE.pdf`) ; à défaut (en-tête absent), un repli local est utilisé.
*/
exporterPdf(): void {
if (!this.anneeValide() || this.export()) {
return;
@@ -112,9 +118,10 @@ export class ResumeAnnuelComponent implements OnInit {
this.export.set(true);
this.trajetService.exportPdfAnnuel(this.annee).subscribe({
next: (blob) => {
next: (reponse) => {
this.export.set(false);
this.declencherTelechargement(blob, `trajets_${this.annee}.pdf`);
const nom = this.trajetService.extraireNomFichier(reponse) ?? `${this.annee}_Trajets.pdf`;
this.declencherTelechargement(reponse.body!, nom);
},
error: () => {
this.export.set(false);
@@ -156,6 +163,15 @@ export class ResumeAnnuelComponent implements OnInit {
URL.revokeObjectURL(url);
}
/**
* Chaîne de format `number` (`1.{p}-{p}`) basée sur la précision courante des
* distances, pour un affichage dynamique des totaux en km.
*/
formatKm(): string {
const precision = this.parametresService.precisionDistance();
return `1.${precision}-${precision}`;
}
/** Vérifie que l'année sélectionnée est dans les bornes attendues. */
private anneeValide(): boolean {
return (