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:
@@ -13,22 +13,30 @@ import {
|
||||
|
||||
import { TrajetService } from '../../core/services/trajet.service';
|
||||
|
||||
/** Mois sélectionné émis par le sélecteur temporel. */
|
||||
export interface MoisSelectionne {
|
||||
annee: number;
|
||||
mois: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sélecteur temporel : navigation successive année → mois → jour.
|
||||
* Sélecteur temporel : navigation successive année → mois.
|
||||
*
|
||||
* Depuis le passage à une consultation PAR MOIS, la sélection du jour a été
|
||||
* retirée : l'utilisateur choisit une année puis un mois, et la carte reste
|
||||
* pilotée par un jour précis géré par le composant conteneur.
|
||||
*
|
||||
* - Sélecteur d'année : année courante + années ayant des données
|
||||
* (`TrajetService.getAnneesDisponibles`). L'année courante est toujours
|
||||
* présente afin que le sélecteur ne soit jamais vide. (Exigence 12.2)
|
||||
* - Sélecteur de mois : les douze mois affichés, surbrillance des mois
|
||||
* comportant des données (`TrajetService.getMoisDisponibles`). (Exigence 12.3)
|
||||
* - Sélecteur de jour : les jours du mois affichés, surbrillance des jours
|
||||
* comportant des données (`TrajetService.getJoursDisponibles`). (Exigence 12.4)
|
||||
* - Aucune date postérieure à aujourd'hui n'est sélectionnable. (Exigence 12.5)
|
||||
* - Émet `dateSelectionnee` (ISO `yyyy-MM-dd`) à chaque sélection utilisateur.
|
||||
* - Se synchronise avec la date affichée par le parent via `@Input date` : lors de
|
||||
* la navigation ± 1 jour, l'année / mois / jour affichés reflètent cette date.
|
||||
* - Aucun mois postérieur au mois courant n'est sélectionnable. (Exigence 12.5)
|
||||
* - Émet `moisSelectionne` (`{ annee, mois }`) à chaque sélection utilisateur.
|
||||
* - Se synchronise avec le mois affiché par le parent via `@Input date` (jour ISO
|
||||
* affiché sur la carte) : l'année / le mois affichés reflètent ce jour.
|
||||
*
|
||||
* _Requirements: 12.1, 12.2, 12.3, 12.4, 12.5, 12.14_
|
||||
* _Requirements: 12.1, 12.2, 12.3, 12.5, 12.14_
|
||||
*/
|
||||
@Component({
|
||||
selector: 'app-selecteur-temporel',
|
||||
@@ -41,14 +49,14 @@ export class SelecteurTemporelComponent implements OnInit, OnChanges {
|
||||
private readonly trajetService = inject(TrajetService);
|
||||
|
||||
/**
|
||||
* Date affichée par le parent (ISO `yyyy-MM-dd`). Quand elle change (navigation
|
||||
* ± 1 jour ou sélection), le sélecteur pré-sélectionne l'année, le mois et le
|
||||
* jour correspondants sans réémettre `dateSelectionnee`.
|
||||
* Jour affiché par le parent (ISO `yyyy-MM-dd`). Quand il change (navigation
|
||||
* jour ± 1 ou sélection d'un mois), le sélecteur pré-sélectionne l'année et le
|
||||
* mois correspondants sans réémettre `moisSelectionne`.
|
||||
*/
|
||||
@Input() date: string | null = null;
|
||||
|
||||
/** Émis à chaque changement de sélection complète (année + mois + jour). */
|
||||
@Output() readonly dateSelectionnee = new EventEmitter<string>();
|
||||
/** Émis à chaque changement de sélection (année + mois). */
|
||||
@Output() readonly moisSelectionne = new EventEmitter<MoisSelectionne>();
|
||||
|
||||
/** Libellés des douze mois (index 0 = janvier). */
|
||||
readonly nomsMois = [
|
||||
@@ -63,17 +71,12 @@ export class SelecteurTemporelComponent implements OnInit, OnChanges {
|
||||
readonly annees = signal<number[]>([]);
|
||||
/** Les douze mois (1..12), toujours affichés. */
|
||||
readonly mois: number[] = Array.from({ length: 12 }, (_, i) => i + 1);
|
||||
/** Jours du mois sélectionné (1..N). */
|
||||
readonly jours = signal<number[]>([]);
|
||||
|
||||
/** Mois comportant au moins un trajet pour l'année sélectionnée. */
|
||||
private readonly moisAvecDonnees = signal<Set<number>>(new Set<number>());
|
||||
/** Jours comportant au moins un trajet pour le mois sélectionné. */
|
||||
private readonly joursAvecDonnees = signal<Set<number>>(new Set<number>());
|
||||
|
||||
readonly anneeSelectionnee = signal<number | null>(null);
|
||||
readonly moisSelectionne = signal<number | null>(null);
|
||||
readonly jourSelectionne = signal<number | null>(null);
|
||||
readonly moisSelectionneSignal = signal<number | null>(null);
|
||||
|
||||
/** État d'ouverture du conteneur repliable (fermé par défaut). */
|
||||
readonly ouvert = signal(false);
|
||||
@@ -85,7 +88,7 @@ export class SelecteurTemporelComponent implements OnInit, OnChanges {
|
||||
|
||||
ngOnInit(): void {
|
||||
// Affichage garanti dès le premier rendu : au minimum l'année courante et,
|
||||
// si le parent a déjà fourni une date, la pré-sélection année/mois/jour.
|
||||
// si le parent a déjà fourni une date, la pré-sélection année/mois.
|
||||
// Ainsi le sélecteur n'est jamais vide même si l'appel réseau tarde ou échoue.
|
||||
this.annees.set(this.avecAnneeCourante([]));
|
||||
this.synchroniserSurDate(this.date);
|
||||
@@ -108,15 +111,15 @@ export class SelecteurTemporelComponent implements OnInit, OnChanges {
|
||||
}
|
||||
|
||||
/**
|
||||
* Aligne la sélection (année, mois, jour) sur la date fournie par le parent,
|
||||
* sans réémettre d'évènement. Charge les mois / jours avec données associés.
|
||||
* Aligne la sélection (année, mois) sur le jour fourni par le parent, sans
|
||||
* réémettre d'évènement. Charge les mois comportant des données associés.
|
||||
*/
|
||||
private synchroniserSurDate(dateIso: string | null): void {
|
||||
if (!dateIso) {
|
||||
return;
|
||||
}
|
||||
const [annee, mois, jour] = dateIso.slice(0, 10).split('-').map(Number);
|
||||
if (!annee || !mois || !jour) {
|
||||
const [annee, mois] = dateIso.slice(0, 10).split('-').map(Number);
|
||||
if (!annee || !mois) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -126,16 +129,11 @@ export class SelecteurTemporelComponent implements OnInit, OnChanges {
|
||||
}
|
||||
|
||||
this.anneeSelectionnee.set(annee);
|
||||
this.moisSelectionne.set(mois);
|
||||
this.jours.set(this.genererJours(annee, mois));
|
||||
this.jourSelectionne.set(jour);
|
||||
this.moisSelectionneSignal.set(mois);
|
||||
|
||||
this.trajetService.getMoisDisponibles(annee).subscribe((m) => {
|
||||
this.moisAvecDonnees.set(new Set<number>(m));
|
||||
});
|
||||
this.trajetService.getJoursDisponibles(annee, mois).subscribe((j) => {
|
||||
this.joursAvecDonnees.set(new Set<number>(j));
|
||||
});
|
||||
}
|
||||
|
||||
/** Sélectionne une année et charge les mois comportant des données. */
|
||||
@@ -144,10 +142,7 @@ export class SelecteurTemporelComponent implements OnInit, OnChanges {
|
||||
return;
|
||||
}
|
||||
this.anneeSelectionnee.set(annee);
|
||||
this.moisSelectionne.set(null);
|
||||
this.jourSelectionne.set(null);
|
||||
this.jours.set([]);
|
||||
this.joursAvecDonnees.set(new Set<number>());
|
||||
this.moisSelectionneSignal.set(null);
|
||||
this.moisAvecDonnees.set(new Set<number>());
|
||||
|
||||
this.trajetService.getMoisDisponibles(annee).subscribe((mois) => {
|
||||
@@ -155,37 +150,14 @@ export class SelecteurTemporelComponent implements OnInit, OnChanges {
|
||||
});
|
||||
}
|
||||
|
||||
/** Sélectionne un mois et charge les jours comportant des données. */
|
||||
/** Sélectionne un mois et émet `{ annee, mois }`. */
|
||||
selectionnerMois(mois: number): void {
|
||||
const annee = this.anneeSelectionnee();
|
||||
if (annee == null || this.estMoisFutur(annee, mois)) {
|
||||
return;
|
||||
}
|
||||
this.moisSelectionne.set(mois);
|
||||
this.jourSelectionne.set(null);
|
||||
this.jours.set(this.genererJours(annee, mois));
|
||||
this.joursAvecDonnees.set(new Set<number>());
|
||||
|
||||
this.trajetService.getJoursDisponibles(annee, mois).subscribe((jours) => {
|
||||
this.joursAvecDonnees.set(new Set<number>(jours));
|
||||
});
|
||||
}
|
||||
|
||||
/** Sélectionne un jour et émet la date ISO correspondante. */
|
||||
selectionnerJour(jour: number): void {
|
||||
const annee = this.anneeSelectionnee();
|
||||
const mois = this.moisSelectionne();
|
||||
if (
|
||||
annee == null ||
|
||||
mois == null ||
|
||||
this.estJourFutur(annee, mois, jour)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.jourSelectionne.set(jour);
|
||||
this.dateSelectionnee.emit(
|
||||
this.versIso(annee, mois, jour)
|
||||
);
|
||||
this.moisSelectionneSignal.set(mois);
|
||||
this.moisSelectionne.emit({ annee, mois });
|
||||
}
|
||||
|
||||
/** Un mois est en surbrillance s'il comporte au moins un trajet. */
|
||||
@@ -193,11 +165,6 @@ export class SelecteurTemporelComponent implements OnInit, OnChanges {
|
||||
return this.moisAvecDonnees().has(mois);
|
||||
}
|
||||
|
||||
/** Un jour est en surbrillance s'il comporte au moins un trajet. */
|
||||
jourADonnees(jour: number): boolean {
|
||||
return this.joursAvecDonnees().has(jour);
|
||||
}
|
||||
|
||||
/** Une année est désactivée si elle est postérieure à l'année courante. */
|
||||
estAnneeFuture(annee: number): boolean {
|
||||
return annee > this.aujourdHui.getFullYear();
|
||||
@@ -208,11 +175,6 @@ export class SelecteurTemporelComponent implements OnInit, OnChanges {
|
||||
return this.debutDeJournee(new Date(annee, mois - 1, 1)) > this.aujourdHui;
|
||||
}
|
||||
|
||||
/** Un jour est désactivé s'il est postérieur à la date courante. */
|
||||
estJourFutur(annee: number, mois: number, jour: number): boolean {
|
||||
return this.debutDeJournee(new Date(annee, mois - 1, jour)) > this.aujourdHui;
|
||||
}
|
||||
|
||||
/** Complète la liste avec l'année courante et trie en ordre décroissant, sans doublon. */
|
||||
private avecAnneeCourante(annees: number[]): number[] {
|
||||
const ensemble = new Set<number>(annees);
|
||||
@@ -220,19 +182,6 @@ export class SelecteurTemporelComponent implements OnInit, OnChanges {
|
||||
return [...ensemble].sort((a, b) => b - a);
|
||||
}
|
||||
|
||||
/** Génère la liste 1..N des jours du mois donné. */
|
||||
private genererJours(annee: number, mois: number): number[] {
|
||||
const nbJours = new Date(annee, mois, 0).getDate();
|
||||
return Array.from({ length: nbJours }, (_, i) => i + 1);
|
||||
}
|
||||
|
||||
/** Formate une date en ISO `yyyy-MM-dd` sans dérive de fuseau horaire. */
|
||||
private versIso(annee: number, mois: number, jour: number): string {
|
||||
const mm = String(mois).padStart(2, '0');
|
||||
const dd = String(jour).padStart(2, '0');
|
||||
return `${annee}-${mm}-${dd}`;
|
||||
}
|
||||
|
||||
/** Retourne une copie de la date à minuit (heure locale). */
|
||||
private debutDeJournee(date: Date): Date {
|
||||
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
||||
|
||||
Reference in New Issue
Block a user