import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:youmazgestion/Components/app_bar.dart'; import 'package:youmazgestion/Components/appDrawer.dart'; import 'package:youmazgestion/Models/client.dart'; import 'package:youmazgestion/Models/users.dart'; import 'package:youmazgestion/Models/produit.dart'; import 'package:youmazgestion/Services/stock_managementDatabase.dart'; class NouvelleCommandePage extends StatefulWidget { const NouvelleCommandePage({super.key}); @override _NouvelleCommandePageState createState() => _NouvelleCommandePageState(); } class _NouvelleCommandePageState extends State { final AppDatabase _appDatabase = AppDatabase.instance; final _formKey = GlobalKey(); bool _isLoading = false; // Contrôleurs client final TextEditingController _nomController = TextEditingController(); final TextEditingController _prenomController = TextEditingController(); final TextEditingController _emailController = TextEditingController(); final TextEditingController _telephoneController = TextEditingController(); final TextEditingController _adresseController = TextEditingController(); // Contrôleurs pour les filtres - NOUVEAU final TextEditingController _searchNameController = TextEditingController(); final TextEditingController _searchImeiController = TextEditingController(); final TextEditingController _searchReferenceController = TextEditingController(); // Panier final List _products = []; final List _filteredProducts = []; // NOUVEAU - Liste filtrée final Map _quantites = {}; // Variables de filtre - NOUVEAU bool _showOnlyInStock = false; // Utilisateurs commerciaux List _commercialUsers = []; Users? _selectedCommercialUser; @override void initState() { super.initState(); _loadProducts(); _loadCommercialUsers(); // Listeners pour les filtres - NOUVEAU _searchNameController.addListener(_filterProducts); _searchImeiController.addListener(_filterProducts); _searchReferenceController.addListener(_filterProducts); } Future _loadProducts() async { final products = await _appDatabase.getProducts(); setState(() { _products.clear(); _products.addAll(products); _filteredProducts.clear(); _filteredProducts.addAll(products); // Initialiser la liste filtrée }); } Future _loadCommercialUsers() async { final commercialUsers = await _appDatabase.getCommercialUsers(); setState(() { _commercialUsers = commercialUsers; if (_commercialUsers.isNotEmpty) { _selectedCommercialUser = _commercialUsers.first; } }); } // NOUVELLE MÉTHODE - Filtrer les produits void _filterProducts() { final nameQuery = _searchNameController.text.toLowerCase(); final imeiQuery = _searchImeiController.text.toLowerCase(); final referenceQuery = _searchReferenceController.text.toLowerCase(); 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); if (matchesName && matchesImei && matchesReference && matchesStock) { _filteredProducts.add(product); } } }); } // NOUVELLE MÉTHODE - Toggle filtre stock void _toggleStockFilter() { setState(() { _showOnlyInStock = !_showOnlyInStock; }); _filterProducts(); } // NOUVELLE MÉTHODE - Réinitialiser les filtres void _clearFilters() { setState(() { _searchNameController.clear(); _searchImeiController.clear(); _searchReferenceController.clear(); _showOnlyInStock = false; }); _filterProducts(); } // NOUVEAU WIDGET - Section des filtres Widget _buildFilterSection() { return Card( elevation: 2, margin: const EdgeInsets.only(bottom: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.filter_list, color: Colors.blue.shade700), const SizedBox(width: 8), const Text( 'Filtres de recherche', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Color.fromARGB(255, 9, 56, 95), ), ), const Spacer(), TextButton.icon( onPressed: _clearFilters, icon: const Icon(Icons.clear, size: 18), label: const Text('Réinitialiser'), style: TextButton.styleFrom( foregroundColor: Colors.grey.shade600, ), ), ], ), const SizedBox(height: 16), // Champ de recherche par nom TextField( controller: _searchNameController, decoration: InputDecoration( labelText: 'Rechercher par nom', prefixIcon: const Icon(Icons.search), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), filled: true, fillColor: Colors.grey.shade50, ), ), const SizedBox(height: 12), // Champs IMEI et Référence sur la même ligne Row( children: [ Expanded( child: TextField( controller: _searchImeiController, decoration: InputDecoration( labelText: 'IMEI', prefixIcon: const Icon(Icons.phone_android), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), filled: true, fillColor: Colors.grey.shade50, ), ), ), const SizedBox(width: 12), Expanded( child: TextField( controller: _searchReferenceController, decoration: InputDecoration( labelText: 'Référence', prefixIcon: const Icon(Icons.qr_code), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), filled: true, fillColor: Colors.grey.shade50, ), ), ), ], ), const SizedBox(height: 16), // Bouton filtre stock et résultats Row( children: [ ElevatedButton.icon( onPressed: _toggleStockFilter, icon: Icon( _showOnlyInStock ? Icons.inventory : Icons.inventory_2, size: 20, ), label: Text(_showOnlyInStock ? 'Afficher tous' : 'Stock disponible'), style: ElevatedButton.styleFrom( backgroundColor: _showOnlyInStock ? Colors.green.shade600 : Colors.blue.shade600, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12 ), ), ), const Spacer(), Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 8 ), decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.circular(20), ), child: Text( '${_filteredProducts.length} produit(s)', style: TextStyle( color: Colors.blue.shade700, fontWeight: FontWeight.w600, ), ), ), ], ), ], ), ), ); } @override Widget build(BuildContext context) { return Scaffold( floatingActionButton: _buildFloatingCartButton(), appBar: CustomAppBar(title: 'Faire un commande'), drawer: CustomDrawer(), body: Column( children: [ // Header // Contenu principal MODIFIÉ - Inclut les filtres Expanded( child: SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ ElevatedButton( style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), backgroundColor: Colors.blue.shade800, foregroundColor: Colors.white, ), onPressed: _showClientFormDialog, child: const Text('Ajouter les informations client'), ), const SizedBox(height: 20), // NOUVEAU - Section des filtres _buildFilterSection(), // Liste des produits _buildProductList(), ], ), ), ), ], ), ); } Widget _buildFloatingCartButton() { return FloatingActionButton.extended( onPressed: () { _showCartBottomSheet(); }, icon: const Icon(Icons.shopping_cart), label: Text('Panier (${_quantites.values.where((q) => q > 0).length})'), backgroundColor: Colors.blue.shade800, foregroundColor: Colors.white, ); } void _showClientFormDialog() { Get.dialog( AlertDialog( title: Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.blue.shade100, borderRadius: BorderRadius.circular(8), ), child: Icon(Icons.person_add, color: Colors.blue.shade700), ), const SizedBox(width: 12), const Text('Informations Client'), ], ), content: Container( width: 600, constraints: const BoxConstraints(maxHeight: 600), child: SingleChildScrollView( child: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildTextFormField( controller: _nomController, label: 'Nom', validator: (value) => value?.isEmpty ?? true ? 'Veuillez entrer un nom' : null, ), const SizedBox(height: 12), _buildTextFormField( controller: _prenomController, label: 'Prénom', validator: (value) => value?.isEmpty ?? true ? 'Veuillez entrer un prénom' : null, ), const SizedBox(height: 12), _buildTextFormField( 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; }, ), const SizedBox(height: 12), _buildTextFormField( controller: _telephoneController, label: 'Téléphone', keyboardType: TextInputType.phone, validator: (value) => value?.isEmpty ?? true ? 'Veuillez entrer un téléphone' : null, ), const SizedBox(height: 12), _buildTextFormField( controller: _adresseController, label: 'Adresse', maxLines: 2, validator: (value) => value?.isEmpty ?? true ? 'Veuillez entrer une adresse' : null, ), const SizedBox(height: 12), _buildCommercialDropdown(), ], ), ), ), ), actions: [ TextButton( onPressed: () => Get.back(), child: const Text('Annuler'), ), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.blue.shade800, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), onPressed: () { if (_formKey.currentState!.validate()) { Get.back(); _submitOrder(); } }, child: const Text('Valider la commande'), ), ], ), ); } Widget _buildTextFormField({ required TextEditingController controller, required String label, TextInputType? keyboardType, String? Function(String?)? validator, int? maxLines, }) { return TextFormField( controller: controller, decoration: InputDecoration( labelText: label, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: Colors.grey.shade400), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: Colors.grey.shade400), ), filled: true, fillColor: Colors.white, contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), ), keyboardType: keyboardType, validator: validator, maxLines: maxLines, ); } Widget _buildCommercialDropdown() { return DropdownButtonFormField( value: _selectedCommercialUser, decoration: InputDecoration( labelText: 'Commercial', border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), filled: true, fillColor: Colors.white, ), items: _commercialUsers.map((Users user) { return DropdownMenuItem( value: user, child: Text('${user.name} ${user.lastName}'), ); }).toList(), onChanged: (Users? newValue) { setState(() { _selectedCommercialUser = newValue; }); }, validator: (value) => value == null ? 'Veuillez sélectionner un commercial' : null, ); } // WIDGET MODIFIÉ - Liste des produits (utilise maintenant _filteredProducts) Widget _buildProductList() { return Card( elevation: 4, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Produits Disponibles', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Color.fromARGB(255, 9, 56, 95), ), ), const SizedBox(height: 16), _filteredProducts.isEmpty ? _buildEmptyState() : ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: _filteredProducts.length, itemBuilder: (context, index) { final product = _filteredProducts[index]; final quantity = _quantites[product.id] ?? 0; return _buildProductListItem(product, quantity); }, ), ], ), ), ); } // NOUVEAU WIDGET - État vide Widget _buildEmptyState() { return Center( child: Padding( padding: const EdgeInsets.all(32.0), child: Column( children: [ Icon( Icons.search_off, size: 64, color: Colors.grey.shade400, ), const SizedBox(height: 16), Text( 'Aucun produit trouvé', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w500, color: Colors.grey.shade600, ), ), const SizedBox(height: 8), Text( 'Essayez de modifier vos critères de recherche', style: TextStyle( fontSize: 14, color: Colors.grey.shade500, ), ), ], ), ), ); } // WIDGET MODIFIÉ - Item de produit (ajout d'informations IMEI/Référence) Widget _buildProductListItem(Product product, int quantity) { final bool isOutOfStock = product.stock != null && product.stock! <= 0; return Card( margin: const EdgeInsets.symmetric(vertical: 8), elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), border: isOutOfStock ? Border.all(color: Colors.red.shade200, width: 1.5) : null, ), child: ListTile( contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 8, ), leading: Container( width: 50, height: 50, decoration: BoxDecoration( color: isOutOfStock ? Colors.red.shade50 : Colors.blue.shade50, borderRadius: BorderRadius.circular(8), ), child: Icon( Icons.shopping_bag, color: isOutOfStock ? Colors.red : Colors.blue ), ), title: Text( product.name, style: TextStyle( fontWeight: FontWeight.bold, color: isOutOfStock ? Colors.red.shade700 : null, ), ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 4), Text( '${product.price.toStringAsFixed(2)} MGA', style: TextStyle( color: Colors.green.shade700, fontWeight: FontWeight.w600, ), ), if (product.stock != null) Text( 'Stock: ${product.stock}${isOutOfStock ? ' (Rupture)' : ''}', style: TextStyle( fontSize: 12, color: isOutOfStock ? Colors.red.shade600 : Colors.grey.shade600, fontWeight: isOutOfStock ? FontWeight.w600 : FontWeight.normal, ), ), // Affichage IMEI et Référence if (product.imei != null && product.imei!.isNotEmpty) Text( 'IMEI: ${product.imei}', style: TextStyle( fontSize: 11, color: Colors.grey.shade600, fontFamily: 'monospace', ), ), if (product.reference != null && product.reference!.isNotEmpty) Text( 'Réf: ${product.reference}', style: TextStyle( fontSize: 11, color: Colors.grey.shade600, ), ), ], ), trailing: Container( decoration: BoxDecoration( color: isOutOfStock ? Colors.grey.shade100 : Colors.blue.shade50, borderRadius: BorderRadius.circular(20), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: const Icon(Icons.remove, size: 18), onPressed: isOutOfStock ? null : () { if (quantity > 0) { setState(() { _quantites[product.id!] = quantity - 1; }); } }, ), Text( quantity.toString(), style: const TextStyle(fontWeight: FontWeight.bold), ), IconButton( icon: const Icon(Icons.add, size: 18), onPressed: isOutOfStock ? null : () { if (product.stock == null || quantity < product.stock!) { setState(() { _quantites[product.id!] = quantity + 1; }); } else { Get.snackbar( 'Stock insuffisant', 'Quantité demandée non disponible', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red, colorText: Colors.white, ); } }, ), ], ), ), ), ), ); } void _showCartBottomSheet() { Get.bottomSheet( Container( height: MediaQuery.of(context).size.height * 0.7, padding: const EdgeInsets.all(16), decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Votre Panier', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), IconButton( icon: const Icon(Icons.close), onPressed: () => Get.back(), ), ], ), const Divider(), Expanded(child: _buildCartItemsList()), const Divider(), _buildCartTotalSection(), const SizedBox(height: 16), _buildSubmitButton(), ], ), ), isScrollControlled: true, ); } Widget _buildCartItemsList() { final itemsInCart = _quantites.entries.where((e) => e.value > 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), ), ], ), ); } return ListView.builder( itemCount: itemsInCart.length, itemBuilder: (context, index) { final entry = itemsInCart[index]; 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), ), direction: DismissDirection.endToStart, onDismissed: (direction) { setState(() { _quantites.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), ), child: ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), leading: Container( width: 40, height: 40, decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.circular(8), ), child: const Icon(Icons.shopping_bag, size: 20), ), title: Text(product.name), subtitle: Text('${entry.value} x ${product.price.toStringAsFixed(2)} MGA'), trailing: Text( '${(entry.value * product.price).toStringAsFixed(2)} MGA', style: TextStyle( fontWeight: FontWeight.bold, color: Colors.blue.shade800, ), ), ), ), ); }, ); } Widget _buildCartTotalSection() { double total = 0; _quantites.forEach((productId, quantity) { final product = _products.firstWhere((p) => p.id == productId); total += quantity * product.price; }); return Column( children: [ 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), Text( '${_quantites.values.where((q) => q > 0).length} article(s)', style: TextStyle(color: Colors.grey.shade600), ), ], ); } Widget _buildSubmitButton() { return SizedBox( width: double.infinity, child: ElevatedButton( style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), backgroundColor: Colors.blue.shade800, foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), elevation: 4, ), onPressed: _submitOrder, child: _isLoading ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white, ), ) : const Text( 'Valider la Commande', style: TextStyle(fontSize: 16), ), ), ); } Future _submitOrder() async { // Vérifier d'abord si le panier est vide final itemsInCart = _quantites.entries.where((e) => e.value > 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(); // Ouvrir le panier pour montrer qu'il est vide return; } // Ensuite vérifier les 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; }); // Créer le client final client = Client( nom: _nomController.text, prenom: _prenomController.text, email: _emailController.text, telephone: _telephoneController.text, adresse: _adresseController.text, dateCreation: DateTime.now(), ); // Calculer le total et préparer les détails double total = 0; final details = []; for (final entry in itemsInCart) { final product = _products.firstWhere((p) => p.id == entry.key); total += entry.value * product.price; details.add(DetailCommande( commandeId: 0, produitId: product.id!, quantite: entry.value, prixUnitaire: product.price, sousTotal: entry.value * product.price, )); } // Créer la commande final commande = Commande( clientId: 0, dateCommande: DateTime.now(), statut: StatutCommande.enAttente, montantTotal: total, notes: 'Commande passée via l\'application', commandeurId: _selectedCommercialUser?.id, ); try { await _appDatabase.createCommandeComplete(client, commande, details); // Afficher le dialogue de confirmation await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Commande Validée'), content: const Text('Votre commande a été enregistrée et expédiée avec succès.'), actions: [ TextButton( onPressed: () { Navigator.pop(context); // Réinitialiser le formulaire _nomController.clear(); _prenomController.clear(); _emailController.clear(); _telephoneController.clear(); _adresseController.clear(); setState(() { _quantites.clear(); _isLoading = false; }); // Recharger les produits pour mettre à jour le stock _loadProducts(); }, child: const Text('OK'), ), ], ), ); } catch (e) { setState(() { _isLoading = false; }); Get.snackbar( 'Erreur', 'Une erreur est survenue: ${e.toString()}', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red, colorText: Colors.white, ); } } @override void dispose() { _nomController.dispose(); _prenomController.dispose(); _emailController.dispose(); _telephoneController.dispose(); _adresseController.dispose(); // Disposal des contrôleurs de filtre _searchNameController.dispose(); _searchImeiController.dispose(); _searchReferenceController.dispose(); super.dispose(); } }