import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; // Ajoutez cet import pour la page panier import 'cart_page.dart'; // Assurez-vous que le fichier cart_page.dart est dans le même dossier class MenuPage extends StatefulWidget { final int tableId; final int personne; const MenuPage({super.key, required this.tableId, required this.personne}); @override State createState() => _MenuPageState(); } class _MenuPageState extends State { int? _selectedCategory; List _categories = []; List _menus = []; final List _cart = []; @override void initState() { super.initState(); fetchCategories(); } Future fetchCategories() async { try { final url = Uri.parse( "https://restaurant.careeracademy.mg/api/menu-categories", ); final response = await http.get(url); if (response.statusCode == 200) { final jsonResponse = json.decode(response.body); final categoriesList = (jsonResponse['data']?['categories'] ?? []) as List; setState(() { _categories = categoriesList; if (_categories.isNotEmpty) { _selectedCategory = _categories[0]['id']; fetchMenus(_selectedCategory!); } }); } else { if (kDebugMode) { print("Erreur API catégories: ${response.statusCode}"); } } } catch (e) { if (kDebugMode) { print("Exception fetchCategories: $e"); } } } Future fetchMenus(int categoryId) async { try { final url = Uri.parse( "https://restaurant.careeracademy.mg/api/menus/category/$categoryId?disponible=true", ); final response = await http.get(url); if (response.statusCode == 200) { final jsonResponse = json.decode(response.body); final List menusList = jsonResponse is List ? jsonResponse : (jsonResponse['data'] ?? []); setState(() { _menus = menusList; }); } else { if (kDebugMode) { print("Erreur API menus: ${response.statusCode}"); } } } catch (e) { if (kDebugMode) { print("Exception fetchMenus: $e"); } } } void changeCategory(int id) { setState(() { _selectedCategory = id; _menus = []; }); fetchMenus(id); } void addToCart(dynamic item, int quantity, String notes) { setState(() { for (int i = 0; i < quantity; i++) { Map cartItem = Map.from(item); if (notes.isNotEmpty) { cartItem['notes'] = notes; } _cart.add(cartItem); } }); } void showAddToCartModal(dynamic item) { showDialog( context: context, builder: (BuildContext context) { return AddToCartModal(item: item, onAddToCart: addToCart); }, ); } // Fonction pour naviguer vers la page panier void _navigateToCart() { Navigator.push( context, MaterialPageRoute( builder: (context) => CartPage( tableId: widget.tableId, personne: widget.personne, cartItems: List.from( _cart, ), // Copie de la liste pour éviter les modifications ), ), ).then((_) { // Optionnel: actualiser le panier au retour de la page panier // Vous pourriez implémenter une logique pour synchroniser les modifications }); } /// Conversion sécurisée du prix en string avec 2 décimales String formatPrix(dynamic prix) { if (prix == null) return ""; double? val; if (prix is num) { val = prix.toDouble(); } else if (prix is String) { val = double.tryParse(prix); } return val != null ? val.toStringAsFixed(2) : ""; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("Menu"), actions: [ IconButton( onPressed: () { if (_selectedCategory != null) fetchMenus(_selectedCategory!); }, icon: const Icon(Icons.refresh), ), ], ), body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.all(8.0), child: Text( "Table ${widget.tableId} • ${widget.personne} personne${widget.personne > 1 ? 's' : ''}", style: const TextStyle(fontSize: 16), ), ), if (_categories.isNotEmpty) Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: _categories.map((cat) { return buildCategoryButton(cat['nom'], cat['id']); }).toList(), ) else const Center(child: CircularProgressIndicator()), Expanded( child: _menus.isNotEmpty ? ListView.builder( itemCount: _menus.length, itemBuilder: (context, index) { final item = _menus[index]; return Card( margin: const EdgeInsets.all(8), child: ListTile( onTap: () => showAddToCartModal( item, ), // Clic sur tout l'item leading: Container( width: 60, height: 60, decoration: BoxDecoration( color: Colors.green[100], borderRadius: BorderRadius.circular(8), ), child: Icon( Icons.restaurant_menu, color: Colors.green[700], size: 30, ), ), title: Text( item['nom'] ?? 'Nom non disponible', style: const TextStyle( fontWeight: FontWeight.bold, ), ), subtitle: Text(item['commentaire'] ?? ''), trailing: Text( "${formatPrix(item['prix'])} MGA", style: TextStyle( color: Colors.green[700], fontWeight: FontWeight.bold, fontSize: 16, ), ), ), ); }, ) : const Center(child: Text("Aucun menu disponible")), ), Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 12), color: Colors.green[700], child: Center( child: TextButton( onPressed: _navigateToCart, // Navigation vers la page panier child: Text( "Voir le panier (${_cart.length})", style: const TextStyle(color: Colors.white, fontSize: 16), ), ), ), ), ], ), ); } Widget buildCategoryButton(String label, int id) { final selected = _selectedCategory == id; return Expanded( child: GestureDetector( onTap: () => changeCategory(id), child: Container( padding: const EdgeInsets.symmetric(vertical: 10), color: selected ? Colors.grey[300] : Colors.grey[100], child: Center( child: Text( label, style: TextStyle( fontWeight: selected ? FontWeight.bold : FontWeight.normal, ), ), ), ), ), ); } } // Modal pour ajouter au panier class AddToCartModal extends StatefulWidget { final dynamic item; final Function(dynamic, int, String) onAddToCart; const AddToCartModal({ super.key, required this.item, required this.onAddToCart, }); @override State createState() => _AddToCartModalState(); } class _AddToCartModalState extends State { int _quantity = 1; final TextEditingController _notesController = TextEditingController(); String formatPrix(dynamic prix) { if (prix == null) return "0.00"; double? val; if (prix is num) { val = prix.toDouble(); } else if (prix is String) { val = double.tryParse(prix); } return val != null ? val.toStringAsFixed(2) : "0.00"; } double calculateTotal() { double prix = 0.0; if (widget.item['prix'] is num) { prix = widget.item['prix'].toDouble(); } else if (widget.item['prix'] is String) { prix = double.tryParse(widget.item['prix']) ?? 0.0; } return prix * _quantity; } @override Widget build(BuildContext context) { return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: Container( padding: const EdgeInsets.all(20), constraints: const BoxConstraints(maxWidth: 400), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( widget.item['nom'] ?? 'Menu', style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), IconButton( onPressed: () => Navigator.of(context).pop(), icon: const Icon(Icons.close), padding: EdgeInsets.zero, constraints: const BoxConstraints(), ), ], ), const SizedBox(height: 16), // Image placeholder Container( width: double.infinity, height: 150, decoration: BoxDecoration( color: Colors.green[100], borderRadius: BorderRadius.circular(12), ), child: Icon( Icons.restaurant_menu, size: 60, color: Colors.green[700], ), ), const SizedBox(height: 16), // Description if (widget.item['commentaire'] != null && widget.item['commentaire'].toString().isNotEmpty) Text( widget.item['commentaire'], style: TextStyle(fontSize: 14, color: Colors.grey[600]), ), const SizedBox(height: 16), // Prix unitaire Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( "Prix unitaire", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), ), Text( "${formatPrix(widget.item['prix'])} MGA", style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.green[700], ), ), ], ), const SizedBox(height: 20), // Quantité const Text( "Quantité", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), ), const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ IconButton( onPressed: _quantity > 1 ? () { setState(() { _quantity--; }); } : null, icon: const Icon(Icons.remove), style: IconButton.styleFrom( backgroundColor: Colors.grey[200], ), ), const SizedBox(width: 20), Text( _quantity.toString(), style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(width: 20), IconButton( onPressed: () { setState(() { _quantity++; }); }, icon: const Icon(Icons.add), style: IconButton.styleFrom( backgroundColor: Colors.grey[200], ), ), ], ), const SizedBox(height: 20), // Notes const Text( "Notes (optionnel)", style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), ), const SizedBox(height: 8), TextField( controller: _notesController, maxLines: 3, decoration: InputDecoration( hintText: "Commentaires spéciaux (sans oignons, bien cuit...)", border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), contentPadding: const EdgeInsets.all(12), ), ), const SizedBox(height: 24), // Total et bouton d'ajout Container( width: double.infinity, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(12), ), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( "Total", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), Text( "${calculateTotal().toStringAsFixed(2)} MGA", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.green[700], ), ), ], ), const SizedBox(height: 16), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () { widget.onAddToCart( widget.item, _quantity, _notesController.text, ); Navigator.of(context).pop(); // Afficher un snackbar de confirmation ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( "${widget.item['nom']} ajouté au panier", ), backgroundColor: Colors.green, duration: const Duration(seconds: 2), ), ); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.green[700], foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: const Text( "Ajouter au panier", style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), ), ], ), ), ], ), ), ); } }