fix(forms): erreurs de validation par champ au blur

- Erreurs affichées sous chaque champ obligatoire (fr-error-text DSFR)
  au lieu d'un message global en haut du formulaire
- Déclenchement sur blur (sortie du champ) et effacement sur input
- Concerne la modale édition API et la modale catégorie

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
z3n
2026-06-09 13:17:18 +00:00
parent af02cf6808
commit abc20bc9e8
2 changed files with 27 additions and 12 deletions

View File

@@ -242,13 +242,19 @@ import { ApiEntry, Category } from '@datacat/shared';
@if (editError()) { @if (editError()) {
<div class="fr-alert fr-alert--error fr-mb-2w"><p>{{ editError() }}</p></div> <div class="fr-alert fr-alert--error fr-mb-2w"><p>{{ editError() }}</p></div>
} }
<div class="fr-input-group fr-mb-2w"> <div class="fr-input-group fr-mb-2w" [class.fr-input-group--error]="editTitleError()">
<label class="fr-label" for="edit-title">Titre <span style="color:var(--text-default-error)">*</span></label> <label class="fr-label" for="edit-title">Titre <span style="color:var(--text-default-error)">*</span></label>
<input id="edit-title" class="fr-input" type="text" [value]="editTitle()" (input)="editTitle.set($any($event.target).value)" /> <input id="edit-title" class="fr-input" [class.fr-input--error]="editTitleError()" type="text" [value]="editTitle()"
(input)="editTitle.set($any($event.target).value); editTitleError.set(false)"
(blur)="editTitleError.set(!editTitle().trim())" />
@if (editTitleError()) { <p class="fr-error-text">Le titre est obligatoire.</p> }
</div> </div>
<div class="fr-input-group fr-mb-2w"> <div class="fr-input-group fr-mb-2w" [class.fr-input-group--error]="editVersionError()">
<label class="fr-label" for="edit-version">Version <span style="color:var(--text-default-error)">*</span></label> <label class="fr-label" for="edit-version">Version <span style="color:var(--text-default-error)">*</span></label>
<input id="edit-version" class="fr-input" type="text" [value]="editVersion()" (input)="editVersion.set($any($event.target).value)" /> <input id="edit-version" class="fr-input" [class.fr-input--error]="editVersionError()" type="text" [value]="editVersion()"
(input)="editVersion.set($any($event.target).value); editVersionError.set(false)"
(blur)="editVersionError.set(!editVersion().trim())" />
@if (editVersionError()) { <p class="fr-error-text">La version est obligatoire.</p> }
</div> </div>
<div class="fr-select-group fr-mb-2w"> <div class="fr-select-group fr-mb-2w">
<label class="fr-label" for="edit-category">Catégorie</label> <label class="fr-label" for="edit-category">Catégorie</label>
@@ -315,6 +321,8 @@ export class ApiDetailComponent implements OnInit, OnDestroy {
showEditModal = signal(false); showEditModal = signal(false);
editLoading = signal(false); editLoading = signal(false);
editError = signal<string | null>(null); editError = signal<string | null>(null);
editTitleError = signal(false);
editVersionError = signal(false);
categories = signal<Category[]>([]); categories = signal<Category[]>([]);
editTitle = signal(''); editTitle = signal('');
editVersion = signal(''); editVersion = signal('');
@@ -365,6 +373,8 @@ export class ApiDetailComponent implements OnInit, OnDestroy {
this.editContactTechnical.set(entry.contactTechnical ?? ''); this.editContactTechnical.set(entry.contactTechnical ?? '');
this.editAccessRights.set(entry.accessRights ?? ''); this.editAccessRights.set(entry.accessRights ?? '');
this.editError.set(null); this.editError.set(null);
this.editTitleError.set(false);
this.editVersionError.set(false);
this.categoryService.list().subscribe((res) => this.categories.set(res.items)); this.categoryService.list().subscribe((res) => this.categories.set(res.items));
this.showEditModal.set(true); this.showEditModal.set(true);
} }
@@ -374,10 +384,9 @@ export class ApiDetailComponent implements OnInit, OnDestroy {
} }
submitEdit() { submitEdit() {
if (!this.editTitle().trim() || !this.editVersion().trim()) { this.editTitleError.set(!this.editTitle().trim());
this.editError.set('Le titre et la version sont obligatoires.'); this.editVersionError.set(!this.editVersion().trim());
return; if (!this.editTitle().trim() || !this.editVersion().trim()) return;
}
this.editLoading.set(true); this.editLoading.set(true);
this.apiService.update(this.id, { this.apiService.update(this.id, {
title: this.editTitle().trim(), title: this.editTitle().trim(),

View File

@@ -246,13 +246,15 @@ import { forkJoin } from 'rxjs';
<p>{{ formError() }}</p> <p>{{ formError() }}</p>
</div> </div>
} }
<div class="fr-input-group fr-mb-2w"> <div class="fr-input-group fr-mb-2w" [class.fr-input-group--error]="formNameError()">
<label class="fr-label" for="form-name"> <label class="fr-label" for="form-name">
Nom <span style="color:var(--text-default-error)">*</span> Nom <span style="color:var(--text-default-error)">*</span>
</label> </label>
<input id="form-name" class="fr-input" type="text" <input id="form-name" class="fr-input" [class.fr-input--error]="formNameError()" type="text"
[value]="formName()" [value]="formName()"
(input)="formName.set($any($event.target).value)" /> (input)="formName.set($any($event.target).value); formNameError.set(false)"
(blur)="formNameError.set(!formName().trim())" />
@if (formNameError()) { <p class="fr-error-text">Le nom est obligatoire.</p> }
</div> </div>
<div class="fr-input-group fr-mb-3w"> <div class="fr-input-group fr-mb-3w">
<label class="fr-label" for="form-desc"> <label class="fr-label" for="form-desc">
@@ -307,6 +309,7 @@ export class BrowseComponent implements OnInit {
formName = signal(''); formName = signal('');
formDescription = signal(''); formDescription = signal('');
formError = signal<string | null>(null); formError = signal<string | null>(null);
formNameError = signal(false);
formLoading = signal(false); formLoading = signal(false);
/* Ancêtres = breadcrumb sans le nœud courant (déjà dans le h1) */ /* Ancêtres = breadcrumb sans le nœud courant (déjà dans le h1) */
@@ -346,6 +349,7 @@ export class BrowseComponent implements OnInit {
this.formName.set(''); this.formName.set('');
this.formDescription.set(''); this.formDescription.set('');
this.formError.set(null); this.formError.set(null);
this.formNameError.set(false);
this.showFormModal.set(true); this.showFormModal.set(true);
} }
@@ -355,6 +359,7 @@ export class BrowseComponent implements OnInit {
this.formName.set(cat.name); this.formName.set(cat.name);
this.formDescription.set(cat.description ?? ''); this.formDescription.set(cat.description ?? '');
this.formError.set(null); this.formError.set(null);
this.formNameError.set(false);
this.showFormModal.set(true); this.showFormModal.set(true);
} }
@@ -364,9 +369,10 @@ export class BrowseComponent implements OnInit {
submitForm() { submitForm() {
if (!this.formName().trim()) { if (!this.formName().trim()) {
this.formError.set('Le nom est obligatoire.'); this.formNameError.set(true);
return; return;
} }
this.formNameError.set(false);
this.formLoading.set(true); this.formLoading.set(true);
const name = this.formName().trim(); const name = this.formName().trim();
const description = this.formDescription().trim() || undefined; const description = this.formDescription().trim() || undefined;