diff --git a/assets/Orange_money.png b/assets/Orange_money.png new file mode 100644 index 0000000..9272526 Binary files /dev/null and b/assets/Orange_money.png differ diff --git a/assets/airtel_money.png b/assets/airtel_money.png new file mode 100644 index 0000000..b69f3b1 Binary files /dev/null and b/assets/airtel_money.png differ diff --git a/assets/mvola.jpg b/assets/mvola.jpg new file mode 100644 index 0000000..ec35389 Binary files /dev/null and b/assets/mvola.jpg differ diff --git a/lib/Components/paymentType.dart b/lib/Components/paymentType.dart new file mode 100644 index 0000000..190d5f0 --- /dev/null +++ b/lib/Components/paymentType.dart @@ -0,0 +1,7 @@ +enum PaymentType { + cash, + card, + mvola, + orange, + airtel +} \ No newline at end of file diff --git a/lib/Views/commandManagement.dart b/lib/Views/commandManagement.dart index 47b7b48..8055196 100644 --- a/lib/Views/commandManagement.dart +++ b/lib/Views/commandManagement.dart @@ -1563,7 +1563,12 @@ class _CommandeActions extends StatelessWidget { } } -enum PaymentType { cash, card } +enum PaymentType { cash, card , + + mvola, + orange, + airtel +} class PaymentMethod { final PaymentType type; @@ -1584,7 +1589,28 @@ class PaymentMethodDialog extends StatefulWidget { class _PaymentMethodDialogState extends State { PaymentType _selectedPayment = PaymentType.cash; final _amountController = TextEditingController(); + void _validatePayment() { + if (_selectedPayment == PaymentType.cash) { + final amountGiven = double.tryParse(_amountController.text) ?? 0; + if (amountGiven < widget.commande.montantTotal) { + Get.snackbar( + 'Erreur', + 'Le montant donné est insuffisant', + snackPosition: SnackPosition.BOTTOM, + backgroundColor: Colors.red, + colorText: Colors.white, + ); + return; + } + } + Navigator.pop(context, PaymentMethod( + type: _selectedPayment, + amountGiven: _selectedPayment == PaymentType.cash + ? double.parse(_amountController.text) + : widget.commande.montantTotal, + )); +} @override void initState() { super.initState(); @@ -1598,94 +1624,189 @@ class _PaymentMethodDialogState extends State { super.dispose(); } + @override - Widget build(BuildContext context) { - final amount = double.tryParse(_amountController.text) ?? 0; - final change = amount - widget.commande.montantTotal; +Widget build(BuildContext context) { + final amount = double.tryParse(_amountController.text) ?? 0; + final change = amount - widget.commande.montantTotal; - return AlertDialog( - title: const Text('Méthode de paiement'), - content: Column( + return AlertDialog( + title: const Text('Méthode de paiement', style: TextStyle(fontWeight: FontWeight.bold)), + content: SingleChildScrollView( + child: Column( mainAxisSize: MainAxisSize.min, children: [ - RadioListTile( - title: const Text('Paiement en liquide'), + // Section Paiement mobile + const Align( + alignment: Alignment.centerLeft, + child: Text('Mobile Money', style: TextStyle(fontWeight: FontWeight.w500)), + ), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + child: _buildMobileMoneyTile( + title: 'Mvola', + imagePath: 'assets/mvola.jpg', + value: PaymentType.mvola, + ), + ), + const SizedBox(width: 8), + Expanded( + child: _buildMobileMoneyTile( + title: 'Orange Money', + imagePath: 'assets/Orange_money.png', + value: PaymentType.orange, + ), + ), + const SizedBox(width: 8), + Expanded( + child: _buildMobileMoneyTile( + title: 'Airtel Money', + imagePath: 'assets/airtel_money.png', + value: PaymentType.airtel, + ), + ), + ], + ), + const SizedBox(height: 16), + + // Section Carte bancaire + const Align( + alignment: Alignment.centerLeft, + child: Text('Carte Bancaire', style: TextStyle(fontWeight: FontWeight.w500)), + ), + const SizedBox(height: 8), + _buildPaymentMethodTile( + title: 'Carte bancaire', + icon: Icons.credit_card, + value: PaymentType.card, + ), + const SizedBox(height: 16), + + // Section Paiement en liquide + const Align( + alignment: Alignment.centerLeft, + child: Text('Espèces', style: TextStyle(fontWeight: FontWeight.w500)), + ), + const SizedBox(height: 8), + _buildPaymentMethodTile( + title: 'Paiement en liquide', + icon: Icons.money, value: PaymentType.cash, - groupValue: _selectedPayment, - onChanged: (value) { - setState(() { - _selectedPayment = value!; - }); - }, ), - if (_selectedPayment == PaymentType.cash) + if (_selectedPayment == PaymentType.cash) ...[ + const SizedBox(height: 12), TextField( controller: _amountController, decoration: const InputDecoration( labelText: 'Montant donné', prefixText: 'MGA ', + border: OutlineInputBorder(), ), - keyboardType: TextInputType.number, - onChanged: (value) { - setState(() {}); // Rafraîchir l'UI pour calculer la monnaie - }, + keyboardType: TextInputType.numberWithOptions(decimal: true), + onChanged: (value) => setState(() {}), ), - RadioListTile( - title: const Text('Carte bancaire'), - value: PaymentType.card, - groupValue: _selectedPayment, - onChanged: (value) { - setState(() { - _selectedPayment = value!; - }); - }, - ), - if (_selectedPayment == PaymentType.cash) - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: Text( - 'Monnaie à rendre: ${change.toStringAsFixed(2)} MGA', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: change >= 0 ? Colors.green : Colors.red, - ), + const SizedBox(height: 8), + Text( + 'Monnaie à rendre: ${change.toStringAsFixed(2)} MGA', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: change >= 0 ? Colors.green : Colors.red, ), ), + ], ], ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('Annuler'), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Annuler', style: TextStyle(color: Colors.grey)), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue.shade800, + foregroundColor: Colors.white, ), - ElevatedButton( - onPressed: () { - final amount = _selectedPayment == PaymentType.cash - ? double.tryParse(_amountController.text) ?? 0 - : widget.commande.montantTotal; - - if (_selectedPayment == PaymentType.cash && amount < widget.commande.montantTotal) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Le montant donné est insuffisant'), - backgroundColor: Colors.red, - ), - ); - return; - } + onPressed: _validatePayment, + child: const Text('Confirmer'), + ), + ], + ); +} - Navigator.pop( - context, - PaymentMethod( - type: _selectedPayment, - amountGiven: amount, - ), - ); - }, - child: const Text('Confirmer'), +Widget _buildMobileMoneyTile({ + required String title, + required String imagePath, + required PaymentType value, +}) { + return Card( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide( + color: _selectedPayment == value ? Colors.blue : Colors.grey.withOpacity(0.2), + width: 2, + ), + ), + child: InkWell( + borderRadius: BorderRadius.circular(8), + onTap: () => setState(() => _selectedPayment = value), + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + children: [ + Image.asset( + imagePath, + height: 30, + width: 30, + fit: BoxFit.contain, + errorBuilder: (context, error, stackTrace) => + const Icon(Icons.mobile_friendly, size: 30), + ), + const SizedBox(height: 8), + Text( + title, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 12), + ), + ], ), - ], - ); - } + ), + ), + ); +} + +Widget _buildPaymentMethodTile({ + required String title, + required IconData icon, + required PaymentType value, +}) { + return Card( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide( + color: _selectedPayment == value ? Colors.blue : Colors.grey.withOpacity(0.2), + width: 2, + ), + ), + child: InkWell( + borderRadius: BorderRadius.circular(8), + onTap: () => setState(() => _selectedPayment = value), + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + children: [ + Icon(icon, size: 24), + const SizedBox(width: 12), + Text(title), + ], + ), + ), + ), + ); +} } diff --git a/lib/Views/mobilepage.dart b/lib/Views/mobilepage.dart index 420dd13..902720e 100644 --- a/lib/Views/mobilepage.dart +++ b/lib/Views/mobilepage.dart @@ -258,14 +258,31 @@ class _NouvelleCommandePageState extends State { } void _showClientFormDialog() { - Get.dialog( - AlertDialog( - title: const Text('Informations Client'), - content: SingleChildScrollView( + 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, @@ -307,38 +324,35 @@ class _NouvelleCommandePageState extends State { ), 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, - ), - onPressed: () { - if (_formKey.currentState!.validate()) { - Get.back(); - Get.snackbar( - 'Succès', - 'Informations client enregistrées', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.green, - colorText: Colors.white, - ); - } - }, - child: const Text('Enregistrer'), - ), - ], ), - ); - } + 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(); + // Au lieu d'afficher juste un message, on valide directement la commande + _submitOrder(); + } + }, + child: const Text('Valider la commande'), // Changement de texte ici + ), + ], + ), + ); +} Widget _buildTextFormField({ required TextEditingController controller, @@ -464,7 +478,7 @@ class _NouvelleCommandePageState extends State { children: [ const SizedBox(height: 4), Text( - '${product.price.toStringAsFixed(2)} DA', + '${product.price.toStringAsFixed(2)} MGA', style: TextStyle( color: Colors.green.shade700, fontWeight: FontWeight.w600, @@ -626,9 +640,9 @@ class _NouvelleCommandePageState extends State { child: const Icon(Icons.shopping_bag, size: 20), ), title: Text(product.name), - subtitle: Text('${entry.value} x ${product.price.toStringAsFixed(2)} DA'), + subtitle: Text('${entry.value} x ${product.price.toStringAsFixed(2)} MGA'), trailing: Text( - '${(entry.value * product.price).toStringAsFixed(2)} DA', + '${(entry.value * product.price).toStringAsFixed(2)} MGA', style: TextStyle( fontWeight: FontWeight.bold, color: Colors.blue.shade800, @@ -658,7 +672,7 @@ class _NouvelleCommandePageState extends State { style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), Text( - '${total.toStringAsFixed(2)} DA', + '${total.toStringAsFixed(2)} MGA', style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -708,122 +722,122 @@ class _NouvelleCommandePageState extends State { } Future _submitOrder() async { - if (_nomController.text.isEmpty || - _prenomController.text.isEmpty || - _emailController.text.isEmpty || - _telephoneController.text.isEmpty || - _adresseController.text.isEmpty) { - Get.back(); // Ferme le bottom sheet - Get.snackbar( - 'Informations manquantes', - 'Veuillez remplir les informations client', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red, - colorText: Colors.white, - ); - _showClientFormDialog(); - return; - } - - 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, - ); - return; - } - - setState(() { - _isLoading = true; - }); + // 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; + } - // 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(), + // 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; + } - // 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, - )); - } + 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, + // 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; + }); + }, + child: const Text('OK'), + ), + ], + ), ); - try { - await _appDatabase.createCommandeComplete(client, commande, details); - - Get.back(); // Ferme le bottom sheet - - // 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 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; - }); - }, - 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, - ); - } + } 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() { diff --git a/pubspec.yaml b/pubspec.yaml index b62d35b..80c52e5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -102,6 +102,9 @@ flutter: - assets/database/usersdb.db - assets/database/work.db - assets/database/roles.db + - assets/airtel_money.png + - assets/mvola.jpg + - assets/Orange_money.png # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware