import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, SimpleChanges, inject, signal } from '@angular/core'; import { Subscription } from 'rxjs'; import { MontantPrevisionnel, Totaux } from '../../core/models'; import { BaremeService } from '../../core/services/bareme.service'; import { ParametresApplicationService } from '../../core/services/parametres-application.service'; import { TrajetService } from '../../core/services/trajet.service'; /** * `PanneauTotauxComponent` — panneau latéral des totaux kilométriques et de * l'indication de versement. * * Reçoit en entrée une `date` (ISO `yyyy-MM-dd`) et un `vehiculeId`, puis affiche : * - le total du jour, le total du mois et le Total_Annuel_Partiel (cumul de * l'année jusqu'au mois sélectionné inclus), formatés dynamiquement selon la * précision courante des distances (0, 1 ou 2 décimales — Exigences 5.4, 5.5) ; * - le Montant_Previsionnel_Mensuel minoré à 80%, calculé sur le seul mois affiché * du véhicule sélectionné, présenté explicitement comme une simple indication de * versement — le montant réellement versé étant le Montant_Regle saisi par * l'utilisateur (Exigences 13.5, 13.6) ; * - le Montant_Previsionnel_Annuel, projection à 100% (sans minoration) sur le * Total_Annuel_Partiel (Exigences 8.1, 8.3, 8.4). * Les montants sont exprimés en euros à deux décimales, indépendamment de la * précision des distances (Exigence 13.7, 8.4). * * Les totaux et le montant prévisionnel sont recalculés à chaque changement de * `date` ou de `vehiculeId` (recalcul au changement, sans rechargement complet). * * _Requirements: 5.4, 5.5, 8.1, 8.3, 8.4, 12.7, 12.8, 13.5, 13.6, 13.7, 13.8_ */ @Component({ selector: 'app-panneau-totaux', standalone: true, imports: [CommonModule], changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: './panneau-totaux.component.html', styleUrl: './panneau-totaux.component.scss' }) export class PanneauTotauxComponent implements OnChanges, OnDestroy { private readonly trajetService = inject(TrajetService); private readonly baremeService = inject(BaremeService); private readonly cdr = inject(ChangeDetectorRef); readonly parametresService = inject(ParametresApplicationService); /** Date sélectionnée au format ISO (`yyyy-MM-dd`). */ @Input() date: string | null = null; /** Véhicule sur lequel portent les totaux et le montant prévisionnel. */ @Input() vehiculeId: number | null = null; /** Totaux jour / mois / année partiel chargés pour la date et le véhicule. */ readonly totaux = signal(null); /** Indication de versement minorée (80%) sur le Total_Annuel_Partiel. */ readonly montantPrevisionnel = signal(null); /** Chargement en cours (totaux et/ou montant prévisionnel). */ readonly chargement = signal(false); private totauxSub?: Subscription; private montantSub?: Subscription; ngOnChanges(changes: SimpleChanges): void { if (changes['date'] || changes['vehiculeId']) { this.rafraichir(); } } ngOnDestroy(): void { this.totauxSub?.unsubscribe(); this.montantSub?.unsubscribe(); } /** * Recharge les totaux et le montant prévisionnel pour la date et le véhicule * courants. Ne déclenche aucun appel tant que les deux entrées ne sont pas * disponibles. * * Public afin que le conteneur (`ConsultationJourComponent`) puisse forcer un * rafraîchissement après une création / modification / suppression / réordonnancement * de trajet sur la MÊME journée (cas où ni `date` ni `vehiculeId` ne changent, donc * `ngOnChanges` ne se déclenche pas). */ rafraichir(): void { this.totauxSub?.unsubscribe(); this.montantSub?.unsubscribe(); if (!this.date || this.vehiculeId == null) { this.totaux.set(null); this.montantPrevisionnel.set(null); this.chargement.set(false); this.cdr.markForCheck(); return; } const date = this.date; const vehiculeId = this.vehiculeId; const annee = this.anneeDe(date); const mois = this.moisDe(date); this.chargement.set(true); this.cdr.markForCheck(); this.totauxSub = this.trajetService.getTotauxJour(date, vehiculeId).subscribe({ next: (totaux) => { this.totaux.set(totaux); this.chargement.set(false); this.cdr.markForCheck(); }, error: () => { // Un mois / jour sans trajet est un cas normal : on affiche des totaux // nuls plutôt qu'une alerte. Les vraies indisponibilités serveur sont // signalées globalement par l'intercepteur (notification). this.totaux.set(null); this.chargement.set(false); this.cdr.markForCheck(); } }); // Le Montant_Previsionnel est purement indicatif : son échec n'empêche pas // l'affichage des totaux, on masque simplement l'indication. this.montantSub = this.baremeService .getMontantPrevisionnel(annee, mois, vehiculeId) .subscribe({ next: (montant) => { this.montantPrevisionnel.set(montant); this.cdr.markForCheck(); }, error: () => { this.montantPrevisionnel.set(null); this.cdr.markForCheck(); } }); } /** * Chaîne de format `number` (`1.{p}-{p}`) basée sur la précision courante des * distances (0, 1 ou 2 décimales), pour un affichage dynamique des totaux en km. */ formatKm(): string { const precision = this.parametresService.precisionDistance(); return `1.${precision}-${precision}`; } /** Année (nombre) extraite d'une date ISO `yyyy-MM-dd`. */ private anneeDe(dateIso: string): number { return Number(dateIso.slice(0, 4)); } /** Mois (1..12) extrait d'une date ISO `yyyy-MM-dd`. */ private moisDe(dateIso: string): number { return Number(dateIso.slice(5, 7)); } }