From 3fcc50195c84ddd3e9c1e101e0899aef7020b892 Mon Sep 17 00:00:00 2001 From: Stephane Date: Thu, 24 Jul 2025 15:29:55 +0300 Subject: [PATCH] email non obligatoire --- ...ects=b2e12b7ce127ba825ffc2656889f5368-json | 1 + lib/Views/newCommand.dart | 5784 +++++++++-------- 2 files changed, 2952 insertions(+), 2833 deletions(-) create mode 100644 ios/build/ios/XCBuildData/PIFCache/workspace/WORKSPACE@v11_hash=(null)_subobjects=b2e12b7ce127ba825ffc2656889f5368-json diff --git a/ios/build/ios/XCBuildData/PIFCache/workspace/WORKSPACE@v11_hash=(null)_subobjects=b2e12b7ce127ba825ffc2656889f5368-json b/ios/build/ios/XCBuildData/PIFCache/workspace/WORKSPACE@v11_hash=(null)_subobjects=b2e12b7ce127ba825ffc2656889f5368-json new file mode 100644 index 0000000..54ba11d --- /dev/null +++ b/ios/build/ios/XCBuildData/PIFCache/workspace/WORKSPACE@v11_hash=(null)_subobjects=b2e12b7ce127ba825ffc2656889f5368-json @@ -0,0 +1 @@ +{"guid":"dc4b70c03e8043e50e38f2068887b1d4","name":"Pods","path":"/Users/rabarisonmimistephanethannio/Documents/DEV/guycom_finale/ios/Pods/Pods.xcodeproj/project.xcworkspace","projects":["PROJECT@v11_mod=7e9f9a517e1b730b3eb5b9aa5a52f2df_hash=bfdfe7dc352907fc980b868725387e98plugins=1OJSG6M1FOV3XYQCBH7Z29RZ0FPR9XDE1"]} \ No newline at end of file diff --git a/lib/Views/newCommand.dart b/lib/Views/newCommand.dart index 83e1e34..d51b936 100644 --- a/lib/Views/newCommand.dart +++ b/lib/Views/newCommand.dart @@ -33,8 +33,9 @@ class _NouvelleCommandePageState extends State { // Contrôleurs pour les filtres final TextEditingController _searchNameController = TextEditingController(); final TextEditingController _searchImeiController = TextEditingController(); - final TextEditingController _searchReferenceController = TextEditingController(); - List> _pointsDeVente = []; + final TextEditingController _searchReferenceController = + TextEditingController(); + List> _pointsDeVente = []; String? _selectedPointDeVente; final UserController _userController = Get.find(); // Panier @@ -53,79 +54,80 @@ class _NouvelleCommandePageState extends State { bool _showNomSuggestions = false; bool _showTelephoneSuggestions = false; - // Variables pour le scanner (identiques à ProductManagementPage) QRViewController? _qrController; bool _isScanning = false; final GlobalKey _qrKey = GlobalKey(debugLabel: 'QR'); @override -void initState() { - super.initState(); - _loadProducts(); - _loadCommercialUsers(); - _loadPointsDeVenteWithDefault(); // Charger les points de vente - _searchNameController.addListener(_filterProducts); - _searchImeiController.addListener(_filterProducts); - _searchReferenceController.addListener(_filterProducts); -} -Future _loadPointsDeVenteWithDefault() async { - try { - final points = await _appDatabase.getPointsDeVente(); - setState(() { - _pointsDeVente = points; + void initState() { + super.initState(); + _loadProducts(); + _loadCommercialUsers(); + _loadPointsDeVenteWithDefault(); // Charger les points de vente + _searchNameController.addListener(_filterProducts); + _searchImeiController.addListener(_filterProducts); + _searchReferenceController.addListener(_filterProducts); + } - if (points.isNotEmpty) { - if (_userController.pointDeVenteId > 0) { - final userPointDeVente = points.firstWhere( - (point) => point['id'] == _userController.pointDeVenteId, - orElse: () => {}, - ); + Future _loadPointsDeVenteWithDefault() async { + try { + final points = await _appDatabase.getPointsDeVente(); + setState(() { + _pointsDeVente = points; + + if (points.isNotEmpty) { + if (_userController.pointDeVenteId > 0) { + final userPointDeVente = points.firstWhere( + (point) => point['id'] == _userController.pointDeVenteId, + orElse: () => {}, + ); - if (userPointDeVente.isNotEmpty) { - _selectedPointDeVente = userPointDeVente['nom'] as String; + if (userPointDeVente.isNotEmpty) { + _selectedPointDeVente = userPointDeVente['nom'] as String; + } else { + _selectedPointDeVente = points[0]['nom'] as String; + } } else { _selectedPointDeVente = points[0]['nom'] as String; } - } else { - _selectedPointDeVente = points[0]['nom'] as String; } - } - }); + }); - _filterProducts(); // Appliquer le filtre dès le chargement - } catch (e) { - Get.snackbar('Erreur', 'Impossible de charger les points de vente: $e'); - print('❌ Erreur chargement points de vente: $e'); + _filterProducts(); // Appliquer le filtre dès le chargement + } catch (e) { + Get.snackbar('Erreur', 'Impossible de charger les points de vente: $e'); + print('❌ Erreur chargement points de vente: $e'); + } } -} -bool _isUserSuperAdmin() { - return _userController.role == 'Super Admin'; -} -bool _isProduitCommandable(Product product) { - if (_isUserSuperAdmin()) { - return true; // Les superadmins peuvent tout commander + + bool _isUserSuperAdmin() { + return _userController.role == 'Super Admin'; } - - // Les autres utilisateurs ne peuvent commander que les produits de leur PV - return product.pointDeVenteId == _userController.pointDeVenteId; -} + bool _isProduitCommandable(Product product) { + if (_isUserSuperAdmin()) { + return true; // Les superadmins peuvent tout commander + } + + // Les autres utilisateurs ne peuvent commander que les produits de leur PV + return product.pointDeVenteId == _userController.pointDeVenteId; + } // 🎯 MÉTHODE UTILITAIRE: Obtenir l'ID du point de vente sélectionné int? _getSelectedPointDeVenteId() { if (_selectedPointDeVente == null) return null; - + final pointDeVente = _pointsDeVente.firstWhere( (point) => point['nom'] == _selectedPointDeVente, orElse: () => {}, ); - + return pointDeVente.isNotEmpty ? pointDeVente['id'] as int : null; } // 2. Ajoutez cette méthode pour charger les points de vente - // 2. Ajoutez cette méthode pour charger les points de vente + // 2. Ajoutez cette méthode pour charger les points de vente Future _loadPointsDeVente() async { try { final points = await _appDatabase.getPointsDeVente(); @@ -145,7 +147,7 @@ bool _isProduitCommandable(Product product) { Future _showRemiseDialog(Product product) async { final detailExistant = _panierDetails[product.id!]; - + final result = await showDialog( context: context, builder: (context) => RemiseDialog( @@ -165,8 +167,6 @@ bool _isProduitCommandable(Product product) { } } - - void _appliquerRemise(int productId, Map remiseData) { final detailExistant = _panierDetails[productId]; if (detailExistant == null) return; @@ -207,146 +207,145 @@ bool _isProduitCommandable(Product product) { duration: const Duration(seconds: 2), ); } -// Ajout des produits au pannier + +// Ajout des produits au pannier // 4. Modifier la méthode pour ajouter des produits au panier // 🎯 MODIFIÉ: Validation avant ajout au panier // 🎯 MODIFIÉ: Validation avant ajout au panier (inchangée) -void _ajouterAuPanier(Product product, int quantite) { - // 🔒 VÉRIFICATION SÉCURITÉ: Non-superadmin ne peut commander que ses produits - if (!_isProduitCommandable(product)) { - Get.snackbar( - 'Produit non commandable', - 'Ce produit appartient à un autre point de vente. Seuls les produits de votre point de vente "${_userController.pointDeVenteDesignation}" sont commandables.', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.orange.shade600, - colorText: Colors.white, - icon: const Icon(Icons.info, color: Colors.white), - duration: const Duration(seconds: 5), - ); - return; - } - - // Vérifier le stock disponible - if (product.stock != null && quantite > product.stock!) { - Get.snackbar( - 'Stock insuffisant', - 'Quantité demandée non disponible', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red, - colorText: Colors.white, - ); - return; - } - - setState(() { - final detail = DetailCommande.sansRemise( - commandeId: 0, - produitId: product.id!, - quantite: quantite, - prixUnitaire: product.price, - produitNom: product.name, - produitReference: product.reference, - ); - _panierDetails[product.id!] = detail; - }); -} - - - // 🎯 MODIFIÉ: Validation lors de la modification de quantité -void _modifierQuantite(int productId, int nouvelleQuantite) { - final detailExistant = _panierDetails[productId]; - if (detailExistant == null) return; + void _ajouterAuPanier(Product product, int quantite) { + // 🔒 VÉRIFICATION SÉCURITÉ: Non-superadmin ne peut commander que ses produits + if (!_isProduitCommandable(product)) { + Get.snackbar( + 'Produit non commandable', + 'Ce produit appartient à un autre point de vente. Seuls les produits de votre point de vente "${_userController.pointDeVenteDesignation}" sont commandables.', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.orange.shade600, + colorText: Colors.white, + icon: const Icon(Icons.info, color: Colors.white), + duration: const Duration(seconds: 5), + ); + return; + } - final product = _products.firstWhere((p) => p.id == productId); - - // 🔒 VÉRIFICATION SÉCURITÉ supplémentaire - if (!_isProduitCommandable(product)) { - Get.snackbar( - 'Modification impossible', - 'Vous ne pouvez modifier que les produits de votre point de vente', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.orange.shade600, - colorText: Colors.white, - ); - return; - } + // Vérifier le stock disponible + if (product.stock != null && quantite > product.stock!) { + Get.snackbar( + 'Stock insuffisant', + 'Quantité demandée non disponible', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + return; + } - if (nouvelleQuantite <= 0) { setState(() { - _panierDetails.remove(productId); + final detail = DetailCommande.sansRemise( + commandeId: 0, + produitId: product.id!, + quantite: quantite, + prixUnitaire: product.price, + produitNom: product.name, + produitReference: product.reference, + ); + _panierDetails[product.id!] = detail; }); - return; } - // ... reste du code existant pour la modification - if (product.stock != null && nouvelleQuantite > product.stock!) { - Get.snackbar( - 'Stock insuffisant', - 'Quantité maximum: ${product.stock}', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.orange, - colorText: Colors.white, - ); - return; - } + // 🎯 MODIFIÉ: Validation lors de la modification de quantité + void _modifierQuantite(int productId, int nouvelleQuantite) { + final detailExistant = _panierDetails[productId]; + if (detailExistant == null) return; - final nouveauSousTotal = nouvelleQuantite * detailExistant.prixUnitaire; - - setState(() { - if (detailExistant.estCadeau) { - // Pour un cadeau, le prix final reste à 0 - _panierDetails[productId] = DetailCommande( - id: detailExistant.id, - commandeId: detailExistant.commandeId, - produitId: detailExistant.produitId, - quantite: nouvelleQuantite, - prixUnitaire: detailExistant.prixUnitaire, - sousTotal: nouveauSousTotal, - prixFinal: 0.0, - estCadeau: true, - produitNom: detailExistant.produitNom, - produitReference: detailExistant.produitReference, - ); - } else if (detailExistant.aRemise) { - // Recalculer la remise si elle existe - final detail = DetailCommande( - id: detailExistant.id, - commandeId: detailExistant.commandeId, - produitId: detailExistant.produitId, - quantite: nouvelleQuantite, - prixUnitaire: detailExistant.prixUnitaire, - sousTotal: nouveauSousTotal, - prixFinal: nouveauSousTotal, - produitNom: detailExistant.produitNom, - produitReference: detailExistant.produitReference, - ).appliquerRemise( - type: detailExistant.remiseType!, - valeur: detailExistant.remiseValeur, + final product = _products.firstWhere((p) => p.id == productId); + + // 🔒 VÉRIFICATION SÉCURITÉ supplémentaire + if (!_isProduitCommandable(product)) { + Get.snackbar( + 'Modification impossible', + 'Vous ne pouvez modifier que les produits de votre point de vente', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.orange.shade600, + colorText: Colors.white, ); - _panierDetails[productId] = detail; - } else { - // Article normal sans remise - _panierDetails[productId] = DetailCommande( - id: detailExistant.id, - commandeId: detailExistant.commandeId, - produitId: detailExistant.produitId, - quantite: nouvelleQuantite, - prixUnitaire: detailExistant.prixUnitaire, - sousTotal: nouveauSousTotal, - prixFinal: nouveauSousTotal, - produitNom: detailExistant.produitNom, - produitReference: detailExistant.produitReference, + return; + } + + if (nouvelleQuantite <= 0) { + setState(() { + _panierDetails.remove(productId); + }); + return; + } + + // ... reste du code existant pour la modification + if (product.stock != null && nouvelleQuantite > product.stock!) { + Get.snackbar( + 'Stock insuffisant', + 'Quantité maximum: ${product.stock}', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.orange, + colorText: Colors.white, ); + return; } - }); -} + final nouveauSousTotal = nouvelleQuantite * detailExistant.prixUnitaire; + + setState(() { + if (detailExistant.estCadeau) { + // Pour un cadeau, le prix final reste à 0 + _panierDetails[productId] = DetailCommande( + id: detailExistant.id, + commandeId: detailExistant.commandeId, + produitId: detailExistant.produitId, + quantite: nouvelleQuantite, + prixUnitaire: detailExistant.prixUnitaire, + sousTotal: nouveauSousTotal, + prixFinal: 0.0, + estCadeau: true, + produitNom: detailExistant.produitNom, + produitReference: detailExistant.produitReference, + ); + } else if (detailExistant.aRemise) { + // Recalculer la remise si elle existe + final detail = DetailCommande( + id: detailExistant.id, + commandeId: detailExistant.commandeId, + produitId: detailExistant.produitId, + quantite: nouvelleQuantite, + prixUnitaire: detailExistant.prixUnitaire, + sousTotal: nouveauSousTotal, + prixFinal: nouveauSousTotal, + produitNom: detailExistant.produitNom, + produitReference: detailExistant.produitReference, + ).appliquerRemise( + type: detailExistant.remiseType!, + valeur: detailExistant.remiseValeur, + ); + _panierDetails[productId] = detail; + } else { + // Article normal sans remise + _panierDetails[productId] = DetailCommande( + id: detailExistant.id, + commandeId: detailExistant.commandeId, + produitId: detailExistant.produitId, + quantite: nouvelleQuantite, + prixUnitaire: detailExistant.prixUnitaire, + sousTotal: nouveauSousTotal, + prixFinal: nouveauSousTotal, + produitNom: detailExistant.produitNom, + produitReference: detailExistant.produitReference, + ); + } + }); + } // === NOUVELLES MÉTHODES DE SCAN AUTOMATIQUE (identiques à ProductManagementPage) === - + void _startAutomaticScanning() { if (_isScanning) return; - + setState(() { _isScanning = true; }); @@ -400,7 +399,7 @@ void _modifierQuantite(int productId, int nouvelleQuantite) { cutOutSize: 250, ), ), - + // Instructions overlay Positioned( bottom: 100, @@ -446,15 +445,15 @@ void _modifierQuantite(int productId, int nouvelleQuantite) { void _onAutomaticQRViewCreated(QRViewController controller) { _qrController = controller; - + controller.scannedDataStream.listen((scanData) { if (scanData.code != null && scanData.code!.isNotEmpty) { // Pauser le scanner pour éviter les scans multiples controller.pauseCamera(); - + // Fermer la page du scanner Get.back(); - + // Traiter le résultat avec identification automatique _processScannedData(scanData.code!); } @@ -517,29 +516,29 @@ void _modifierQuantite(int productId, int nouvelleQuantite) { final detailExistant = _panierDetails[foundProduct!.id!]; // Vérifier si le produit peut être ajouté (stock disponible) final currentQuantity = _quantites[foundProduct.id] ?? 0; - if (foundProduct.stock != null && currentQuantity >= foundProduct.stock!) { - Get.snackbar( - 'Stock limite atteint', - 'Quantité maximum atteinte pour "${foundProduct.name}"', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.orange.shade600, - colorText: Colors.white, - duration: const Duration(seconds: 3), - icon: const Icon(Icons.warning_amber, color: Colors.white), - ); - return; - } + if (foundProduct.stock != null && + currentQuantity >= foundProduct.stock!) { + Get.snackbar( + 'Stock limite atteint', + 'Quantité maximum atteinte pour "${foundProduct.name}"', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.orange.shade600, + colorText: Colors.white, + duration: const Duration(seconds: 3), + icon: const Icon(Icons.warning_amber, color: Colors.white), + ); + return; + } // Ajouter le produit au panier - _modifierQuantite(foundProduct.id!, currentQuantity + 1); - - // Afficher le dialogue de succès - _showProductFoundAndAddedDialog(foundProduct, currentQuantity + 1); + _modifierQuantite(foundProduct.id!, currentQuantity + 1); + // Afficher le dialogue de succès + _showProductFoundAndAddedDialog(foundProduct, currentQuantity + 1); } catch (e) { // Fermer l'indicateur de chargement si il est encore ouvert if (Get.isDialogOpen!) Get.back(); - + Get.snackbar( 'Erreur', 'Une erreur est survenue: ${e.toString()}', @@ -554,39 +553,41 @@ void _modifierQuantite(int productId, int nouvelleQuantite) { Future _findProductAutomatically(String scannedData) async { // Nettoyer les données scannées final cleanedData = scannedData.trim(); - + // 1. Essayer de trouver par IMEI exact for (var product in _products) { if (product.imei?.toLowerCase().trim() == cleanedData.toLowerCase()) { return product; } } - + // 2. Essayer de trouver par référence exacte for (var product in _products) { - if (product.reference?.toLowerCase().trim() == cleanedData.toLowerCase()) { + if (product.reference?.toLowerCase().trim() == + cleanedData.toLowerCase()) { return product; } } - + // 3. Si c'est une URL QR code, extraire la référence if (cleanedData.contains('stock.guycom.mg/')) { final reference = cleanedData.split('/').last; for (var product in _products) { - if (product.reference?.toLowerCase().trim() == reference.toLowerCase()) { + if (product.reference?.toLowerCase().trim() == + reference.toLowerCase()) { return product; } } } - + // 4. Recherche par correspondance partielle dans le nom for (var product in _products) { - if (product.name.toLowerCase().contains(cleanedData.toLowerCase()) && + if (product.name.toLowerCase().contains(cleanedData.toLowerCase()) && cleanedData.length >= 3) { return product; } } - + // 5. Utiliser la base de données pour une recherche plus approfondie try { // Recherche par IMEI dans la base @@ -594,305 +595,312 @@ void _modifierQuantite(int productId, int nouvelleQuantite) { if (productByImei != null) { return productByImei; } - + // Recherche par référence dans la base - final productByRef = await _appDatabase.getProductByReference(cleanedData); + final productByRef = + await _appDatabase.getProductByReference(cleanedData); if (productByRef != null) { return productByRef; } } catch (e) { print('Erreur recherche base de données: $e'); } - + return null; } void _showProductFoundAndAddedDialog(Product product, int newQuantity) { - final isProduitCommandable = _isProduitCommandable(product); - final canRequestTransfer = product.stock != null && product.stock! >= 1; - - Get.dialog( - Dialog( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - child: Container( - padding: const EdgeInsets.all(20), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Header avec icône de succès - Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.green.shade50, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.green.shade200), - ), - child: Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.green.shade100, - shape: BoxShape.circle, - ), - child: Icon( - Icons.check_circle, - color: Colors.green.shade700, - size: 24, - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'Produit identifié !', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black87, - ), - ), - Text( - 'Ajouté au panier avec succès', - style: TextStyle( - fontSize: 12, - color: Colors.green.shade700, - fontWeight: FontWeight.w500, - ), - ), - ], - ), - ), - ], - ), - ), - - const SizedBox(height: 20), - - // Informations du produit - Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.grey.shade50, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.grey.shade200), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - product.name, - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.black87, - ), - ), - const SizedBox(height: 12), - - // Détails du produit en grille - _buildProductDetailRow('Prix', '${product.price.toStringAsFixed(2)} MGA'), - _buildProductDetailRow('Quantité ajoutée', '$newQuantity'), - - if (product.imei != null && product.imei!.isNotEmpty) - _buildProductDetailRow('IMEI', product.imei!), - - if (product.reference != null && product.reference!.isNotEmpty) - _buildProductDetailRow('Référence', product.reference!), - - if (product.stock != null) - _buildProductDetailRow('Stock restant', '${product.stock! - newQuantity}'), - ], - ), - ), - - const SizedBox(height: 20), - - // Badge identification automatique - Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - decoration: BoxDecoration( - color: Colors.blue.shade50, - borderRadius: BorderRadius.circular(20), - border: Border.all(color: Colors.blue.shade200), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.auto_awesome, - color: Colors.blue.shade700, size: 16), - const SizedBox(width: 6), - Text( - 'Identifié automatiquement', - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Colors.blue.shade700, - ), - ), - ], - ), - ), - - const SizedBox(height: 24), - - // Boutons d'action redessinés - Column( - children: [ - // Bouton principal selon les permissions - SizedBox( - width: double.infinity, - height: 48, - child: (!isProduitCommandable && !_isUserSuperAdmin()) - ? ElevatedButton.icon( - onPressed: canRequestTransfer - ? () { - Get.back(); - _showDemandeTransfertDialog(product); - } - : () { - Get.snackbar( - 'Stock insuffisant', - 'Impossible de demander un transfert : produit en rupture de stock', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.orange.shade600, - colorText: Colors.white, - margin: const EdgeInsets.all(16), - ); - }, - icon: const Icon(Icons.swap_horiz, size: 20), - label: const Text( - 'Demander un transfert', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), - ), - style: ElevatedButton.styleFrom( - backgroundColor: canRequestTransfer - ? Colors.orange.shade600 - : Colors.grey.shade400, - foregroundColor: Colors.white, - elevation: 2, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - ) - : ElevatedButton.icon( - onPressed: () { - _ajouterAuPanier(product, 1); - Get.back(); - _showCartBottomSheet(); - }, - icon: const Icon(Icons.shopping_cart, size: 20), - label: const Text( - 'Voir le panier', - style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600), - ), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.green.shade600, - foregroundColor: Colors.white, - elevation: 2, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - ), + final isProduitCommandable = _isProduitCommandable(product); + final canRequestTransfer = product.stock != null && product.stock! >= 1; + + Get.dialog( + Dialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + child: Container( + padding: const EdgeInsets.all(20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Header avec icône de succès + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.green.shade50, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.green.shade200), ), - - const SizedBox(height: 12), - - // Boutons secondaires - Row( + child: Row( children: [ - // Continuer - Expanded( - child: SizedBox( - height: 44, - child: OutlinedButton.icon( - onPressed: () => Get.back(), - icon: const Icon(Icons.close, size: 18), - label: const Text('Continuer'), - style: OutlinedButton.styleFrom( - foregroundColor: Colors.grey.shade700, - side: BorderSide(color: Colors.grey.shade300), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - ), + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.green.shade100, + shape: BoxShape.circle, + ), + child: Icon( + Icons.check_circle, + color: Colors.green.shade700, + size: 24, ), ), - const SizedBox(width: 12), - - // Scanner encore Expanded( - child: SizedBox( - height: 44, - child: ElevatedButton.icon( - onPressed: () { - Get.back(); - _startAutomaticScanning(); - }, - icon: const Icon(Icons.qr_code_scanner, size: 18), - label: const Text('Scanner'), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue.shade600, - foregroundColor: Colors.white, - elevation: 1, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Produit identifié !', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black87, ), ), - ), + Text( + 'Ajouté au panier avec succès', + style: TextStyle( + fontSize: 12, + color: Colors.green.shade700, + fontWeight: FontWeight.w500, + ), + ), + ], ), ), ], ), - ], - ), - ], + ), + + const SizedBox(height: 20), + + // Informations du produit + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey.shade50, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey.shade200), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + product.name, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + const SizedBox(height: 12), + + // Détails du produit en grille + _buildProductDetailRow( + 'Prix', '${product.price.toStringAsFixed(2)} MGA'), + _buildProductDetailRow('Quantité ajoutée', '$newQuantity'), + + if (product.imei != null && product.imei!.isNotEmpty) + _buildProductDetailRow('IMEI', product.imei!), + + if (product.reference != null && + product.reference!.isNotEmpty) + _buildProductDetailRow('Référence', product.reference!), + + if (product.stock != null) + _buildProductDetailRow( + 'Stock restant', '${product.stock! - newQuantity}'), + ], + ), + ), + + const SizedBox(height: 20), + + // Badge identification automatique + Container( + padding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: Colors.blue.shade50, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: Colors.blue.shade200), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.auto_awesome, + color: Colors.blue.shade700, size: 16), + const SizedBox(width: 6), + Text( + 'Identifié automatiquement', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Colors.blue.shade700, + ), + ), + ], + ), + ), + + const SizedBox(height: 24), + + // Boutons d'action redessinés + Column( + children: [ + // Bouton principal selon les permissions + SizedBox( + width: double.infinity, + height: 48, + child: (!isProduitCommandable && !_isUserSuperAdmin()) + ? ElevatedButton.icon( + onPressed: canRequestTransfer + ? () { + Get.back(); + _showDemandeTransfertDialog(product); + } + : () { + Get.snackbar( + 'Stock insuffisant', + 'Impossible de demander un transfert : produit en rupture de stock', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.orange.shade600, + colorText: Colors.white, + margin: const EdgeInsets.all(16), + ); + }, + icon: const Icon(Icons.swap_horiz, size: 20), + label: const Text( + 'Demander un transfert', + style: TextStyle( + fontSize: 16, fontWeight: FontWeight.w600), + ), + style: ElevatedButton.styleFrom( + backgroundColor: canRequestTransfer + ? Colors.orange.shade600 + : Colors.grey.shade400, + foregroundColor: Colors.white, + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ) + : ElevatedButton.icon( + onPressed: () { + _ajouterAuPanier(product, 1); + Get.back(); + _showCartBottomSheet(); + }, + icon: const Icon(Icons.shopping_cart, size: 20), + label: const Text( + 'Voir le panier', + style: TextStyle( + fontSize: 16, fontWeight: FontWeight.w600), + ), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green.shade600, + foregroundColor: Colors.white, + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + ), + + const SizedBox(height: 12), + + // Boutons secondaires + Row( + children: [ + // Continuer + Expanded( + child: SizedBox( + height: 44, + child: OutlinedButton.icon( + onPressed: () => Get.back(), + icon: const Icon(Icons.close, size: 18), + label: const Text('Continuer'), + style: OutlinedButton.styleFrom( + foregroundColor: Colors.grey.shade700, + side: BorderSide(color: Colors.grey.shade300), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + ), + ), + + const SizedBox(width: 12), + + // Scanner encore + Expanded( + child: SizedBox( + height: 44, + child: ElevatedButton.icon( + onPressed: () { + Get.back(); + _startAutomaticScanning(); + }, + icon: const Icon(Icons.qr_code_scanner, size: 18), + label: const Text('Scanner'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue.shade600, + foregroundColor: Colors.white, + elevation: 1, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + ), + ), + ], + ), + ], + ), + ], + ), ), ), - ), - ); -} + ); + } // Widget helper pour les détails du produit -Widget _buildProductDetailRow(String label, String value) { - return Padding( - padding: const EdgeInsets.only(bottom: 8), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 100, - child: Text( - '$label:', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Colors.grey.shade600, + Widget _buildProductDetailRow(String label, String value) { + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 100, + child: Text( + '$label:', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.grey.shade600, + ), ), ), - ), - Expanded( - child: Text( - value, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Colors.black87, + Expanded( + child: Text( + value, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), ), ), - ), - ], - ), - ); -} + ], + ), + ); + } void _showProductNotFoundDialog(String scannedData) { Get.dialog( @@ -1019,7 +1027,7 @@ Widget _buildProductDetailRow(String label, String value) { ), ElevatedButton.icon( onPressed: _isScanning ? null : _startAutomaticScanning, - icon: _isScanning + icon: _isScanning ? const SizedBox( width: 16, height: 16, @@ -1031,9 +1039,11 @@ Widget _buildProductDetailRow(String label, String value) { : const Icon(Icons.qr_code_scanner, size: 18), label: Text(_isScanning ? 'Scan...' : 'Scanner'), style: ElevatedButton.styleFrom( - backgroundColor: _isScanning ? Colors.grey : Colors.green.shade700, + backgroundColor: + _isScanning ? Colors.grey : Colors.green.shade700, foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 8), ), ), ], @@ -1053,31 +1063,30 @@ Widget _buildProductDetailRow(String label, String value) { _emailController.clear(); _telephoneController.clear(); _adresseController.clear(); - + // Vider le nouveau panier _panierDetails.clear(); - + // Réinitialiser le commercial au premier de la liste if (_commercialUsers.isNotEmpty) { _selectedCommercialUser = _commercialUsers.first; } - + // Masquer toutes les suggestions _hideAllSuggestions(); - + // Réinitialiser l'état de chargement _isLoading = false; }); } - - Future _showClientSuggestions(String query, {required bool isNom}) async { + Future _showClientSuggestions(String query, + {required bool isNom}) async { if (query.length < 3) { _hideAllSuggestions(); return; } - - + setState(() { if (isNom) { _showNomSuggestions = true; @@ -1089,8 +1098,6 @@ Widget _buildProductDetailRow(String label, String value) { }); } - - void _hideNomSuggestions() { if (mounted && _showNomSuggestions) { setState(() { @@ -1100,7 +1107,7 @@ Widget _buildProductDetailRow(String label, String value) { } void _hideTelephoneSuggestions() { - if (mounted && _showTelephoneSuggestions){ + if (mounted && _showTelephoneSuggestions) { setState(() { _showTelephoneSuggestions = false; }); @@ -1113,18 +1120,18 @@ Widget _buildProductDetailRow(String label, String value) { } // 🎯 MODIFIÉ: Chargement de TOUS les produits (visibilité totale) -Future _loadProducts() async { - final products = await _appDatabase.getProducts(); - setState(() { - _products.clear(); - // ✅ TOUS les utilisateurs voient TOUS les produits - _products.addAll(products); - print("✅ Produits chargés: ${products.length} (tous visibles)"); - - _filteredProducts.clear(); - _filteredProducts.addAll(_products); - }); -} + Future _loadProducts() async { + final products = await _appDatabase.getProducts(); + setState(() { + _products.clear(); + // ✅ TOUS les utilisateurs voient TOUS les produits + _products.addAll(products); + print("✅ Produits chargés: ${products.length} (tous visibles)"); + + _filteredProducts.clear(); + _filteredProducts.addAll(_products); + }); + } Future _loadCommercialUsers() async { final commercialUsers = await _appDatabase.getCommercialUsers(); @@ -1136,47 +1143,47 @@ Future _loadProducts() async { }); } + // 🎯 MODIFIÉ: Filtrage avec visibilité totale mais indication des restrictions + void _filterProducts() { + final nameQuery = _searchNameController.text.toLowerCase(); + final imeiQuery = _searchImeiController.text.toLowerCase(); + final referenceQuery = _searchReferenceController.text.toLowerCase(); + final selectedPointDeVenteId = _getSelectedPointDeVenteId(); - - - // 🎯 MODIFIÉ: Filtrage avec visibilité totale mais indication des restrictions -void _filterProducts() { - final nameQuery = _searchNameController.text.toLowerCase(); - final imeiQuery = _searchImeiController.text.toLowerCase(); - final referenceQuery = _searchReferenceController.text.toLowerCase(); - final selectedPointDeVenteId = _getSelectedPointDeVenteId(); + setState(() { + _filteredProducts.clear(); - setState(() { - _filteredProducts.clear(); + for (var product in _products) { + bool matchesName = + nameQuery.isEmpty || product.name.toLowerCase().contains(nameQuery); + bool matchesImei = imeiQuery.isEmpty || + (product.imei?.toLowerCase().contains(imeiQuery) ?? false); + bool matchesReference = referenceQuery.isEmpty || + (product.reference?.toLowerCase().contains(referenceQuery) ?? + false); + bool matchesStock = + !_showOnlyInStock || (product.stock != null && product.stock! > 0); + + // Appliquer le filtre par point de vente uniquement si un point est sélectionné + bool matchesPointDeVente = true; + if (selectedPointDeVenteId != null) { + matchesPointDeVente = + product.pointDeVenteId == selectedPointDeVenteId; + } - for (var product in _products) { - bool matchesName = nameQuery.isEmpty || - product.name.toLowerCase().contains(nameQuery); - bool matchesImei = imeiQuery.isEmpty || - (product.imei?.toLowerCase().contains(imeiQuery) ?? false); - bool matchesReference = referenceQuery.isEmpty || - (product.reference?.toLowerCase().contains(referenceQuery) ?? false); - bool matchesStock = - !_showOnlyInStock || (product.stock != null && product.stock! > 0); - - // Appliquer le filtre par point de vente uniquement si un point est sélectionné - bool matchesPointDeVente = true; - if (selectedPointDeVenteId != null) { - matchesPointDeVente = product.pointDeVenteId == selectedPointDeVenteId; + if (matchesName && + matchesImei && + matchesReference && + matchesStock && + matchesPointDeVente) { + _filteredProducts.add(product); + } } + }); - if (matchesName && - matchesImei && - matchesReference && - matchesStock && - matchesPointDeVente) { - _filteredProducts.add(product); - } - } - }); + print("🔍 Filtrage: ${_filteredProducts.length} produits visibles"); + } - print("🔍 Filtrage: ${_filteredProducts.length} produits visibles"); -} void _toggleStockFilter() { setState(() { _showOnlyInStock = !_showOnlyInStock; @@ -1184,40 +1191,39 @@ void _filterProducts() { _filterProducts(); } - // 🎯 MÉTHODE UTILITAIRE: Reset des filtres avec point de vente utilisateur + // 🎯 MÉTHODE UTILITAIRE: Reset des filtres avec point de vente utilisateur void _clearFilters() { - setState(() { - _searchNameController.clear(); - _searchImeiController.clear(); - _searchReferenceController.clear(); - _showOnlyInStock = false; - - // Réinitialiser au point de vente de l'utilisateur connecté - if (_userController.pointDeVenteId > 0) { - final userPointDeVente = _pointsDeVente.firstWhere( - (point) => point['id'] == _userController.pointDeVenteId, - orElse: () => {}, - ); - if (userPointDeVente.isNotEmpty) { - _selectedPointDeVente = userPointDeVente['nom'] as String; + setState(() { + _searchNameController.clear(); + _searchImeiController.clear(); + _searchReferenceController.clear(); + _showOnlyInStock = false; + + // Réinitialiser au point de vente de l'utilisateur connecté + if (_userController.pointDeVenteId > 0) { + final userPointDeVente = _pointsDeVente.firstWhere( + (point) => point['id'] == _userController.pointDeVenteId, + orElse: () => {}, + ); + if (userPointDeVente.isNotEmpty) { + _selectedPointDeVente = userPointDeVente['nom'] as String; + } else { + _selectedPointDeVente = + null; // Fallback si le point de vente n'existe plus + } } else { - _selectedPointDeVente = null; // Fallback si le point de vente n'existe plus + _selectedPointDeVente = null; } - } else { - _selectedPointDeVente = null; - } - }); - - _filterProducts(); - print("🔄 Filtres réinitialisés - Point de vente: $_selectedPointDeVente"); -} + }); - + _filterProducts(); + print("🔄 Filtres réinitialisés - Point de vente: $_selectedPointDeVente"); + } // 11. Modifiez la section des filtres pour inclure le bouton de réinitialisation Widget _buildFilterSection() { final isMobile = MediaQuery.of(context).size.width < 600; - + return Card( elevation: 2, margin: const EdgeInsets.only(bottom: 16), @@ -1245,7 +1251,8 @@ void _filterProducts() { TextButton.icon( onPressed: _clearFilters, icon: const Icon(Icons.clear, size: 18), - label: isMobile ? const SizedBox() : const Text('Réinitialiser'), + label: + isMobile ? const SizedBox() : const Text('Réinitialiser'), style: TextButton.styleFrom( foregroundColor: Colors.grey.shade600, ), @@ -1253,7 +1260,7 @@ void _filterProducts() { ], ), const SizedBox(height: 16), - + // Champ de recherche par nom TextField( controller: _searchNameController, @@ -1268,7 +1275,7 @@ void _filterProducts() { ), ), const SizedBox(height: 12), - + if (!isMobile) ...[ // Version desktop - champs sur la même ligne Row( @@ -1333,7 +1340,7 @@ void _filterProducts() { ), ], const SizedBox(height: 16), - + // Boutons de filtre adaptés pour mobile Wrap( spacing: 8, @@ -1345,31 +1352,30 @@ void _filterProducts() { _showOnlyInStock ? Icons.inventory : Icons.inventory_2, size: 20, ), - label: Text(_showOnlyInStock - ? isMobile ? 'Tous' : 'Afficher tous' - : isMobile ? 'En stock' : 'Stock disponible'), + label: Text(_showOnlyInStock + ? isMobile + ? 'Tous' + : 'Afficher tous' + : isMobile + ? 'En stock' + : 'Stock disponible'), style: ElevatedButton.styleFrom( - backgroundColor: _showOnlyInStock - ? Colors.green.shade600 + backgroundColor: _showOnlyInStock + ? Colors.green.shade600 : Colors.blue.shade600, foregroundColor: Colors.white, padding: EdgeInsets.symmetric( - horizontal: isMobile ? 12 : 16, - vertical: 8 - ), + horizontal: isMobile ? 12 : 16, vertical: 8), ), ), ], ), - + const SizedBox(height: 8), - + // Compteur de résultats avec indicateurs de filtres actifs Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 8 - ), + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.circular(20), @@ -1386,7 +1392,8 @@ void _filterProducts() { ), ), // Indicateurs de filtres actifs - if (_selectedPointDeVente != null || _showOnlyInStock || + if (_selectedPointDeVente != null || + _showOnlyInStock || _searchNameController.text.isNotEmpty || _searchImeiController.text.isNotEmpty || _searchReferenceController.text.isNotEmpty) ...[ @@ -1396,14 +1403,16 @@ void _filterProducts() { children: [ if (_selectedPointDeVente != null) _buildFilterChip('PV: $_selectedPointDeVente'), - if (_showOnlyInStock) - _buildFilterChip('En stock'), + if (_showOnlyInStock) _buildFilterChip('En stock'), if (_searchNameController.text.isNotEmpty) - _buildFilterChip('Nom: ${_searchNameController.text}'), + _buildFilterChip( + 'Nom: ${_searchNameController.text}'), if (_searchImeiController.text.isNotEmpty) - _buildFilterChip('IMEI: ${_searchImeiController.text}'), + _buildFilterChip( + 'IMEI: ${_searchImeiController.text}'), if (_searchReferenceController.text.isNotEmpty) - _buildFilterChip('Réf: ${_searchReferenceController.text}'), + _buildFilterChip( + 'Réf: ${_searchReferenceController.text}'), ], ), ], @@ -1415,7 +1424,8 @@ void _filterProducts() { ), ); } -Widget _buildFilterChip(String label) { + + Widget _buildFilterChip(String label) { return Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( @@ -1435,86 +1445,87 @@ Widget _buildFilterChip(String label) { } Widget _buildFloatingCartButton() { - final isMobile = MediaQuery.of(context).size.width < 600; - final cartItemCount = _panierDetails.values.where((d) => d.quantite > 0).length; - - if (isMobile) { - return Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - FloatingActionButton( - heroTag: "scan_btn", - onPressed: _isScanning ? null : _startAutomaticScanning, - backgroundColor: _isScanning ? Colors.grey : Colors.green.shade700, - foregroundColor: Colors.white, - mini: true, - child: _isScanning - ? const SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator( - strokeWidth: 2, - color: Colors.white, - ), - ) - : const Icon(Icons.qr_code_scanner), + final isMobile = MediaQuery.of(context).size.width < 600; + final cartItemCount = + _panierDetails.values.where((d) => d.quantite > 0).length; + + if (isMobile) { + return Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + FloatingActionButton( + heroTag: "scan_btn", + onPressed: _isScanning ? null : _startAutomaticScanning, + backgroundColor: _isScanning ? Colors.grey : Colors.green.shade700, + foregroundColor: Colors.white, + mini: true, + child: _isScanning + ? const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white, + ), + ) + : const Icon(Icons.qr_code_scanner), + ), + const SizedBox(width: 8), + FloatingActionButton.extended( + onPressed: _showCartBottomSheet, + icon: const Icon(Icons.shopping_cart), + label: Text('$cartItemCount'), + backgroundColor: Colors.blue.shade800, + foregroundColor: Colors.white, + ), + ], + ); + } else { + return FloatingActionButton.extended( + onPressed: _showCartBottomSheet, + icon: const Icon(Icons.shopping_cart), + label: Text('Panier ($cartItemCount)'), + backgroundColor: Colors.blue.shade800, + foregroundColor: Colors.white, + ); + } + } + +// Nouvelle méthode pour afficher les filtres sur mobile + void _showMobileFilters(BuildContext context) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (context) => SingleChildScrollView( + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, ), - const SizedBox(width: 8), - FloatingActionButton.extended( - onPressed: _showCartBottomSheet, - icon: const Icon(Icons.shopping_cart), - label: Text('$cartItemCount'), - backgroundColor: Colors.blue.shade800, - foregroundColor: Colors.white, + child: Column( + children: [ + _buildPointDeVenteFilter(), + _buildFilterSection(), + ], ), - ], - ); - } else { - return FloatingActionButton.extended( - onPressed: _showCartBottomSheet, - icon: const Icon(Icons.shopping_cart), - label: Text('Panier ($cartItemCount)'), - backgroundColor: Colors.blue.shade800, - foregroundColor: Colors.white, + ), ); } -} - -// Nouvelle méthode pour afficher les filtres sur mobile -void _showMobileFilters(BuildContext context) { - showModalBottomSheet( - context: context, - isScrollControlled: true, - builder: (context) => SingleChildScrollView( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).viewInsets.bottom, - ), - child: Column( - children: [ - _buildPointDeVenteFilter(), - _buildFilterSection(), - ], - ), - ), - ); -} - + void _showClientFormDialog() { final isMobile = MediaQuery.of(context).size.width < 600; - + // Variables locales pour les suggestions dans le dialog bool showNomSuggestions = false; bool showPrenomSuggestions = false; bool showEmailSuggestions = false; bool showTelephoneSuggestions = false; List localClientSuggestions = []; - + // GlobalKeys pour positionner les overlays final GlobalKey nomFieldKey = GlobalKey(); final GlobalKey prenomFieldKey = GlobalKey(); final GlobalKey emailFieldKey = GlobalKey(); final GlobalKey telephoneFieldKey = GlobalKey(); - + Get.dialog( StatefulBuilder( builder: (context, setDialogState) { @@ -1529,7 +1540,8 @@ void _showMobileFilters(BuildContext context) { color: Colors.blue.shade100, borderRadius: BorderRadius.circular(8), ), - child: Icon(Icons.person_add, color: Colors.blue.shade700), + child: + Icon(Icons.person_add, color: Colors.blue.shade700), ), const SizedBox(width: 12), Expanded( @@ -1557,11 +1569,13 @@ void _showMobileFilters(BuildContext context) { key: nomFieldKey, controller: _nomController, label: 'Nom', - validator: (value) => value?.isEmpty ?? true - ? 'Veuillez entrer un nom' : null, + validator: (value) => value?.isEmpty ?? true + ? 'Veuillez entrer un nom' + : null, onChanged: (value) async { if (value.length >= 2) { - final suggestions = await _appDatabase.suggestClients(value); + final suggestions = + await _appDatabase.suggestClients(value); setDialogState(() { localClientSuggestions = suggestions; showNomSuggestions = suggestions.isNotEmpty; @@ -1578,20 +1592,23 @@ void _showMobileFilters(BuildContext context) { }, ), const SizedBox(height: 12), - + // Champ Prénom avec suggestions (SANS bouton recherche) _buildTextFormFieldWithKey( key: prenomFieldKey, controller: _prenomController, label: 'Prénom', - validator: (value) => value?.isEmpty ?? true - ? 'Veuillez entrer un prénom' : null, + validator: (value) => value?.isEmpty ?? true + ? 'Veuillez entrer un prénom' + : null, onChanged: (value) async { if (value.length >= 2) { - final suggestions = await _appDatabase.suggestClients(value); + final suggestions = + await _appDatabase.suggestClients(value); setDialogState(() { localClientSuggestions = suggestions; - showPrenomSuggestions = suggestions.isNotEmpty; + showPrenomSuggestions = + suggestions.isNotEmpty; showNomSuggestions = false; showEmailSuggestions = false; showTelephoneSuggestions = false; @@ -1605,54 +1622,60 @@ void _showMobileFilters(BuildContext context) { }, ), const SizedBox(height: 12), - + // Champ Email avec suggestions (SANS bouton recherche) - _buildTextFormFieldWithKey( - key: emailFieldKey, - controller: _emailController, - label: 'Email', - keyboardType: TextInputType.emailAddress, - validator: (value) { - if (value?.isEmpty ?? true) return 'Veuillez entrer un email'; - if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value!)) { - return 'Email invalide'; - } - return null; - }, - onChanged: (value) async { - if (value.length >= 3) { - final suggestions = await _appDatabase.suggestClients(value); - setDialogState(() { - localClientSuggestions = suggestions; - showEmailSuggestions = suggestions.isNotEmpty; - showNomSuggestions = false; - showPrenomSuggestions = false; - showTelephoneSuggestions = false; - }); - } else { - setDialogState(() { - showEmailSuggestions = false; - localClientSuggestions = []; - }); - } - }, - ), + _buildTextFormFieldWithKey( + key: emailFieldKey, + controller: _emailController, + label: 'Email', + keyboardType: TextInputType.emailAddress, + validator: (value) { + // if (value?.isEmpty ?? true) return 'Veuillez entrer un email'; + if (value?.isEmpty ?? true) return null; + if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$') + .hasMatch(value!)) { + return 'Email invalide'; + } + return null; + }, + onChanged: (value) async { + if (value.length >= 3) { + final suggestions = + await _appDatabase.suggestClients(value); + setDialogState(() { + localClientSuggestions = suggestions; + showEmailSuggestions = suggestions.isNotEmpty; + showNomSuggestions = false; + showPrenomSuggestions = false; + showTelephoneSuggestions = false; + }); + } else { + setDialogState(() { + showEmailSuggestions = false; + localClientSuggestions = []; + }); + } + }, + ), const SizedBox(height: 12), - + // Champ Téléphone avec suggestions (SANS bouton recherche) _buildTextFormFieldWithKey( key: telephoneFieldKey, controller: _telephoneController, label: 'Téléphone', keyboardType: TextInputType.phone, - validator: (value) => value?.isEmpty ?? true - ? 'Veuillez entrer un téléphone' : null, + validator: (value) => value?.isEmpty ?? true + ? 'Veuillez entrer un téléphone' + : null, onChanged: (value) async { if (value.length >= 3) { - final suggestions = await _appDatabase.suggestClients(value); + final suggestions = + await _appDatabase.suggestClients(value); setDialogState(() { localClientSuggestions = suggestions; - showTelephoneSuggestions = suggestions.isNotEmpty; + showTelephoneSuggestions = + suggestions.isNotEmpty; showNomSuggestions = false; showPrenomSuggestions = false; showEmailSuggestions = false; @@ -1666,13 +1689,14 @@ void _showMobileFilters(BuildContext context) { }, ), const SizedBox(height: 12), - + _buildTextFormField( controller: _adresseController, label: 'Adresse', maxLines: 2, - validator: (value) => value?.isEmpty ?? true - ? 'Veuillez entrer une adresse' : null, + validator: (value) => value?.isEmpty ?? true + ? 'Veuillez entrer une adresse' + : null, ), const SizedBox(height: 12), _buildCommercialDropdown(), @@ -1691,9 +1715,8 @@ void _showMobileFilters(BuildContext context) { backgroundColor: Colors.blue.shade800, foregroundColor: Colors.white, padding: EdgeInsets.symmetric( - horizontal: isMobile ? 16 : 20, - vertical: isMobile ? 10 : 12 - ), + horizontal: isMobile ? 16 : 20, + vertical: isMobile ? 10 : 12), ), onPressed: () { if (_formKey.currentState!.validate()) { @@ -1716,7 +1739,7 @@ void _showMobileFilters(BuildContext context) { ), ], ), - + // Overlay pour les suggestions du nom if (showNomSuggestions) _buildSuggestionOverlay( @@ -1739,7 +1762,7 @@ void _showMobileFilters(BuildContext context) { }); }, ), - + // Overlay pour les suggestions du prénom if (showPrenomSuggestions) _buildSuggestionOverlay( @@ -1762,7 +1785,7 @@ void _showMobileFilters(BuildContext context) { }); }, ), - + // Overlay pour les suggestions de l'email if (showEmailSuggestions) _buildSuggestionOverlay( @@ -1785,7 +1808,7 @@ void _showMobileFilters(BuildContext context) { }); }, ), - + // Overlay pour les suggestions du téléphone if (showTelephoneSuggestions) _buildSuggestionOverlay( @@ -1839,111 +1862,113 @@ void _showMobileFilters(BuildContext context) { } // Widget pour l'overlay des suggestions - // Widget pour l'overlay des suggestions -Widget _buildSuggestionOverlay({ - required GlobalKey fieldKey, - required List suggestions, - required Function(Client) onClientSelected, - required VoidCallback onDismiss, -}) { - return Positioned.fill( - child: GestureDetector( - onTap: onDismiss, - child: Material( - color: Colors.transparent, - child: Builder( - builder: (context) { - // Obtenir la position du champ - final RenderBox? renderBox = fieldKey.currentContext?.findRenderObject() as RenderBox?; - if (renderBox == null) return const SizedBox(); - - final position = renderBox.localToGlobal(Offset.zero); - final size = renderBox.size; - - return Stack( - children: [ - Positioned( - left: position.dx, - top: position.dy + size.height + 4, - width: size.width, - child: GestureDetector( - onTap: () {}, // Empêcher la fermeture au tap sur la liste - child: Container( - constraints: const BoxConstraints( - maxHeight: 200, // Hauteur maximum pour la scrollabilité - ), - decoration: BoxDecoration( - color: Colors.white, - border: Border.all(color: Colors.grey.shade300), - borderRadius: BorderRadius.circular(8), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.15), - blurRadius: 8, - offset: const Offset(0, 4), - ), - ], - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(8), - child: Scrollbar( - thumbVisibility: suggestions.length > 3, - child: ListView.separated( - padding: EdgeInsets.zero, - shrinkWrap: true, - itemCount: suggestions.length, - separatorBuilder: (context, index) => Divider( - height: 1, - color: Colors.grey.shade200, + // Widget pour l'overlay des suggestions + Widget _buildSuggestionOverlay({ + required GlobalKey fieldKey, + required List suggestions, + required Function(Client) onClientSelected, + required VoidCallback onDismiss, + }) { + return Positioned.fill( + child: GestureDetector( + onTap: onDismiss, + child: Material( + color: Colors.transparent, + child: Builder( + builder: (context) { + // Obtenir la position du champ + final RenderBox? renderBox = + fieldKey.currentContext?.findRenderObject() as RenderBox?; + if (renderBox == null) return const SizedBox(); + + final position = renderBox.localToGlobal(Offset.zero); + final size = renderBox.size; + + return Stack( + children: [ + Positioned( + left: position.dx, + top: position.dy + size.height + 4, + width: size.width, + child: GestureDetector( + onTap: () {}, // Empêcher la fermeture au tap sur la liste + child: Container( + constraints: const BoxConstraints( + maxHeight: + 200, // Hauteur maximum pour la scrollabilité + ), + decoration: BoxDecoration( + color: Colors.white, + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.15), + blurRadius: 8, + offset: const Offset(0, 4), ), - itemBuilder: (context, index) { - final client = suggestions[index]; - return ListTile( - dense: true, - contentPadding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 4, - ), - leading: CircleAvatar( - radius: 16, - backgroundColor: Colors.blue.shade100, - child: Icon( - Icons.person, - size: 16, - color: Colors.blue.shade700, + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(8), + child: Scrollbar( + thumbVisibility: suggestions.length > 3, + child: ListView.separated( + padding: EdgeInsets.zero, + shrinkWrap: true, + itemCount: suggestions.length, + separatorBuilder: (context, index) => Divider( + height: 1, + color: Colors.grey.shade200, + ), + itemBuilder: (context, index) { + final client = suggestions[index]; + return ListTile( + dense: true, + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 4, ), - ), - title: Text( - '${client.nom} ${client.prenom}', - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, + leading: CircleAvatar( + radius: 16, + backgroundColor: Colors.blue.shade100, + child: Icon( + Icons.person, + size: 16, + color: Colors.blue.shade700, + ), ), - ), - subtitle: Text( - '${client.telephone} • ${client.email}', - style: TextStyle( - fontSize: 12, - color: Colors.grey.shade600, + title: Text( + '${client.nom} ${client.prenom}', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + ), ), - ), - onTap: () => onClientSelected(client), - hoverColor: Colors.blue.shade50, - ); - }, + subtitle: Text( + '${client.telephone} • ${client.email}', + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, + ), + ), + onTap: () => onClientSelected(client), + hoverColor: Colors.blue.shade50, + ); + }, + ), ), ), ), ), ), - ), - ], - ); - }, + ], + ); + }, + ), ), ), - ), - ); + ); } // Méthode pour remplir le formulaire avec les données du client @@ -1953,7 +1978,7 @@ Widget _buildSuggestionOverlay({ _emailController.text = client.email; _telephoneController.text = client.telephone; _adresseController.text = client.adresse ?? ''; - + Get.snackbar( 'Client trouvé', 'Les informations ont été remplies automatiquement', @@ -2011,10 +2036,12 @@ Widget _buildSuggestionOverlay({ _selectedCommercialUser = newValue; }); }, - validator: (value) => value == null ? 'Veuillez sélectionner un commercial' : null, + validator: (value) => + value == null ? 'Veuillez sélectionner un commercial' : null, ); } -Widget _buildUserPointDeVenteInfo() { + + Widget _buildUserPointDeVenteInfo() { if (_userController.pointDeVenteId <= 0) { return const SizedBox.shrink(); } @@ -2088,199 +2115,176 @@ Widget _buildUserPointDeVenteInfo() { ); } - // 6. Ajoutez cette méthode pour filtrer les produits par point de vente + // 6. Ajoutez cette méthode pour filtrer les produits par point de vente // 🎯 MODIFIÉ: Dropdown avec gestion améliorée -Widget _buildPointDeVenteFilter() { - // if (!_isUserSuperAdmin()) { - // return const SizedBox.shrink(); // Cacher pour les non-admins - // } - - return Card( - elevation: 2, - margin: const EdgeInsets.only(bottom: 8), - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon(Icons.filter_list, color: Colors.green.shade700), - const SizedBox(width: 8), - const Text('Filtrer par point de vente (Admin)', - style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600)), - ], - ), - const SizedBox(height: 8), - DropdownButtonFormField( - value: _selectedPointDeVente, - decoration: InputDecoration(labelText: 'Point de vente'), - items: [ - const DropdownMenuItem( - value: null, child: Text('Tous les points de vente')), - ..._pointsDeVente.map((point) { - return DropdownMenuItem( - value: point['nom'] as String, - child: Text(point['nom'] as String), - ); - }).toList(), - ], - onChanged: (value) { - setState(() { - _selectedPointDeVente = value; - _filterProducts(); - }); - }, - ), - ], + Widget _buildPointDeVenteFilter() { + // if (!_isUserSuperAdmin()) { + // return const SizedBox.shrink(); // Cacher pour les non-admins + // } + + return Card( + elevation: 2, + margin: const EdgeInsets.only(bottom: 8), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.filter_list, color: Colors.green.shade700), + const SizedBox(width: 8), + const Text('Filtrer par point de vente (Admin)', + style: + TextStyle(fontSize: 14, fontWeight: FontWeight.w600)), + ], + ), + const SizedBox(height: 8), + DropdownButtonFormField( + value: _selectedPointDeVente, + decoration: InputDecoration(labelText: 'Point de vente'), + items: [ + const DropdownMenuItem( + value: null, child: Text('Tous les points de vente')), + ..._pointsDeVente.map((point) { + return DropdownMenuItem( + value: point['nom'] as String, + child: Text(point['nom'] as String), + ); + }).toList(), + ], + onChanged: (value) { + setState(() { + _selectedPointDeVente = value; + _filterProducts(); + }); + }, + ), + ], + ), ), - ), - ); -} + ); + } // 🎯 MODIFIÉ: Interface utilisateur adaptée selon le rôle // 🎯 NOUVEAU: Header d'information adapté -Widget _buildRoleBasedHeader() { - final commandableCount = _products.where((p) => _isProduitCommandable(p)).length; - final totalCount = _products.length; - - return Card( - elevation: 2, - margin: const EdgeInsets.only(bottom: 8), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Column( - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: _isUserSuperAdmin() - ? Colors.purple.shade100 - : Colors.blue.shade100, - borderRadius: BorderRadius.circular(8), - ), - child: Icon( - _isUserSuperAdmin() ? Icons.admin_panel_settings : Icons.visibility, - color: _isUserSuperAdmin() - ? Colors.purple.shade700 - : Colors.blue.shade700, - size: 20, - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - _isUserSuperAdmin() ? 'Mode Administrateur' : 'Mode Consultation étendue', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: _isUserSuperAdmin() - ? Colors.purple.shade700 - : Colors.blue.shade700, - ), - ), - const SizedBox(height: 4), - Text( - _isUserSuperAdmin() - ? 'Tous les produits sont visibles et commandables' - : 'Tous les produits sont visibles • Commandes limitées à votre PV', - style: TextStyle( - fontSize: 12, - color: Colors.grey.shade600, - ), - ), - ], - ), - ), - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - decoration: BoxDecoration( - color: _isUserSuperAdmin() - ? Colors.purple.shade50 - : Colors.blue.shade50, - borderRadius: BorderRadius.circular(8), - border: Border.all( - color: _isUserSuperAdmin() - ? Colors.purple.shade200 - : Colors.blue.shade200 - ), - ), - child: Text( - _userController.role.toUpperCase(), - style: TextStyle( - fontSize: 10, - color: _isUserSuperAdmin() - ? Colors.purple.shade600 - : Colors.blue.shade600, - fontWeight: FontWeight.w600, - ), - ), - ), - ], - ), - - // Statistiques de produits - const SizedBox(height: 12), - Row( - children: [ - // Produits visibles - Expanded( - child: Container( + Widget _buildRoleBasedHeader() { + final commandableCount = + _products.where((p) => _isProduitCommandable(p)).length; + final totalCount = _products.length; + + return Card( + elevation: 2, + margin: const EdgeInsets.only(bottom: 8), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + children: [ + Row( + children: [ + Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: Colors.blue.shade50, + color: _isUserSuperAdmin() + ? Colors.purple.shade100 + : Colors.blue.shade100, borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.blue.shade200), ), - child: Row( + child: Icon( + _isUserSuperAdmin() + ? Icons.admin_panel_settings + : Icons.visibility, + color: _isUserSuperAdmin() + ? Colors.purple.shade700 + : Colors.blue.shade700, + size: 20, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Icon(Icons.visibility, size: 16, color: Colors.blue.shade600), - const SizedBox(width: 8), - Expanded( - child: Text( - '$totalCount produit(s) visibles', - style: TextStyle( - fontSize: 12, - color: Colors.blue.shade600, - fontWeight: FontWeight.w500, - ), + Text( + _isUserSuperAdmin() + ? 'Mode Administrateur' + : 'Mode Consultation étendue', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: _isUserSuperAdmin() + ? Colors.purple.shade700 + : Colors.blue.shade700, + ), + ), + const SizedBox(height: 4), + Text( + _isUserSuperAdmin() + ? 'Tous les produits sont visibles et commandables' + : 'Tous les produits sont visibles • Commandes limitées à votre PV', + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, ), ), ], ), ), - ), - - if (!_isUserSuperAdmin()) ...[ - const SizedBox(width: 8), - // Produits commandables + Container( + padding: + const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: _isUserSuperAdmin() + ? Colors.purple.shade50 + : Colors.blue.shade50, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: _isUserSuperAdmin() + ? Colors.purple.shade200 + : Colors.blue.shade200), + ), + child: Text( + _userController.role.toUpperCase(), + style: TextStyle( + fontSize: 10, + color: _isUserSuperAdmin() + ? Colors.purple.shade600 + : Colors.blue.shade600, + fontWeight: FontWeight.w600, + ), + ), + ), + ], + ), + + // Statistiques de produits + const SizedBox(height: 12), + Row( + children: [ + // Produits visibles Expanded( child: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: Colors.green.shade50, + color: Colors.blue.shade50, borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.green.shade200), + border: Border.all(color: Colors.blue.shade200), ), child: Row( children: [ - Icon(Icons.shopping_cart, size: 16, color: Colors.green.shade600), + Icon(Icons.visibility, + size: 16, color: Colors.blue.shade600), const SizedBox(width: 8), Expanded( child: Text( - '$commandableCount commandables', + '$totalCount produit(s) visibles', style: TextStyle( fontSize: 12, - color: Colors.green.shade600, + color: Colors.blue.shade600, fontWeight: FontWeight.w500, ), ), @@ -2289,17 +2293,49 @@ Widget _buildRoleBasedHeader() { ), ), ), + + if (!_isUserSuperAdmin()) ...[ + const SizedBox(width: 8), + // Produits commandables + Expanded( + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.green.shade50, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.green.shade200), + ), + child: Row( + children: [ + Icon(Icons.shopping_cart, + size: 16, color: Colors.green.shade600), + const SizedBox(width: 8), + Expanded( + child: Text( + '$commandableCount commandables', + style: TextStyle( + fontSize: 12, + color: Colors.green.shade600, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + ), + ], ], - ], - ), - ], + ), + ], + ), ), - ), - ); -} + ); + } + Widget _buildProductList() { final isMobile = MediaQuery.of(context).size.width < 600; - + return _filteredProducts.isEmpty ? _buildEmptyState() : ListView.builder( @@ -2308,7 +2344,7 @@ Widget _buildRoleBasedHeader() { itemBuilder: (context, index) { final product = _filteredProducts[index]; final quantity = _quantites[product.id] ?? 0; - + return _buildProductListItem(product, quantity, isMobile); }, ); @@ -2349,1009 +2385,1091 @@ Widget _buildRoleBasedHeader() { } // 🎯 MODIFIÉ: Interface produit avec indication visuelle de la commandabilité -Widget _buildProductListItem(Product product, int quantity, bool isMobile) { - final bool isOutOfStock = product.stock != null && product.stock! <= 0; - final detailPanier = _panierDetails[product.id!]; - final int currentQuantity = detailPanier?.quantite ?? 0; - final isCurrentUserPointDeVente = product.pointDeVenteId == _userController.pointDeVenteId; - final isProduitCommandable = _isProduitCommandable(product); - - return FutureBuilder( - future: _appDatabase.getPointDeVenteNomById(product.pointDeVenteId ?? 0), - builder: (context, snapshot) { - String pointDeVenteText = 'Chargement...'; - if (snapshot.connectionState == ConnectionState.done) { - if (snapshot.hasError) { - pointDeVenteText = 'Erreur de chargement'; - } else { - pointDeVenteText = snapshot.data ?? 'Non spécifié'; + Widget _buildProductListItem(Product product, int quantity, bool isMobile) { + final bool isOutOfStock = product.stock != null && product.stock! <= 0; + final detailPanier = _panierDetails[product.id!]; + final int currentQuantity = detailPanier?.quantite ?? 0; + final isCurrentUserPointDeVente = + product.pointDeVenteId == _userController.pointDeVenteId; + final isProduitCommandable = _isProduitCommandable(product); + + return FutureBuilder( + future: _appDatabase.getPointDeVenteNomById(product.pointDeVenteId ?? 0), + builder: (context, snapshot) { + String pointDeVenteText = 'Chargement...'; + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.hasError) { + pointDeVenteText = 'Erreur de chargement'; + } else { + pointDeVenteText = snapshot.data ?? 'Non spécifié'; + } } - } - return Card( - margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - elevation: 2, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: isCurrentUserPointDeVente - ? BorderSide(color: Colors.orange.shade300, width: 2) - : !isProduitCommandable - ? BorderSide(color: Colors.grey.shade300, width: 1.5) - : BorderSide.none, - ), - child: Opacity( - opacity: isProduitCommandable ? 1.0 : 0.7, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - border: isOutOfStock - ? Border.all(color: Colors.red.shade200, width: 1.5) - : detailPanier?.estCadeau == true - ? Border.all(color: Colors.green.shade300, width: 2) - : detailPanier?.aRemise == true - ? Border.all(color: Colors.orange.shade300, width: 2) - : isCurrentUserPointDeVente - ? Border.all(color: Colors.orange.shade300, width: 2) - : !isProduitCommandable - ? Border.all(color: Colors.grey.shade200, width: 1) - : null, - ), - child: Padding( - padding: const EdgeInsets.all(12.0), - child: Column( - children: [ - Row( - children: [ - Container( - width: isMobile ? 40 : 50, - height: isMobile ? 40 : 50, - decoration: BoxDecoration( - color: !isProduitCommandable - ? Colors.grey.shade100 - : isOutOfStock - ? Colors.red.shade50 - : detailPanier?.estCadeau == true - ? Colors.green.shade50 - : detailPanier?.aRemise == true - ? Colors.orange.shade50 - : isCurrentUserPointDeVente - ? Colors.orange.shade50 - : Colors.blue.shade50, - borderRadius: BorderRadius.circular(8), - ), - child: Icon( - !isProduitCommandable - ? Icons.lock_outline - : detailPanier?.estCadeau == true - ? Icons.card_giftcard - : detailPanier?.aRemise == true - ? Icons.discount - : isCurrentUserPointDeVente - ? Icons.store - : Icons.shopping_bag, - size: isMobile ? 20 : 24, - color: !isProduitCommandable - ? Colors.grey.shade500 - : isOutOfStock - ? Colors.red - : detailPanier?.estCadeau == true - ? Colors.green.shade700 - : detailPanier?.aRemise == true - ? Colors.orange.shade700 - : isCurrentUserPointDeVente - ? Colors.orange.shade700 - : Colors.blue, + return Card( + margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: isCurrentUserPointDeVente + ? BorderSide(color: Colors.orange.shade300, width: 2) + : !isProduitCommandable + ? BorderSide(color: Colors.grey.shade300, width: 1.5) + : BorderSide.none, + ), + child: Opacity( + opacity: isProduitCommandable ? 1.0 : 0.7, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: isOutOfStock + ? Border.all(color: Colors.red.shade200, width: 1.5) + : detailPanier?.estCadeau == true + ? Border.all(color: Colors.green.shade300, width: 2) + : detailPanier?.aRemise == true + ? Border.all( + color: Colors.orange.shade300, width: 2) + : isCurrentUserPointDeVente + ? Border.all( + color: Colors.orange.shade300, width: 2) + : !isProduitCommandable + ? Border.all( + color: Colors.grey.shade200, width: 1) + : null, + ), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + children: [ + Row( + children: [ + Container( + width: isMobile ? 40 : 50, + height: isMobile ? 40 : 50, + decoration: BoxDecoration( + color: !isProduitCommandable + ? Colors.grey.shade100 + : isOutOfStock + ? Colors.red.shade50 + : detailPanier?.estCadeau == true + ? Colors.green.shade50 + : detailPanier?.aRemise == true + ? Colors.orange.shade50 + : isCurrentUserPointDeVente + ? Colors.orange.shade50 + : Colors.blue.shade50, + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + !isProduitCommandable + ? Icons.lock_outline + : detailPanier?.estCadeau == true + ? Icons.card_giftcard + : detailPanier?.aRemise == true + ? Icons.discount + : isCurrentUserPointDeVente + ? Icons.store + : Icons.shopping_bag, + size: isMobile ? 20 : 24, + color: !isProduitCommandable + ? Colors.grey.shade500 + : isOutOfStock + ? Colors.red + : detailPanier?.estCadeau == true + ? Colors.green.shade700 + : detailPanier?.aRemise == true + ? Colors.orange.shade700 + : isCurrentUserPointDeVente + ? Colors.orange.shade700 + : Colors.blue, + ), ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Expanded( - child: Text( - product.name, - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: isMobile ? 14 : 16, - color: !isProduitCommandable - ? Colors.grey.shade600 - : isOutOfStock - ? Colors.red.shade700 - : null, + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + product.name, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: isMobile ? 14 : 16, + color: !isProduitCommandable + ? Colors.grey.shade600 + : isOutOfStock + ? Colors.red.shade700 + : null, + ), ), ), - ), - // Indicateurs de statut - if (!isProduitCommandable && !_isUserSuperAdmin()) - Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), - decoration: BoxDecoration( - color: Colors.grey.shade200, - borderRadius: BorderRadius.circular(10), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.lock_outline, size: 10, color: Colors.grey.shade600), - const SizedBox(width: 2), - Text( - 'AUTRE PV', - style: TextStyle( - fontSize: 9, - fontWeight: FontWeight.bold, - color: Colors.grey.shade600, + // Indicateurs de statut + if (!isProduitCommandable && + !_isUserSuperAdmin()) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: Colors.grey.shade200, + borderRadius: BorderRadius.circular(10), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.lock_outline, + size: 10, + color: Colors.grey.shade600), + const SizedBox(width: 2), + Text( + 'AUTRE PV', + style: TextStyle( + fontSize: 9, + fontWeight: FontWeight.bold, + color: Colors.grey.shade600, + ), ), - ), - ], + ], + ), ), - ), - if (detailPanier?.estCadeau == true) - Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), - decoration: BoxDecoration( - color: Colors.green.shade100, - borderRadius: BorderRadius.circular(10), - ), - child: Text( - 'CADEAU', - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.bold, - color: Colors.green.shade700, + if (detailPanier?.estCadeau == true) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: Colors.green.shade100, + borderRadius: BorderRadius.circular(10), + ), + child: Text( + 'CADEAU', + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + color: Colors.green.shade700, + ), ), ), - ), - if (isCurrentUserPointDeVente && detailPanier?.estCadeau != true && isProduitCommandable) - Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), - decoration: BoxDecoration( - color: Colors.orange.shade100, - borderRadius: BorderRadius.circular(10), + if (isCurrentUserPointDeVente && + detailPanier?.estCadeau != true && + isProduitCommandable) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: Colors.orange.shade100, + borderRadius: BorderRadius.circular(10), + ), + child: Text( + 'MON PV', + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + color: Colors.orange.shade700, + ), + ), ), - child: Text( - 'MON PV', + ], + ), + const SizedBox(height: 4), + + // ===== PRIX AVEC GESTION CADEAUX/REMISES ===== + Row( + children: [ + if (detailPanier?.estCadeau == true) ...[ + Text( + 'Gratuit', style: TextStyle( - fontSize: 10, + color: Colors.green.shade700, fontWeight: FontWeight.bold, - color: Colors.orange.shade700, + fontSize: isMobile ? 12 : 14, ), ), - ), - ], - ), - const SizedBox(height: 4), - - // ===== PRIX AVEC GESTION CADEAUX/REMISES ===== - Row( - children: [ - if (detailPanier?.estCadeau == true) ...[ - Text( - 'Gratuit', - style: TextStyle( - color: Colors.green.shade700, - fontWeight: FontWeight.bold, - fontSize: isMobile ? 12 : 14, - ), - ), - const SizedBox(width: 8), - Text( - '${product.price.toStringAsFixed(2)} MGA', - style: TextStyle( - color: Colors.grey.shade500, - fontWeight: FontWeight.w600, - fontSize: isMobile ? 11 : 13, - decoration: TextDecoration.lineThrough, - ), - ), - ] else ...[ - Text( - '${product.price.toStringAsFixed(2)} MGA', - style: TextStyle( - color: Colors.green.shade700, - fontWeight: FontWeight.w600, - fontSize: isMobile ? 12 : 14, - decoration: detailPanier?.aRemise == true - ? TextDecoration.lineThrough - : null, - ), - ), - if (detailPanier?.aRemise == true) ...[ const SizedBox(width: 8), Text( - '${(detailPanier!.prixFinal / detailPanier.quantite).toStringAsFixed(2)} MGA', + '${product.price.toStringAsFixed(2)} MGA', style: TextStyle( - color: Colors.orange.shade700, - fontWeight: FontWeight.bold, + color: Colors.grey.shade500, + fontWeight: FontWeight.w600, + fontSize: isMobile ? 11 : 13, + decoration: TextDecoration.lineThrough, + ), + ), + ] else ...[ + Text( + '${product.price.toStringAsFixed(2)} MGA', + style: TextStyle( + color: Colors.green.shade700, + fontWeight: FontWeight.w600, fontSize: isMobile ? 12 : 14, + decoration: + detailPanier?.aRemise == true + ? TextDecoration.lineThrough + : null, ), ), + if (detailPanier?.aRemise == true) ...[ + const SizedBox(width: 8), + Text( + '${(detailPanier!.prixFinal / detailPanier.quantite).toStringAsFixed(2)} MGA', + style: TextStyle( + color: Colors.orange.shade700, + fontWeight: FontWeight.bold, + fontSize: isMobile ? 12 : 14, + ), + ), + ], ], ], - ], - ), - - // Affichage remise - if (detailPanier?.aRemise == true && !detailPanier!.estCadeau) - Text( - 'Remise: ${detailPanier!.remiseDescription}', - style: TextStyle( - fontSize: isMobile ? 10 : 12, - color: Colors.orange.shade600, - fontWeight: FontWeight.w500, - ), ), - - // Stock - if (product.stock != null) - Text( - 'Stock: ${product.stock}${isOutOfStock ? ' (Rupture)' : ''}', - style: TextStyle( - fontSize: isMobile ? 10 : 12, - color: isOutOfStock - ? Colors.red.shade600 - : Colors.grey.shade600, - fontWeight: isOutOfStock ? FontWeight.w600 : FontWeight.normal, + + // Affichage remise + if (detailPanier?.aRemise == true && + !detailPanier!.estCadeau) + Text( + 'Remise: ${detailPanier!.remiseDescription}', + style: TextStyle( + fontSize: isMobile ? 10 : 12, + color: Colors.orange.shade600, + fontWeight: FontWeight.w500, + ), ), - ), - - // ===== AFFICHAGE IMEI ET RÉFÉRENCE ===== - if (product.imei != null && product.imei!.isNotEmpty) - Text( - 'IMEI: ${product.imei}', - style: TextStyle( - fontSize: isMobile ? 9 : 11, - color: Colors.grey.shade600, - fontFamily: 'monospace', + + // Stock + if (product.stock != null) + Text( + 'Stock: ${product.stock}${isOutOfStock ? ' (Rupture)' : ''}', + style: TextStyle( + fontSize: isMobile ? 10 : 12, + color: isOutOfStock + ? Colors.red.shade600 + : Colors.grey.shade600, + fontWeight: isOutOfStock + ? FontWeight.w600 + : FontWeight.normal, + ), ), - ), - if (product.reference != null && product.reference!.isNotEmpty) - Text( - 'Réf: ${product.reference}', - style: TextStyle( - fontSize: isMobile ? 9 : 11, - color: Colors.grey.shade600, + + // ===== AFFICHAGE IMEI ET RÉFÉRENCE ===== + if (product.imei != null && + product.imei!.isNotEmpty) + Text( + 'IMEI: ${product.imei}', + style: TextStyle( + fontSize: isMobile ? 9 : 11, + color: Colors.grey.shade600, + fontFamily: 'monospace', + ), ), - ), - - // Point de vente - const SizedBox(height: 4), - Row( - children: [ - Icon( - Icons.store, - size: 12, - color: isCurrentUserPointDeVente - ? Colors.orange.shade700 - : !isProduitCommandable - ? Colors.grey.shade500 - : Colors.grey.shade600 + if (product.reference != null && + product.reference!.isNotEmpty) + Text( + 'Réf: ${product.reference}', + style: TextStyle( + fontSize: isMobile ? 9 : 11, + color: Colors.grey.shade600, + ), ), - const SizedBox(width: 4), - Expanded( - child: Text( - 'PV: $pointDeVenteText', - style: TextStyle( - fontSize: isMobile ? 9 : 11, + + // Point de vente + const SizedBox(height: 4), + Row( + children: [ + Icon(Icons.store, + size: 12, color: isCurrentUserPointDeVente ? Colors.orange.shade700 : !isProduitCommandable ? Colors.grey.shade500 - : Colors.grey.shade600, - fontWeight: isCurrentUserPointDeVente - ? FontWeight.w600 - : FontWeight.normal, + : Colors.grey.shade600), + const SizedBox(width: 4), + Expanded( + child: Text( + 'PV: $pointDeVenteText', + style: TextStyle( + fontSize: isMobile ? 9 : 11, + color: isCurrentUserPointDeVente + ? Colors.orange.shade700 + : !isProduitCommandable + ? Colors.grey.shade500 + : Colors.grey.shade600, + fontWeight: isCurrentUserPointDeVente + ? FontWeight.w600 + : FontWeight.normal, + ), ), ), - ), - if (!isProduitCommandable && !_isUserSuperAdmin()) - Icon( - Icons.lock_outline, - size: 12, - color: Colors.grey.shade500, - ), - ], - ), - ], - ), - ), - - // ===== CONTRÔLES QUANTITÉ ET ACTIONS ===== - Column( - children: [ - // Boutons d'actions (seulement si commandable ET dans le panier) - if (isProduitCommandable && currentQuantity > 0) ...[ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - // Bouton cadeau - Container( - margin: const EdgeInsets.only(right: 4), - child: IconButton( - icon: Icon( - detailPanier?.estCadeau == true - ? Icons.card_giftcard - : Icons.card_giftcard_outlined, - size: isMobile ? 16 : 18, - color: detailPanier?.estCadeau == true - ? Colors.green.shade700 - : Colors.grey.shade600, - ), - onPressed: isOutOfStock ? null : () => _basculerStatutCadeau(product.id!), - tooltip: detailPanier?.estCadeau == true - ? 'Retirer le statut cadeau' - : 'Marquer comme cadeau', - style: IconButton.styleFrom( - backgroundColor: detailPanier?.estCadeau == true - ? Colors.green.shade100 - : Colors.grey.shade100, - minimumSize: Size(isMobile ? 32 : 36, isMobile ? 32 : 36), + if (!isProduitCommandable && + !_isUserSuperAdmin()) + Icon( + Icons.lock_outline, + size: 12, + color: Colors.grey.shade500, ), - ), - ), - // Bouton remise (seulement pour les articles non-cadeaux) - if (!detailPanier!.estCadeau) + ], + ), + ], + ), + ), + + // ===== CONTRÔLES QUANTITÉ ET ACTIONS ===== + Column( + children: [ + // Boutons d'actions (seulement si commandable ET dans le panier) + if (isProduitCommandable && + currentQuantity > 0) ...[ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + // Bouton cadeau Container( margin: const EdgeInsets.only(right: 4), child: IconButton( icon: Icon( - detailPanier.aRemise - ? Icons.discount - : Icons.local_offer, + detailPanier?.estCadeau == true + ? Icons.card_giftcard + : Icons.card_giftcard_outlined, size: isMobile ? 16 : 18, - color: detailPanier.aRemise - ? Colors.orange.shade700 + color: detailPanier?.estCadeau == true + ? Colors.green.shade700 : Colors.grey.shade600, ), - onPressed: isOutOfStock ? null : () => _showRemiseDialog(product), - tooltip: detailPanier.aRemise - ? 'Modifier la remise' - : 'Ajouter une remise', + onPressed: isOutOfStock + ? null + : () => _basculerStatutCadeau( + product.id!), + tooltip: detailPanier?.estCadeau == true + ? 'Retirer le statut cadeau' + : 'Marquer comme cadeau', style: IconButton.styleFrom( - backgroundColor: detailPanier.aRemise - ? Colors.orange.shade100 - : Colors.grey.shade100, - minimumSize: Size(isMobile ? 32 : 36, isMobile ? 32 : 36), + backgroundColor: + detailPanier?.estCadeau == true + ? Colors.green.shade100 + : Colors.grey.shade100, + minimumSize: Size(isMobile ? 32 : 36, + isMobile ? 32 : 36), ), ), ), - // Bouton pour ajouter un cadeau à un autre produit - Container( - margin: const EdgeInsets.only(left: 4), - child: IconButton( - icon: Icon( - Icons.add_circle_outline, - size: isMobile ? 16 : 18, - color: Colors.green.shade600, - ), - onPressed: isOutOfStock ? null : () => _showCadeauDialog(product), - tooltip: 'Ajouter un cadeau', - style: IconButton.styleFrom( - backgroundColor: Colors.green.shade50, - minimumSize: Size(isMobile ? 32 : 36, isMobile ? 32 : 36), + // Bouton remise (seulement pour les articles non-cadeaux) + if (!detailPanier!.estCadeau) + Container( + margin: const EdgeInsets.only(right: 4), + child: IconButton( + icon: Icon( + detailPanier.aRemise + ? Icons.discount + : Icons.local_offer, + size: isMobile ? 16 : 18, + color: detailPanier.aRemise + ? Colors.orange.shade700 + : Colors.grey.shade600, + ), + onPressed: isOutOfStock + ? null + : () => _showRemiseDialog(product), + tooltip: detailPanier.aRemise + ? 'Modifier la remise' + : 'Ajouter une remise', + style: IconButton.styleFrom( + backgroundColor: detailPanier.aRemise + ? Colors.orange.shade100 + : Colors.grey.shade100, + minimumSize: Size(isMobile ? 32 : 36, + isMobile ? 32 : 36), + ), + ), ), - ), - ), - ], - ), - const SizedBox(height: 8), - ], - - // Contrôles de quantité (seulement si commandable) - if (isProduitCommandable) - Container( - decoration: BoxDecoration( - color: isOutOfStock - ? Colors.grey.shade100 - : detailPanier?.estCadeau == true - ? Colors.green.shade50 - : isCurrentUserPointDeVente - ? Colors.orange.shade50 - : Colors.blue.shade50, - borderRadius: BorderRadius.circular(20), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: Icon(Icons.remove, size: isMobile ? 16 : 18), - onPressed: isOutOfStock ? null : () { - if (currentQuantity > 0) { - _modifierQuantite(product.id!, currentQuantity - 1); - } - }, - ), - Text( - currentQuantity.toString(), - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: isMobile ? 12 : 14, + // Bouton pour ajouter un cadeau à un autre produit + Container( + margin: const EdgeInsets.only(left: 4), + child: IconButton( + icon: Icon( + Icons.add_circle_outline, + size: isMobile ? 16 : 18, + color: Colors.green.shade600, + ), + onPressed: isOutOfStock + ? null + : () => _showCadeauDialog(product), + tooltip: 'Ajouter un cadeau', + style: IconButton.styleFrom( + backgroundColor: Colors.green.shade50, + minimumSize: Size(isMobile ? 32 : 36, + isMobile ? 32 : 36), + ), ), ), - IconButton( - icon: Icon(Icons.add, size: isMobile ? 16 : 18), - onPressed: isOutOfStock ? null : () { - if (product.stock == null || currentQuantity < product.stock!) { - if (currentQuantity == 0) { - _ajouterAuPanier(product, 1); - } else { - _modifierQuantite(product.id!, currentQuantity + 1); - } - } else { - Get.snackbar( - 'Stock insuffisant', - 'Quantité demandée non disponible', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red, - colorText: Colors.white, - ); - } - }, - ), ], ), - ) - else - // Message informatif pour produits non-commandables - Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - decoration: BoxDecoration( - color: Colors.grey.shade100, - borderRadius: BorderRadius.circular(20), - border: Border.all(color: Colors.grey.shade300), - ), - child: Column( - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.info_outline, size: 14, color: Colors.grey.shade600), - const SizedBox(width: 4), - Text( - 'Consultation', - style: TextStyle( - fontSize: 11, - color: Colors.grey.shade600, - fontWeight: FontWeight.w500, + const SizedBox(height: 8), + ], + + // Contrôles de quantité (seulement si commandable) + if (isProduitCommandable) + Container( + decoration: BoxDecoration( + color: isOutOfStock + ? Colors.grey.shade100 + : detailPanier?.estCadeau == true + ? Colors.green.shade50 + : isCurrentUserPointDeVente + ? Colors.orange.shade50 + : Colors.blue.shade50, + borderRadius: BorderRadius.circular(20), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon(Icons.remove, + size: isMobile ? 16 : 18), + onPressed: isOutOfStock + ? null + : () { + if (currentQuantity > 0) { + _modifierQuantite(product.id!, + currentQuantity - 1); + } + }, + ), + Text( + currentQuantity.toString(), + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: isMobile ? 12 : 14, + ), + ), + IconButton( + icon: Icon(Icons.add, + size: isMobile ? 16 : 18), + onPressed: isOutOfStock + ? null + : () { + if (product.stock == null || + currentQuantity < + product.stock!) { + if (currentQuantity == 0) { + _ajouterAuPanier(product, 1); + } else { + _modifierQuantite(product.id!, + currentQuantity + 1); + } + } else { + Get.snackbar( + 'Stock insuffisant', + 'Quantité demandée non disponible', + snackPosition: + SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + } + }, + ), + ], + ), + ) + else + // Message informatif pour produits non-commandables + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, vertical: 8), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(20), + border: + Border.all(color: Colors.grey.shade300), + ), + child: Column( + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.info_outline, + size: 14, + color: Colors.grey.shade600), + const SizedBox(width: 4), + Text( + 'Consultation', + style: TextStyle( + fontSize: 11, + color: Colors.grey.shade600, + fontWeight: FontWeight.w500, + ), ), + ], + ), + const SizedBox(height: 4), + ElevatedButton.icon( + icon: const Icon(Icons.swap_horiz, + size: 14), + label: !isMobile + ? const Text('Demander transfertt') + : const SizedBox.shrink(), + style: ElevatedButton.styleFrom( + backgroundColor: + (product.stock != null && + product.stock! >= 1) + ? Colors.blue.shade700 + : Colors.grey.shade400, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), ), - ], - ), - const SizedBox(height: 4), - ElevatedButton.icon( - icon: const Icon(Icons.swap_horiz, size: 14), - label:!isMobile ? const Text('Demander transfertt'):const SizedBox.shrink(), - style: ElevatedButton.styleFrom( - backgroundColor: (product.stock != null && product.stock! >= 1) - ? Colors.blue.shade700 - : Colors.grey.shade400, - foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - ), - onPressed: (product.stock != null && product.stock! >= 1) - ? () => _showDemandeTransfertDialog(product) - : () { - Get.snackbar( - 'Stock insuffisant', - 'Impossible de demander un transfert : produit en rupture de stock', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.orange.shade600, - colorText: Colors.white, - ); - }, -), - ], + onPressed: (product.stock != null && + product.stock! >= 1) + ? () => _showDemandeTransfertDialog( + product) + : () { + Get.snackbar( + 'Stock insuffisant', + 'Impossible de demander un transfert : produit en rupture de stock', + snackPosition: + SnackPosition.BOTTOM, + backgroundColor: + Colors.orange.shade600, + colorText: Colors.white, + ); + }, + ), + ], + ), ), - ), - ], - ), - ], - ), - ], + ], + ), + ], + ), + ], + ), ), ), ), - ), - ); - }, - ); -} + ); + }, + ); + } // 🎨 INTERFACE AMÉLIORÉE: Dialog moderne pour demande de transfert -Future _showDemandeTransfertDialog(Product product) async { - final quantiteController = TextEditingController(text: '1'); - final notesController = TextEditingController(); - final _formKey = GlobalKey(); - - // Récupérer les infos du point de vente source - final pointDeVenteSource = await _appDatabase.getPointDeVenteNomById(product.pointDeVenteId ?? 0); - final pointDeVenteDestination = await _appDatabase.getPointDeVenteNomById(_userController.pointDeVenteId); - - await showDialog( - context: context, - barrierDismissible: false, - builder: (context) => AlertDialog( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - contentPadding: EdgeInsets.zero, - content: Container( - width: 400, - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // En-tête avec design moderne - Container( - width: double.infinity, - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [Colors.blue.shade600, Colors.blue.shade700], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - ), - ), - child: Column( - children: [ - Icon( - Icons.swap_horizontal_circle, - size: 48, - color: Colors.white, - ), - const SizedBox(height: 8), - Text( - 'Demande de transfert', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.white, - ), + Future _showDemandeTransfertDialog(Product product) async { + final quantiteController = TextEditingController(text: '1'); + final notesController = TextEditingController(); + final _formKey = GlobalKey(); + + // Récupérer les infos du point de vente source + final pointDeVenteSource = + await _appDatabase.getPointDeVenteNomById(product.pointDeVenteId ?? 0); + final pointDeVenteDestination = await _appDatabase + .getPointDeVenteNomById(_userController.pointDeVenteId); + + await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => AlertDialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + contentPadding: EdgeInsets.zero, + content: Container( + width: 400, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // En-tête avec design moderne + Container( + width: double.infinity, + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [Colors.blue.shade600, Colors.blue.shade700], + begin: Alignment.topLeft, + end: Alignment.bottomRight, ), - Text( - 'Transférer un produit entre points de vente', - style: TextStyle( - fontSize: 14, - color: Colors.white.withOpacity(0.9), - ), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), ), - ], - ), - ), - - // Contenu principal - Padding( - padding: const EdgeInsets.all(20), - child: Form( - key: _formKey, + ), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Informations du produit - Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.grey.shade50, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.grey.shade200), + Icon( + Icons.swap_horizontal_circle, + size: 48, + color: Colors.white, + ), + const SizedBox(height: 8), + Text( + 'Demande de transfert', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.white, ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.blue.shade100, - borderRadius: BorderRadius.circular(8), - ), - child: Icon( - Icons.inventory_2, - color: Colors.blue.shade700, - size: 20, - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Produit à transférer', - style: TextStyle( - fontSize: 12, - color: Colors.grey.shade600, - fontWeight: FontWeight.w500, - ), - ), - Text( - product.name, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ], - ), - const SizedBox(height: 12), - Row( - children: [ - Expanded( - child: _buildInfoCard( - 'Prix unitaire', - '${product.price.toStringAsFixed(2)} MGA', - Icons.attach_money, - Colors.green, + ), + Text( + 'Transférer un produit entre points de vente', + style: TextStyle( + fontSize: 14, + color: Colors.white.withOpacity(0.9), + ), + ), + ], + ), + ), + + // Contenu principal + Padding( + padding: const EdgeInsets.all(20), + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Informations du produit + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.grey.shade50, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey.shade200), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.blue.shade100, + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.inventory_2, + color: Colors.blue.shade700, + size: 20, + ), ), - ), - const SizedBox(width: 8), - Expanded( - child: _buildInfoCard( - 'Stock disponible', - '${product.stock ?? 0}', - Icons.inventory, - product.stock != null && product.stock! > 0 - ? Colors.green - : Colors.red, + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + 'Produit à transférer', + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade600, + fontWeight: FontWeight.w500, + ), + ), + Text( + product.name, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), ), - ), - ], - ), - if (product.reference != null && product.reference!.isNotEmpty) ...[ - const SizedBox(height: 8), - Text( - 'Référence: ${product.reference}', - style: TextStyle( - fontSize: 12, - color: Colors.grey.shade600, - fontFamily: 'monospace', - ), + ], ), - ], - ], - ), - ), - - const SizedBox(height: 20), - - // Informations de transfert - Container( - width: double.infinity, - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.orange.shade50, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.orange.shade200), - ), - child: Column( - children: [ - Row( - children: [ - Icon(Icons.arrow_forward, color: Colors.orange.shade700), - const SizedBox(width: 8), + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: _buildInfoCard( + 'Prix unitaire', + '${product.price.toStringAsFixed(2)} MGA', + Icons.attach_money, + Colors.green, + ), + ), + const SizedBox(width: 8), + Expanded( + child: _buildInfoCard( + 'Stock disponible', + '${product.stock ?? 0}', + Icons.inventory, + product.stock != null && + product.stock! > 0 + ? Colors.green + : Colors.red, + ), + ), + ], + ), + if (product.reference != null && + product.reference!.isNotEmpty) ...[ + const SizedBox(height: 8), Text( - 'Informations de transfert', + 'Référence: ${product.reference}', style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: Colors.orange.shade700, + fontSize: 12, + color: Colors.grey.shade600, + fontFamily: 'monospace', ), ), ], - ), - const SizedBox(height: 12), - Row( - children: [ - Expanded( - child: _buildTransferStep( - 'DE', - pointDeVenteSource ?? 'Chargement...', - Icons.store_outlined, - Colors.red.shade600, + ], + ), + ), + + const SizedBox(height: 20), + + // Informations de transfert + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.orange.shade50, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.orange.shade200), + ), + child: Column( + children: [ + Row( + children: [ + Icon(Icons.arrow_forward, + color: Colors.orange.shade700), + const SizedBox(width: 8), + Text( + 'Informations de transfert', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.orange.shade700, + ), ), - ), - Container( - margin: const EdgeInsets.symmetric(horizontal: 8), - child: Icon( - Icons.arrow_forward, - color: Colors.orange.shade700, - size: 24, + ], + ), + const SizedBox(height: 12), + Row( + children: [ + Expanded( + child: _buildTransferStep( + 'DE', + pointDeVenteSource ?? 'Chargement...', + Icons.store_outlined, + Colors.red.shade600, + ), ), - ), - Expanded( - child: _buildTransferStep( - 'VERS', - pointDeVenteDestination ?? 'Chargement...', - Icons.store, - Colors.green.shade600, + Container( + margin: const EdgeInsets.symmetric( + horizontal: 8), + child: Icon( + Icons.arrow_forward, + color: Colors.orange.shade700, + size: 24, + ), ), - ), - ], - ), - ], - ), - ), - - const SizedBox(height: 20), - - // Champ quantité avec design amélioré - Text( - 'Quantité à transférer', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Colors.grey.shade700, + Expanded( + child: _buildTransferStep( + 'VERS', + pointDeVenteDestination ?? + 'Chargement...', + Icons.store, + Colors.green.shade600, + ), + ), + ], + ), + ], + ), ), - ), - const SizedBox(height: 8), - Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.grey.shade300), + + const SizedBox(height: 20), + + // Champ quantité avec design amélioré + Text( + 'Quantité à transférer', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.grey.shade700, + ), ), - child: Row( - children: [ - IconButton( - onPressed: () { - int currentQty = int.tryParse(quantiteController.text) ?? 1; - if (currentQty > 1) { - quantiteController.text = (currentQty - 1).toString(); - } - }, - icon: Icon(Icons.remove, color: Colors.grey.shade600), - ), - Expanded( - child: TextFormField( - controller: quantiteController, - decoration: const InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(horizontal: 16), - hintText: 'Quantité', - ), - textAlign: TextAlign.center, - keyboardType: TextInputType.number, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - validator: (value) { - if (value == null || value.isEmpty) { - return 'Veuillez entrer une quantité'; - } - final qty = int.tryParse(value) ?? 0; - if (qty <= 0) { - return 'Quantité invalide'; + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.grey.shade300), + ), + child: Row( + children: [ + IconButton( + onPressed: () { + int currentQty = + int.tryParse(quantiteController.text) ?? + 1; + if (currentQty > 1) { + quantiteController.text = + (currentQty - 1).toString(); } - if (product.stock != null && qty > product.stock!) { - return 'Quantité supérieure au stock disponible'; + }, + icon: Icon(Icons.remove, + color: Colors.grey.shade600), + ), + Expanded( + child: TextFormField( + controller: quantiteController, + decoration: const InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 16), + hintText: 'Quantité', + ), + textAlign: TextAlign.center, + keyboardType: TextInputType.number, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Veuillez entrer une quantité'; + } + final qty = int.tryParse(value) ?? 0; + if (qty <= 0) { + return 'Quantité invalide'; + } + if (product.stock != null && + qty > product.stock!) { + return 'Quantité supérieure au stock disponible'; + } + return null; + }, + ), + ), + IconButton( + onPressed: () { + int currentQty = + int.tryParse(quantiteController.text) ?? + 1; + int maxStock = product.stock ?? 999; + if (currentQty < maxStock) { + quantiteController.text = + (currentQty + 1).toString(); } - return null; }, + icon: Icon(Icons.add, + color: Colors.grey.shade600), ), - ), - IconButton( - onPressed: () { - int currentQty = int.tryParse(quantiteController.text) ?? 1; - int maxStock = product.stock ?? 999; - if (currentQty < maxStock) { - quantiteController.text = (currentQty + 1).toString(); - } - }, - icon: Icon(Icons.add, color: Colors.grey.shade600), - ), - ], + ], + ), ), - ), - - - - // Boutons d'action avec design moderne - Row( - children: [ - Expanded( - child: TextButton( - onPressed: () => Navigator.pop(context), - style: TextButton.styleFrom( - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - side: BorderSide(color: Colors.grey.shade300), + + // Boutons d'action avec design moderne + Row( + children: [ + Expanded( + child: TextButton( + onPressed: () => Navigator.pop(context), + style: TextButton.styleFrom( + padding: + const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + side: + BorderSide(color: Colors.grey.shade300), + ), ), - ), - child: Text( - 'Annuler', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.grey.shade700, + child: Text( + 'Annuler', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.grey.shade700, + ), ), ), ), - ), - const SizedBox(width: 12), - Expanded( - flex: 2, - child: ElevatedButton.icon( - onPressed: () async { - if (!_formKey.currentState!.validate()) return; - - final qty = int.tryParse(quantiteController.text) ?? 0; - if (qty <= 0) { - Get.snackbar( - 'Erreur', - 'Quantité invalide', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red, - colorText: Colors.white, - ); - return; - } - - try { - setState(() => _isLoading = true); - Navigator.pop(context); - - await _appDatabase.createDemandeTransfert( - produitId: product.id!, - pointDeVenteSourceId: product.pointDeVenteId!, - pointDeVenteDestinationId: _userController.pointDeVenteId, - demandeurId: _userController.userId, - quantite: qty, - notes: notesController.text.isNotEmpty - ? notesController.text - : 'Demande de transfert depuis l\'application mobile', - ); - - Get.snackbar( - 'Demande envoyée ✅', - 'Votre demande de transfert de $qty unité(s) a été enregistrée et sera traitée prochainement.', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.green, - colorText: Colors.white, - duration: const Duration(seconds: 4), - icon: const Icon(Icons.check_circle, color: Colors.white), - ); - } catch (e) { - Get.snackbar( - 'Erreur', - 'Impossible d\'envoyer la demande: ${e.toString()}', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red, - colorText: Colors.white, - duration: const Duration(seconds: 4), - ); - } finally { - setState(() => _isLoading = false); - } - }, - icon: const Icon(Icons.send, color: Colors.white), - label: const Text( - 'Envoyer la demande', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.white, + const SizedBox(width: 12), + Expanded( + flex: 2, + child: ElevatedButton.icon( + onPressed: () async { + if (!_formKey.currentState!.validate()) + return; + + final qty = + int.tryParse(quantiteController.text) ?? + 0; + if (qty <= 0) { + Get.snackbar( + 'Erreur', + 'Quantité invalide', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + return; + } + + try { + setState(() => _isLoading = true); + Navigator.pop(context); + + await _appDatabase.createDemandeTransfert( + produitId: product.id!, + pointDeVenteSourceId: + product.pointDeVenteId!, + pointDeVenteDestinationId: + _userController.pointDeVenteId, + demandeurId: _userController.userId, + quantite: qty, + notes: notesController.text.isNotEmpty + ? notesController.text + : 'Demande de transfert depuis l\'application mobile', + ); + + Get.snackbar( + 'Demande envoyée ✅', + 'Votre demande de transfert de $qty unité(s) a été enregistrée et sera traitée prochainement.', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.green, + colorText: Colors.white, + duration: const Duration(seconds: 4), + icon: const Icon(Icons.check_circle, + color: Colors.white), + ); + } catch (e) { + Get.snackbar( + 'Erreur', + 'Impossible d\'envoyer la demande: ${e.toString()}', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + duration: const Duration(seconds: 4), + ); + } finally { + setState(() => _isLoading = false); + } + }, + icon: + const Icon(Icons.send, color: Colors.white), + label: const Text( + 'Envoyer la demande', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white, + ), ), - ), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue.shade600, - padding: const EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue.shade600, + padding: + const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 2, ), - elevation: 2, ), ), - ), - ], - ), - ], + ], + ), + ], + ), ), ), - ), - ], + ], + ), ), ), ), - ), - ); -} + ); + } // 🎨 Widget pour les cartes d'information -Widget _buildInfoCard(String label, String value, IconData icon, Color color) { - return Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: color.withOpacity(0.1), - borderRadius: BorderRadius.circular(8), - border: Border.all(color: color.withOpacity(0.3)), - ), - child: Column( - children: [ - Icon(icon, color: color, size: 20), - const SizedBox(height: 4), - Text( - label, - style: TextStyle( - fontSize: 10, - color: Colors.grey.shade600, - fontWeight: FontWeight.w500, + Widget _buildInfoCard( + String label, String value, IconData icon, Color color) { + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all(color: color.withOpacity(0.3)), + ), + child: Column( + children: [ + Icon(icon, color: color, size: 20), + const SizedBox(height: 4), + Text( + label, + style: TextStyle( + fontSize: 10, + color: Colors.grey.shade600, + fontWeight: FontWeight.w500, + ), + textAlign: TextAlign.center, ), - textAlign: TextAlign.center, - ), - Text( - value, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: color, + Text( + value, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: color, + ), + textAlign: TextAlign.center, ), - textAlign: TextAlign.center, - ), - ], - ), - ); -} + ], + ), + ); + } // 🎨 Widget pour les étapes de transfert -Widget _buildTransferStep(String label, String pointDeVente, IconData icon, Color color) { - return Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8), - border: Border.all(color: color.withOpacity(0.3)), - ), - child: Column( - children: [ - Container( - padding: const EdgeInsets.all(6), - decoration: BoxDecoration( - color: color.withOpacity(0.1), - borderRadius: BorderRadius.circular(6), + Widget _buildTransferStep( + String label, String pointDeVente, IconData icon, Color color) { + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: color.withOpacity(0.3)), + ), + child: Column( + children: [ + Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + color: color.withOpacity(0.1), + borderRadius: BorderRadius.circular(6), + ), + child: Icon(icon, color: color, size: 16), ), - child: Icon(icon, color: color, size: 16), - ), - const SizedBox(height: 6), - Text( - label, - style: TextStyle( - fontSize: 10, - color: Colors.grey.shade600, - fontWeight: FontWeight.bold, + const SizedBox(height: 6), + Text( + label, + style: TextStyle( + fontSize: 10, + color: Colors.grey.shade600, + fontWeight: FontWeight.bold, + ), ), - ), - Text( - pointDeVente, - style: TextStyle( - fontSize: 11, - fontWeight: FontWeight.w600, - color: color, + Text( + pointDeVente, + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w600, + color: color, + ), + textAlign: TextAlign.center, + maxLines: 2, + overflow: TextOverflow.ellipsis, ), - textAlign: TextAlign.center, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - ], - ), - ); -} + ], + ), + ); + } // 🎨 BOUTON AMÉLIORÉ dans le widget principal // Remplacez le bouton "Demander transfert" existant par celui-ci : void _showCartBottomSheet() { final isMobile = MediaQuery.of(context).size.width < 600; - + Get.bottomSheet( Container( height: MediaQuery.of(context).size.height * (isMobile ? 0.85 : 0.7), @@ -3368,9 +3486,8 @@ Widget _buildTransferStep(String label, String pointDeVente, IconData icon, Colo Text( 'Votre Panier', style: TextStyle( - fontSize: isMobile ? 18 : 20, - fontWeight: FontWeight.bold - ), + fontSize: isMobile ? 18 : 20, + fontWeight: FontWeight.bold), ), IconButton( icon: const Icon(Icons.close), @@ -3393,441 +3510,441 @@ Widget _buildTransferStep(String label, String pointDeVente, IconData icon, Colo // 6. Modifier _buildCartItemsList pour afficher les remises Widget _buildCartItemsList() { - final itemsInCart = _panierDetails.entries.where((e) => e.value.quantite > 0).toList(); - - if (itemsInCart.isEmpty) { - return const Center( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.shopping_cart_outlined, size: 60, color: Colors.grey), - SizedBox(height: 16), - Text( - 'Votre panier est vide', - style: TextStyle(fontSize: 16, color: Colors.grey), - ), - ], - ), - ); - } + final itemsInCart = + _panierDetails.entries.where((e) => e.value.quantite > 0).toList(); - return ListView.builder( - itemCount: itemsInCart.length, - itemBuilder: (context, index) { - final entry = itemsInCart[index]; - final detail = entry.value; - final product = _products.firstWhere((p) => p.id == entry.key); - - return Dismissible( - key: Key(entry.key.toString()), - background: Container( - color: Colors.red.shade100, - alignment: Alignment.centerRight, - padding: const EdgeInsets.only(right: 20), - child: const Icon(Icons.delete, color: Colors.red), + if (itemsInCart.isEmpty) { + return const Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.shopping_cart_outlined, size: 60, color: Colors.grey), + SizedBox(height: 16), + Text( + 'Votre panier est vide', + style: TextStyle(fontSize: 16, color: Colors.grey), + ), + ], ), - direction: DismissDirection.endToStart, - onDismissed: (direction) { - setState(() { - _panierDetails.remove(entry.key); - }); - Get.snackbar( - 'Produit retiré', - '${product.name} a été retiré du panier', - snackPosition: SnackPosition.BOTTOM, - ); - }, - child: Card( - margin: const EdgeInsets.only(bottom: 8), - elevation: 1, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - side: detail.estCadeau - ? BorderSide(color: Colors.green.shade300, width: 1.5) - : detail.aRemise - ? BorderSide(color: Colors.orange.shade300, width: 1.5) - : BorderSide.none, + ); + } + + return ListView.builder( + itemCount: itemsInCart.length, + itemBuilder: (context, index) { + final entry = itemsInCart[index]; + final detail = entry.value; + final product = _products.firstWhere((p) => p.id == entry.key); + + return Dismissible( + key: Key(entry.key.toString()), + background: Container( + color: Colors.red.shade100, + alignment: Alignment.centerRight, + padding: const EdgeInsets.only(right: 20), + child: const Icon(Icons.delete, color: Colors.red), ), - child: ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - leading: Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: detail.estCadeau - ? Colors.green.shade50 - : detail.aRemise - ? Colors.orange.shade50 - : Colors.blue.shade50, - borderRadius: BorderRadius.circular(8), - ), - child: Icon( - detail.estCadeau - ? Icons.card_giftcard - : detail.aRemise - ? Icons.discount - : Icons.shopping_bag, - size: 20, - color: detail.estCadeau - ? Colors.green.shade700 - : detail.aRemise - ? Colors.orange.shade700 - : Colors.blue.shade700, - ), - ), - title: Row( - children: [ - Expanded(child: Text(product.name)), - if (detail.estCadeau) - Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), - decoration: BoxDecoration( - color: Colors.green.shade100, - borderRadius: BorderRadius.circular(10), - ), - child: Text( - 'CADEAU', - style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.bold, - color: Colors.green.shade700, - ), - ), - ), - ], + direction: DismissDirection.endToStart, + onDismissed: (direction) { + setState(() { + _panierDetails.remove(entry.key); + }); + Get.snackbar( + 'Produit retiré', + '${product.name} a été retiré du panier', + snackPosition: SnackPosition.BOTTOM, + ); + }, + child: Card( + margin: const EdgeInsets.only(bottom: 8), + elevation: 1, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: detail.estCadeau + ? BorderSide(color: Colors.green.shade300, width: 1.5) + : detail.aRemise + ? BorderSide(color: Colors.orange.shade300, width: 1.5) + : BorderSide.none, ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Text('${detail.quantite} x '), - if (detail.estCadeau) ...[ - Text( - 'GRATUIT', - style: TextStyle( - color: Colors.green.shade700, - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(width: 8), - Text( - '(${detail.prixUnitaire.toStringAsFixed(2)} MGA)', - style: TextStyle( - fontSize: 11, - color: Colors.grey.shade500, - decoration: TextDecoration.lineThrough, - ), - ), - ] else if (detail.aRemise) ...[ - Text( - '${detail.prixUnitaire.toStringAsFixed(2)}', - style: const TextStyle( - decoration: TextDecoration.lineThrough, - color: Colors.grey, - ), + child: ListTile( + contentPadding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + leading: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: detail.estCadeau + ? Colors.green.shade50 + : detail.aRemise + ? Colors.orange.shade50 + : Colors.blue.shade50, + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + detail.estCadeau + ? Icons.card_giftcard + : detail.aRemise + ? Icons.discount + : Icons.shopping_bag, + size: 20, + color: detail.estCadeau + ? Colors.green.shade700 + : detail.aRemise + ? Colors.orange.shade700 + : Colors.blue.shade700, + ), + ), + title: Row( + children: [ + Expanded(child: Text(product.name)), + if (detail.estCadeau) + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: Colors.green.shade100, + borderRadius: BorderRadius.circular(10), ), - const Text(' → '), - Text( - '${(detail.prixFinal / detail.quantite).toStringAsFixed(2)} MGA', + child: Text( + 'CADEAU', style: TextStyle( - color: Colors.orange.shade700, + fontSize: 10, fontWeight: FontWeight.bold, + color: Colors.green.shade700, ), ), - ] else - Text('${detail.prixUnitaire.toStringAsFixed(2)} MGA'), - ], - ), - if (detail.aRemise && !detail.estCadeau) - Text( - 'Remise: ${detail.remiseDescription} (-${detail.montantRemise.toStringAsFixed(2)} MGA)', - style: TextStyle( - fontSize: 11, - color: Colors.orange.shade600, - fontStyle: FontStyle.italic, ), - ), - if (detail.estCadeau) + ], + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ Row( children: [ - Icon( - Icons.card_giftcard, - size: 12, - color: Colors.green.shade600, - ), - const SizedBox(width: 4), - Text( - 'Article offert gracieusement', - style: TextStyle( - fontSize: 11, - color: Colors.green.shade600, - fontStyle: FontStyle.italic, + Text('${detail.quantite} x '), + if (detail.estCadeau) ...[ + Text( + 'GRATUIT', + style: TextStyle( + color: Colors.green.shade700, + fontWeight: FontWeight.bold, + ), ), - ), + const SizedBox(width: 8), + Text( + '(${detail.prixUnitaire.toStringAsFixed(2)} MGA)', + style: TextStyle( + fontSize: 11, + color: Colors.grey.shade500, + decoration: TextDecoration.lineThrough, + ), + ), + ] else if (detail.aRemise) ...[ + Text( + '${detail.prixUnitaire.toStringAsFixed(2)}', + style: const TextStyle( + decoration: TextDecoration.lineThrough, + color: Colors.grey, + ), + ), + const Text(' → '), + Text( + '${(detail.prixFinal / detail.quantite).toStringAsFixed(2)} MGA', + style: TextStyle( + color: Colors.orange.shade700, + fontWeight: FontWeight.bold, + ), + ), + ] else + Text('${detail.prixUnitaire.toStringAsFixed(2)} MGA'), ], ), - ], - ), - trailing: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - if (detail.estCadeau) ...[ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.card_giftcard, - size: 16, - color: Colors.green.shade700, + if (detail.aRemise && !detail.estCadeau) + Text( + 'Remise: ${detail.remiseDescription} (-${detail.montantRemise.toStringAsFixed(2)} MGA)', + style: TextStyle( + fontSize: 11, + color: Colors.orange.shade600, + fontStyle: FontStyle.italic, ), - const SizedBox(width: 4), - Text( - 'GRATUIT', - style: TextStyle( - fontWeight: FontWeight.bold, + ), + if (detail.estCadeau) + Row( + children: [ + Icon( + Icons.card_giftcard, + size: 12, + color: Colors.green.shade600, + ), + const SizedBox(width: 4), + Text( + 'Article offert gracieusement', + style: TextStyle( + fontSize: 11, + color: Colors.green.shade600, + fontStyle: FontStyle.italic, + ), + ), + ], + ), + ], + ), + trailing: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + if (detail.estCadeau) ...[ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.card_giftcard, + size: 16, color: Colors.green.shade700, - fontSize: 14, ), + const SizedBox(width: 4), + Text( + 'GRATUIT', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.green.shade700, + fontSize: 14, + ), + ), + ], + ), + Text( + 'Valeur: ${detail.sousTotal.toStringAsFixed(2)} MGA', + style: TextStyle( + fontSize: 10, + color: Colors.grey.shade500, + fontStyle: FontStyle.italic, ), - ], - ), - Text( - 'Valeur: ${detail.sousTotal.toStringAsFixed(2)} MGA', - style: TextStyle( - fontSize: 10, - color: Colors.grey.shade500, - fontStyle: FontStyle.italic, ), - ), - ] else if (detail.aRemise && detail.sousTotal != detail.prixFinal) ...[ - Text( - '${detail.sousTotal.toStringAsFixed(2)} MGA', - style: const TextStyle( - fontSize: 11, - decoration: TextDecoration.lineThrough, - color: Colors.grey, + ] else if (detail.aRemise && + detail.sousTotal != detail.prixFinal) ...[ + Text( + '${detail.sousTotal.toStringAsFixed(2)} MGA', + style: const TextStyle( + fontSize: 11, + decoration: TextDecoration.lineThrough, + color: Colors.grey, + ), ), - ), - Text( - '${detail.prixFinal.toStringAsFixed(2)} MGA', - style: TextStyle( - fontWeight: FontWeight.bold, - color: Colors.orange.shade700, - fontSize: 14, + Text( + '${detail.prixFinal.toStringAsFixed(2)} MGA', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.orange.shade700, + fontSize: 14, + ), ), - ), - ] else - Text( - '${detail.prixFinal.toStringAsFixed(2)} MGA', - style: TextStyle( - fontWeight: FontWeight.bold, - color: Colors.blue.shade800, - fontSize: 14, + ] else + Text( + '${detail.prixFinal.toStringAsFixed(2)} MGA', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.blue.shade800, + fontSize: 14, + ), ), - ), - ], + ], + ), + onTap: () { + if (detail.estCadeau) { + _basculerStatutCadeau(product.id!); + } else { + _showRemiseDialog(product); + } + }, ), - onTap: () { - if (detail.estCadeau) { - _basculerStatutCadeau(product.id!); - } else { - _showRemiseDialog(product); - } - }, ), - ), - ); - }, - ); -} - + ); + }, + ); + } // 7. Modifier _buildCartTotalSection pour afficher les totaux avec remises Widget _buildCartTotalSection() { - double sousTotal = 0; - double totalRemises = 0; - double totalCadeaux = 0; - double total = 0; - int nombreCadeaux = 0; - - _panierDetails.forEach((productId, detail) { - sousTotal += detail.sousTotal; - if (detail.estCadeau) { - totalCadeaux += detail.sousTotal; - nombreCadeaux += detail.quantite; - } else { - totalRemises += detail.montantRemise; - } - total += detail.prixFinal; - }); + double sousTotal = 0; + double totalRemises = 0; + double totalCadeaux = 0; + double total = 0; + int nombreCadeaux = 0; - return Column( - children: [ - // Sous-total - if (totalRemises > 0 || totalCadeaux > 0) ...[ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text('Sous-total:', style: TextStyle(fontSize: 14)), - Text( - '${sousTotal.toStringAsFixed(2)} MGA', - style: const TextStyle(fontSize: 14), - ), - ], - ), - const SizedBox(height: 4), - ], - - // Remises totales - if (totalRemises > 0) ...[ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Remises totales:', - style: TextStyle( - fontSize: 14, - color: Colors.orange.shade700, + _panierDetails.forEach((productId, detail) { + sousTotal += detail.sousTotal; + if (detail.estCadeau) { + totalCadeaux += detail.sousTotal; + nombreCadeaux += detail.quantite; + } else { + totalRemises += detail.montantRemise; + } + total += detail.prixFinal; + }); + + return Column( + children: [ + // Sous-total + if (totalRemises > 0 || totalCadeaux > 0) ...[ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('Sous-total:', style: TextStyle(fontSize: 14)), + Text( + '${sousTotal.toStringAsFixed(2)} MGA', + style: const TextStyle(fontSize: 14), ), - ), - Text( - '-${totalRemises.toStringAsFixed(2)} MGA', - style: TextStyle( - fontSize: 14, - color: Colors.orange.shade700, - fontWeight: FontWeight.bold, + ], + ), + const SizedBox(height: 4), + ], + + // Remises totales + if (totalRemises > 0) ...[ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Remises totales:', + style: TextStyle( + fontSize: 14, + color: Colors.orange.shade700, + ), ), - ), - ], - ), - const SizedBox(height: 4), - ], - - // Cadeaux offerts - if (totalCadeaux > 0) ...[ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Icon( - Icons.card_giftcard, - size: 16, - color: Colors.green.shade700, + Text( + '-${totalRemises.toStringAsFixed(2)} MGA', + style: TextStyle( + fontSize: 14, + color: Colors.orange.shade700, + fontWeight: FontWeight.bold, ), - const SizedBox(width: 4), - Text( - 'Cadeaux offerts ($nombreCadeaux):', - style: TextStyle( - fontSize: 14, + ), + ], + ), + const SizedBox(height: 4), + ], + + // Cadeaux offerts + if (totalCadeaux > 0) ...[ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Icon( + Icons.card_giftcard, + size: 16, color: Colors.green.shade700, ), + const SizedBox(width: 4), + Text( + 'Cadeaux offerts ($nombreCadeaux):', + style: TextStyle( + fontSize: 14, + color: Colors.green.shade700, + ), + ), + ], + ), + Text( + '-${totalCadeaux.toStringAsFixed(2)} MGA', + style: TextStyle( + fontSize: 14, + color: Colors.green.shade700, + fontWeight: FontWeight.bold, ), - ], + ), + ], + ), + const SizedBox(height: 4), + ], + + if (totalRemises > 0 || totalCadeaux > 0) const Divider(height: 16), + + // Total final + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Total:', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), Text( - '-${totalCadeaux.toStringAsFixed(2)} MGA', - style: TextStyle( - fontSize: 14, - color: Colors.green.shade700, + '${total.toStringAsFixed(2)} MGA', + style: const TextStyle( + fontSize: 18, fontWeight: FontWeight.bold, + color: Colors.green, ), ), ], ), - const SizedBox(height: 4), - ], - - if (totalRemises > 0 || totalCadeaux > 0) - const Divider(height: 16), - - // Total final - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Total:', - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - Text( - '${total.toStringAsFixed(2)} MGA', - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.green, + + const SizedBox(height: 8), + + // Résumé + Text( + '${_panierDetails.values.where((d) => d.quantite > 0).length} article(s)', + style: TextStyle(color: Colors.grey.shade600), + ), + + // Économies totales + if (totalRemises > 0 || totalCadeaux > 0) ...[ + const SizedBox(height: 4), + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: Colors.green.shade50, + borderRadius: BorderRadius.circular(12), + border: Border.all(color: Colors.green.shade200), ), - ), - ], - ), - - const SizedBox(height: 8), - - // Résumé - Text( - '${_panierDetails.values.where((d) => d.quantite > 0).length} article(s)', - style: TextStyle(color: Colors.grey.shade600), - ), - - // Économies totales - if (totalRemises > 0 || totalCadeaux > 0) ...[ - const SizedBox(height: 4), - Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - decoration: BoxDecoration( - color: Colors.green.shade50, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.green.shade200), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - Icons.savings, - size: 16, - color: Colors.green.shade700, - ), - const SizedBox(width: 4), - Text( - 'Économies totales: ${(totalRemises + totalCadeaux).toStringAsFixed(2)} MGA', - style: TextStyle( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.savings, + size: 16, color: Colors.green.shade700, - fontWeight: FontWeight.bold, - fontSize: 12, ), - ), - ], + const SizedBox(width: 4), + Text( + 'Économies totales: ${(totalRemises + totalCadeaux).toStringAsFixed(2)} MGA', + style: TextStyle( + color: Colors.green.shade700, + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ), + ], + ), ), - ), - ], - - // Détail des économies - if (totalRemises > 0 && totalCadeaux > 0) ...[ - const SizedBox(height: 4), - Text( - 'Remises: ${totalRemises.toStringAsFixed(2)} MGA • Cadeaux: ${totalCadeaux.toStringAsFixed(2)} MGA', - style: TextStyle( - color: Colors.grey.shade600, - fontSize: 11, - fontStyle: FontStyle.italic, + ], + + // Détail des économies + if (totalRemises > 0 && totalCadeaux > 0) ...[ + const SizedBox(height: 4), + Text( + 'Remises: ${totalRemises.toStringAsFixed(2)} MGA • Cadeaux: ${totalCadeaux.toStringAsFixed(2)} MGA', + style: TextStyle( + color: Colors.grey.shade600, + fontSize: 11, + fontStyle: FontStyle.italic, + ), ), - ), + ], ], - ], - ); -} + ); + } Widget _buildSubmitButton() { final isMobile = MediaQuery.of(context).size.width < 600; - + return SizedBox( width: double.infinity, child: ElevatedButton( style: ElevatedButton.styleFrom( - padding: EdgeInsets.symmetric( - vertical: isMobile ? 12 : 16 - ), + padding: EdgeInsets.symmetric(vertical: isMobile ? 12 : 16), backgroundColor: Colors.blue.shade800, foregroundColor: Colors.white, shape: RoundedRectangleBorder( @@ -3853,106 +3970,110 @@ Widget _buildTransferStep(String label, String pointDeVente, IconData icon, Colo ); } - // 🎯 MODIFIÉ: Validation finale avant soumission -Future _submitOrder() async { - // Vérification panier vide - final itemsInCart = _panierDetails.entries.where((e) => e.value.quantite > 0).toList(); - if (itemsInCart.isEmpty) { - Get.snackbar( - 'Panier vide', - 'Veuillez ajouter des produits à votre commande', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red, - colorText: Colors.white, - ); - _showCartBottomSheet(); - return; - } + // 🎯 MODIFIÉ: Validation finale avant soumission + Future _submitOrder() async { + // Vérification panier vide + final itemsInCart = + _panierDetails.entries.where((e) => e.value.quantite > 0).toList(); + if (itemsInCart.isEmpty) { + Get.snackbar( + 'Panier vide', + 'Veuillez ajouter des produits à votre commande', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + _showCartBottomSheet(); + return; + } - // 🔒 VALIDATION SÉCURITÉ FINALE: Vérifier tous les produits du panier - if (!_isUserSuperAdmin()) { - final produitsNonAutorises = []; - - for (final entry in itemsInCart) { - final product = _products.firstWhere((p) => p.id == entry.key); - if (product.pointDeVenteId != _userController.pointDeVenteId) { - produitsNonAutorises.add(product.name); + // 🔒 VALIDATION SÉCURITÉ FINALE: Vérifier tous les produits du panier + if (!_isUserSuperAdmin()) { + final produitsNonAutorises = []; + + for (final entry in itemsInCart) { + final product = _products.firstWhere((p) => p.id == entry.key); + if (product.pointDeVenteId != _userController.pointDeVenteId) { + produitsNonAutorises.add(product.name); + } } - } - - if (produitsNonAutorises.isNotEmpty) { - Get.dialog( - AlertDialog( - title: Row( - children: [ - Icon(Icons.security, color: Colors.red.shade600), - const SizedBox(width: 8), - const Text('Commande non autorisée'), - ], - ), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text('Les produits suivants ne sont pas autorisés pour votre point de vente:'), - const SizedBox(height: 8), - ...produitsNonAutorises.map((nom) => Padding( - padding: const EdgeInsets.only(left: 16, top: 4), - child: Text('• $nom', style: TextStyle(color: Colors.red.shade700)), - )), - const SizedBox(height: 12), - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.orange.shade50, - borderRadius: BorderRadius.circular(8), - ), - child: Row( - children: [ - Icon(Icons.info_outline, color: Colors.orange.shade700, size: 16), - const SizedBox(width: 8), - Expanded( - child: Text( - 'Contactez un administrateur pour commander ces produits.', - style: TextStyle( - fontSize: 12, - color: Colors.orange.shade700, + + if (produitsNonAutorises.isNotEmpty) { + Get.dialog( + AlertDialog( + title: Row( + children: [ + Icon(Icons.security, color: Colors.red.shade600), + const SizedBox(width: 8), + const Text('Commande non autorisée'), + ], + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Les produits suivants ne sont pas autorisés pour votre point de vente:'), + const SizedBox(height: 8), + ...produitsNonAutorises.map((nom) => Padding( + padding: const EdgeInsets.only(left: 16, top: 4), + child: Text('• $nom', + style: TextStyle(color: Colors.red.shade700)), + )), + const SizedBox(height: 12), + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.orange.shade50, + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + Icon(Icons.info_outline, + color: Colors.orange.shade700, size: 16), + const SizedBox(width: 8), + Expanded( + child: Text( + 'Contactez un administrateur pour commander ces produits.', + style: TextStyle( + fontSize: 12, + color: Colors.orange.shade700, + ), ), ), - ), - ], + ], + ), ), + ], + ), + actions: [ + TextButton( + onPressed: () => Get.back(), + child: const Text('Compris'), ), ], ), - actions: [ - TextButton( - onPressed: () => Get.back(), - child: const Text('Compris'), - ), - ], - ), + ); + return; + } + } + + // Vérification informations client + if (_nomController.text.isEmpty || + _prenomController.text.isEmpty || + _emailController.text.isEmpty || + _telephoneController.text.isEmpty || + _adresseController.text.isEmpty) { + Get.snackbar( + 'Informations manquantes', + 'Veuillez remplir les informations client', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, ); + _showClientFormDialog(); return; } - } - - // Vérification informations client - if (_nomController.text.isEmpty || - _prenomController.text.isEmpty || - _emailController.text.isEmpty || - _telephoneController.text.isEmpty || - _adresseController.text.isEmpty) { - Get.snackbar( - 'Informations manquantes', - 'Veuillez remplir les informations client', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red, - colorText: Colors.white, - ); - _showClientFormDialog(); - return; - } setState(() { _isLoading = true; @@ -3996,7 +4117,7 @@ Future _submitOrder() async { // Afficher le dialogue de confirmation final isMobile = MediaQuery.of(context).size.width < 600; - + await showDialog( context: context, barrierDismissible: false, @@ -4031,9 +4152,7 @@ Future _submitOrder() async { style: ElevatedButton.styleFrom( backgroundColor: Colors.green.shade700, foregroundColor: Colors.white, - padding: EdgeInsets.symmetric( - vertical: isMobile ? 12 : 16 - ), + padding: EdgeInsets.symmetric(vertical: isMobile ? 12 : 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), @@ -4052,12 +4171,11 @@ Future _submitOrder() async { ], ), ); - } catch (e) { setState(() { _isLoading = false; }); - + Get.snackbar( 'Erreur', 'Une erreur est survenue: ${e.toString()}', @@ -4068,109 +4186,111 @@ Future _submitOrder() async { } } -Future _showCadeauDialog(Product product) async { - final detailExistant = _panierDetails[product.id!]; - - if (detailExistant == null || detailExistant.quantite == 0) { - Get.snackbar( - 'Produit requis', - 'Vous devez d\'abord ajouter ce produit au panier', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.orange, - colorText: Colors.white, - ); - return; - } + Future _showCadeauDialog(Product product) async { + final detailExistant = _panierDetails[product.id!]; + + if (detailExistant == null || detailExistant.quantite == 0) { + Get.snackbar( + 'Produit requis', + 'Vous devez d\'abord ajouter ce produit au panier', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.orange, + colorText: Colors.white, + ); + return; + } - final result = await showDialog>( - context: context, - builder: (context) => CadeauDialog( - product: product, - quantite: detailExistant.quantite, - detailExistant: detailExistant, - ), - ); - - if (result != null) { - _ajouterCadeauAuPanier( - result['produit'] as Product, - result['quantite'] as int, + final result = await showDialog>( + context: context, + builder: (context) => CadeauDialog( + product: product, + quantite: detailExistant.quantite, + detailExistant: detailExistant, + ), ); + + if (result != null) { + _ajouterCadeauAuPanier( + result['produit'] as Product, + result['quantite'] as int, + ); + } } -} -void _ajouterCadeauAuPanier(Product produitCadeau, int quantite) { - // Vérifier le stock disponible - if (produitCadeau.stock != null && quantite > produitCadeau.stock!) { + + void _ajouterCadeauAuPanier(Product produitCadeau, int quantite) { + // Vérifier le stock disponible + if (produitCadeau.stock != null && quantite > produitCadeau.stock!) { + Get.snackbar( + 'Stock insuffisant', + 'Quantité de cadeau demandée non disponible', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + return; + } + + setState(() { + final detailCadeau = DetailCommande.cadeau( + commandeId: 0, // Sera défini lors de la création + produitId: produitCadeau.id!, + quantite: quantite, + prixUnitaire: produitCadeau.price, + produitNom: produitCadeau.name, + produitReference: produitCadeau.reference, + ); + _panierDetails[produitCadeau.id!] = detailCadeau; + }); + Get.snackbar( - 'Stock insuffisant', - 'Quantité de cadeau demandée non disponible', + 'Cadeau ajouté', + '${produitCadeau.name} a été ajouté comme cadeau', snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red, + backgroundColor: Colors.green.shade600, colorText: Colors.white, + duration: const Duration(seconds: 3), + icon: const Icon(Icons.card_giftcard, color: Colors.white), ); - return; } - setState(() { - final detailCadeau = DetailCommande.cadeau( - commandeId: 0, // Sera défini lors de la création - produitId: produitCadeau.id!, - quantite: quantite, - prixUnitaire: produitCadeau.price, - produitNom: produitCadeau.name, - produitReference: produitCadeau.reference, - ); - _panierDetails[produitCadeau.id!] = detailCadeau; - }); - - Get.snackbar( - 'Cadeau ajouté', - '${produitCadeau.name} a été ajouté comme cadeau', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.green.shade600, - colorText: Colors.white, - duration: const Duration(seconds: 3), - icon: const Icon(Icons.card_giftcard, color: Colors.white), - ); -} + void _basculerStatutCadeau(int productId) { + final detailExistant = _panierDetails[productId]; + if (detailExistant == null) return; -void _basculerStatutCadeau(int productId) { - final detailExistant = _panierDetails[productId]; - if (detailExistant == null) return; + setState(() { + if (detailExistant.estCadeau) { + // Convertir en article normal + _panierDetails[productId] = detailExistant.convertirEnArticleNormal(); + + Get.snackbar( + 'Statut modifié', + 'L\'article n\'est plus un cadeau', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.blue.shade600, + colorText: Colors.white, + duration: const Duration(seconds: 2), + ); + } else { + // Convertir en cadeau + _panierDetails[productId] = detailExistant.convertirEnCadeau(); + + Get.snackbar( + 'Cadeau offert', + 'L\'article est maintenant un cadeau', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.green.shade600, + colorText: Colors.white, + duration: const Duration(seconds: 2), + icon: const Icon(Icons.card_giftcard, color: Colors.white), + ); + } + }); + } - setState(() { - if (detailExistant.estCadeau) { - // Convertir en article normal - _panierDetails[productId] = detailExistant.convertirEnArticleNormal(); - - Get.snackbar( - 'Statut modifié', - 'L\'article n\'est plus un cadeau', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.blue.shade600, - colorText: Colors.white, - duration: const Duration(seconds: 2), - ); - } else { - // Convertir en cadeau - _panierDetails[productId] = detailExistant.convertirEnCadeau(); - - Get.snackbar( - 'Cadeau offert', - 'L\'article est maintenant un cadeau', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.green.shade600, - colorText: Colors.white, - duration: const Duration(seconds: 2), - icon: const Icon(Icons.card_giftcard, color: Colors.white), - ); - } - }); -} @override void dispose() { _qrController?.dispose(); - + // Vos disposals existants... _hideAllSuggestions(); _nomController.dispose(); @@ -4181,470 +4301,468 @@ void _basculerStatutCadeau(int productId) { _searchNameController.dispose(); _searchImeiController.dispose(); _searchReferenceController.dispose(); - + super.dispose(); } - // 10. Modifier le Widget build pour utiliser le nouveau scan automatique + // 10. Modifier le Widget build pour utiliser le nouveau scan automatique // 8. Modifiez votre méthode build pour inclure les nouvelles cartes d'information // VERSION OPTIMISÉE DE VOTRE INTERFACE EN-TÊTES ET RECHERCHE -@override -Widget build(BuildContext context) { - final isMobile = MediaQuery.of(context).size.width < 600; - - return Scaffold( - floatingActionButton: _buildFloatingCartButton(), - appBar: CustomAppBar(title: 'Nouvelle commande'), - drawer: CustomDrawer(), - body: GestureDetector( - onTap: _hideAllSuggestions, - child: Column( - children: [ - // 🎯 EN-TÊTE OPTIMISÉ - Container( - decoration: BoxDecoration( - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 4, - offset: const Offset(0, 2), - ), - ], - ), - child: Column( - children: [ - // Info utilisateur (toujours visible mais compacte) - _buildCompactUserInfo(), - - // Zone de recherche principale - _buildMainSearchSection(isMobile), - - // Filtres rapides (toujours visibles) - _buildQuickFilters(isMobile), - ], - ), - ), - - // Liste des produits avec indicateur de résultats - Expanded( - child: Column( - children: [ - _buildResultsHeader(), - Expanded(child: _buildProductList()), - ], - ), - ), - ], - ), - ), - ); -} - -// 🎯 INFORMATION UTILISATEUR COMPACTE -Widget _buildCompactUserInfo() { - if (_userController.pointDeVenteId <= 0) return const SizedBox.shrink(); - - return Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - decoration: BoxDecoration( - color: Colors.blue.shade50, - border: Border(bottom: BorderSide(color: Colors.blue.shade100)), - ), - child: Row( - children: [ - Icon(Icons.store, size: 16, color: Colors.blue.shade700), - const SizedBox(width: 8), - Text( - _userController.pointDeVenteDesignation, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Colors.blue.shade700, - ), - ), - const Spacer(), - Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), - decoration: BoxDecoration( - color: Colors.blue.shade100, - borderRadius: BorderRadius.circular(8), - ), - child: Text( - 'ID: ${_userController.pointDeVenteId}', - style: TextStyle( - fontSize: 10, - color: Colors.blue.shade600, - ), - ), - ), - ], - ), - ); -} + @override + Widget build(BuildContext context) { + final isMobile = MediaQuery.of(context).size.width < 600; -// 🎯 ZONE DE RECHERCHE PRINCIPALE OPTIMISÉE -Widget _buildMainSearchSection(bool isMobile) { - return Padding( - padding: const EdgeInsets.all(16), - child: Column( - children: [ - // Recherche principale avec actions intégrées - Row( + return Scaffold( + floatingActionButton: _buildFloatingCartButton(), + appBar: CustomAppBar(title: 'Nouvelle commande'), + drawer: CustomDrawer(), + body: GestureDetector( + onTap: _hideAllSuggestions, + child: Column( children: [ - Expanded( - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 4, - offset: const Offset(0, 2), - ), - ], - ), - child: TextField( - controller: _searchNameController, - decoration: InputDecoration( - hintText: 'Rechercher par nom, IMEI, référence...', - prefixIcon: const Icon(Icons.search), - suffixIcon: _searchNameController.text.isNotEmpty - ? IconButton( - icon: const Icon(Icons.clear), - onPressed: () { - _searchNameController.clear(); - _filterProducts(); - }, - ) - : null, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide.none, - ), - filled: true, - fillColor: Colors.white, - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 12 - ), - ), - ), - ), - ), - const SizedBox(width: 8), - - // Bouton Scanner toujours visible + // 🎯 EN-TÊTE OPTIMISÉ Container( decoration: BoxDecoration( - color: Colors.green.shade700, - borderRadius: BorderRadius.circular(12), + color: Colors.white, boxShadow: [ BoxShadow( - color: Colors.green.withOpacity(0.3), + color: Colors.black.withOpacity(0.05), blurRadius: 4, offset: const Offset(0, 2), ), ], ), - child: IconButton( - icon: _isScanning - ? const SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - strokeWidth: 2, - color: Colors.white, - ), - ) - : const Icon(Icons.qr_code_scanner), - color: Colors.white, - onPressed: _isScanning ? null : _startAutomaticScanning, - tooltip: 'Scanner un produit', + child: Column( + children: [ + // Info utilisateur (toujours visible mais compacte) + _buildCompactUserInfo(), + + // Zone de recherche principale + _buildMainSearchSection(isMobile), + + // Filtres rapides (toujours visibles) + _buildQuickFilters(isMobile), + ], ), ), - - if (!isMobile) ...[ - const SizedBox(width: 8), - // Bouton filtres avancés - Container( - decoration: BoxDecoration( - color: Colors.blue.shade700, - borderRadius: BorderRadius.circular(12), - ), - child: IconButton( - icon: const Icon(Icons.tune), - color: Colors.white, - onPressed: () => _showAdvancedFiltersDialog(), - tooltip: 'Filtres avancés', - ), + + // Liste des produits avec indicateur de résultats + Expanded( + child: Column( + children: [ + _buildResultsHeader(), + Expanded(child: _buildProductList()), + ], ), - ], + ), ], ), - - // Recherche multicritères (desktop uniquement) - if (!isMobile) ...[ - const SizedBox(height: 12), + ), + ); + } + +// 🎯 INFORMATION UTILISATEUR COMPACTE + Widget _buildCompactUserInfo() { + if (_userController.pointDeVenteId <= 0) return const SizedBox.shrink(); + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: Colors.blue.shade50, + border: Border(bottom: BorderSide(color: Colors.blue.shade100)), + ), + child: Row( + children: [ + Icon(Icons.store, size: 16, color: Colors.blue.shade700), + const SizedBox(width: 8), + Text( + _userController.pointDeVenteDesignation, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Colors.blue.shade700, + ), + ), + const Spacer(), + Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: Colors.blue.shade100, + borderRadius: BorderRadius.circular(8), + ), + child: Text( + 'ID: ${_userController.pointDeVenteId}', + style: TextStyle( + fontSize: 10, + color: Colors.blue.shade600, + ), + ), + ), + ], + ), + ); + } + +// 🎯 ZONE DE RECHERCHE PRINCIPALE OPTIMISÉE + Widget _buildMainSearchSection(bool isMobile) { + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + // Recherche principale avec actions intégrées Row( children: [ Expanded( - child: TextField( - controller: _searchImeiController, - decoration: InputDecoration( - hintText: 'IMEI', - prefixIcon: const Icon(Icons.phone_android, size: 20), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - isDense: true, - filled: true, - fillColor: Colors.grey.shade50, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], ), - ), - ), - const SizedBox(width: 8), - Expanded( - child: TextField( - controller: _searchReferenceController, - decoration: InputDecoration( - hintText: 'Référence', - prefixIcon: const Icon(Icons.qr_code, size: 20), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), + child: TextField( + controller: _searchNameController, + decoration: InputDecoration( + hintText: 'Rechercher par nom, IMEI, référence...', + prefixIcon: const Icon(Icons.search), + suffixIcon: _searchNameController.text.isNotEmpty + ? IconButton( + icon: const Icon(Icons.clear), + onPressed: () { + _searchNameController.clear(); + _filterProducts(); + }, + ) + : null, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + filled: true, + fillColor: Colors.white, + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 12), ), - isDense: true, - filled: true, - fillColor: Colors.grey.shade50, ), ), ), const SizedBox(width: 8), - Expanded( - child: DropdownButtonFormField( - value: _selectedPointDeVente, - decoration: InputDecoration( - hintText: 'Point de vente', - prefixIcon: const Icon(Icons.store, size: 20), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - isDense: true, - filled: true, - fillColor: Colors.grey.shade50, - ), - items: [ - const DropdownMenuItem( - value: null, - child: Text('Tous les PV'), + + // Bouton Scanner toujours visible + Container( + decoration: BoxDecoration( + color: Colors.green.shade700, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.green.withOpacity(0.3), + blurRadius: 4, + offset: const Offset(0, 2), ), - ..._pointsDeVente.map((point) { - return DropdownMenuItem( - value: point['nom'] as String, - child: Text(point['nom'] as String), - ); - }).toList(), ], - onChanged: (value) { - setState(() { - _selectedPointDeVente = value; - _filterProducts(); - }); - }, + ), + child: IconButton( + icon: _isScanning + ? const SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white, + ), + ) + : const Icon(Icons.qr_code_scanner), + color: Colors.white, + onPressed: _isScanning ? null : _startAutomaticScanning, + tooltip: 'Scanner un produit', ), ), + + if (!isMobile) ...[ + const SizedBox(width: 8), + // Bouton filtres avancés + Container( + decoration: BoxDecoration( + color: Colors.blue.shade700, + borderRadius: BorderRadius.circular(12), + ), + child: IconButton( + icon: const Icon(Icons.tune), + color: Colors.white, + onPressed: () => _showAdvancedFiltersDialog(), + tooltip: 'Filtres avancés', + ), + ), + ], ], ), - ], - ], - ), - ); -} -// 🎯 FILTRES RAPIDES OPTIMISÉS -Widget _buildQuickFilters(bool isMobile) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - decoration: BoxDecoration( - color: Colors.grey.shade50, - border: Border(top: BorderSide(color: Colors.grey.shade200)), - ), - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: [ - // Filtre stock - FilterChip( - label: Row( - mainAxisSize: MainAxisSize.min, + // Recherche multicritères (desktop uniquement) + if (!isMobile) ...[ + const SizedBox(height: 12), + Row( children: [ - Icon( - _showOnlyInStock ? Icons.inventory : Icons.inventory_2, - size: 16, + Expanded( + child: TextField( + controller: _searchImeiController, + decoration: InputDecoration( + hintText: 'IMEI', + prefixIcon: const Icon(Icons.phone_android, size: 20), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + isDense: true, + filled: true, + fillColor: Colors.grey.shade50, + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: TextField( + controller: _searchReferenceController, + decoration: InputDecoration( + hintText: 'Référence', + prefixIcon: const Icon(Icons.qr_code, size: 20), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + isDense: true, + filled: true, + fillColor: Colors.grey.shade50, + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: DropdownButtonFormField( + value: _selectedPointDeVente, + decoration: InputDecoration( + hintText: 'Point de vente', + prefixIcon: const Icon(Icons.store, size: 20), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + isDense: true, + filled: true, + fillColor: Colors.grey.shade50, + ), + items: [ + const DropdownMenuItem( + value: null, + child: Text('Tous les PV'), + ), + ..._pointsDeVente.map((point) { + return DropdownMenuItem( + value: point['nom'] as String, + child: Text(point['nom'] as String), + ); + }).toList(), + ], + onChanged: (value) { + setState(() { + _selectedPointDeVente = value; + _filterProducts(); + }); + }, + ), ), - const SizedBox(width: 4), - Text(_showOnlyInStock ? 'En stock' : 'Tous'), ], ), - selected: _showOnlyInStock, - onSelected: (selected) => _toggleStockFilter(), - selectedColor: Colors.green.shade100, - checkmarkColor: Colors.green.shade700, - ), - - const SizedBox(width: 8), - - // Filtre mobile pour ouvrir les filtres avancés - if (isMobile) - ActionChip( - label: const Row( + ], + ], + ), + ); + } + +// 🎯 FILTRES RAPIDES OPTIMISÉS + Widget _buildQuickFilters(bool isMobile) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: Colors.grey.shade50, + border: Border(top: BorderSide(color: Colors.grey.shade200)), + ), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + // Filtre stock + FilterChip( + label: Row( mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.tune, size: 16), - SizedBox(width: 4), - Text('Filtres'), + Icon( + _showOnlyInStock ? Icons.inventory : Icons.inventory_2, + size: 16, + ), + const SizedBox(width: 4), + Text(_showOnlyInStock ? 'En stock' : 'Tous'), ], ), - onPressed: () => _showMobileFilters(context), + selected: _showOnlyInStock, + onSelected: (selected) => _toggleStockFilter(), + selectedColor: Colors.green.shade100, + checkmarkColor: Colors.green.shade700, ), - - const SizedBox(width: 8), - - // Bouton reset si filtres actifs - if (_hasActiveFilters()) - ActionChip( - label: const Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.clear, size: 16), - SizedBox(width: 4), - Text('Reset'), - ], + + const SizedBox(width: 8), + + // Filtre mobile pour ouvrir les filtres avancés + if (isMobile) + ActionChip( + label: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.tune, size: 16), + SizedBox(width: 4), + Text('Filtres'), + ], + ), + onPressed: () => _showMobileFilters(context), ), - onPressed: _clearFilters, - backgroundColor: Colors.orange.shade100, - ), - ], + + const SizedBox(width: 8), + + // Bouton reset si filtres actifs + if (_hasActiveFilters()) + ActionChip( + label: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.clear, size: 16), + SizedBox(width: 4), + Text('Reset'), + ], + ), + onPressed: _clearFilters, + backgroundColor: Colors.orange.shade100, + ), + ], + ), ), - ), - ); -} + ); + } // 🎯 EN-TÊTE DES RÉSULTATS -Widget _buildResultsHeader() { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - decoration: BoxDecoration( - color: Colors.white, - border: Border(bottom: BorderSide(color: Colors.grey.shade200)), - ), - child: Row( - children: [ - Text( - '${_filteredProducts.length} produit(s)', - style: TextStyle( - fontWeight: FontWeight.w600, - color: Colors.grey.shade700, - ), - ), - const Spacer(), - - // Indicateurs de filtres actifs - if (_hasActiveFilters()) ...[ - Wrap( - spacing: 4, - children: _getActiveFilterChips(), + Widget _buildResultsHeader() { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + decoration: BoxDecoration( + color: Colors.white, + border: Border(bottom: BorderSide(color: Colors.grey.shade200)), + ), + child: Row( + children: [ + Text( + '${_filteredProducts.length} produit(s)', + style: TextStyle( + fontWeight: FontWeight.w600, + color: Colors.grey.shade700, + ), ), + const Spacer(), + + // Indicateurs de filtres actifs + if (_hasActiveFilters()) ...[ + Wrap( + spacing: 4, + children: _getActiveFilterChips(), + ), + ], ], - ], - ), - ); -} + ), + ); + } // 🎯 MÉTHODES UTILITAIRES -bool _hasActiveFilters() { - return _selectedPointDeVente != null || - _showOnlyInStock || - _searchNameController.text.isNotEmpty || - _searchImeiController.text.isNotEmpty || - _searchReferenceController.text.isNotEmpty; -} - -List _getActiveFilterChips() { - List chips = []; - - if (_selectedPointDeVente != null) { - chips.add(_buildMiniFilterChip('PV: $_selectedPointDeVente')); - } - if (_showOnlyInStock) { - chips.add(_buildMiniFilterChip('En stock')); - } - if (_searchNameController.text.isNotEmpty) { - chips.add(_buildMiniFilterChip('Nom: ${_searchNameController.text}')); + bool _hasActiveFilters() { + return _selectedPointDeVente != null || + _showOnlyInStock || + _searchNameController.text.isNotEmpty || + _searchImeiController.text.isNotEmpty || + _searchReferenceController.text.isNotEmpty; } - if (_searchImeiController.text.isNotEmpty) { - chips.add(_buildMiniFilterChip('IMEI: ${_searchImeiController.text}')); - } - if (_searchReferenceController.text.isNotEmpty) { - chips.add(_buildMiniFilterChip('Réf: ${_searchReferenceController.text}')); + + List _getActiveFilterChips() { + List chips = []; + + if (_selectedPointDeVente != null) { + chips.add(_buildMiniFilterChip('PV: $_selectedPointDeVente')); + } + if (_showOnlyInStock) { + chips.add(_buildMiniFilterChip('En stock')); + } + if (_searchNameController.text.isNotEmpty) { + chips.add(_buildMiniFilterChip('Nom: ${_searchNameController.text}')); + } + if (_searchImeiController.text.isNotEmpty) { + chips.add(_buildMiniFilterChip('IMEI: ${_searchImeiController.text}')); + } + if (_searchReferenceController.text.isNotEmpty) { + chips + .add(_buildMiniFilterChip('Réf: ${_searchReferenceController.text}')); + } + + return chips; } - - return chips; -} -Widget _buildMiniFilterChip(String label) { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), - decoration: BoxDecoration( - color: Colors.blue.shade100, - borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.blue.shade300), - ), - child: Text( - label, - style: TextStyle( - fontSize: 10, - color: Colors.blue.shade700, - fontWeight: FontWeight.w500, + Widget _buildMiniFilterChip(String label) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: Colors.blue.shade100, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.blue.shade300), ), - ), - ); -} + child: Text( + label, + style: TextStyle( + fontSize: 10, + color: Colors.blue.shade700, + fontWeight: FontWeight.w500, + ), + ), + ); + } // 🎯 DIALOGUE FILTRES AVANCÉS -void _showAdvancedFiltersDialog() { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Filtres avancés'), - content: SizedBox( - width: 400, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Tous vos filtres existants ici - _buildPointDeVenteFilter(), - const SizedBox(height: 16), - // Autres filtres selon vos besoins - ], + void _showAdvancedFiltersDialog() { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Filtres avancés'), + content: SizedBox( + width: 400, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Tous vos filtres existants ici + _buildPointDeVenteFilter(), + const SizedBox(height: 16), + // Autres filtres selon vos besoins + ], + ), ), + actions: [ + TextButton( + onPressed: () => Get.back(), + child: const Text('Fermer'), + ), + ElevatedButton( + onPressed: () { + _filterProducts(); + Get.back(); + }, + child: const Text('Appliquer'), + ), + ], ), - actions: [ - TextButton( - onPressed: () => Get.back(), - child: const Text('Fermer'), - ), - ElevatedButton( - onPressed: () { - _filterProducts(); - Get.back(); - }, - child: const Text('Appliquer'), - ), - ], - ), - ); + ); + } } - -} \ No newline at end of file