import 'package:flutter/material.dart'; import 'package:geosector_app/core/theme/app_theme.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:geosector_app/core/data/models/passage_model.dart'; import 'package:geosector_app/core/repositories/passage_repository.dart'; import 'package:geosector_app/core/repositories/user_repository.dart'; import 'package:geosector_app/core/repositories/operation_repository.dart'; import 'package:geosector_app/core/utils/api_exception.dart'; import 'package:geosector_app/core/constants/app_keys.dart'; import 'package:geosector_app/presentation/widgets/custom_text_field.dart'; import 'package:geosector_app/presentation/widgets/form_section.dart'; class PassageFormDialog extends StatefulWidget { final PassageModel? passage; final String title; final bool readOnly; final PassageRepository passageRepository; final UserRepository userRepository; final OperationRepository operationRepository; final VoidCallback? onSuccess; const PassageFormDialog({ super.key, this.passage, required this.title, this.readOnly = false, required this.passageRepository, required this.userRepository, required this.operationRepository, this.onSuccess, }); @override State createState() => _PassageFormDialogState(); } class _PassageFormDialogState extends State { final _formKey = GlobalKey(); bool _isSubmitting = false; // Sélection du type de passage int? _selectedPassageType; bool _showForm = false; // Controllers late final TextEditingController _numeroController; late final TextEditingController _rueBisController; late final TextEditingController _rueController; late final TextEditingController _villeController; late final TextEditingController _nameController; late final TextEditingController _emailController; late final TextEditingController _phoneController; late final TextEditingController _montantController; late final TextEditingController _apptController; late final TextEditingController _niveauController; late final TextEditingController _residenceController; late final TextEditingController _remarqueController; late final TextEditingController _dateController; late final TextEditingController _timeController; // Form values int _fkHabitat = 1; // Par défaut Maison int _fkTypeReglement = 4; // Par défaut Non renseigné DateTime _passedAt = DateTime.now(); // Date et heure de passage // Helpers de validation 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; } 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; } String? _validateVille(String? value) { if (value == null || value.trim().isEmpty) { return 'La ville est obligatoire'; } return null; } String? _validateNomOccupant(String? value) { if (_selectedPassageType == 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; } 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; } String? _validateMontant(String? value) { if (_selectedPassageType == 1 || _selectedPassageType == 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; } @override void initState() { super.initState(); try { debugPrint('=== DEBUT PassageFormDialog.initState ==='); // Initialize controllers with passage data if available final passage = widget.passage; debugPrint('Passage reçu: ${passage != null}'); if (passage != null) { debugPrint('Passage ID: ${passage.id}'); debugPrint('Passage fkType: ${passage.fkType}'); debugPrint('Passage numero: ${passage.numero}'); debugPrint('Passage rueBis: ${passage.rueBis}'); debugPrint('Passage rue: ${passage.rue}'); debugPrint('Passage ville: ${passage.ville}'); debugPrint('Passage name: ${passage.name}'); debugPrint('Passage email: ${passage.email}'); debugPrint('Passage phone: ${passage.phone}'); debugPrint('Passage montant: ${passage.montant}'); debugPrint('Passage remarque: ${passage.remarque}'); debugPrint('Passage fkHabitat: ${passage.fkHabitat}'); debugPrint('Passage fkTypeReglement: ${passage.fkTypeReglement}'); } _selectedPassageType = passage?.fkType; _showForm = false; // Toujours commencer par la sélection de type _fkHabitat = passage?.fkHabitat ?? 1; _fkTypeReglement = passage?.fkTypeReglement ?? 4; debugPrint('Initialisation des controllers...'); // S'assurer que toutes les valeurs null deviennent des chaînes vides final String numero = passage?.numero.toString() ?? ''; final String rueBis = passage?.rueBis.toString() ?? ''; final String rue = passage?.rue.toString() ?? ''; final String ville = passage?.ville.toString() ?? ''; final String name = passage?.name.toString() ?? ''; final String email = passage?.email.toString() ?? ''; final String phone = passage?.phone.toString() ?? ''; // Pour le montant, afficher vide si c'est 0.00 final String montantRaw = passage?.montant.toString() ?? '0.00'; final String montant = (montantRaw == '0.00' || montantRaw == '0' || montantRaw == '0.0') ? '' : montantRaw; final String appt = passage?.appt.toString() ?? ''; final String niveau = passage?.niveau.toString() ?? ''; final String residence = passage?.residence.toString() ?? ''; final String remarque = passage?.remarque.toString() ?? ''; // Initialiser la date de passage _passedAt = passage?.passedAt ?? DateTime.now(); final String dateFormatted = '${_passedAt.day.toString().padLeft(2, '0')}/${_passedAt.month.toString().padLeft(2, '0')}/${_passedAt.year}'; final String timeFormatted = '${_passedAt.hour.toString().padLeft(2, '0')}:${_passedAt.minute.toString().padLeft(2, '0')}'; debugPrint('Valeurs pour controllers:'); debugPrint(' numero: "$numero"'); debugPrint(' rueBis: "$rueBis"'); debugPrint(' rue: "$rue"'); debugPrint(' ville: "$ville"'); debugPrint(' name: "$name"'); debugPrint(' email: "$email"'); debugPrint(' phone: "$phone"'); debugPrint(' montant: "$montant"'); debugPrint(' remarque: "$remarque"'); debugPrint(' passedAt: "$_passedAt"'); debugPrint(' dateFormatted: "$dateFormatted"'); debugPrint(' timeFormatted: "$timeFormatted"'); _numeroController = TextEditingController(text: numero); _rueBisController = TextEditingController(text: rueBis); _rueController = TextEditingController(text: rue); _villeController = TextEditingController(text: ville); _nameController = TextEditingController(text: name); _emailController = TextEditingController(text: email); _phoneController = TextEditingController(text: phone); _montantController = TextEditingController(text: montant); _apptController = TextEditingController(text: appt); _niveauController = TextEditingController(text: niveau); _residenceController = TextEditingController(text: residence); _remarqueController = TextEditingController(text: remarque); _dateController = TextEditingController(text: dateFormatted); _timeController = TextEditingController(text: timeFormatted); debugPrint('=== FIN PassageFormDialog.initState ==='); } catch (e, stackTrace) { debugPrint('=== ERREUR PassageFormDialog.initState ==='); debugPrint('Erreur: $e'); debugPrint('StackTrace: $stackTrace'); rethrow; } } @override void dispose() { _numeroController.dispose(); _rueBisController.dispose(); _rueController.dispose(); _villeController.dispose(); _nameController.dispose(); _emailController.dispose(); _phoneController.dispose(); _montantController.dispose(); _apptController.dispose(); _niveauController.dispose(); _residenceController.dispose(); _remarqueController.dispose(); _dateController.dispose(); _timeController.dispose(); super.dispose(); } void _selectPassageType(int typeId) { setState(() { _selectedPassageType = typeId; _showForm = true; // Réinitialiser montant et type de règlement selon le type de passage if (typeId == 1 || typeId == 5) { // Pour "Effectué" (1) ou "Lot" (5), laisser le choix à l'utilisateur // Ne pas forcer un type de règlement spécifique } else { // Pour tous les autres types, réinitialiser _montantController.text = ''; _fkTypeReglement = 4; // Non renseigné } // Si c'est un nouveau passage et qu'on change de type, réinitialiser la date à maintenant if (widget.passage == null) { _passedAt = DateTime.now(); _dateController.text = '${_passedAt.day.toString().padLeft(2, '0')}/${_passedAt.month.toString().padLeft(2, '0')}/${_passedAt.year}'; _timeController.text = '${_passedAt.hour.toString().padLeft(2, '0')}:${_passedAt.minute.toString().padLeft(2, '0')}'; } }); } void _handleSubmit() async { if (_isSubmitting) return; // ✅ Validation intégrée avec focus automatique sur erreur if (!_formKey.currentState!.validate()) { // Le focus est automatiquement mis sur le premier champ en erreur // Les bordures rouges et messages d'erreur sont affichés automatiquement return; } setState(() { _isSubmitting = true; }); try { final currentUser = widget.userRepository.getCurrentUser(); if (currentUser == null) { throw Exception("Utilisateur non connecté"); } final currentOperation = widget.operationRepository.getCurrentOperation(); if (currentOperation == null && widget.passage == null) { throw Exception("Aucune opération active trouvée"); } // Déterminer les valeurs de montant et type de règlement selon le type de passage final String finalMontant = (_selectedPassageType == 1 || _selectedPassageType == 5) ? _montantController.text.trim() : '0'; // Déterminer le type de règlement final selon le type de passage final int finalTypeReglement; if (_selectedPassageType == 1 || _selectedPassageType == 5) { // Pour les types 1 et 5, utiliser la valeur sélectionnée (qui a été validée) finalTypeReglement = _fkTypeReglement; } else { // Pour tous les autres types, forcer "Non renseigné" finalTypeReglement = 4; } final passageData = widget.passage?.copyWith( fkType: _selectedPassageType!, numero: _numeroController.text.trim(), rueBis: _rueBisController.text.trim(), rue: _rueController.text.trim(), ville: _villeController.text.trim(), name: _nameController.text.trim(), email: _emailController.text.trim(), phone: _phoneController.text.trim(), montant: finalMontant, fkHabitat: _fkHabitat, appt: _apptController.text.trim(), niveau: _niveauController.text.trim(), residence: _residenceController.text.trim(), remarque: _remarqueController.text.trim(), fkTypeReglement: finalTypeReglement, passedAt: _passedAt, lastSyncedAt: DateTime.now(), ) ?? PassageModel( id: 0, // Nouveau passage fkOperation: currentOperation!.id, // Opération active fkSector: 0, // Secteur par défaut fkUser: currentUser.id, // Utilisateur actuel fkType: _selectedPassageType!, fkAdresse: "0", // Adresse par défaut pour nouveau passage passedAt: _passedAt, numero: _numeroController.text.trim(), rue: _rueController.text.trim(), rueBis: _rueBisController.text.trim(), ville: _villeController.text.trim(), residence: _residenceController.text.trim(), fkHabitat: _fkHabitat, appt: _apptController.text.trim(), niveau: _niveauController.text.trim(), gpsLat: '0.0', // GPS par défaut gpsLng: '0.0', // GPS par défaut nomRecu: _nameController.text.trim(), remarque: _remarqueController.text.trim(), montant: finalMontant, fkTypeReglement: finalTypeReglement, emailErreur: '', nbPassages: 1, name: _nameController.text.trim(), email: _emailController.text.trim(), phone: _phoneController.text.trim(), lastSyncedAt: DateTime.now(), isActive: true, isSynced: false, ); final success = widget.passage == null ? await widget.passageRepository.createPassage(passageData) : await widget.passageRepository.updatePassage(passageData); if (success && mounted) { Future.delayed(const Duration(milliseconds: 200), () { if (mounted) { Navigator.of(context, rootNavigator: false).pop(); widget.onSuccess?.call(); Future.delayed(const Duration(milliseconds: 100), () { if (mounted) { ApiException.showSuccess( context, widget.passage == null ? "Nouveau passage créé avec succès" : "Passage modifié avec succès", ); } }); } }); } else if (mounted) { ApiException.showError( context, Exception(widget.passage == null ? "Échec de la création du passage" : "Échec de la mise à jour du passage"), ); } } catch (e) { if (mounted) { ApiException.showError(context, e); } } finally { if (mounted) { setState(() { _isSubmitting = false; }); } } } Widget _buildPassageTypeSelection() { final theme = Theme.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Type de passage', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, color: theme.colorScheme.primary, ), ), const SizedBox(height: 16), GridView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, childAspectRatio: MediaQuery.of(context).size.width < 600 ? 1.8 : 2.5, crossAxisSpacing: 12, mainAxisSpacing: 12, ), itemCount: AppKeys.typesPassages.length, itemBuilder: (context, index) { try { final typeId = AppKeys.typesPassages.keys.elementAt(index); final typeData = AppKeys.typesPassages[typeId]; if (typeData == null) { debugPrint('ERREUR: typeData null pour typeId: $typeId'); return const SizedBox(); } final isSelected = _selectedPassageType == typeId; return InkWell( onTap: widget.readOnly ? null : () => _selectPassageType(typeId), borderRadius: BorderRadius.circular(12), child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Color(typeData['couleur2'] as int? ?? 0xFF000000) .withValues(alpha: 0.15), border: Border.all( color: Color(typeData['couleur2'] as int? ?? 0xFF000000), width: isSelected ? 3 : 2, ), borderRadius: BorderRadius.circular(12), boxShadow: isSelected ? [ BoxShadow( color: Color(typeData['couleur2'] as int? ?? 0xFF000000) .withValues(alpha: 0.2), blurRadius: 8, offset: const Offset(0, 2), ) ] : null, ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( typeData['icon_data'] as IconData? ?? Icons.help, color: Color(typeData['couleur2'] as int? ?? 0xFF000000), size: 36, // Icône plus grande ), const SizedBox(height: 8), Text( typeData['titre'] as String? ?? 'Type inconnu', style: theme.textTheme.bodyMedium?.copyWith( fontWeight: isSelected ? FontWeight.bold : FontWeight.w600, color: isSelected ? Color( typeData['couleur2'] as int? ?? 0xFF000000) : theme.colorScheme.onSurface, ), textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, maxLines: 2, ), ], ), ), ); } catch (e) { debugPrint('ERREUR dans itemBuilder pour index $index: $e'); return const SizedBox(); } }, ), ], ); } Widget _buildPassageForm() { try { debugPrint('=== DEBUT _buildPassageForm ==='); debugPrint('Building Form...'); return Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Section Date et Heure FormSection( title: 'Date et Heure de passage', icon: Icons.schedule, children: [ Row( children: [ Expanded( child: CustomTextField( controller: _dateController, label: "Date", isRequired: true, readOnly: true, showLabel: false, hintText: "DD/MM/YYYY", suffixIcon: const Icon(Icons.calendar_today), onTap: widget.readOnly ? null : _selectDate, ), ), const SizedBox(width: 12), Expanded( child: CustomTextField( controller: _timeController, label: "Heure", isRequired: true, readOnly: true, showLabel: false, hintText: "HH:MM", suffixIcon: const Icon(Icons.access_time), onTap: widget.readOnly ? null : _selectTime, ), ), ], ), ], ), const SizedBox(height: 24), // Section Adresse FormSection( title: 'Adresse', icon: Icons.location_on, children: [ Row( children: [ Expanded( flex: 1, child: CustomTextField( controller: _numeroController, label: "Numéro", isRequired: true, showLabel: false, textAlign: TextAlign.right, keyboardType: TextInputType.number, readOnly: widget.readOnly, validator: _validateNumero, ), ), const SizedBox(width: 12), Expanded( flex: 1, child: CustomTextField( controller: _rueBisController, label: "Bis, Ter...", showLabel: false, readOnly: widget.readOnly, ), ), ], ), const SizedBox(height: 16), CustomTextField( controller: _rueController, label: "Rue", isRequired: true, showLabel: false, readOnly: widget.readOnly, validator: _validateRue, ), const SizedBox(height: 16), CustomTextField( controller: _villeController, label: "Ville", isRequired: true, showLabel: false, readOnly: widget.readOnly, validator: _validateVille, ), ], ), const SizedBox(height: 24), // Section Habitat FormSection( title: 'Habitat', icon: Icons.home, children: [ // Boutons radio pour le type d'habitat Row( children: [ Expanded( child: RadioListTile( title: const Text('Maison'), value: 1, groupValue: _fkHabitat, onChanged: widget.readOnly ? null : (value) { setState(() { _fkHabitat = value!; }); }, contentPadding: EdgeInsets.zero, ), ), Expanded( child: RadioListTile( title: const Text('Appart'), value: 2, groupValue: _fkHabitat, onChanged: widget.readOnly ? null : (value) { setState(() { _fkHabitat = value!; }); }, contentPadding: EdgeInsets.zero, ), ), ], ), // Champs spécifiques aux appartements if (_fkHabitat == 2) ...[ const SizedBox(height: 16), Row( children: [ Expanded( flex: 1, child: TextField( controller: _niveauController, decoration: const InputDecoration( labelText: "Niveau", border: OutlineInputBorder(), ), maxLength: 5, readOnly: widget.readOnly, ), ), const SizedBox(width: 12), Expanded( flex: 1, child: TextField( controller: _apptController, decoration: const InputDecoration( labelText: "Appt", border: OutlineInputBorder(), ), maxLength: 5, readOnly: widget.readOnly, ), ), ], ), const SizedBox(height: 16), TextField( controller: _residenceController, decoration: const InputDecoration( labelText: "Résidence", border: OutlineInputBorder(), ), maxLength: 50, readOnly: widget.readOnly, ), ], ], ), const SizedBox(height: 24), // Section Occupant FormSection( title: 'Occupant', icon: Icons.person, children: [ CustomTextField( controller: _nameController, label: _selectedPassageType == 1 ? "Nom de l'occupant" : "Nom de l'occupant", isRequired: _selectedPassageType == 1, showLabel: false, readOnly: widget.readOnly, validator: _validateNomOccupant, ), const SizedBox(height: 16), Row( children: [ Expanded( child: CustomTextField( controller: _emailController, label: "Email", showLabel: false, keyboardType: TextInputType.emailAddress, readOnly: widget.readOnly, validator: _validateEmail, prefixIcon: Icons.email, ), ), const SizedBox(width: 12), Expanded( child: CustomTextField( controller: _phoneController, label: "Téléphone", showLabel: false, keyboardType: TextInputType.phone, readOnly: widget.readOnly, prefixIcon: Icons.phone, ), ), ], ), ], ), const SizedBox(height: 24), // Section Règlement et Remarque FormSection( title: (_selectedPassageType == 1 || _selectedPassageType == 5) ? 'Règlement et Note' : 'Note', icon: Icons.note, children: [ // Afficher montant et type de règlement seulement pour fkType 1 (Effectué) ou 5 (Lot) if (_selectedPassageType == 1 || _selectedPassageType == 5) ...[ Row( children: [ Expanded( child: CustomTextField( controller: _montantController, label: "Montant", isRequired: true, showLabel: false, hintText: "0.00", textAlign: TextAlign.right, keyboardType: const TextInputType.numberWithOptions( decimal: true), readOnly: widget.readOnly, validator: _validateMontant, prefixIcon: Icons.euro, ), ), const SizedBox(width: 12), Expanded( child: DropdownButtonFormField( initialValue: _fkTypeReglement, decoration: const InputDecoration( labelText: "Type de règlement *", border: OutlineInputBorder(), ), items: AppKeys.typesReglements.entries.map((entry) { return DropdownMenuItem( value: entry.key, child: Row( children: [ Icon( entry.value['icon_data'] as IconData, color: Color(entry.value['couleur'] as int), size: 16, ), const SizedBox(width: 8), Text(entry.value['titre'] as String), ], ), ); }).toList(), onChanged: widget.readOnly ? null : (value) { setState(() { _fkTypeReglement = value!; }); }, validator: (_selectedPassageType == 1 || _selectedPassageType == 5) ? (value) { if (value == null || value < 1 || value > 3) { return 'Type de règlement requis'; } return null; } : null, ), ), ], ), const SizedBox(height: 16), ], CustomTextField( controller: _remarqueController, label: "Note", showLabel: false, hintText: "Commentaire sur le passage...", maxLines: 2, readOnly: widget.readOnly, ), ], ), ], ), ); } catch (e, stackTrace) { debugPrint('=== ERREUR _buildPassageForm ==='); debugPrint('Erreur: $e'); debugPrint('StackTrace: $stackTrace'); return Container( padding: const EdgeInsets.all(16), child: Column( children: [ const Icon(Icons.error, color: Colors.red), const SizedBox(height: 8), Text('Erreur dans le formulaire: $e'), ], ), ); } } Future _selectDate() async { final DateTime? picked = await showDatePicker( context: context, initialDate: _passedAt, firstDate: DateTime(2020), lastDate: DateTime(2030), ); if (picked != null) { setState(() { _passedAt = DateTime( picked.year, picked.month, picked.day, _passedAt.hour, _passedAt.minute, ); _dateController.text = '${_passedAt.day.toString().padLeft(2, '0')}/${_passedAt.month.toString().padLeft(2, '0')}/${_passedAt.year}'; }); } } Future _selectTime() async { final TimeOfDay? picked = await showTimePicker( context: context, initialTime: TimeOfDay.fromDateTime(_passedAt), ); if (picked != null) { setState(() { _passedAt = DateTime( _passedAt.year, _passedAt.month, _passedAt.day, picked.hour, picked.minute, ); _timeController.text = '${_passedAt.hour.toString().padLeft(2, '0')}:${_passedAt.minute.toString().padLeft(2, '0')}'; }); } } // Méthode pour détecter si on est sur mobile bool _isMobile(BuildContext context) { // Détecter si on est sur mobile natif ou web mobile (largeur < 600px) return Theme.of(context).platform == TargetPlatform.iOS || Theme.of(context).platform == TargetPlatform.android || (kIsWeb && MediaQuery.of(context).size.width < 600); } // Méthode pour construire l'en-tête du formulaire Widget _buildHeader() { final theme = Theme.of(context); return Container( decoration: BoxDecoration( color: _selectedPassageType != null && AppKeys.typesPassages.containsKey(_selectedPassageType) ? Color(AppKeys.typesPassages[_selectedPassageType]!['couleur2'] as int? ?? 0xFF000000) .withValues(alpha: 0.1) : null, borderRadius: BorderRadius.circular(8), ), padding: const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Row( children: [ Icon( widget.passage == null ? Icons.add_circle : Icons.edit, color: _selectedPassageType != null && AppKeys.typesPassages .containsKey(_selectedPassageType) ? Color(AppKeys.typesPassages[_selectedPassageType]![ 'couleur2'] as int? ?? 0xFF000000) : theme.colorScheme.primary, ), const SizedBox(width: 8), Flexible( child: Text( widget.title, style: theme.textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.bold, color: _selectedPassageType != null && AppKeys.typesPassages .containsKey(_selectedPassageType) ? Color(AppKeys.typesPassages[_selectedPassageType]![ 'couleur2'] as int? ?? 0xFF000000) : theme.colorScheme.primary, ), overflow: TextOverflow.ellipsis, ), ), if (_selectedPassageType != null && AppKeys.typesPassages .containsKey(_selectedPassageType)) ...[ const SizedBox(width: 12), Icon( AppKeys.typesPassages[_selectedPassageType]!['icon_data'] as IconData? ?? Icons.help, color: Color( AppKeys.typesPassages[_selectedPassageType]!['couleur2'] as int? ?? 0xFF000000), size: 20, ), const SizedBox(width: 4), Text( AppKeys.typesPassages[_selectedPassageType]!['titre'] as String? ?? 'Inconnu', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w600, color: Color(AppKeys.typesPassages[_selectedPassageType]![ 'couleur2'] as int? ?? 0xFF000000), ), ), ], ], ), ), IconButton( icon: const Icon(Icons.close), onPressed: _isSubmitting ? null : () { Navigator.of(context, rootNavigator: false).pop(); }, ), ], ), ); } // Méthode pour construire le contenu principal Widget _buildContent() { return SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (!_showForm) ...[ () { debugPrint('Building passage type selection...'); return _buildPassageTypeSelection(); }(), ] else ...[ () { debugPrint('Building passage form...'); return _buildPassageForm(); }(), ], ], ), ); } // Méthode pour construire les boutons du footer Widget _buildFooterButtons() { final theme = Theme.of(context); return Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: _isSubmitting ? null : () { Navigator.of(context, rootNavigator: false).pop(); }, child: const Text('Annuler'), ), const SizedBox(width: 16), if (!widget.readOnly && _showForm && _selectedPassageType != null) ElevatedButton.icon( onPressed: _isSubmitting ? null : _handleSubmit, icon: _isSubmitting ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2), ) : Icon(widget.passage == null ? Icons.add : Icons.save), label: Text(_isSubmitting ? 'Enregistrement...' : (widget.passage == null ? 'Créer' : 'Enregistrer')), style: ElevatedButton.styleFrom( backgroundColor: theme.colorScheme.primary, foregroundColor: Colors.white, ), ), ], ); } // Méthode pour construire le contenu du Dialog Widget _buildDialogContent() { return Column( mainAxisSize: MainAxisSize.min, children: [ // Header _buildHeader(), const Divider(), // Contenu Expanded( child: _buildContent(), ), const SizedBox(height: 24), // Footer _buildFooterButtons(), ], ); } // Méthode pour construire l'AppBar mobile AppBar _buildMobileAppBar() { final theme = Theme.of(context); final typeColor = _selectedPassageType != null && AppKeys.typesPassages.containsKey(_selectedPassageType) ? Color( AppKeys.typesPassages[_selectedPassageType]!['couleur2'] as int? ?? 0xFF000000) : theme.colorScheme.primary; return AppBar( backgroundColor: typeColor.withValues(alpha: 0.1), elevation: 0, leading: IconButton( icon: Icon(Icons.close, color: typeColor), onPressed: _isSubmitting ? null : () { Navigator.of(context, rootNavigator: false).pop(); }, ), title: Row( children: [ Icon( widget.passage == null ? Icons.add_circle : Icons.edit, color: typeColor, size: 24, ), const SizedBox(width: 8), Expanded( child: Text( widget.title, style: TextStyle( color: typeColor, fontWeight: FontWeight.bold, fontSize: AppTheme.r(context, 18), ), overflow: TextOverflow.ellipsis, ), ), ], ), actions: _selectedPassageType != null && AppKeys.typesPassages.containsKey(_selectedPassageType) ? [ Padding( padding: const EdgeInsets.only(right: 8), child: Row( children: [ Icon( AppKeys.typesPassages[_selectedPassageType]!['icon_data'] as IconData? ?? Icons.help, color: typeColor, size: 20, ), const SizedBox(width: 4), Text( AppKeys.typesPassages[_selectedPassageType]!['titre'] as String? ?? 'Inconnu', style: TextStyle( fontWeight: FontWeight.w600, color: typeColor, fontSize: AppTheme.r(context, 14), ), ), ], ), ), ] : null, ); } @override Widget build(BuildContext context) { try { debugPrint('=== DEBUT PassageFormDialog.build ==='); final isMobile = _isMobile(context); debugPrint('Platform mobile détectée: $isMobile'); if (isMobile) { // Mode plein écran pour mobile return Scaffold( appBar: _buildMobileAppBar(), body: SafeArea( child: Padding( padding: const EdgeInsets.all(16), child: Column( children: [ Expanded( child: _buildContent(), ), ], ), ), ), bottomNavigationBar: _showForm && _selectedPassageType != null ? SafeArea( child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.1), blurRadius: 4, offset: const Offset(0, -2), ), ], ), child: _buildFooterButtons(), ), ) : null, ); } else { // Mode Dialog pour desktop/tablette return Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), insetPadding: const EdgeInsets.all(24), child: Container( width: MediaQuery.of(context).size.width * 0.6, constraints: const BoxConstraints( maxWidth: 800, maxHeight: 900, ), padding: const EdgeInsets.all(24), child: _buildDialogContent(), ), ); } } catch (e, stackTrace) { debugPrint('=== ERREUR PassageFormDialog.build ==='); debugPrint('Erreur: $e'); debugPrint('StackTrace: $stackTrace'); // Retourner un widget d'erreur simple return Dialog( child: Container( padding: const EdgeInsets.all(16), child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.error, color: Colors.red, size: 48), const SizedBox(height: 16), Text('Erreur lors de l\'affichage du formulaire: $e'), const SizedBox(height: 16), ElevatedButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Fermer'), ), ], ), ), ); } } }