import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:image_picker/image_picker.dart'; import 'package:path_provider/path_provider.dart'; import 'package:youmazgestion/Components/app_bar.dart'; import 'package:youmazgestion/Components/appDrawer.dart'; import 'package:youmazgestion/Services/stock_managementDatabase.dart'; class AjoutPointDeVentePage extends StatefulWidget { const AjoutPointDeVentePage({super.key}); @override _AjoutPointDeVentePageState createState() => _AjoutPointDeVentePageState(); } class _AjoutPointDeVentePageState extends State { final AppDatabase _appDatabase = AppDatabase.instance; final _formKey = GlobalKey(); bool _isLoading = false; // Contrôleurs final TextEditingController _nomController = TextEditingController(); final TextEditingController _codeController = TextEditingController(); // Liste des points de vente List> _pointsDeVente = []; List> _filteredPointsDeVente = []; final TextEditingController _searchController = TextEditingController(); @override void initState() { super.initState(); _loadPointsDeVente(); _searchController.addListener(_filterPointsDeVente); } Future _loadPointsDeVente() async { if (mounted) { setState(() { _isLoading = true; }); } try { final points = await _appDatabase.getPointsDeVente(); // Enrichir chaque point de vente avec les informations de contraintes for (var point in points) { final verification = await _appDatabase.checkCanDeletePointDeVente(point['id']); point['canDelete'] = verification['canDelete']; point['constraintCount'] = (verification['reasons'] as List).length; } if (mounted) { setState(() { _pointsDeVente = points; _filteredPointsDeVente = List.from(points); _isLoading = false; }); } } catch (e) { if (mounted) { setState(() { _isLoading = false; }); Get.snackbar( 'Erreur', 'Impossible de charger les points de vente: ${e.toString()}', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red, colorText: Colors.white, ); } } } void _filterPointsDeVente() { final query = _searchController.text.toLowerCase().trim(); if (mounted) { setState(() { if (query.isEmpty) { _filteredPointsDeVente = List.from(_pointsDeVente); } else { _filteredPointsDeVente = _pointsDeVente.where((point) { final nom = point['nom']?.toString().toLowerCase() ?? ''; final code = point['code']?.toString().toLowerCase() ?? ''; return nom.contains(query) || code.contains(query); }).toList(); } }); } } Future _submitForm() async { if (_formKey.currentState!.validate()) { setState(() { _isLoading = true; }); try { await _appDatabase.createPointDeVente( _nomController.text.trim(), _codeController.text.trim(), ); // Réinitialiser le formulaire _nomController.clear(); _codeController.clear(); // Recharger la liste await _loadPointsDeVente(); Get.snackbar( 'Succès', 'Point de vente ajouté avec succès', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.green, colorText: Colors.white, ); } catch (e) { Get.snackbar( 'Erreur', 'Impossible d\'ajouter le point de vente: ${e.toString()}', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red, colorText: Colors.white, ); } finally { if (mounted) { setState(() { _isLoading = false; }); } } } } Future editPointDeVente(BuildContext context, Map pointDeVente) async { print('=== DIAGNOSTIC DES DONNÉES ==='); print('ID: ${pointDeVente['id']}'); print('Nom: ${pointDeVente['nom']}'); print('Code: ${pointDeVente['code']}'); print('Content (ticket): "${pointDeVente['content']}" (Type: ${pointDeVente['content'].runtimeType})'); print('Livraison: "${pointDeVente['livraison']}" (Type: ${pointDeVente['livraison'].runtimeType})'); print('Facture: "${pointDeVente['facture']}" (Type: ${pointDeVente['facture'].runtimeType})'); print('Logo: ${pointDeVente['logo']?.runtimeType}'); print('Toutes les clés: ${pointDeVente.keys.toList()}'); print('==============================='); final _editFormKey = GlobalKey(); final ImagePicker _picker = ImagePicker(); // Fonction helper pour convertir les valeurs en String de manière sûre String safeStringConversion(dynamic value) { if (value == null) return ''; if (value is String) return value; // Vérifier si c'est un type binaire (Uint8List, List, etc.) if (value is Uint8List || value is List || value is List) { // Si c'est des données binaires, on retourne une chaîne vide // car on ne peut pas les convertir en texte lisible return ''; } // Pour tous les autres types, essayer la conversion toString() try { return value.toString(); } catch (e) { print('Erreur conversion vers String: $e, type: ${value.runtimeType}'); return ''; } } // Initialisation dynamique des contrôleurs avec conversion sécurisée final _editNomController = TextEditingController( text: safeStringConversion(pointDeVente['nom']) ); final _editCodeController = TextEditingController( text: safeStringConversion(pointDeVente['code']) ); final _editTicketController = TextEditingController( text: safeStringConversion(pointDeVente['content']) ); final _editLivraisonController = TextEditingController( text: safeStringConversion(pointDeVente['livraison']) ); final _editFactureController = TextEditingController( text: safeStringConversion(pointDeVente['facture']) ); File? _selectedImage; Uint8List? _currentImageBlob; // Gérer la conversion du logo de manière sécurisée final logoData = pointDeVente['logo']; if (logoData != null) { try { if (logoData is Uint8List) { _currentImageBlob = logoData; } else if (logoData is List) { _currentImageBlob = Uint8List.fromList(logoData); } else if (logoData is List) { // Cas où c'est une List contenant des int _currentImageBlob = Uint8List.fromList(logoData.cast()); } else { // Type non supporté (comme Blob), laisser null et logger print('Type de logo non supporté: ${logoData.runtimeType}'); _currentImageBlob = null; } } catch (e) { print('Erreur lors de la conversion du logo: $e'); _currentImageBlob = null; } } bool _isEditLoading = false; Future pickImage(ImageSource source) async { try { final XFile? image = await _picker.pickImage(source: source); if (image != null) { _selectedImage = File(image.path); _currentImageBlob = await _selectedImage!.readAsBytes(); } } catch (e) { print('Erreur lors de la sélection de l\'image : $e'); } } Future saveChanges() async { if (_editFormKey.currentState?.validate() ?? false) { _isEditLoading = true; try { await _appDatabase.updatePointDeVentes( pointDeVente['id'], _editNomController.text.trim(), _editCodeController.text.trim(), content: _editTicketController.text.trim(), livraison: _editLivraisonController.text.trim(), facture: _editFactureController.text.trim(), imagePath: _currentImageBlob, ); Get.back(); // Fermer le dialog ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Modification enregistrée avec succès')), ); await _loadPointsDeVente(); // Rafraîchir la liste } catch (e) { print('Erreur lors de la sauvegarde : $e'); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Erreur lors de la modification')), ); } finally { _isEditLoading = false; } } } // Affichage de la boîte de dialogue await Get.dialog( Dialog( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 500, maxHeight: 700), child: Padding( padding: const EdgeInsets.all(16.0), child: Form( key: _editFormKey, child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Text('Modifier le point de vente', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 16), TextFormField( controller: _editNomController, decoration: const InputDecoration(labelText: 'Nom du point de vente'), validator: (value) => value == null || value.isEmpty ? 'Le nom est requis' : null, ), const SizedBox(height: 8), TextFormField( controller: _editCodeController, decoration: const InputDecoration(labelText: 'Code du point de vente'), ), const SizedBox(height: 8), TextFormField( controller: _editTicketController, decoration: const InputDecoration(labelText: 'Info ticket'), maxLines: 3, ), const SizedBox(height: 8), TextFormField( controller: _editLivraisonController, decoration: const InputDecoration(labelText: 'Info bon de livraison'), maxLines: 3, ), const SizedBox(height: 8), TextFormField( controller: _editFactureController, decoration: const InputDecoration(labelText: 'Info facture'), maxLines: 3, ), const SizedBox(height: 16), Row( children: [ Expanded( child: ElevatedButton.icon( onPressed: () => pickImage(ImageSource.gallery), icon: const Icon(Icons.image), label: const Text('Galerie'), ), ), const SizedBox(width: 10), Expanded( child: ElevatedButton.icon( onPressed: () => pickImage(ImageSource.camera), icon: const Icon(Icons.camera_alt), label: const Text('Caméra'), ), ), ], ), const SizedBox(height: 10), if (_currentImageBlob != null) Container( height: 100, width: double.infinity, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey.shade300), ), child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.memory( _currentImageBlob!, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) => const Center(child: Text('Erreur image')), ), ), ), const SizedBox(height: 16), SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: _isEditLoading ? null : saveChanges, icon: const Icon(Icons.save), label: Text(_isEditLoading ? 'Sauvegarde...' : 'Enregistrer'), ), ), ], ), ), ), ), ), ), ); // Nettoyage await Future.delayed(const Duration(milliseconds: 200)); _editNomController.dispose(); _editCodeController.dispose(); _editTicketController.dispose(); _editLivraisonController.dispose(); _editFactureController.dispose(); } Future _showConstraintDialog(int id, Map verificationResult) async { final reasons = verificationResult['reasons'] as List; final suggestions = verificationResult['suggestions'] as List; await Get.dialog( AlertDialog( title: const Row( children: [ Icon(Icons.warning, color: Colors.orange), SizedBox(width: 8), Expanded(child: Text('Suppression impossible')), ], ), content: ConstrainedBox( constraints: const BoxConstraints(maxHeight: 400), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ const Text( 'Ce point de vente ne peut pas être supprimé pour les raisons suivantes :', style: TextStyle(fontWeight: FontWeight.bold), ), const SizedBox(height: 12), ...reasons.map((reason) => Padding( padding: const EdgeInsets.only(bottom: 4), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('• ', style: TextStyle(color: Colors.red)), Expanded(child: Text(reason)), ], ), )), if (suggestions.isNotEmpty) ...[ const SizedBox(height: 16), const Text( 'Solutions possibles :', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.blue), ), const SizedBox(height: 8), ...suggestions.map((suggestion) => Padding( padding: const EdgeInsets.only(bottom: 4), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('💡 ', style: TextStyle(fontSize: 12)), Expanded(child: Text(suggestion, style: const TextStyle(fontSize: 13))), ], ), )), ], ], ), ), ), actions: [ TextButton( onPressed: () => Get.back(), child: const Text('Fermer'), ), if (reasons.any((r) => r.contains('produit'))) ElevatedButton( onPressed: () { Get.back(); _showTransferDialog(id); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, foregroundColor: Colors.white, ), child: const Text('Transférer'), ), ], ), ); } Future _showTransferDialog(int sourcePointDeVenteId) async { final pointsDeVente = await _appDatabase.getPointsDeVenteForTransfer(sourcePointDeVenteId); if (pointsDeVente.isEmpty) { Get.snackbar( 'Erreur', 'Aucun autre point de vente disponible pour le transfert', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red, colorText: Colors.white, ); return; } int? selectedPointDeVenteId; await Get.dialog( AlertDialog( title: const Text('Transférer les produits'), content: ConstrainedBox( constraints: const BoxConstraints(minWidth: 300), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('Sélectionnez le point de vente de destination pour les produits :'), const SizedBox(height: 16), DropdownButtonFormField( value: selectedPointDeVenteId, isExpanded: true, decoration: const InputDecoration( labelText: 'Point de vente de destination', border: OutlineInputBorder(), ), items: pointsDeVente.map((pv) => DropdownMenuItem( value: pv['id'] as int, child: Text( pv['nom'] as String, overflow: TextOverflow.ellipsis, ), )).toList(), onChanged: (value) { selectedPointDeVenteId = value; }, ), ], ), ), actions: [ TextButton( onPressed: () => Get.back(), child: const Text('Annuler'), ), ElevatedButton( onPressed: () async { if (selectedPointDeVenteId != null) { Get.back(); await _performTransferAndDelete(sourcePointDeVenteId, selectedPointDeVenteId!); } else { Get.snackbar( 'Erreur', 'Veuillez sélectionner un point de vente de destination', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red, colorText: Colors.white, ); } }, style: ElevatedButton.styleFrom( backgroundColor: Colors.green, foregroundColor: Colors.white, ), child: const Text('Transférer'), ), ], ), ); } Future _performTransferAndDelete(int sourceId, int targetId) async { if (mounted) { setState(() { _isLoading = true; }); } try { final confirmed = await Get.dialog( AlertDialog( title: const Text('Confirmation finale'), content: const Text( 'Cette action va transférer tous les produits vers le point de vente sélectionné ' 'puis supprimer définitivement le point de vente original. ' 'Cette action est irréversible. Continuer ?' ), actions: [ TextButton( onPressed: () => Get.back(result: false), child: const Text('Annuler'), ), ElevatedButton( onPressed: () => Get.back(result: true), style: ElevatedButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white, ), child: const Text('Confirmer'), ), ], ), ); if (confirmed == true) { await _appDatabase.deletePointDeVenteWithTransfer(sourceId, targetId); await _loadPointsDeVente(); Get.snackbar( 'Succès', 'Produits transférés et point de vente supprimé avec succès', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.green, colorText: Colors.white, duration: const Duration(seconds: 4), ); } } catch (e) { Get.snackbar( 'Erreur', 'Erreur lors du transfert: ${e.toString()}', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red, colorText: Colors.white, ); } finally { if (mounted) { setState(() { _isLoading = false; }); } } } Future _showPointDeVenteDetails(Map pointDeVente) async { final id = pointDeVente['id'] as int; try { final stats = await _getPointDeVenteStats(id); await Get.dialog( AlertDialog( title: Text( 'Détails: ${pointDeVente['nom']}', style: const TextStyle(fontSize: 16), ), content: ConstrainedBox( constraints: const BoxConstraints( maxWidth: 400, maxHeight: 500, ), child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ if (pointDeVente['logo'] != null) Container( height: 150, width: double.infinity, margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey.shade300), ), child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.memory( pointDeVente['logo'] as Uint8List, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { return Container( color: Colors.grey.shade200, child: const Center( child: Text('Image non disponible'), ), ); }, ), ), ), _buildStatRow('Produits associés', '${stats['produits']}'), _buildStatRow('Utilisateurs associés', '${stats['utilisateurs']}'), _buildStatRow('Demandes de transfert', '${stats['transferts']}'), const SizedBox(height: 8), if (pointDeVente['content'] != null && pointDeVente['content'].toString().isNotEmpty) Padding( padding: const EdgeInsets.only(top: 4), child: Text( 'Ticket: ${pointDeVente['content']}', style: const TextStyle(fontSize: 12, color: Colors.grey), ), ), if (pointDeVente['livraison'] != null && pointDeVente['livraison'].toString().isNotEmpty) Padding( padding: const EdgeInsets.only(top: 4), child: Text( 'Bon de livraison: ${pointDeVente['livraison']}', style: const TextStyle(fontSize: 12, color: Colors.grey), ), ), if (pointDeVente['facture'] != null && pointDeVente['facture'].toString().isNotEmpty) Padding( padding: const EdgeInsets.only(top: 4), child: Text( 'Facture: ${pointDeVente['facture']}', style: const TextStyle(fontSize: 12, color: Colors.grey), ), ), ], ), ), ), actions: [ TextButton( onPressed: () => Get.back(), child: const Text('Fermer'), ), ], ), ); } catch (e) { Get.snackbar( 'Erreur', 'Impossible de récupérer les détails: ${e.toString()}', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red, colorText: Colors.white, ); } } Widget _buildStatRow(String label, String value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( label, style: const TextStyle(fontSize: 13), ), ), Text( value, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13), ), ], ), ); } Future> _getPointDeVenteStats(int id) async { final verification = await _appDatabase.checkCanDeletePointDeVente(id); // Parser les raisons pour extraire les nombres int produits = 0, utilisateurs = 0, transferts = 0; for (String reason in verification['reasons']) { if (reason.contains('produit')) { produits = int.tryParse(reason.split(' ')[0]) ?? 0; } else if (reason.contains('utilisateur')) { utilisateurs = int.tryParse(reason.split(' ')[0]) ?? 0; } else if (reason.contains('transfert')) { transferts = int.tryParse(reason.split(' ')[0]) ?? 0; } } return { 'produits': produits, 'utilisateurs': utilisateurs, 'transferts': transferts, }; } Future _deletePointDeVente(int id) async { final verificationResult = await _appDatabase.checkCanDeletePointDeVente(id); if (!verificationResult['canDelete']) { await _showConstraintDialog(id, verificationResult); return; } final confirmed = await Get.dialog( AlertDialog( title: const Text('Confirmer la suppression'), content: const Text('Voulez-vous vraiment supprimer ce point de vente ?'), actions: [ TextButton( onPressed: () => Get.back(result: false), child: const Text('Annuler'), ), TextButton( onPressed: () => Get.back(result: true), child: const Text('Supprimer', style: TextStyle(color: Colors.red)), ), ], ), ); if (confirmed == true) { if (mounted) { setState(() { _isLoading = true; }); } try { await _appDatabase.deletePointDeVente(id); await _loadPointsDeVente(); Get.snackbar( 'Succès', 'Point de vente supprimé avec succès', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.green, colorText: Colors.white, ); } catch (e) { Get.snackbar( 'Erreur', 'Impossible de supprimer le point de vente: ${e.toString()}', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red, colorText: Colors.white, ); } finally { if (mounted) { setState(() { _isLoading = false; }); } } } } @override Widget build(BuildContext context) { return Scaffold( appBar: CustomAppBar(title: 'Gestion des points de vente'), drawer: CustomDrawer(), body: Column( children: [ // Formulaire d'ajout Card( margin: const EdgeInsets.all(16), elevation: 4, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), child: Padding( padding: const EdgeInsets.all(16.0), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const Text( 'Ajouter un point de vente', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Color.fromARGB(255, 9, 56, 95), ), ), const SizedBox(height: 16), TextFormField( controller: _nomController, decoration: InputDecoration( labelText: 'Nom du point de vente', prefixIcon: const Icon(Icons.store), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), filled: true, fillColor: Colors.grey.shade50, ), validator: (value) { if (value == null || value.isEmpty) { return 'Veuillez entrer un nom'; } return null; }, ), const SizedBox(height: 12), TextFormField( controller: _codeController, decoration: InputDecoration( labelText: 'Code (optionnel)', prefixIcon: const Icon(Icons.code), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), filled: true, fillColor: Colors.grey.shade50, ), ), const SizedBox(height: 16), ElevatedButton( onPressed: _isLoading ? null : _submitForm, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), backgroundColor: Colors.blue.shade800, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: _isLoading ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : const Text( 'Ajouter le point de vente', style: TextStyle(fontSize: 16), ), ), ], ), ), ), ), // Liste des points de vente Expanded( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( children: [ // Barre de recherche Padding( padding: const EdgeInsets.only(bottom: 16), child: TextField( controller: _searchController, decoration: InputDecoration( labelText: 'Rechercher un point de vente', prefixIcon: const Icon(Icons.search), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), filled: true, fillColor: Colors.grey.shade50, suffixIcon: _searchController.text.isNotEmpty ? IconButton( icon: const Icon(Icons.clear), onPressed: () { _searchController.clear(); _filterPointsDeVente(); }, ) : null, ), ), ), // Liste Expanded( child: _isLoading && _filteredPointsDeVente.isEmpty ? const Center(child: CircularProgressIndicator()) : _filteredPointsDeVente.isEmpty ? const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.store_mall_directory_outlined, size: 60, color: Colors.grey), SizedBox(height: 16), Text( 'Aucun point de vente trouvé', style: TextStyle( fontSize: 16, color: Colors.grey), ), ], ), ) : ListView.builder( itemCount: _filteredPointsDeVente.length, itemBuilder: (context, index) { final point = _filteredPointsDeVente[index]; final canDelete = point['canDelete'] ?? true; final constraintCount = point['constraintCount'] ?? 0; return Card( margin: const EdgeInsets.only(bottom: 8), elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: !canDelete ? Border.all( color: Colors.orange.shade300, width: 1, ) : null, ), child: Padding( padding: const EdgeInsets.all(8.0), child: Row( children: [ // Icône avec indicateur de statut Stack( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: canDelete ? Colors.blue.shade50 : Colors.orange.shade50, borderRadius: BorderRadius.circular(6), ), child: Icon( Icons.store, color: canDelete ? Colors.blue.shade700 : Colors.orange.shade700, size: 20, ), ), if (!canDelete) Positioned( right: 0, top: 0, child: Container( padding: const EdgeInsets.all(2), decoration: const BoxDecoration( color: Colors.orange, shape: BoxShape.circle, ), child: Text( '$constraintCount', style: const TextStyle( color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold, ), ), ), ), ], ), const SizedBox(width: 12), // Informations du point de vente Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( point['nom'] ?? 'N/A', style: const TextStyle( fontWeight: FontWeight.w600, fontSize: 15, ), ), ), if (!canDelete) Icon( Icons.link, size: 16, color: Colors.orange.shade600, ), ], ), Row( children: [ if (point['code'] != null && point['code'].toString().isNotEmpty) Text( 'Code: ${point['code']}', style: TextStyle( color: Colors.grey.shade600, fontSize: 12, ), ), if (!canDelete) ...[ const SizedBox(width: 8), Text( '$constraintCount contrainte(s)', style: TextStyle( color: Colors.orange.shade600, fontSize: 11, fontWeight: FontWeight.w500, ), ), ], ], ), ], ), ), // Boutons d'actions Row( mainAxisSize: MainAxisSize.min, children: [ // Bouton détails IconButton( icon: Icon( Icons.info_outline, size: 20, color: Colors.blue.shade600, ), onPressed: () => _showPointDeVenteDetails(point), tooltip: 'Voir les détails', ), // Bouton modifier IconButton( icon: Icon( Icons.edit, size: 20, color: Colors.blue.shade600, ), onPressed: () => editPointDeVente(context, point), tooltip: 'Modifier point de vente', ), // Bouton suppression avec indication visuelle IconButton( icon: Icon( canDelete ? Icons.delete_outline : Icons.delete_forever_outlined, size: 20, color: canDelete ? Colors.red : Colors.orange.shade700, ), onPressed: () => _deletePointDeVente(point['id']), tooltip: canDelete ? 'Supprimer' : 'Supprimer (avec contraintes)', ), ], ), ], ), ), ), ); }, ), ), ], ), ), ), ], ), ); } @override void dispose() { _nomController.dispose(); _codeController.dispose(); _searchController.dispose(); super.dispose(); } }