Carnet d'adresses : ajout/modification via une popup modale

- Formulaire de création/modification affiché dans une fenêtre modale
  (bouton « Ajouter une adresse » + action « Modifier » d'une ligne)
- Fermeture au succès, sur « Annuler » ou clic sur l'arrière-plan
- Suggestions BAN rendues dans le flux du panneau pour éviter la troncature
- Specs : Exigence 15 (nouveau critère 17) + description CarnetAdressesComponent
This commit is contained in:
2026-09-23 09:59:15 +02:00
parent ee47d04b90
commit 1c65c4dc74
5 changed files with 89 additions and 11 deletions
+3 -2
View File
@@ -337,8 +337,9 @@ src/
- Montants en euros à 2 décimales - Montants en euros à 2 décimales
**`CarnetAdressesComponent`** **`CarnetAdressesComponent`**
- Liste des Adresses_Nommées (Dénomination + adresse + indication si coordonnées disponibles), triée par Dénomination - Liste des Adresses_Nommées (Dénomination + adresse + indication si coordonnées disponibles), triée par Dénomination, avec un bouton « Ajouter une adresse » au-dessus de la liste
- Formulaire de création / modification : champ `denomination` (1–100 car.) et champ `adresse` avec autocomplétion BAN (1–255 car.) - Formulaire de création / modification présenté dans une **fenêtre modale** (overlay `.modale-overlay` + panneau `.modale-panneau`, motif partagé avec `ConfirmDialogComponent` / `ConsultationJourComponent`), ouvert via le bouton « Ajouter une adresse » (mode création) ou l'action « Modifier » d'une ligne (mode édition) ; fermée par enregistrement réussi, par le bouton « Annuler », ou par un clic sur l'arrière-plan. Le signal `modaleOuverte` pilote l'affichage
- Champ `denomination` (1–100 car.) et champ `adresse` avec autocomplétion BAN (1–255 car.) ; dans la modale, les suggestions BAN sont rendues **dans le flux** du panneau (et non en position absolue) pour éviter toute troncature par le défilement du panneau
- **Autocomplétion BAN** : appel à `https://api-adresse.data.gouv.fr/search/?q={saisie}&limit=5` dès 3 caractères saisis, avec debounce de 300 ms, maximum 5 suggestions ; à la sélection, le libellé normalisé remplit le champ et les coordonnées `(lat, lon)` sont conservées pour transmission - **Autocomplétion BAN** : appel à `https://api-adresse.data.gouv.fr/search/?q={saisie}&limit=5` dès 3 caractères saisis, avec debounce de 300 ms, maximum 5 suggestions ; à la sélection, le libellé normalisé remplit le champ et les coordonnées `(lat, lon)` sont conservées pour transmission
- Actions par ligne : modifier (→ formulaire pré-rempli), supprimer (→ dialog de confirmation) - Actions par ligne : modifier (→ formulaire pré-rempli), supprimer (→ dialog de confirmation)
- À chaque succès de création, modification ou suppression, affiche un message de confirmation via `NotificationService` - À chaque succès de création, modification ou suppression, affiche un message de confirmation via `NotificationService`
@@ -376,6 +376,7 @@ L'application est composée d'un frontend Angular/TypeScript et d'un backend C#.
14. WHEN l'utilisateur confirme la suppression d'une Adresse_Nommee, THE Backend SHALL supprimer l'Adresse_Nommee du Carnet_Adresses en conservant inchangés les Trajets déjà enregistrés, qui gardent leur texte d'adresse et leurs coordonnées géographiques stockés. 14. WHEN l'utilisateur confirme la suppression d'une Adresse_Nommee, THE Backend SHALL supprimer l'Adresse_Nommee du Carnet_Adresses en conservant inchangés les Trajets déjà enregistrés, qui gardent leur texte d'adresse et leurs coordonnées géographiques stockés.
15. WHEN le Backend a créé, modifié ou supprimé une Adresse_Nommee avec succès, THE Frontend SHALL afficher un message de confirmation indiquant que l'opération a réussi. 15. WHEN le Backend a créé, modifié ou supprimé une Adresse_Nommee avec succès, THE Frontend SHALL afficher un message de confirmation indiquant que l'opération a réussi.
16. WHEN le Frontend affiche le formulaire de saisie ou d'insertion d'un Trajet, THE Frontend SHALL proposer la liste des Adresses_Nommées du Carnet_Adresses par leur Dénomination pour le champ adresse de départ et pour le champ adresse d'arrivée. 16. WHEN le Frontend affiche le formulaire de saisie ou d'insertion d'un Trajet, THE Frontend SHALL proposer la liste des Adresses_Nommées du Carnet_Adresses par leur Dénomination pour le champ adresse de départ et pour le champ adresse d'arrivée.
17. WHEN l'utilisateur active la création d'une Adresse_Nommee (bouton « Ajouter une adresse ») ou la modification d'une Adresse_Nommee existante depuis la liste, THE Frontend SHALL présenter le formulaire de saisie dans une fenêtre modale superposée à la liste, et SHALL fermer cette fenêtre modale après un enregistrement réussi, après annulation par l'utilisateur, ou lors d'un clic sur l'arrière-plan de la fenêtre modale.
--- ---
@@ -1,8 +1,18 @@
<section class="carnet"> <section class="carnet">
<h1>Carnet d'adresses</h1> <div class="carnet__entete">
<h1>Carnet d'adresses</h1>
<button type="button" class="btn--ajouter" (click)="ouvrirCreation()">
+ Ajouter une adresse
</button>
</div>
<!-- Formulaire de création / modification --> <!-- Popup de création / modification -->
<form class="carnet__form" [formGroup]="form" (ngSubmit)="enregistrer()"> <div
class="modale-overlay"
*ngIf="modaleOuverte()"
(click)="fermerSurOverlay($event)"
>
<form class="modale-panneau carnet__form" [formGroup]="form" (ngSubmit)="enregistrer()">
<h2>{{ enEdition ? 'Modifier une adresse' : 'Ajouter une adresse' }}</h2> <h2>{{ enEdition ? 'Modifier une adresse' : 'Ajouter une adresse' }}</h2>
<div class="field"> <div class="field">
@@ -56,11 +66,12 @@
<button type="submit" [disabled]="form.invalid || enregistrement()"> <button type="submit" [disabled]="form.invalid || enregistrement()">
{{ enEdition ? 'Enregistrer' : 'Ajouter' }} {{ enEdition ? 'Enregistrer' : 'Ajouter' }}
</button> </button>
<button type="button" class="btn--secondary" (click)="annuler()" *ngIf="enEdition"> <button type="button" class="btn--secondary" (click)="annuler()">
Annuler Annuler
</button> </button>
</div> </div>
</form> </form>
</div>
<!-- Liste des adresses nommées, triée par dénomination --> <!-- Liste des adresses nommées, triée par dénomination -->
<div class="carnet__liste"> <div class="carnet__liste">
@@ -17,14 +17,57 @@
} }
} }
.carnet__entete {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
}
.btn--ajouter {
background: #0074b8;
color: #fff;
white-space: nowrap;
&:hover {
background: #f28c28;
}
}
.modale-overlay {
position: fixed;
inset: 0;
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
background: rgba(0, 0, 0, 0.45);
}
.modale-panneau {
width: 100%;
max-width: 480px;
max-height: 90vh;
overflow-y: auto;
background: #fff;
border-radius: 10px;
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.25);
}
// Dans la popup, l'overlay 'overflow-y: auto' du panneau tronque un menu
// positionné en absolu : on affiche donc les suggestions dans le flux normal.
.modale-panneau .suggestions {
position: static;
box-shadow: none;
max-height: 180px;
}
.carnet__form { .carnet__form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1rem; gap: 1rem;
padding: 1rem; padding: 1.25rem;
border: 1px solid #dcdcdc;
border-radius: 8px;
background: #fafafa;
} }
.field { .field {
@@ -60,6 +60,9 @@ export class CarnetAdressesComponent implements OnInit, OnDestroy {
/** Identifiant de l'adresse en cours d'édition (null = création). */ /** Identifiant de l'adresse en cours d'édition (null = création). */
readonly editionId = signal<number | null>(null); readonly editionId = signal<number | null>(null);
/** Ouverture de la popup de création / modification. */
readonly modaleOuverte = signal(false);
/** Suggestions BAN courantes pour le champ adresse. */ /** Suggestions BAN courantes pour le champ adresse. */
readonly suggestions = signal<BANFeature[]>([]); readonly suggestions = signal<BANFeature[]>([]);
@@ -131,6 +134,24 @@ export class CarnetAdressesComponent implements OnInit, OnDestroy {
denomination: adresse.denomination, denomination: adresse.denomination,
adresse: adresse.adresse, adresse: adresse.adresse,
}); });
this.modaleOuverte.set(true);
}
/** Ouvre la popup en mode création (formulaire vierge). */
ouvrirCreation(): void {
this.editionId.set(null);
this.latitude = null;
this.longitude = null;
this.suggestions.set([]);
this.form.reset({ denomination: '', adresse: '' });
this.modaleOuverte.set(true);
}
/** Ferme la popup uniquement lors d'un clic sur le fond (pas sur le panneau). */
fermerSurOverlay(event: MouseEvent): void {
if (event.target === event.currentTarget) {
this.annuler();
}
} }
/** Réinitialise le formulaire en mode création. */ /** Réinitialise le formulaire en mode création. */
@@ -140,6 +161,7 @@ export class CarnetAdressesComponent implements OnInit, OnDestroy {
this.longitude = null; this.longitude = null;
this.suggestions.set([]); this.suggestions.set([]);
this.form.reset({ denomination: '', adresse: '' }); this.form.reset({ denomination: '', adresse: '' });
this.modaleOuverte.set(false);
} }
/** Réagit à la saisie du champ adresse pour alimenter l'autocomplétion BAN. */ /** Réagit à la saisie du champ adresse pour alimenter l'autocomplétion BAN. */