From e83f7af7301739449109a78be2cdf2a3ec470dd8 Mon Sep 17 00:00:00 2001 From: Stephane Date: Sun, 3 Aug 2025 19:49:06 +0300 Subject: [PATCH] command --- lib/models/command_detail.dart | 4 + lib/pages/cart_page.dart | 320 +++++++++++++++++++++++++++++---- lib/pages/facture_screen.dart | 22 +-- lib/services/pdf_service.dart | 12 +- 4 files changed, 304 insertions(+), 54 deletions(-) diff --git a/lib/models/command_detail.dart b/lib/models/command_detail.dart index ee3825d..4a53156 100644 --- a/lib/models/command_detail.dart +++ b/lib/models/command_detail.dart @@ -17,6 +17,7 @@ class CommandeDetail { final DateTime createdAt; final DateTime updatedAt; final List items; + final String? tablename; CommandeDetail({ required this.id, @@ -36,6 +37,7 @@ class CommandeDetail { required this.createdAt, required this.updatedAt, required this.items, + this.tablename, }); factory CommandeDetail.fromJson(Map json) { @@ -76,6 +78,7 @@ class CommandeDetail { ?.map((item) => CommandeItem.fromJson(item)) .toList() ?? [], + tablename: data['tablename'] ?? 'Table inconnue', ); } @@ -98,6 +101,7 @@ class CommandeDetail { 'created_at': createdAt.toIso8601String(), 'updated_at': updatedAt.toIso8601String(), 'items': items.map((item) => item.toJson()).toList(), + 'tablename': tablename, }; } diff --git a/lib/pages/cart_page.dart b/lib/pages/cart_page.dart index c312031..368a1f4 100644 --- a/lib/pages/cart_page.dart +++ b/lib/pages/cart_page.dart @@ -1,9 +1,14 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; +import 'package:itrimobe/models/command_detail.dart'; +import 'package:itrimobe/models/payment_method.dart'; +import 'package:itrimobe/pages/caisse_screen.dart'; +import 'package:itrimobe/pages/facture_screen.dart'; import 'dart:convert'; import 'package:itrimobe/pages/tables.dart'; +import 'package:itrimobe/services/pdf_service.dart'; import '../layouts/main_layout.dart'; @@ -315,7 +320,12 @@ class _CartPageState extends State { return sum + (item.prix * item.quantity); }); - // Confirmer le paiement + // Sélectionner le moyen de paiement + PaymentMethod? selectedPaymentMethod = await _showPaymentMethodDialog(); + + if (selectedPaymentMethod == null) return; + + // Confirmer le paiement avec le moyen sélectionné final bool? confirm = await showDialog( context: context, builder: @@ -337,6 +347,49 @@ class _CartPageState extends State { ), ), const SizedBox(height: 16), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: selectedPaymentMethod.color.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: selectedPaymentMethod.color.withOpacity(0.3), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + selectedPaymentMethod.icon, + color: selectedPaymentMethod.color, + size: 24, + ), + const SizedBox(width: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Paiement: ${selectedPaymentMethod.name}', + style: TextStyle( + fontWeight: FontWeight.w600, + color: selectedPaymentMethod.color, + ), + ), + Text( + selectedPaymentMethod.description, + style: TextStyle( + fontSize: 12, + color: selectedPaymentMethod.color.withOpacity( + 0.7, + ), + ), + ), + ], + ), + ], + ), + ), + const SizedBox(height: 16), const Text('Valider et payer cette commande ?'), ], ), @@ -348,9 +401,12 @@ class _CartPageState extends State { ElevatedButton( onPressed: () => Navigator.of(context).pop(true), style: ElevatedButton.styleFrom( - backgroundColor: Colors.orange[600], + backgroundColor: selectedPaymentMethod.color, + ), + child: const Text( + 'Confirmer le paiement', + style: TextStyle(color: Colors.white), ), - child: const Text('Confirmer le paiement'), ), ], ), @@ -363,13 +419,13 @@ class _CartPageState extends State { }); try { - // 1. Créer la commande avec les items du panier + // Créer la commande avec le moyen de paiement sélectionné final response = await http.post( Uri.parse('https://restaurant.careeracademy.mg/api/commandes'), headers: {'Content-Type': 'application/json'}, body: jsonEncode({ 'table_id': widget.tableId, - 'statut': 'payee', // Directement payée + 'statut': 'payee', 'items': _cartItems .map( @@ -378,33 +434,47 @@ class _CartPageState extends State { 'quantite': item.quantity, 'prix_unitaire': item.prix, 'commentaire': item.commentaire, - // Pas de réservation }, ) .toList(), - 'methode_paiement': 'especes', + 'methode_paiement': + selectedPaymentMethod.id, // mvola, carte, ou especes 'montant_paye': total, 'date_paiement': DateTime.now().toIso8601String(), - 'client_id': 1, // Valeur par défaut + 'client_id': 1, 'reservation_id': 1, - 'serveur': 'Serveur par défaut', // Valeur par défaut + 'serveur': 'Serveur par défaut', }), ); if (response.statusCode == 201) { final commandeData = jsonDecode(response.body); - // 2. Libérer la table + // Libérer la table await http.patch( Uri.parse( 'https://restaurant.careeracademy.mg/api/tables/${widget.tableId}', ), headers: {'Content-Type': 'application/json'}, - body: jsonEncode({'statut': 'libre'}), + body: jsonEncode({'status': 'available'}), + ); + + // Convertir le Map en objet CommandeDetail + final commandeDetail = CommandeDetail.fromJson(commandeData['data']); + + // Navigation avec l'objet converti + Navigator.of(context).pushReplacement( + MaterialPageRoute( + builder: + (context) => FactureScreen( + commande: + commandeDetail, // Maintenant c'est un objet CommandeDetail + paymentMethod: selectedPaymentMethod!.id, + ), + ), ); - // 3. Succès - _showPaymentSuccessDialog(commandeData, total); + // _showPaymentSuccessDialog(commandeData, total, selectedPaymentMethod); } else { throw Exception('Erreur lors du paiement'); } @@ -422,9 +492,104 @@ class _CartPageState extends State { } } + // Dialog de sélection du moyen de paiement + Future _showPaymentMethodDialog() async { + return await showDialog( + context: context, + builder: + (context) => AlertDialog( + title: const Row( + children: [ + Icon(Icons.payment, color: Colors.blue), + SizedBox(width: 8), + Text('Choisir le moyen de paiement'), + ], + ), + content: SizedBox( + width: double.maxFinite, + child: ListView.separated( + shrinkWrap: true, + itemCount: paymentMethods.length, + separatorBuilder: (context, index) => const SizedBox(height: 8), + itemBuilder: (context, index) { + final method = paymentMethods[index]; + return InkWell( + onTap: () => Navigator.of(context).pop(method), + borderRadius: BorderRadius.circular(8), + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + border: Border.all( + color: method.color.withOpacity(0.3), + ), + borderRadius: BorderRadius.circular(8), + color: method.color.withOpacity(0.05), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: method.color.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + method.icon, + color: method.color, + size: 24, + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + method.name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: method.color, + ), + ), + const SizedBox(height: 2), + Text( + method.description, + style: TextStyle( + fontSize: 12, + color: method.color.withOpacity(0.7), + ), + ), + ], + ), + ), + Icon( + Icons.arrow_forward_ios, + color: method.color, + size: 16, + ), + ], + ), + ), + ); + }, + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Annuler'), + ), + ], + ), + ); + } + + // Dialog de succès mis à jour void _showPaymentSuccessDialog( Map commandeData, double total, + PaymentMethod paymentMethod, ) { showDialog( context: context, @@ -446,41 +611,55 @@ class _CartPageState extends State { Text('Table ${widget.tablename} libérée'), const SizedBox(height: 8), Text( - 'Montant: ${total.toStringAsFixed(2)} €', + 'Montant: ${total.toStringAsFixed(0)} MGA', style: const TextStyle(fontWeight: FontWeight.bold), ), + const SizedBox(height: 12), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + decoration: BoxDecoration( + color: paymentMethod.color.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: paymentMethod.color.withOpacity(0.3), + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + paymentMethod.icon, + color: paymentMethod.color, + size: 16, + ), + const SizedBox(width: 6), + Text( + paymentMethod.name, + style: TextStyle( + color: paymentMethod.color, + fontWeight: FontWeight.w600, + fontSize: 14, + ), + ), + ], + ), + ), const SizedBox(height: 16), const Text('Voulez-vous imprimer un reçu ?'), ], ), actions: [ TextButton( - onPressed: () { - Navigator.of(context).pop(); // Fermer le dialog - // Naviguer vers la page des tables - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute( - builder: - (context) => const MainLayout(child: TablesScreen()), - ), - (route) => false, - ); - }, + onPressed: () => _navigateToTables(), child: const Text('Non merci'), ), ElevatedButton( - onPressed: () async { - Navigator.of(context).pop(); // Fermer le dialog - await _imprimerTicketPaiement(commandeData, total); - // Naviguer vers la page des tables après l'impression - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute( - builder: - (context) => const MainLayout(child: TablesScreen()), - ), - (route) => false, - ); - }, + onPressed: + () => + _imprimerEtNaviguer(commandeData, total, paymentMethod), style: ElevatedButton.styleFrom(backgroundColor: Colors.green), child: const Text('Imprimer'), ), @@ -489,6 +668,73 @@ class _CartPageState extends State { ); } + void _navigateToTables() { + Navigator.of(context).pop(); // Fermer le dialog + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (context) => const MainLayout(child: TablesScreen()), + ), + (route) => false, + ); + } + + Future _imprimerEtNaviguer( + Map commandeData, + double total, + PaymentMethod paymentMethod, + ) async { + Navigator.of(context).pop(); // Fermer le dialog + + // Afficher un indicateur de chargement pendant l'impression + showDialog( + context: context, + barrierDismissible: false, + builder: + (context) => const AlertDialog( + content: Row( + mainAxisSize: MainAxisSize.min, + children: [ + CircularProgressIndicator(), + SizedBox(width: 16), + Text('Impression en cours...'), + ], + ), + ), + ); + + try { + await _imprimerTicketPaiement(commandeData, total); + + // Fermer le dialog d'impression + Navigator.of(context).pop(); + + // Afficher un message de succès + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Ticket imprimé avec succès'), + backgroundColor: Colors.green, + duration: Duration(seconds: 2), + ), + ); + } catch (e) { + // Fermer le dialog d'impression + Navigator.of(context).pop(); + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Erreur d\'impression: ${e.toString()}'), + backgroundColor: Colors.orange, + duration: const Duration(seconds: 3), + ), + ); + } + + // Attendre un peu puis naviguer + Future.delayed(const Duration(milliseconds: 500), () { + _navigateToTables(); + }); + } + Future _imprimerTicketPaiement( Map commandeData, double total, diff --git a/lib/pages/facture_screen.dart b/lib/pages/facture_screen.dart index a647305..f4975fb 100644 --- a/lib/pages/facture_screen.dart +++ b/lib/pages/facture_screen.dart @@ -11,10 +11,10 @@ class FactureScreen extends StatefulWidget { final String paymentMethod; const FactureScreen({ - Key? key, + super.key, required this.commande, required this.paymentMethod, - }) : super(key: key); + }); @override _FactureScreenState createState() => _FactureScreenState(); @@ -114,23 +114,23 @@ class _FactureScreenState extends State { } Widget _buildHeader() { - return Column( + return const Column( children: [ - const Text( - 'RESTAURANT', + Text( + 'RESTAURANT ITRIMOBE', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, letterSpacing: 1.2, ), ), - const SizedBox(height: 12), - const Text( - 'Adresse: 123 Rue de la Paix', + SizedBox(height: 12), + Text( + 'Adresse: Moramanga, Madagascar', style: TextStyle(fontSize: 12, color: Colors.black87), ), - const Text( - 'Contact: +33 1 23 45 67 89', + Text( + 'Contact: +261 34 12 34 56', style: TextStyle(fontSize: 12, color: Colors.black87), ), ], @@ -157,7 +157,7 @@ class _FactureScreenState extends State { ), const SizedBox(height: 4), Text( - 'Table: ${widget.commande.tableId}', + 'Via: ${widget.commande.tablename}', style: const TextStyle(fontSize: 12, color: Colors.black87), ), const SizedBox(height: 4), diff --git a/lib/services/pdf_service.dart b/lib/services/pdf_service.dart index 06a60df..831c809 100644 --- a/lib/services/pdf_service.dart +++ b/lib/services/pdf_service.dart @@ -55,11 +55,11 @@ class PlatformPrintService { const double lineHeight = 1.2; final restaurantInfo = { - 'nom': 'RESTAURANT', - 'adresse': '123 Rue de la Paix', - 'ville': '75000 PARIS', - 'contact': '01.23.45.67.89', - 'email': 'contact@restaurant.fr', + 'nom': 'RESTAURANT ITRIMOBE', + 'adresse': 'Moramanga, Antananarivo', + 'ville': 'Madagascar', + 'contact': '261348415301', + 'email': 'contact@careeragency.mg', }; final factureNumber = @@ -389,7 +389,7 @@ class PlatformPrintService { return await printTicket(commande: commande, paymentMethod: paymentMethod); } - // Utilitaires de formatage + // Utilitaires de formatageπ static String _formatDate(DateTime dateTime) { return '${dateTime.day.toString().padLeft(2, '0')}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.year}'; }