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:
@@ -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">
|
||||||
|
<div class="carnet__entete">
|
||||||
<h1>Carnet d'adresses</h1>
|
<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. */
|
||||||
|
|||||||
Reference in New Issue
Block a user