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:
@@ -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 (
|
||||
|
||||
Reference in New Issue
Block a user