import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_map/flutter_map.dart'; import 'package:geosector_app/core/data/models/amicale_model.dart'; import 'package:geosector_app/core/repositories/user_repository.dart'; import 'package:geosector_app/core/services/api_service.dart'; import 'package:geosector_app/presentation/widgets/mapbox_map.dart'; import 'package:latlong2/latlong.dart'; import 'package:provider/provider.dart'; import 'custom_text_field.dart'; class AmicaleForm extends StatefulWidget { final AmicaleModel? amicale; final Function(AmicaleModel)? onSubmit; final bool readOnly; const AmicaleForm({ Key? key, this.amicale, this.onSubmit, this.readOnly = false, }) : super(key: key); @override State createState() => _AmicaleFormState(); } class _AmicaleFormState extends State { final _formKey = GlobalKey(); // Controllers late final TextEditingController _nameController; late final TextEditingController _adresse1Controller; late final TextEditingController _adresse2Controller; late final TextEditingController _codePostalController; late final TextEditingController _villeController; late final TextEditingController _phoneController; late final TextEditingController _mobileController; late final TextEditingController _emailController; late final TextEditingController _gpsLatController; late final TextEditingController _gpsLngController; late final TextEditingController _stripeIdController; // Form values int? _fkRegion; String? _libRegion; bool _chkDemo = false; bool _chkCopieMailRecu = false; bool _chkAcceptSms = false; bool _chkActive = true; bool _chkStripe = false; @override void initState() { super.initState(); // Initialize controllers with amicale data if available final amicale = widget.amicale; _nameController = TextEditingController(text: amicale?.name ?? ''); _adresse1Controller = TextEditingController(text: amicale?.adresse1 ?? ''); _adresse2Controller = TextEditingController(text: amicale?.adresse2 ?? ''); _codePostalController = TextEditingController(text: amicale?.codePostal ?? ''); _villeController = TextEditingController(text: amicale?.ville ?? ''); _phoneController = TextEditingController(text: amicale?.phone ?? ''); _mobileController = TextEditingController(text: amicale?.mobile ?? ''); _emailController = TextEditingController(text: amicale?.email ?? ''); _gpsLatController = TextEditingController(text: amicale?.gpsLat ?? ''); _gpsLngController = TextEditingController(text: amicale?.gpsLng ?? ''); _stripeIdController = TextEditingController(text: amicale?.stripeId ?? ''); _fkRegion = amicale?.fkRegion; _libRegion = amicale?.libRegion; _chkDemo = amicale?.chkDemo ?? false; _chkCopieMailRecu = amicale?.chkCopieMailRecu ?? false; _chkAcceptSms = amicale?.chkAcceptSms ?? false; _chkActive = amicale?.chkActive ?? true; _chkStripe = amicale?.chkStripe ?? false; } @override void dispose() { _nameController.dispose(); _adresse1Controller.dispose(); _adresse2Controller.dispose(); _codePostalController.dispose(); _villeController.dispose(); _phoneController.dispose(); _mobileController.dispose(); _emailController.dispose(); _gpsLatController.dispose(); _gpsLngController.dispose(); _stripeIdController.dispose(); super.dispose(); } // Appeler l'API pour mettre à jour l'entité Future _updateAmicale(AmicaleModel amicale) async { try { // Afficher un indicateur de chargement showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return const Center( child: CircularProgressIndicator(), ); }, ); // Préparer les données pour l'API final Map data = { 'id': amicale.id, 'name': amicale.name, 'adresse1': amicale.adresse1, 'adresse2': amicale.adresse2, 'code_postal': amicale.codePostal, 'ville': amicale.ville, 'phone': amicale.phone, 'mobile': amicale.mobile, 'email': amicale.email, 'chk_copie_mail_recu': amicale.chkCopieMailRecu, 'chk_accept_sms': amicale.chkAcceptSms, 'chk_stripe': amicale.chkStripe, }; // Ajouter les champs réservés aux administrateurs si l'utilisateur est admin final userRepository = Provider.of(context, listen: false); final userRole = userRepository.getUserRole(); if (userRole > 2) { data['gps_lat'] = amicale.gpsLat; data['gps_lng'] = amicale.gpsLng; data['stripe_id'] = amicale.stripeId; data['chk_demo'] = amicale.chkDemo; data['chk_active'] = amicale.chkActive; } // Appeler l'API try { // Obtenir l'instance du service API final apiService = Provider.of(context, listen: false); // Appeler la méthode post du service API await apiService.post('/entite/update', data: data); // Fermer l'indicateur de chargement Navigator.of(context).pop(); // Afficher un message de succès ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Amicale mise à jour avec succès'), backgroundColor: Colors.green, ), ); // Appeler la fonction onSubmit si elle existe if (widget.onSubmit != null) { widget.onSubmit!(amicale); } // Fermer le formulaire Navigator.of(context).pop(); } catch (error) { // Fermer l'indicateur de chargement Navigator.of(context).pop(); // Afficher un message d'erreur ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Erreur lors de la mise à jour de l\'amicale: $error'), backgroundColor: Colors.red, ), ); } } catch (e) { // Fermer l'indicateur de chargement Navigator.of(context).pop(); // Afficher un message d'erreur ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Erreur: ${e.toString()}'), backgroundColor: Colors.red, ), ); } } void _submitForm() { if (_formKey.currentState!.validate()) { // Vérifier qu'au moins un numéro de téléphone est renseigné if (_phoneController.text.isEmpty && _mobileController.text.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Veuillez renseigner au moins un numéro de téléphone'), backgroundColor: Colors.red, ), ); return; } final amicale = widget.amicale?.copyWith( name: _nameController.text, adresse1: _adresse1Controller.text, adresse2: _adresse2Controller.text, codePostal: _codePostalController.text, ville: _villeController.text, fkRegion: _fkRegion, libRegion: _libRegion, phone: _phoneController.text, mobile: _mobileController.text, email: _emailController.text, gpsLat: _gpsLatController.text, gpsLng: _gpsLngController.text, stripeId: _stripeIdController.text, chkDemo: _chkDemo, chkCopieMailRecu: _chkCopieMailRecu, chkAcceptSms: _chkAcceptSms, chkActive: _chkActive, chkStripe: _chkStripe, ) ?? AmicaleModel( id: 0, // Sera remplacé par l'API name: _nameController.text, adresse1: _adresse1Controller.text, adresse2: _adresse2Controller.text, codePostal: _codePostalController.text, ville: _villeController.text, fkRegion: _fkRegion, libRegion: _libRegion, phone: _phoneController.text, mobile: _mobileController.text, email: _emailController.text, gpsLat: _gpsLatController.text, gpsLng: _gpsLngController.text, stripeId: _stripeIdController.text, chkDemo: _chkDemo, chkCopieMailRecu: _chkCopieMailRecu, chkAcceptSms: _chkAcceptSms, chkActive: _chkActive, ); // Appeler l'API pour mettre à jour l'amicale _updateAmicale(amicale); // Appeler la fonction onSubmit si elle existe (pour la compatibilité avec le code existant) if (widget.onSubmit != null) { widget.onSubmit!(amicale); } } } // Construire la section logo Widget _buildLogoSection() { return Container( width: 150, height: 150, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Stack( children: [ // Image par défaut Center( child: Image.asset( 'assets/images/logo_recu.png', width: 150, height: 150, fit: BoxFit.contain, ), ), // Overlay pour indiquer que l'image est modifiable (si non en lecture seule) if (!widget.readOnly) Positioned.fill( child: Material( color: Colors.transparent, child: InkWell( onTap: () { // TODO: Implémenter la sélection d'image ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text( 'Fonctionnalité de modification du logo à venir'), ), ); }, child: Container( decoration: BoxDecoration( color: Colors.black.withOpacity(0.1), ), child: const Center( child: Icon( Icons.camera_alt, color: Colors.white, size: 40, ), ), ), ), ), ), ], ), ), ); } // Construire la minimap Widget _buildMiniMap() { // Vérifier si les coordonnées GPS sont valides double? lat = double.tryParse(_gpsLatController.text); double? lng = double.tryParse(_gpsLngController.text); // Si les coordonnées ne sont pas valides, afficher un message if (lat == null || lng == null) { return Container( width: 150, height: 150, decoration: BoxDecoration( color: Colors.grey.shade200, borderRadius: BorderRadius.circular(8), ), child: const Center( child: Text( 'Aucune coordonnée GPS', textAlign: TextAlign.center, style: TextStyle( color: Colors.grey, fontSize: 12, ), ), ), ); } // Créer la position pour la carte final position = LatLng(lat, lng); // Créer un marqueur pour la position de l'amicale final markers = [ Marker( point: position, width: 20, height: 20, child: const Icon( Icons.fireplace_rounded, color: Color.fromARGB(255, 212, 34, 31), size: 20, ), ), ]; // Retourner la minimap return Container( width: 150, height: 150, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: ClipRRect( borderRadius: BorderRadius.circular(8), child: MapboxMap( initialPosition: position, initialZoom: 15.0, markers: markers, showControls: false, ), ), ); } // Construire le dropdown pour la région Widget _buildRegionDropdown(bool restrictedFieldsReadOnly) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Afficher le libellé de la région en lecture seule if (_libRegion != null && _libRegion!.isNotEmpty) Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), decoration: BoxDecoration( color: Theme.of(context).inputDecorationTheme.fillColor, borderRadius: BorderRadius.circular(4), ), child: Text( _libRegion!, style: Theme.of(context).textTheme.bodyLarge?.copyWith( color: Theme.of(context).colorScheme.onSurface, ), ), ) else Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), decoration: BoxDecoration( color: Theme.of(context).inputDecorationTheme.fillColor, borderRadius: BorderRadius.circular(4), ), child: Text( 'Aucune région définie', style: Theme.of(context).textTheme.bodyLarge?.copyWith( color: Theme.of(context).hintColor, ), ), ), ], ); } // Construire une option checkbox Widget _buildCheckboxOption({ required String label, required bool value, required void Function(bool?)? onChanged, }) { return Row( children: [ Checkbox( value: value, onChanged: onChanged, activeColor: Theme.of(context).colorScheme.primary, ), Expanded( child: Text( label, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onBackground, fontWeight: FontWeight.w500, ), ), ), ], ); } // Construire le formulaire principal Widget _buildMainForm(ThemeData theme, bool restrictedFieldsReadOnly) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Nom CustomTextField( controller: _nameController, label: "Nom", readOnly: widget.readOnly, isRequired: true, validator: (value) { if (value == null || value.isEmpty) { return "Veuillez entrer un nom"; } return null; }, ), const SizedBox(height: 16), // Bloc Adresse Text( "Adresse", style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, color: theme.colorScheme.onBackground, ), ), const SizedBox(height: 8), // Adresse 1 CustomTextField( controller: _adresse1Controller, label: "Adresse ligne 1", readOnly: widget.readOnly, isRequired: true, validator: (value) { if (value == null || value.isEmpty) { return "Veuillez entrer une adresse"; } return null; }, ), const SizedBox(height: 16), // Adresse 2 CustomTextField( controller: _adresse2Controller, label: "Adresse ligne 2", readOnly: widget.readOnly, ), const SizedBox(height: 16), // Code Postal et Ville Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Code Postal Expanded( flex: 1, child: CustomTextField( controller: _codePostalController, label: "Code Postal", keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(5), ], readOnly: widget.readOnly, isRequired: true, validator: (value) { if (value == null || value.isEmpty) { return "Veuillez entrer un code postal"; } if (value.length < 5) { return "Le code postal doit contenir 5 chiffres"; } return null; }, ), ), const SizedBox(width: 16), // Ville Expanded( flex: 2, child: CustomTextField( controller: _villeController, label: "Ville", readOnly: widget.readOnly, isRequired: true, validator: (value) { if (value == null || value.isEmpty) { return "Veuillez entrer une ville"; } return null; }, ), ), ], ), const SizedBox(height: 16), // Région Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Région", style: theme.textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w500, color: theme.colorScheme.onBackground, ), ), const SizedBox(height: 8), _buildRegionDropdown(restrictedFieldsReadOnly), ], ), const SizedBox(height: 16), // Contact Text( "Contact", style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, color: theme.colorScheme.onBackground, ), ), const SizedBox(height: 8), // Téléphone fixe et mobile sur la même ligne Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Téléphone fixe Expanded( child: CustomTextField( controller: _phoneController, label: "Téléphone fixe", keyboardType: TextInputType.phone, readOnly: widget.readOnly, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(10), ], validator: (value) { if (value != null && value.isNotEmpty && value.length < 10) { return "Le numéro de téléphone doit contenir 10 chiffres"; } return null; }, ), ), const SizedBox(width: 16), // Téléphone mobile Expanded( child: CustomTextField( controller: _mobileController, label: "Téléphone mobile", keyboardType: TextInputType.phone, readOnly: widget.readOnly, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(10), ], validator: (value) { if (value != null && value.isNotEmpty && value.length < 10) { return "Le numéro de mobile doit contenir 10 chiffres"; } return null; }, ), ), ], ), const SizedBox(height: 16), // Email CustomTextField( controller: _emailController, label: "Email", keyboardType: TextInputType.emailAddress, readOnly: widget.readOnly, isRequired: true, validator: (value) { if (value == null || value.isEmpty) { return "Veuillez entrer l'adresse email"; } if (!value.contains('@') || !value.contains('.')) { return "Veuillez entrer une adresse email valide"; } return null; }, ), const SizedBox(height: 16), // Informations avancées (visibles uniquement pour les administrateurs) if (_shouldShowAdvancedInfo()) ...[ Text( "Informations avancées", style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, color: theme.colorScheme.onBackground, ), ), const SizedBox(height: 8), // GPS Latitude et Longitude Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ // GPS Latitude Expanded( child: CustomTextField( controller: _gpsLatController, label: "GPS Latitude", keyboardType: const TextInputType.numberWithOptions(decimal: true), readOnly: restrictedFieldsReadOnly, ), ), const SizedBox(width: 16), // GPS Longitude Expanded( child: CustomTextField( controller: _gpsLngController, label: "GPS Longitude", keyboardType: const TextInputType.numberWithOptions(decimal: true), readOnly: restrictedFieldsReadOnly, ), ), ], ), const SizedBox(height: 16), // Stripe Checkbox et Stripe ID sur la même ligne Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ // Checkbox Stripe Checkbox( value: _chkStripe, onChanged: restrictedFieldsReadOnly ? null : (value) { if (value == true) { // Afficher une boîte de dialogue de confirmation showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Confirmation'), content: const Text( 'En acceptant les règlements par carte bancaire, des commissions de 1.4% seront prélevées sur les montants encaissés. Souhaitez-vous continuer ?'), actions: [ TextButton( onPressed: () { // L'utilisateur a répondu "non" setState(() { _chkStripe = false; }); Navigator.of(context).pop(); }, child: const Text('Non'), ), ElevatedButton( onPressed: () { // L'utilisateur a répondu "oui" setState(() { _chkStripe = true; }); Navigator.of(context).pop(); }, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF20335E), foregroundColor: Colors.white, ), child: const Text('Oui'), ), ], ), ); } else { // Si l'utilisateur décoche la case, pas besoin de confirmation setState(() { _chkStripe = false; }); } }, activeColor: const Color(0xFF20335E), ), Text( "Accepte les règlements en CB", style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onBackground, fontWeight: FontWeight.w500, ), ), const SizedBox(width: 16), // Stripe ID Expanded( child: CustomTextField( controller: _stripeIdController, label: "ID Stripe Paiements CB", readOnly: restrictedFieldsReadOnly, helperText: "Les règlements par CB sont taxés d'une commission de 1.4%", ), ), ], ), const SizedBox(height: 16), ], // Options Text( "Options", style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, color: theme.colorScheme.onBackground, ), ), const SizedBox(height: 8), // Checkbox Demo _buildCheckboxOption( label: "Mode démo", value: _chkDemo, onChanged: restrictedFieldsReadOnly ? null : (value) { setState(() { _chkDemo = value!; }); }, ), const SizedBox(height: 8), // Checkbox Copie Mail Reçu _buildCheckboxOption( label: "Copie des mails reçus", value: _chkCopieMailRecu, onChanged: widget.readOnly ? null : (value) { setState(() { _chkCopieMailRecu = value!; }); }, ), const SizedBox(height: 8), // Checkbox Accept SMS _buildCheckboxOption( label: "Accepte les SMS", value: _chkAcceptSms, onChanged: widget.readOnly ? null : (value) { setState(() { _chkAcceptSms = value!; }); }, ), const SizedBox(height: 8), // Checkbox Active _buildCheckboxOption( label: "Actif", value: _chkActive, onChanged: restrictedFieldsReadOnly ? null : (value) { setState(() { _chkActive = value!; }); }, ), const SizedBox(height: 25), // Boutons Fermer et Enregistrer if (!widget.readOnly) Center( child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ // Bouton Fermer OutlinedButton( onPressed: () { Navigator.of(context).pop(); }, style: OutlinedButton.styleFrom( foregroundColor: const Color(0xFF20335E), side: const BorderSide(color: Color(0xFF20335E)), padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(50), ), minimumSize: const Size(150, 50), ), child: const Text( 'Fermer', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w500, ), ), ), const SizedBox(width: 20), // Bouton Enregistrer ElevatedButton( onPressed: _submitForm, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF20335E), foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(50), ), minimumSize: const Size(150, 50), ), child: const Text( 'Enregistrer', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w500, ), ), ), ], ), ), ], ); } // Vérifier si les informations avancées doivent être affichées bool _shouldShowAdvancedInfo() { final userRepository = Provider.of(context, listen: false); final userRole = userRepository.getUserRole(); final bool canEditRestrictedFields = userRole > 2; return canEditRestrictedFields || _gpsLatController.text.isNotEmpty || _gpsLngController.text.isNotEmpty || _stripeIdController.text.isNotEmpty; } @override Widget build(BuildContext context) { final theme = Theme.of(context); final userRepository = Provider.of(context, listen: false); final userRole = userRepository.getUserRole(); // Déterminer si l'utilisateur peut modifier les champs restreints final bool canEditRestrictedFields = userRole > 2; // Lecture seule pour les champs restreints si l'utilisateur n'a pas les droits final bool restrictedFieldsReadOnly = widget.readOnly || !canEditRestrictedFields; // Calculer la largeur maximale du formulaire pour les écrans larges final screenWidth = MediaQuery.of(context).size.width; final maxFormWidth = screenWidth > 800 ? 800.0 : screenWidth; return Scaffold( appBar: AppBar( title: Text( widget.readOnly ? 'Détails de l\'amicale' : 'Modifier l\'amicale'), backgroundColor: theme.appBarTheme.backgroundColor, foregroundColor: theme.appBarTheme.foregroundColor, ), body: Center( child: Container( width: maxFormWidth, padding: const EdgeInsets.all(16), child: Form( key: _formKey, child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header avec logo et minimap Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ // Section Logo _buildLogoSection(), // Section MiniMap _buildMiniMap(), ], ), const SizedBox(height: 24), // Formulaire principal _buildMainForm(theme, restrictedFieldsReadOnly), ], ), ), ), ), ), ); } }