diff --git a/lib/pages/cart_page.dart b/lib/pages/cart_page.dart index 1047bf9..bd68637 100644 --- a/lib/pages/cart_page.dart +++ b/lib/pages/cart_page.dart @@ -2,6 +2,10 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; +import 'package:itrimobe/pages/tables.dart'; + +import '../layouts/main_layout.dart'; + class CartPage extends StatefulWidget { final int tableId; final int personne; @@ -119,30 +123,28 @@ class _CartPageState extends State { child: Text('Annuler', style: TextStyle(color: Colors.grey[600])), ), ElevatedButton( - onPressed: - _isValidating - ? null - : () { - Navigator.of(context).pop(); - _validateOrder(); - }, + onPressed: _isValidating + ? null + : () { + Navigator.of(context).pop(); + _validateOrder(); + }, 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, - ), + child: _isValidating + ? SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation( + Colors.white, ), - ) - : Text('Valider'), + ), + ) + : Text('Valider'), ), ], ); @@ -163,16 +165,15 @@ class _CartPageState extends State { "reservation_id": 1, // Peut être null si pas de réservation "serveur": "Serveur par défaut", // Valeur par défaut comme demandé "commentaires": _getOrderComments(), - "items": - _cartItems - .map( - (item) => { - "menu_id": item.id, - "quantite": item.quantity, - "commentaires": item.notes.isNotEmpty ? item.notes : null, - }, - ) - .toList(), + "items": _cartItems + .map( + (item) => { + "menu_id": item.id, + "quantite": item.quantity, + "commentaires": item.notes.isNotEmpty ? item.notes : null, + }, + ) + .toList(), }; // Appel API pour créer la commande @@ -187,7 +188,7 @@ class _CartPageState extends State { if (response.statusCode == 200 || response.statusCode == 201) { // Succès - _updateTableStatus(); + _updateTableStatus(); _showSuccessDialog(); } else { // Erreur @@ -206,15 +207,15 @@ class _CartPageState extends State { String _getOrderComments() { // Concaténer toutes les notes des articles pour les commentaires généraux - List allNotes = - _cartItems - .where((item) => item.notes.isNotEmpty) - .map((item) => '${item.nom}: ${item.notes}') - .toList(); + List allNotes = _cartItems + .where((item) => item.notes.isNotEmpty) + .map((item) => '${item.nom}: ${item.notes}') + .toList(); return allNotes.join('; '); } + // FONCTION CORRIGÉE POUR LA NAVIGATION VERS LES TABLES void _showSuccessDialog() { showDialog( context: context, @@ -234,14 +235,13 @@ class _CartPageState extends State { actions: [ ElevatedButton( onPressed: () { - Navigator.of(context).pop(); // Fermer le dialog - Navigator.of(context).pop(); // Retourner au menu - Navigator.of(context).pop(); // Retourner aux tables + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (context) => MainLayout(child: TablesScreen()), + ), + (route) => false, + ); }, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.green[700], - foregroundColor: Colors.white, - ), child: Text('OK'), ), ], @@ -251,17 +251,17 @@ class _CartPageState extends State { } Future _updateTableStatus() async { - try { - final updateResponse = await http.put( - Uri.parse('https://restaurant.careeracademy.mg/api/tables/${widget.tableId}'), - headers: {'Content-Type': 'application/json'}, - body: json.encode({"status": "occupied"}), - ); - } catch (e) { - print("Erreur lors de la mise à jour du statut de la table: $e"); + try { + final updateResponse = await http.put( + Uri.parse( + 'https://restaurant.careeracademy.mg/api/tables/${widget.tableId}'), + headers: {'Content-Type': 'application/json'}, + body: json.encode({"status": "occupied"}), + ); + } catch (e) { + print("Erreur lors de la mise à jour du statut de la table: $e"); + } } -} - void _showErrorDialog(String message) { showDialog( @@ -317,297 +317,290 @@ class _CartPageState extends State { SizedBox(width: 16), ], ), - body: - _cartItems.isEmpty - ? Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.shopping_cart_outlined, - size: 80, - color: Colors.grey[400], - ), - SizedBox(height: 16), - Text( - 'Votre panier est vide', - style: TextStyle(fontSize: 18, color: Colors.grey[600]), - ), - ], - ), - ) - : Column( + body: _cartItems.isEmpty + ? Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, children: [ - // Header avec infos table - Container( - width: double.infinity, - padding: EdgeInsets.all(20), - color: Colors.white, - child: Text( - 'Table ${widget.tableId} • ${widget.personne} personne${widget.personne > 1 ? 's' : ''}', - style: TextStyle(fontSize: 16, color: Colors.grey[600]), - ), + Icon( + Icons.shopping_cart_outlined, + size: 80, + color: Colors.grey[400], ), + SizedBox(height: 16), + Text( + 'Votre panier est vide', + style: TextStyle(fontSize: 18, color: Colors.grey[600]), + ), + ], + ), + ) + : Column( + children: [ + // Header avec infos table + Container( + width: double.infinity, + padding: EdgeInsets.all(20), + color: Colors.white, + child: Text( + 'Table ${widget.tableId} • ${widget.personne} personne${widget.personne > 1 ? 's' : ''}', + style: TextStyle(fontSize: 16, color: Colors.grey[600]), + ), + ), - // Liste des articles - Expanded( - child: ListView.separated( - padding: EdgeInsets.all(16), - itemCount: _cartItems.length, - separatorBuilder: - (context, index) => SizedBox(height: 12), - itemBuilder: (context, index) { - final item = _cartItems[index]; - return Container( - padding: EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - 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: Text( - item.nom, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), + // Liste des articles + Expanded( + child: ListView.separated( + padding: EdgeInsets.all(16), + itemCount: _cartItems.length, + separatorBuilder: (context, index) => SizedBox(height: 12), + itemBuilder: (context, index) { + final item = _cartItems[index]; + return Container( + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + 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: Text( + item.nom, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, ), ), - IconButton( - onPressed: () => _removeItem(index), - icon: Icon( - Icons.delete_outline, - color: Colors.red, - ), - constraints: BoxConstraints(), - padding: EdgeInsets.zero, + ), + IconButton( + onPressed: () => _removeItem(index), + icon: Icon( + Icons.delete_outline, + color: Colors.red, ), - ], + 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( - '${item.prix.toStringAsFixed(2)} MGA l\'unité', + 'Notes: ${item.notes}', style: TextStyle( fontSize: 14, color: Colors.grey[600], + fontStyle: FontStyle.italic, ), ), - if (item.notes.isNotEmpty) ...[ - SizedBox(height: 8), - Text( - 'Notes: ${item.notes}', - 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(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), - Text( - item.quantity.toString(), - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), + ), + SizedBox(width: 16), + IconButton( + onPressed: () => _updateQuantity( + index, + item.quantity + 1, ), - 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), - ), + 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], ), + ], + ), + // 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 - Container( - padding: EdgeInsets.all(20), - decoration: BoxDecoration( - color: Colors.white, - border: Border(top: BorderSide(color: Colors.grey[200]!)), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Récapitulatif', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - ), - ), - SizedBox(height: 16), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('Articles:', style: TextStyle(fontSize: 16)), - Text( - _getTotalArticles().toString(), - style: TextStyle(fontSize: 16), - ), - ], - ), - SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('Table:', style: TextStyle(fontSize: 16)), - Text( - widget.tableId.toString(), - style: TextStyle(fontSize: 16), + ), + ], ), ], ), - SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('Personnes:', style: TextStyle(fontSize: 16)), - Text( - widget.personne.toString(), - style: TextStyle(fontSize: 16), - ), - ], + ); + }, + ), + ), + + // Récapitulatif + Container( + padding: EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.white, + border: Border(top: BorderSide(color: Colors.grey[200]!)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Récapitulatif', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, ), - SizedBox(height: 16), - Divider(), - SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - 'Total:', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), + ), + SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Articles:', style: TextStyle(fontSize: 16)), + Text( + _getTotalArticles().toString(), + style: TextStyle(fontSize: 16), + ), + ], + ), + SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Table:', style: TextStyle(fontSize: 16)), + Text( + widget.tableId.toString(), + style: TextStyle(fontSize: 16), + ), + ], + ), + SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Personnes:', style: TextStyle(fontSize: 16)), + Text( + widget.personne.toString(), + style: TextStyle(fontSize: 16), + ), + ], + ), + SizedBox(height: 16), + Divider(), + SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Total:', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, ), - Text( - '${_calculateTotal().toStringAsFixed(2)} MGA', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - ), + ), + Text( + '${_calculateTotal().toStringAsFixed(2)} MGA', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, ), - ], - ), - SizedBox(height: 20), - SizedBox( - width: double.infinity, - child: ElevatedButton( - onPressed: - _cartItems.isNotEmpty && !_isValidating - ? _showConfirmationDialog - : null, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.green[700], - foregroundColor: Colors.white, - padding: EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - disabledBackgroundColor: Colors.grey[300], + ), + ], + ), + SizedBox(height: 20), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _cartItems.isNotEmpty && !_isValidating + ? _showConfirmationDialog + : null, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green[700], + foregroundColor: Colors.white, + padding: EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (_isValidating) ...[ - SizedBox( - width: 20, - height: 20, - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation( - Colors.white, - ), + disabledBackgroundColor: Colors.grey[300], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (_isValidating) ...[ + SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation( + Colors.white, ), ), - SizedBox(width: 8), - Text( - 'Validation en cours...', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), + ), + SizedBox(width: 8), + Text( + 'Validation en cours...', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, ), - ] else ...[ - Icon(Icons.check, size: 20), - SizedBox(width: 8), - Text( - 'Valider la commande', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), + ), + ] else ...[ + Icon(Icons.check, size: 20), + SizedBox(width: 8), + Text( + 'Valider la commande', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, ), - ], + ), ], - ), + ], ), ), - ], - ), + ), + ], ), - ], - ), + ), + ], + ), ); } } @@ -626,4 +619,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 83a7264..8828413 100644 --- a/lib/pages/commandes_screen.dart +++ b/lib/pages/commandes_screen.dart @@ -600,37 +600,33 @@ class OrderCard extends StatelessWidget { // Header row Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GestureDetector( - onTap: onViewDetails, - child: Text( - 'Table ${order.tableId}', - style: const TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.black87, - ), - ), - ), - 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: [ + 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(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: () => _showAddItemDialog(context, order), + ), + ], ), const SizedBox(height: 8), @@ -821,6 +817,135 @@ class OrderCard extends StatelessWidget { } } +void _showAddItemDialog(BuildContext context, Order order) async { + List menuItems = []; + MenuItem? selectedMenuItem; + int quantity = 1; + + try { + final response = await http.get( + Uri.parse('https://restaurant.careeracademy.mg/api/menus'), + headers: {'Accept': 'application/json'}, + ); + +if (response.statusCode == 200) { + final data = json.decode(response.body); + + final rawItems = data['data']?['menus'] ?? data['data']?['items']; + + if (rawItems != null && rawItems is List) { + menuItems = rawItems.map((item) => MenuItem.fromJson(item)).toList(); + } else { + throw Exception('Aucun article trouvé dans la réponse de l\'API.'); + } +} + + } catch (e) { + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text('Erreur: $e'), + backgroundColor: Colors.red, + )); + return; + } + + showDialog( + context: context, + builder: (BuildContext dialogContext) { + return StatefulBuilder(builder: (context, setState) { + return AlertDialog( + title: const Text('Ajouter un article'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + DropdownButton( + hint: const Text("Sélectionnez un article"), + value: selectedMenuItem, + isExpanded: true, + onChanged: (value) { + setState(() { + selectedMenuItem = value; + }); + }, + items: menuItems.map((item) { + return DropdownMenuItem( + value: item, + child: Text(item.nom), + ); + }).toList(), + ), + const SizedBox(height: 12), + TextFormField( + initialValue: quantity.toString(), + keyboardType: TextInputType.number, + decoration: const InputDecoration(labelText: 'Quantité'), + onChanged: (val) { + quantity = int.tryParse(val) ?? 1; + }, + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(dialogContext), + child: const Text('Annuler'), + ), + ElevatedButton( + onPressed: () async { + if (selectedMenuItem == null || quantity < 1) return; + final body = { + 'menu_id': selectedMenuItem!.id, + 'quantite': quantity, + 'commande_id': order.id, + 'table_id': order.tableId, + }; + print(body); + + final response = await http.post( + Uri.parse('https://restaurant.careeracademy.mg/api/commandes/${order.id}/items'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode(body), + ); + // print(response.body); + + if (response.statusCode == 200 || response.statusCode == 201) { + Navigator.pop(dialogContext); + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text("Article ajouté à la commande"), + backgroundColor: Colors.green, + )); + } else { + Navigator.pop(dialogContext); + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text("Erreur ajout article: ${response.body}"), + backgroundColor: Colors.red, + )); + } + }, + child: const Text('Ajouter'), + ) + ], + ); + }); + }, + ); +} + +class MenuItem { + final int id; + final String nom; + + MenuItem({required this.id, required this.nom}); + + factory MenuItem.fromJson(Map json) { + return MenuItem( + id: json['id'], + nom: json['nom'], + ); + } +} + + // Updated Order model to include items class Order { final int id;