feat: Gestion des secteurs et migration v3.0.4+304
- Ajout système complet de gestion des secteurs avec contours géographiques - Import des contours départementaux depuis GeoJSON - API REST pour la gestion des secteurs (/api/sectors) - Service de géolocalisation pour déterminer les secteurs - Migration base de données avec tables x_departements_contours et sectors_adresses - Interface Flutter pour visualisation et gestion des secteurs - Ajout thème sombre dans l'application - Corrections diverses et optimisations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
180
app/lib/presentation/widgets/passage_validation_helpers.dart
Executable file
180
app/lib/presentation/widgets/passage_validation_helpers.dart
Executable file
@@ -0,0 +1,180 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Helpers de validation pour le formulaire de passage
|
||||
class PassageValidationHelpers {
|
||||
|
||||
/// Validation pour numéro de rue
|
||||
static String? validateNumero(String? value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'Le numéro est obligatoire';
|
||||
}
|
||||
|
||||
final numero = int.tryParse(value.trim());
|
||||
if (numero == null || numero <= 0) {
|
||||
return 'Numéro invalide';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Validation pour rue
|
||||
static String? validateRue(String? value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'La rue est obligatoire';
|
||||
}
|
||||
|
||||
if (value.trim().length < 3) {
|
||||
return 'La rue doit contenir au moins 3 caractères';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Validation pour ville
|
||||
static String? validateVille(String? value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'La ville est obligatoire';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Validation pour nom d'occupant (selon type de passage)
|
||||
static String? validateNomOccupant(String? value, int? passageType) {
|
||||
// Nom obligatoire seulement pour les passages effectués (type 1)
|
||||
if (passageType == 1) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'Le nom est obligatoire pour les passages effectués';
|
||||
}
|
||||
|
||||
if (value.trim().length < 2) {
|
||||
return 'Le nom doit contenir au moins 2 caractères';
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Validation pour email
|
||||
static String? validateEmail(String? value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return null; // Email optionnel
|
||||
}
|
||||
|
||||
const emailRegex = r'^[^@]+@[^@]+\.[^@]+$';
|
||||
if (!RegExp(emailRegex).hasMatch(value.trim())) {
|
||||
return 'Format email invalide';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Validation pour téléphone
|
||||
static String? validatePhone(String? value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return null; // Téléphone optionnel
|
||||
}
|
||||
|
||||
// Enlever espaces et tirets
|
||||
final cleanPhone = value.replaceAll(RegExp(r'[\s\-\.]'), '');
|
||||
|
||||
// Vérifier format français basique
|
||||
if (cleanPhone.length < 10) {
|
||||
return 'Numéro trop court';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Validation pour montant (selon type de passage)
|
||||
static String? validateMontant(String? value, int? passageType) {
|
||||
// Montant obligatoire seulement pour types 1 (Effectué) et 5 (Lot)
|
||||
if (passageType == 1 || passageType == 5) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return 'Le montant est obligatoire pour ce type';
|
||||
}
|
||||
|
||||
final montant = double.tryParse(value.replaceAll(',', '.'));
|
||||
if (montant == null) {
|
||||
return 'Montant invalide';
|
||||
}
|
||||
|
||||
if (montant <= 0) {
|
||||
return 'Le montant doit être supérieur à 0';
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Validation pour remarque
|
||||
static String? validateRemarque(String? value) {
|
||||
if (value != null && value.length > 500) {
|
||||
return 'La remarque ne peut pas dépasser 500 caractères';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Focus sur le premier champ en erreur avec scroll
|
||||
static void focusFirstError(BuildContext context, GlobalKey<FormState> formKey) {
|
||||
// Déclencher la validation
|
||||
if (!formKey.currentState!.validate()) {
|
||||
// Flutter met automatiquement le focus sur le premier champ en erreur
|
||||
// Mais on peut ajouter un scroll pour s'assurer que le champ est visible
|
||||
|
||||
// Attendre que le focus soit mis
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
// Trouver le champ qui a le focus
|
||||
final FocusNode? focusedNode = FocusScope.of(context).focusedChild;
|
||||
if (focusedNode != null) {
|
||||
// Scroll vers le champ focusé
|
||||
Scrollable.ensureVisible(
|
||||
focusedNode.context!,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Validation globale du formulaire de passage
|
||||
static bool validatePassageForm({
|
||||
required GlobalKey<FormState> formKey,
|
||||
required BuildContext context,
|
||||
required int? selectedPassageType,
|
||||
required int fkTypeReglement,
|
||||
String? montant,
|
||||
String? nomOccupant,
|
||||
}) {
|
||||
// Validation des champs via les validators des TextFormField
|
||||
if (!formKey.currentState!.validate()) {
|
||||
// Le focus est automatiquement mis sur le premier champ en erreur
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validations spécifiques métier (comme dans l'original)
|
||||
if (selectedPassageType == 1) {
|
||||
if (nomOccupant == null || nomOccupant.trim().isEmpty) {
|
||||
// Ici on pourrait aussi mettre le focus sur le champ nom
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedPassageType == 1 || selectedPassageType == 5) {
|
||||
final montantValue = double.tryParse(montant?.replaceAll(',', '.') ?? '');
|
||||
if (montantValue == null || montantValue <= 0) {
|
||||
// Focus sur montant si erreur
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fkTypeReglement < 1 || fkTypeReglement > 3) {
|
||||
// Focus sur dropdown type règlement
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user