diff --git a/lib/pages/commande_item_validation.dart b/lib/pages/commande_item_validation.dart index b7fa273..e8f5e6b 100644 --- a/lib/pages/commande_item_validation.dart +++ b/lib/pages/commande_item_validation.dart @@ -6,7 +6,8 @@ class ValidateAddItemsPage extends StatefulWidget { final int commandeId; final String numeroCommande; final List newItems; // Nouveaux articles à ajouter - final Map? commandeDetails; // Détails de la commande existante + final Map? + commandeDetails; // Détails de la commande existante const ValidateAddItemsPage({ Key? key, @@ -88,14 +89,17 @@ class _ValidateAddItemsPageState extends State { // Calculer le total des articles existants double _calculateExistingItemsTotal() { - if (widget.commandeDetails == null || widget.commandeDetails!['items'] == null) { + if (widget.commandeDetails == null || + widget.commandeDetails!['items'] == null) { return 0.0; } double total = 0.0; for (var item in widget.commandeDetails!['items']) { // Correction: utiliser 'menu_prix_actuel' au lieu de 'menu_prix' - double prix = _parsePrice(item['menu_prix_actuel'] ?? item['prix_unitaire']); + double prix = _parsePrice( + item['menu_prix_actuel'] ?? item['prix_unitaire'], + ); int quantite = item['quantite'] ?? 1; total += prix * quantite; } @@ -112,10 +116,14 @@ class _ValidateAddItemsPageState extends State { } int _getTotalExistingArticles() { - if (widget.commandeDetails == null || widget.commandeDetails!['items'] == null) { + if (widget.commandeDetails == null || + widget.commandeDetails!['items'] == null) { return 0; } - return widget.commandeDetails!['items'].fold(0, (sum, item) => sum + (item['quantite'] ?? 1)); + return widget.commandeDetails!['items'].fold( + 0, + (sum, item) => sum + (item['quantite'] ?? 1), + ); } void _showConfirmationDialog() { @@ -132,7 +140,9 @@ class _ValidateAddItemsPageState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('Êtes-vous sûr de vouloir ajouter ces articles à la commande ?'), + Text( + 'Êtes-vous sûr de vouloir ajouter ces articles à la commande ?', + ), SizedBox(height: 16), Text( 'Récapitulatif:', @@ -140,7 +150,9 @@ class _ValidateAddItemsPageState extends State { ), Text('• Commande: ${widget.numeroCommande}'), Text('• Nouveaux articles: ${_getTotalNewArticles()}'), - Text('• Nouveau total: ${_calculateGrandTotal().toStringAsFixed(2)} MGA'), + Text( + '• Nouveau total: ${_calculateGrandTotal().toStringAsFixed(2)} MGA', + ), ], ), actions: [ @@ -149,26 +161,30 @@ class _ValidateAddItemsPageState extends State { child: Text('Annuler', style: TextStyle(color: Colors.grey[600])), ), ElevatedButton( - onPressed: _isValidating - ? null - : () { - Navigator.of(context).pop(); - _validateAddItems(); - }, + onPressed: + _isValidating + ? null + : () { + Navigator.of(context).pop(); + _validateAddItems(); + }, style: ElevatedButton.styleFrom( backgroundColor: Colors.green[700], foregroundColor: Colors.white, ), - child: _isValidating - ? SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation(Colors.white), - ), - ) - : Text('Confirmer'), + child: + _isValidating + ? SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation( + Colors.white, + ), + ), + ) + : Text('Confirmer'), ), ], ); @@ -193,36 +209,36 @@ class _ValidateAddItemsPageState extends State { try { // Préparer les données des nouveaux articles -// 1. Construire la liste des items sans commande_id à l’intérieur -List> items = _newCartItems.map((cartItem) { - return { - 'menu_id': cartItem.id, - 'quantite': cartItem.quantity, - 'commentaires': cartItem.notes.isNotEmpty ? cartItem.notes : null, - }; -}).toList(); - -// 2. Construire le body correctement avec commande_id à la racine -final body = { - 'commande_id': widget.commandeId, - 'items': items, -}; - -print("📦 Données envoyées : ${json.encode(body)}"); - -// 3. Envoi vers l'API -final response = await http.post( - Uri.parse('https://restaurant.careeracademy.mg/api/commande-items/add-multiple'), - headers: { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - }, - body: json.encode(body), -); - -// 4. Afficher la réponse backend -final responseData = jsonDecode(response.body); -print('✅ Réponse backend : $responseData'); + // 1. Construire la liste des items sans commande_id à l’intérieur + List> items = + _newCartItems.map((cartItem) { + return { + 'menu_id': cartItem.id, + 'quantite': cartItem.quantity, + 'commentaires': cartItem.notes.isNotEmpty ? cartItem.notes : null, + }; + }).toList(); + + // 2. Construire le body correctement avec commande_id à la racine + final body = {'commande_id': widget.commandeId, 'items': items}; + + print("📦 Données envoyées : ${json.encode(body)}"); + + // 3. Envoi vers l'API + final response = await http.post( + Uri.parse( + 'https://restaurant.careeracademy.mg/api/commande-items/add-multiple', + ), + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + body: json.encode(body), + ); + + // 4. Afficher la réponse backend + final responseData = jsonDecode(response.body); + print('✅ Réponse backend : $responseData'); if (response.statusCode == 200 || response.statusCode == 201) { final responseData = json.decode(response.body); @@ -349,60 +365,59 @@ print('✅ Réponse backend : $responseData'); ), ), SizedBox(height: 16), - + // Résumé des articles existants - if (widget.commandeDetails != null && widget.commandeDetails!['items'] != null) ...[ - Text( - 'Articles déjà commandés:', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.grey[700], - ), - ), - SizedBox(height: 8), - ...widget.commandeDetails!['items'].map((item) { - final nom = item['menu_nom'] ?? 'Inconnu'; - final quantite = item['quantite'] ?? 1; - // Correction: utiliser 'menu_prix_actuel' au lieu de 'menu_prix' - final prix = _parsePrice(item['menu_prix_actuel'] ?? item['prix_unitaire']); - - return Padding( - padding: const EdgeInsets.symmetric(vertical: 2), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - '$nom x$quantite', - style: TextStyle(fontSize: 14, color: Colors.grey[600]), - ), - ), - Text( - '${(prix * quantite).toStringAsFixed(2)} MGA', - style: TextStyle(fontSize: 14, color: Colors.grey[600]), - ), - ], - ), - ); - }).toList(), - SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Sous-total existant:', - style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500), - ), - Text( - '${_calculateExistingItemsTotal().toStringAsFixed(2)} MGA', - style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500), - ), - ], - ), - Divider(height: 20), - ], - + // if (widget.commandeDetails != null && widget.commandeDetails!['items'] != null) ...[ + // Text( + // 'Articles déjà commandés:', + // style: TextStyle( + // fontSize: 16, + // fontWeight: FontWeight.w600, + // color: Colors.grey[700], + // ), + // ), + // SizedBox(height: 8), + // ...widget.commandeDetails!['items'].map((item) { + // final nom = item['menu_nom'] ?? 'Inconnu'; + // final quantite = item['quantite'] ?? 1; + // // Correction: utiliser 'menu_prix_actuel' au lieu de 'menu_prix' + // final prix = _parsePrice(item['menu_prix_actuel'] ?? item['prix_unitaire']); + + // return Padding( + // padding: const EdgeInsets.symmetric(vertical: 2), + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // Expanded( + // child: Text( + // '$nom x$quantite', + // style: TextStyle(fontSize: 14, color: Colors.grey[600]), + // ), + // ), + // Text( + // '${(prix * quantite).toStringAsFixed(2)} MGA', + // style: TextStyle(fontSize: 14, color: Colors.grey[600]), + // ), + // ], + // ), + // ); + // }).toList(), + // SizedBox(height: 8), + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // Text( + // 'Sous-total existant:', + // style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + // ), + // Text( + // '${_calculateExistingItemsTotal().toStringAsFixed(2)} MGA', + // style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500), + // ), + // ], + // ), + // Divider(height: 20), + // ], Text( 'Nouveaux articles à ajouter: ${_getTotalNewArticles()}', style: TextStyle( @@ -417,152 +432,168 @@ print('✅ Réponse backend : $responseData'); // Liste des nouveaux articles Expanded( - child: _newCartItems.isEmpty - ? Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.shopping_cart_outlined, - size: 80, - color: Colors.grey[400], - ), - SizedBox(height: 16), - Text( - 'Aucun nouvel article sélectionné', - style: TextStyle(fontSize: 18, color: Colors.grey[600]), - ), - ], - ), - ) - : ListView.separated( - padding: EdgeInsets.all(16), - itemCount: _newCartItems.length, - separatorBuilder: (context, index) => SizedBox(height: 12), - itemBuilder: (context, index) { - final item = _newCartItems[index]; - return Container( - padding: EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - border: Border.all(color: Colors.green[200]!, width: 1), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 5, - offset: Offset(0, 2), + child: + _newCartItems.isEmpty + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.shopping_cart_outlined, + size: 80, + color: Colors.grey[400], + ), + SizedBox(height: 16), + Text( + 'Aucun nouvel article sélectionné', + style: TextStyle( + fontSize: 18, + color: Colors.grey[600], ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Row( - children: [ - Icon(Icons.add_circle, color: Colors.green[600], size: 20), - SizedBox(width: 8), - Expanded( - child: Text( - item.nom, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, + ), + ], + ), + ) + : ListView.separated( + padding: EdgeInsets.all(16), + itemCount: _newCartItems.length, + separatorBuilder: + (context, index) => SizedBox(height: 12), + itemBuilder: (context, index) { + final item = _newCartItems[index]; + return Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: Colors.green[200]!, + width: 1, + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 5, + offset: Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Row( + children: [ + Icon( + Icons.add_circle, + color: Colors.green[600], + size: 20, + ), + SizedBox(width: 8), + Expanded( + child: Text( + item.nom, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), ), ), - ), - ], + ], + ), ), - ), - IconButton( - onPressed: () => _removeItem(index), - icon: Icon( - Icons.delete_outline, - color: Colors.red, + IconButton( + onPressed: () => _removeItem(index), + icon: Icon( + Icons.delete_outline, + color: Colors.red, + ), + constraints: BoxConstraints(), + padding: EdgeInsets.zero, ), - constraints: BoxConstraints(), - padding: EdgeInsets.zero, - ), - ], - ), - Text( - '${item.prix.toStringAsFixed(2)} MGA l\'unité', - style: TextStyle( - fontSize: 14, - color: Colors.grey[600], + ], ), - ), - if (item.notes.isNotEmpty) ...[ - SizedBox(height: 8), Text( - 'Notes: ${item.notes}', + '${item.prix.toStringAsFixed(2)} MGA l\'unité', style: TextStyle( fontSize: 14, color: Colors.grey[600], - fontStyle: FontStyle.italic, ), ), - ], - SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - // Contrôles de quantité - Row( - children: [ - IconButton( - onPressed: () => _updateQuantity( - index, - item.quantity - 1, - ), - icon: Icon(Icons.remove), - style: IconButton.styleFrom( - backgroundColor: Colors.grey[200], - minimumSize: Size(40, 40), - ), - ), - SizedBox(width: 16), - Text( - item.quantity.toString(), - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), - ), - SizedBox(width: 16), - IconButton( - onPressed: () => _updateQuantity( - index, - item.quantity + 1, - ), - icon: Icon(Icons.add), - style: IconButton.styleFrom( - backgroundColor: Colors.grey[200], - minimumSize: Size(40, 40), - ), - ), - ], - ), - // Prix total de l'article + if (item.notes.isNotEmpty) ...[ + SizedBox(height: 8), Text( - '${(item.prix * item.quantity).toStringAsFixed(2)} MGA', + 'Notes: ${item.notes}', style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.green[700], + fontSize: 14, + color: Colors.grey[600], + fontStyle: FontStyle.italic, ), ), ], - ), - ], - ), - ); - }, - ), + SizedBox(height: 16), + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + // Contrôles de quantité + Row( + children: [ + IconButton( + onPressed: + () => _updateQuantity( + index, + item.quantity - 1, + ), + icon: Icon(Icons.remove), + style: IconButton.styleFrom( + backgroundColor: Colors.grey[200], + minimumSize: Size(40, 40), + ), + ), + SizedBox(width: 16), + Text( + item.quantity.toString(), + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(width: 16), + IconButton( + onPressed: + () => _updateQuantity( + index, + item.quantity + 1, + ), + icon: Icon(Icons.add), + style: IconButton.styleFrom( + backgroundColor: Colors.grey[200], + minimumSize: Size(40, 40), + ), + ), + ], + ), + // Prix total de l'article + Text( + '${(item.prix * item.quantity).toStringAsFixed(2)} MGA', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.green[700], + ), + ), + ], + ), + ], + ), + ); + }, + ), ), // Récapitulatif final @@ -577,10 +608,7 @@ print('✅ Réponse backend : $responseData'); children: [ Text( 'Récapitulatif final', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), SizedBox(height: 16), Row( @@ -596,7 +624,10 @@ print('✅ Réponse backend : $responseData'); Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('Sous-total existant:', style: TextStyle(fontSize: 16)), + Text( + 'Sous-total existant:', + style: TextStyle(fontSize: 16), + ), Text( '${_calculateExistingItemsTotal().toStringAsFixed(2)} MGA', style: TextStyle(fontSize: 16), @@ -607,7 +638,10 @@ print('✅ Réponse backend : $responseData'); Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('Nouveaux articles:', style: TextStyle(fontSize: 16, color: Colors.green[700])), + Text( + 'Nouveaux articles:', + style: TextStyle(fontSize: 16, color: Colors.green[700]), + ), Text( _getTotalNewArticles().toString(), style: TextStyle(fontSize: 16, color: Colors.green[700]), @@ -617,7 +651,10 @@ print('✅ Réponse backend : $responseData'); Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text('Sous-total nouveaux:', style: TextStyle(fontSize: 16, color: Colors.green[700])), + Text( + 'Sous-total nouveaux:', + style: TextStyle(fontSize: 16, color: Colors.green[700]), + ), Text( '${_calculateNewItemsTotal().toStringAsFixed(2)} MGA', style: TextStyle(fontSize: 16, color: Colors.green[700]), @@ -651,9 +688,10 @@ print('✅ Réponse backend : $responseData'); SizedBox( width: double.infinity, child: ElevatedButton( - onPressed: _newCartItems.isNotEmpty && !_isValidating - ? _showConfirmationDialog - : null, + onPressed: + _newCartItems.isNotEmpty && !_isValidating + ? _showConfirmationDialog + : null, style: ElevatedButton.styleFrom( backgroundColor: Colors.green[700], foregroundColor: Colors.white, @@ -672,7 +710,9 @@ print('✅ Réponse backend : $responseData'); height: 20, child: CircularProgressIndicator( strokeWidth: 2, - valueColor: AlwaysStoppedAnimation(Colors.white), + valueColor: AlwaysStoppedAnimation( + Colors.white, + ), ), ), SizedBox(width: 8), @@ -687,7 +727,7 @@ print('✅ Réponse backend : $responseData'); Icon(Icons.add_shopping_cart, size: 20), SizedBox(width: 8), Text( - _newCartItems.isEmpty + _newCartItems.isEmpty ? 'Aucun article à ajouter' : 'Ajouter à la commande (${_getTotalNewArticles()})', style: TextStyle( @@ -724,4 +764,4 @@ class CartItemModel { required this.quantity, required this.notes, }); -} \ No newline at end of file +} diff --git a/lib/pages/commandes_screen.dart b/lib/pages/commandes_screen.dart index 11d31b5..49af7c0 100644 --- a/lib/pages/commandes_screen.dart +++ b/lib/pages/commandes_screen.dart @@ -140,8 +140,6 @@ class _OrdersManagementScreenState extends State { return null; } - - Future updateOrderStatus( Order order, String newStatus, { @@ -289,9 +287,6 @@ class _OrdersManagementScreenState extends State { } } - - - List get activeOrders { return orders .where( @@ -347,7 +342,6 @@ class _OrdersManagementScreenState extends State { style: TextStyle(color: Colors.white), ), ), - ], ); }, @@ -356,22 +350,23 @@ class _OrdersManagementScreenState extends State { ); } - Future updateTableStatus(int tableId, String newStatus) async { - const String apiUrl = 'https://restaurant.careeracademy.mg/api/tables'; // ← adapte l’URL si besoin + Future updateTableStatus(int tableId, String newStatus) async { + const String apiUrl = + 'https://restaurant.careeracademy.mg/api/tables'; // ← adapte l’URL si besoin - final response = await http.put( - Uri.parse('$apiUrl/$tableId'), - headers: {'Content-Type': 'application/json'}, - body: jsonEncode({'status': newStatus}), - ); + final response = await http.put( + Uri.parse('$apiUrl/$tableId'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({'status': newStatus}), + ); - if (response.statusCode != 200) { - print('Erreur lors de la mise à jour du statut de la table'); - throw Exception('Erreur: ${response.body}'); - } else { - print('✅ Table $tableId mise à jour en $newStatus'); + if (response.statusCode != 200) { + print('Erreur lors de la mise à jour du statut de la table'); + throw Exception('Erreur: ${response.body}'); + } else { + print('✅ Table $tableId mise à jour en $newStatus'); + } } -} @override Widget build(BuildContext context) { @@ -602,44 +597,37 @@ class OrderCard extends StatelessWidget { // Header row Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 4, - ), - decoration: BoxDecoration( - color: _getStatusColor(order.statut), - borderRadius: BorderRadius.circular(12), - ), - child: Text( - _getStatusText(order.statut), - style: const TextStyle( - color: Colors.white, - fontSize: 12, - fontWeight: FontWeight.w500, - ), - ), + children: [ + GestureDetector( + onTap: onViewDetails, + child: Text( + 'Table ${order.tableId}', + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.black87, ), - const SizedBox(width: 4), - if (order.statut == 'en_attente') - IconButton( - icon: const Icon(Icons.add_circle_outline, size: 20, color: Colors.blue), - tooltip: 'Ajouter un article', - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AddItemsToOrderPage( - commandeId: order.id, - numeroCommande: order.numero ?? 'Commande #${order.id}', - ), - ), - ); - }, - ), - - ], + ), + ), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 4, + ), + decoration: BoxDecoration( + color: _getStatusColor(order.statut), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + _getStatusText(order.statut), + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ), + ], ), const SizedBox(height: 8), @@ -731,7 +719,8 @@ class OrderCard extends StatelessWidget { order.statut == 'en_preparation') Row( children: [ - if (order.statut == 'en_attente') + if (order.statut == 'en_attente' || + order.statut == 'en_preparation') Expanded( child: ElevatedButton( onPressed: @@ -741,22 +730,28 @@ class OrderCard extends StatelessWidget { MaterialPageRoute( builder: (context) => AddItemsToOrderPage( - commandId: order.id, + commandeId: order.id, numeroCommande: order.numeroCommande, ), ), ), }, style: ElevatedButton.styleFrom( - backgroundColor: Colors.orange, + backgroundColor: const Color.fromARGB( + 255, + 0, + 76, + 255, + ), padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(6), ), ), - child: const Text( - 'Préparer', - style: TextStyle(color: Colors.white, fontSize: 12), + child: const Icon( + Icons.add, + color: Colors.white, + size: 16, ), ), ), @@ -860,8 +855,6 @@ class OrderCard extends StatelessWidget { } } - - // Updated Order model to include items class Order { final int id;