SchotzIAD KM Tracker : suivi des trajets kilométriques (Angular + .NET 8)
Application complète de saisie et de suivi des trajets kilométriques avec calcul de distance routière (OpenRouteService), barème kilométrique, résumés et régularisation annuelle. Backend (ASP.NET Core .NET 8, EF Core, SQLite) : - Trajets (CRUD, réordonnancement, insertion), calcul de distance ORS avec fallback Haversine, géométrie d'itinéraire stockée par trajet, recalcul en masse. - Profil, véhicules (avec dénomination), carnet d'adresses nommées, cache d'adresses. - Barème kilométrique : tranches multiples par puissance fiscale, borne max exclusive avec 'illimité' (KmMax nullable), coefficient à 3 décimales, recopie du dernier barème connu. - Résumés mensuel/annuel, montants réglés, régularisation, export PDF. Frontend (Angular 22, zoneless + signals) : - Consultation par jour, navigation temporelle synchronisée, carte Leaflet, formulaire de trajet (adresses nommées, domicile, autocomplétion BAN). - Détection de changement zoneless (indépendante de zone.js). Hébergement : Docker Compose (nginx + backend), scripts run-dev.sh / run-docker.sh.
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
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 { 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), tous à deux décimales
|
||||
* (Exigences 12.7, 12.8) ;
|
||||
* - le Montant_Previsionnel minoré à 80%, calculé sur le Total_Annuel_Partiel 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). Les montants sont exprimés en euros à
|
||||
* deux décimales (Exigence 13.7).
|
||||
*
|
||||
* Les totaux et le montant prévisionnel sont recalculés à chaque changement de
|
||||
* `date` ou de `vehiculeId` (recalcul au changement, sans rechargement complet).
|
||||
*
|
||||
* _Requirements: 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);
|
||||
|
||||
/** 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<Totaux | null>(null);
|
||||
|
||||
/** Indication de versement minorée (80%) sur le Total_Annuel_Partiel. */
|
||||
readonly montantPrevisionnel = signal<MontantPrevisionnel | null>(null);
|
||||
|
||||
/** Chargement en cours (totaux et/ou montant prévisionnel). */
|
||||
readonly chargement = signal(false);
|
||||
|
||||
/** Message d'erreur affiché si le chargement échoue. */
|
||||
readonly erreur = signal<string | null>(null);
|
||||
|
||||
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.
|
||||
*/
|
||||
private 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.erreur.set(null);
|
||||
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.erreur.set(null);
|
||||
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: () => {
|
||||
this.totaux.set(null);
|
||||
this.chargement.set(false);
|
||||
this.erreur.set('Impossible de charger les totaux.');
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** 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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user