Consultation par mois : liste mensuelle groupée par jour, carte par jour
Refonte de la navigation temporelle suite au retour d'usage :
- Sélecteur temporel limité à année/mois (suppression du segment jour).
Nouvel Output moisSelectionne: {annee, mois} en remplacement de
dateSelectionnee (jour ISO). Surbrillance des mois avec données conservée.
- Consultation : deux états distincts. Le signal mois pilote le chargement
de la liste (tous les trajets du mois, triés par date), le signal date
pilote la carte (trajets du jour) et la date par défaut à l'ajout/insertion.
Bande « ‹ Jour préc. / date / Jour suiv. › » au-dessus de la carte, bornée
au mois sélectionné. La navigation jour ne recharge que la carte.
- Liste : trajets du mois groupés par journée (en-tête de date). Un
cdkDropList indépendant par jour (listes non connectées) : le
réordonnancement par glisser-déposer est limité à chaque journée, avec
recalcul des positions internes et rétablissement en cas d'échec.
Builds dev et prod OK. Backend inchangé (API résumé mensuel/réordonner
déjà en place).
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
computed,
|
||||
inject,
|
||||
signal
|
||||
} from '@angular/core';
|
||||
@@ -40,6 +41,18 @@ export interface TrajetInsertionContext {
|
||||
positionAvant?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Groupe de trajets d'une même journée. La liste mensuelle est affichée en groupes
|
||||
* ordonnés par date croissante ; à l'intérieur d'un groupe, les trajets sont triés
|
||||
* par `Position` croissante. Le glisser-déposer est limité à chaque groupe.
|
||||
*/
|
||||
export interface GroupeJour {
|
||||
/** Date ISO `yyyy-MM-dd` de la journée. */
|
||||
date: string;
|
||||
/** Trajets de la journée, triés par position croissante. */
|
||||
trajets: Trajet[];
|
||||
}
|
||||
|
||||
/**
|
||||
* `TrajetListComponent` — liste des trajets d'une journée (Exigences 5.1, 5.4, 5.7, 11.6, 11.9).
|
||||
*
|
||||
@@ -76,12 +89,13 @@ export class TrajetListComponent {
|
||||
private readonly notifications = inject(NotificationService);
|
||||
|
||||
/**
|
||||
* Trajets de la journée. Toujours affichés triés par `Position` croissante :
|
||||
* une copie triée est maintenue dans `trajetsTries` à chaque changement d'entrée.
|
||||
* Trajets du mois. Ils sont regroupés par journée (`groupes`), chaque groupe
|
||||
* étant trié par `Position` croissante, les groupes eux-mêmes étant ordonnés par
|
||||
* date croissante.
|
||||
*/
|
||||
@Input() set trajets(valeur: Trajet[] | null) {
|
||||
this._trajets = valeur ?? [];
|
||||
this.trajetsTries.set([...this._trajets].sort((a, b) => a.position - b.position));
|
||||
this.trajetsSource.set([...this._trajets]);
|
||||
}
|
||||
get trajets(): Trajet[] {
|
||||
return this._trajets;
|
||||
@@ -115,8 +129,41 @@ export class TrajetListComponent {
|
||||
this.survol.emit(id);
|
||||
}
|
||||
|
||||
/** Liste triée par Position (source d'affichage). */
|
||||
readonly trajetsTries = signal<Trajet[]>([]);
|
||||
/** Source brute des trajets du mois (mise à jour à chaque changement d'entrée). */
|
||||
private readonly trajetsSource = signal<Trajet[]>([]);
|
||||
|
||||
/**
|
||||
* Trajets regroupés par journée, source d'affichage. Les groupes sont triés par
|
||||
* date croissante ; à l'intérieur d'un groupe, les trajets sont triés par position
|
||||
* croissante. Chaque groupe est une `cdkDropList` indépendante (drag limité au jour).
|
||||
*/
|
||||
readonly groupes = computed<GroupeJour[]>(() => {
|
||||
const parJour = new Map<string, Trajet[]>();
|
||||
for (const trajet of this.trajetsSource()) {
|
||||
const cle = (trajet.date ?? '').slice(0, 10);
|
||||
const liste = parJour.get(cle);
|
||||
if (liste) {
|
||||
liste.push(trajet);
|
||||
} else {
|
||||
parJour.set(cle, [trajet]);
|
||||
}
|
||||
}
|
||||
return [...parJour.entries()]
|
||||
.sort(([a], [b]) => a.localeCompare(b))
|
||||
.map(([date, trajets]) => ({
|
||||
date,
|
||||
trajets: trajets.sort((a, b) => a.position - b.position)
|
||||
}));
|
||||
});
|
||||
|
||||
/**
|
||||
* Identifiants des listes de dépôt, un par groupe/jour. Bien qu'on NE connecte PAS
|
||||
* les listes entre elles (pas de déplacement inter-journées), fournir des ids
|
||||
* distincts stabilise le suivi CDK et l'accessibilité.
|
||||
*/
|
||||
readonly dropListIds = computed<string[]>(() =>
|
||||
this.groupes().map((g) => `drop-${g.date}`)
|
||||
);
|
||||
|
||||
/** Trajet en attente de confirmation de suppression (null si aucune). */
|
||||
readonly trajetASupprimer = signal<Trajet | null>(null);
|
||||
@@ -126,9 +173,9 @@ export class TrajetListComponent {
|
||||
|
||||
// --- Helpers d'affichage ---
|
||||
|
||||
/** Vrai si le trajet est le dernier de la journée (pas d'« insérer avant le suivant »). */
|
||||
estDernier(index: number): boolean {
|
||||
return index === this.trajetsTries().length - 1;
|
||||
/** Vrai si le trajet est le dernier DE SA JOURNÉE (pas d'« insérer avant le suivant »). */
|
||||
estDernier(groupe: GroupeJour, index: number): boolean {
|
||||
return index === groupe.trajets.length - 1;
|
||||
}
|
||||
|
||||
/** Distance formatée à 2 décimales, ou « — » si non calculée (Exigence 2.4). */
|
||||
@@ -144,6 +191,11 @@ export class TrajetListComponent {
|
||||
return trajet.id;
|
||||
}
|
||||
|
||||
/** Identité stable pour le suivi d'un groupe/jour. */
|
||||
trackByDate(_index: number, groupe: GroupeJour): string {
|
||||
return groupe.date;
|
||||
}
|
||||
|
||||
// --- Actions par trajet ---
|
||||
|
||||
/** Demande la modification d'un trajet (Exigence 5.1). */
|
||||
@@ -170,11 +222,10 @@ export class TrajetListComponent {
|
||||
* du trajet courant et l'adresse d'arrivée avec le départ du trajet suivant
|
||||
* (Exigence 11.3/11.4).
|
||||
*/
|
||||
insererAvantSuivant(index: number): void {
|
||||
const trajetsTries = this.trajetsTries();
|
||||
const courant = trajetsTries[index];
|
||||
const suivant = trajetsTries[index + 1];
|
||||
if (!suivant) {
|
||||
insererAvantSuivant(groupe: GroupeJour, index: number): void {
|
||||
const courant = groupe.trajets[index];
|
||||
const suivant = groupe.trajets[index + 1];
|
||||
if (!courant || !suivant) {
|
||||
return;
|
||||
}
|
||||
this.insertBetween.emit({
|
||||
@@ -223,30 +274,39 @@ export class TrajetListComponent {
|
||||
// --- Glisser-déposer (Exigence 11.9) ---
|
||||
|
||||
/**
|
||||
* Au dépôt d'un trajet : réordonne localement, recalcule les positions (pas de 1.0)
|
||||
* et persiste via `PATCH /api/trajets/reordonner`. En cas d'échec, l'ordre local est
|
||||
* rétabli à partir des positions d'origine.
|
||||
* Au dépôt d'un trajet DANS SA JOURNÉE : réordonne localement les trajets de ce
|
||||
* jour uniquement, recalcule leurs positions séquentielles et persiste via
|
||||
* `PATCH /api/trajets/reordonner`. Le glisser-déposer est limité à chaque journée
|
||||
* (aucune liste connectée), donc `event.container === event.previousContainer`.
|
||||
* En cas d'échec, l'ordre local est rétabli.
|
||||
*/
|
||||
onDrop(event: CdkDragDrop<Trajet[]>): void {
|
||||
onDrop(groupe: GroupeJour, event: CdkDragDrop<Trajet[]>): void {
|
||||
if (event.previousIndex === event.currentIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ordreInitial = [...this.trajetsTries()];
|
||||
const reordonnes = [...this.trajetsTries()];
|
||||
moveItemInArray(reordonnes, event.previousIndex, event.currentIndex);
|
||||
const ordreInitialSource = [...this.trajetsSource()];
|
||||
|
||||
// Recalcule les positions séquentielles (1.0, 2.0, …) selon le nouvel ordre.
|
||||
const positions: TrajetPosition[] = reordonnes.map((trajet, index) => ({
|
||||
// Réordonne uniquement les trajets de la journée concernée.
|
||||
const reordonnesJour = [...groupe.trajets];
|
||||
moveItemInArray(reordonnesJour, event.previousIndex, event.currentIndex);
|
||||
|
||||
// Recalcule les positions séquentielles (1, 2, …) AU SEIN de la journée.
|
||||
const positions: TrajetPosition[] = reordonnesJour.map((trajet, index) => ({
|
||||
id: trajet.id,
|
||||
position: index + 1
|
||||
}));
|
||||
|
||||
// Reflète immédiatement le nouvel ordre dans l'UI (feedback < 1 000 ms).
|
||||
this.trajetsTries.set(reordonnes.map((trajet, index) => ({
|
||||
...trajet,
|
||||
position: index + 1
|
||||
})));
|
||||
// Reflète immédiatement le nouvel ordre dans l'UI (feedback < 1 000 ms) :
|
||||
// on met à jour la source en réécrivant les positions des trajets de ce jour.
|
||||
const nouvellesPositions = new Map(positions.map((p) => [p.id, p.position]));
|
||||
this.trajetsSource.set(
|
||||
this.trajetsSource().map((t) =>
|
||||
nouvellesPositions.has(t.id)
|
||||
? { ...t, position: nouvellesPositions.get(t.id)! }
|
||||
: t
|
||||
)
|
||||
);
|
||||
|
||||
this.operationEnCours.set(true);
|
||||
const dto: ReordonnerTrajets = { trajets: positions };
|
||||
@@ -259,7 +319,7 @@ export class TrajetListComponent {
|
||||
},
|
||||
error: (err) => {
|
||||
// Rétablit l'ordre précédent en cas d'échec.
|
||||
this.trajetsTries.set(ordreInitial);
|
||||
this.trajetsSource.set(ordreInitialSource);
|
||||
this.gererErreur(err, 'Le réordonnancement a échoué.');
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user