diff --git a/lib/layouts/main_layout.dart b/lib/layouts/main_layout.dart index 8bfaa6c..d13b67b 100644 --- a/lib/layouts/main_layout.dart +++ b/lib/layouts/main_layout.dart @@ -6,11 +6,7 @@ class MainLayout extends StatefulWidget { final Widget child; final String? currentRoute; - const MainLayout({ - super.key, - required this.child, - this.currentRoute, - }); + const MainLayout({super.key, required this.child, this.currentRoute}); @override _MainLayoutState createState() => _MainLayoutState(); @@ -36,7 +32,7 @@ class _MainLayoutState extends State { return 2; case '/menu': return 3; - case '/cart': + case '/plats': return 4; default: return 0; @@ -63,7 +59,7 @@ class _MainLayoutState extends State { route = '/menu'; break; case 4: - route = '/cart'; + route = '/plats'; break; default: route = '/tables'; @@ -91,13 +87,14 @@ class _MainLayoutState extends State { ], ), // Show mobile navigation on smaller screens - bottomNavigationBar: isDesktop - ? null - : MobileBottomNavigation( - currentRoute: widget.currentRoute ?? '/tables', - selectedIndex: _selectedIndex, - onItemTapped: _onItemTapped, - ), + bottomNavigationBar: + isDesktop + ? null + : MobileBottomNavigation( + currentRoute: widget.currentRoute ?? '/tables', + selectedIndex: _selectedIndex, + onItemTapped: _onItemTapped, + ), ); } -} \ No newline at end of file +} diff --git a/lib/main.dart b/lib/main.dart index e7dda84..ab944ab 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:itrimobe/pages/menu.dart'; import 'layouts/main_layout.dart'; import 'pages/tables.dart'; import 'pages/categorie.dart'; import 'pages/commandes_screen.dart'; import 'pages/login_screen.dart'; +import 'pages/menus_screen.dart'; void main() { runApp(const MyApp()); @@ -25,24 +25,29 @@ class MyApp extends StatelessWidget { initialRoute: '/login', routes: { '/login': (context) => const LoginScreen(), - '/tables': (context) => const MainLayout( + '/tables': + (context) => const MainLayout( currentRoute: '/tables', child: TablesScreen(), ), - '/categories': (context) => const MainLayout( + '/categories': + (context) => const MainLayout( currentRoute: '/categories', child: CategoriesPage(), ), - '/commandes': (context) => const MainLayout( + '/commandes': + (context) => const MainLayout( currentRoute: '/commandes', child: OrdersManagementScreen(), ), // MODIFICATION : Route simple pour le menu - // '/menu': (context) => const MainLayout( - // currentRoute: '/menu', - // child: MenuPage(), // Pas de paramètres requis maintenant - // ), + '/plats': + (context) => const MainLayout( + currentRoute: '/plats', + child: + PlatsManagementScreen(), // Pas de paramètres requis maintenant + ), }, ); } -} \ No newline at end of file +} diff --git a/lib/pages/PlatEdit_screen.dart b/lib/pages/PlatEdit_screen.dart new file mode 100644 index 0000000..69cf6c5 --- /dev/null +++ b/lib/pages/PlatEdit_screen.dart @@ -0,0 +1,357 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; + +// Use your actual models and http logic +class MenuPlat { + final int id; + final String nom; + final String? commentaire; + final double prix; + final String? imageUrl; + final bool disponible; + final List categories; // Default to true + MenuPlat({ + required this.id, + required this.nom, + this.commentaire, + required this.prix, + this.imageUrl, + required this.disponible, + required this.categories, + }); +} + +class MenuCategory { + final int id; + final String nom; + + MenuCategory({required this.id, required this.nom}); + + factory MenuCategory.fromJson(Map json) { + return MenuCategory(id: json['id'], nom: json['nom']); + } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is MenuCategory && + runtimeType == other.runtimeType && + id == other.id; + + @override + int get hashCode => id.hashCode; +} + +class PlatEditPage extends StatefulWidget { + final List categories; + final MenuPlat? plat; // if present = edit, else create + final Function()? onSaved; // callback when saved + + const PlatEditPage({ + super.key, + required this.categories, + this.plat, + this.onSaved, + }); + + @override + State createState() => _PlatEditPageState(); +} + +class _PlatEditPageState extends State { + final _formKey = GlobalKey(); + late TextEditingController nomCtrl, descCtrl, prixCtrl, imgCtrl; + late bool disponible; + late List selectedCategories; + + @override + void initState() { + super.initState(); + nomCtrl = TextEditingController(text: widget.plat?.nom ?? ""); + descCtrl = TextEditingController(text: widget.plat?.commentaire ?? ""); + prixCtrl = TextEditingController( + text: widget.plat != null ? widget.plat!.prix.toStringAsFixed(2) : "", + ); + imgCtrl = TextEditingController(text: widget.plat?.imageUrl ?? ""); + disponible = widget.plat?.disponible ?? true; + selectedCategories = widget.plat?.categories.toList() ?? []; + } + + @override + void dispose() { + nomCtrl.dispose(); + descCtrl.dispose(); + prixCtrl.dispose(); + imgCtrl.dispose(); + super.dispose(); + } + + void submit() async { + if (!_formKey.currentState!.validate()) return; + if (selectedCategories.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Sélectionnez au moins une catégorie.')), + ); + return; + } + + // Build request data + final body = { + "nom": nomCtrl.text, + "commentaire": descCtrl.text, + "prix": double.tryParse(prixCtrl.text) ?? 0, + "categories": selectedCategories.map((c) => c.id).toList(), + "disponible": disponible, + "categorie_id": + selectedCategories.isNotEmpty + ? selectedCategories.first.id + : null, // Use first category if available + }; + + try { + final res = await http.post( + Uri.parse( + 'https://restaurant.careeracademy.mg/api/menus', + ), // your API URL here + headers: {"Content-Type": "application/json"}, + body: json.encode(body), + ); + + if (res.statusCode == 200 || res.statusCode == 201) { + widget.onSaved?.call(); + Navigator.pop(context); + } else { + // show error + print('Error creating plat: ${res.body}\n here is body: $body'); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Erreur lors de la création du plat')), + ); + } + } catch (e) { + // show error + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('Erreur réseau: $e'))); + } + } + + @override + Widget build(BuildContext context) { + final isEdit = widget.plat != null; + return Scaffold( + appBar: AppBar( + title: Text(isEdit ? 'Éditer le plat' : 'Nouveau plat'), + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () => Navigator.pop(context), + ), + centerTitle: true, + elevation: 0, + backgroundColor: Colors.white, + foregroundColor: Colors.black, + ), + body: Center( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 550), + child: Card( + elevation: 2, + margin: const EdgeInsets.all(32), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: const EdgeInsets.all(28), + child: Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Informations du plat', + style: TextStyle( + fontSize: 21, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 14), + TextFormField( + controller: nomCtrl, + decoration: const InputDecoration( + labelText: "Nom du plat *", + hintText: "Ex: Steak frites", + ), + validator: + (v) => + (v == null || v.isEmpty) ? "Obligatoire" : null, + ), + const SizedBox(height: 13), + TextFormField( + controller: descCtrl, + decoration: const InputDecoration( + labelText: "Description", + hintText: "Description détaillée du plat...", + ), + maxLines: 3, + ), + const SizedBox(height: 13), + Row( + children: [ + Expanded( + child: TextFormField( + controller: prixCtrl, + decoration: const InputDecoration( + labelText: "Prix (MGA) *", + ), + validator: + (v) => + (v == null || + v.isEmpty || + double.tryParse(v) == null) + ? "Obligatoire" + : null, + keyboardType: const TextInputType.numberWithOptions( + decimal: true, + ), + ), + ), + const SizedBox(width: 16), + // Multi-category picker + Expanded( + child: InkWell( + onTap: () async { + final result = await showDialog( + context: context, + builder: (context) { + MenuCategory? temp = + selectedCategories.isNotEmpty + ? selectedCategories.first + : null; + + return StatefulBuilder( + builder: (context, setStateDialog) { + return AlertDialog( + title: const Text("Catégories"), + content: SizedBox( + width: 330, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: + widget.categories.map((cat) { + return RadioListTile< + MenuCategory + >( + value: cat, + groupValue: temp, + title: Text(cat.nom), + onChanged: (value) { + setStateDialog(() { + temp = value; + }); + }, + ); + }).toList(), + ), + ), + ), + actions: [ + TextButton( + onPressed: () { + if (temp != null) { + Navigator.pop(context, temp); + } else { + Navigator.pop(context, null); + } + }, + child: const Text('OK'), + ), + ], + ); + }, + ); + }, + ); + + if (result != null) { + setState(() => selectedCategories = [result]); + } + }, + child: InputDecorator( + decoration: const InputDecoration( + labelText: "Catégorie *", + border: OutlineInputBorder(gapPadding: 2), + ), + child: + selectedCategories.isEmpty + ? const Text( + "Aucune sélection", + style: TextStyle(color: Colors.grey), + ) + : Text(selectedCategories.first.nom), + ), + ), + ), + ], + ), + // const SizedBox(height: 13), + // TextFormField( + // controller: imgCtrl, + // decoration: const InputDecoration( + // labelText: "URL de l'image", + // hintText: "/api/placeholder/300/200", + // ), + // ), + const SizedBox(height: 13), + Row( + children: [ + Switch( + value: disponible, + activeColor: Colors.green, + onChanged: (v) => setState(() => disponible = v), + ), + const SizedBox(width: 7), + const Text('Plat disponible'), + ], + ), + const SizedBox(height: 22), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + OutlinedButton( + onPressed: () => Navigator.pop(context), + child: const Text( + "Annuler", + style: TextStyle(color: Colors.black), + ), + ), + const SizedBox(width: 18), + ElevatedButton.icon( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green, + ), + onPressed: submit, + icon: const Icon( + Icons.save, + size: 18, + color: Colors.white, + ), + label: Text( + isEdit ? "Enregistrer" : "Ajouter", + style: const TextStyle(color: Colors.white), + ), + ), + ], + ), + ], + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/commande_item_screen.dart b/lib/pages/commande_item_screen.dart new file mode 100644 index 0000000..1d0129d --- /dev/null +++ b/lib/pages/commande_item_screen.dart @@ -0,0 +1,695 @@ +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; + +// Import de la page de validation (à ajuster selon votre structure de dossiers) +import 'commande_item_validation.dart'; + +class AddItemsToOrderPage extends StatefulWidget { + final int commandeId; + final String numeroCommande; + + const AddItemsToOrderPage({ + Key? key, + required this.commandeId, + required this.numeroCommande + }) : super(key: key); + + @override + State createState() => _AddItemsToOrderPageState(); +} + +class _AddItemsToOrderPageState extends State { + int? _selectedCategory; + List _categories = []; + List _menus = []; + List _cart = []; + bool _isLoading = false; + Map? _commandeDetails; + + @override + void initState() { + super.initState(); + fetchCategories(); + fetchCommandeDetails(); + } + + 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 { + print("Erreur API catégories: ${response.statusCode}"); + } + } catch (e) { + 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 { + print("Erreur API menus: ${response.statusCode}"); + } + } catch (e) { + print("Exception fetchMenus: $e"); + } + } + + Future fetchCommandeDetails() async { + try { + final url = Uri.parse("https://restaurant.careeracademy.mg/api/commandes/${widget.commandeId}"); + final response = await http.get(url); + + if (response.statusCode == 200) { + final jsonResponse = json.decode(response.body); + final data = jsonResponse['data']; + print(data); + + setState(() { + _commandeDetails = data; + }); + } else { + print("Erreur chargement commande: ${response.statusCode}"); + } + } catch (e) { + print("Exception fetchCommandeDetails: $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); + }, + ); + } + + // NOUVELLE FONCTION: Navigation vers la page de validation + Future _navigateToValidationPage() async { + if (_cart.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Aucun article à ajouter'), + backgroundColor: Colors.orange, + ), + ); + return; + } + + // Naviguer vers la page de validation + final result = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => ValidateAddItemsPage( + commandeId: widget.commandeId, + numeroCommande: widget.numeroCommande, + newItems: _cart, + commandeDetails: _commandeDetails, + ), + ), + ); + + // Si l'ajout a été effectué avec succès, retourner à la page précédente + if (result == true) { + Navigator.of(context).pop(true); + } + } + + /// 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("Ajouter des articles"), + backgroundColor: Colors.white, + elevation: 1, + actions: [ + IconButton( + onPressed: () { + if (_selectedCategory != null) fetchMenus(_selectedCategory!); + }, + icon: const Icon(Icons.refresh), + ), + ], + ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Header avec info commande + Container( + width: double.infinity, + padding: const EdgeInsets.all(16.0), + color: Colors.grey[50], + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Commande: ${widget.numeroCommande}", + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + const SizedBox(height: 4), + const Text( + "Sélectionnez les articles à ajouter", + style: TextStyle( + fontSize: 14, + color: Colors.grey, + ), + ), + const SizedBox(height: 12), + + if (_commandeDetails != null && _commandeDetails!['items'] != null) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Articles déjà commandés:", + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.grey, + ), + ), + const SizedBox(height: 8), + ..._commandeDetails!['items'].map((item) { + final nom = item['menu_nom'] ?? 'Inconnu'; + final quantite = item['quantite'] ?? 1; + final description = item['menu_description'] ?? ''; + // Correction: utiliser 'menu_prix_actuel' pour afficher le prix + final prix = item['menu_prix_actuel'] ?? item['prix_unitaire'] ?? 0; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + nom, + style: const TextStyle(fontSize: 14) + ), + ), + Text( + "x$quantite - ${formatPrix(prix)} MGA", + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500) + ), + ], + ), + if (description.isNotEmpty) + Text( + description, + style: const TextStyle(fontSize: 12, color: Colors.grey), + ), + const Divider(height: 8), + ], + ), + ); + }).toList(), + ], + ) + else + const Text( + "Aucun article encore commandé.", + style: TextStyle(fontSize: 14, color: Colors.grey), + ), + ], + ), + ), + + // Catégories + if (_categories.isNotEmpty) + Container( + color: Colors.white, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: _categories.map((cat) { + return buildCategoryButton(cat['nom'], cat['id']); + }).toList(), + ), + ) + else + const Center(child: CircularProgressIndicator()), + + // Liste des menus + Expanded( + child: _menus.isNotEmpty + ? ListView.builder( + itemCount: _menus.length, + itemBuilder: (context, index) { + final item = _menus[index]; + return Card( + margin: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + child: ListTile( + onTap: () => showAddToCartModal(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")), + ), + + // Bouton pour valider l'ajout - MODIFIÉ + Container( + width: double.infinity, + padding: const EdgeInsets.all(16), + color: Colors.white, + child: Column( + children: [ + if (_cart.isNotEmpty) + Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 8), + child: Text( + "${_cart.length} article(s) sélectionné(s)", + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 14, + color: Colors.grey, + ), + ), + ), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _cart.isEmpty ? null : _navigateToValidationPage, // CHANGÉ ICI + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green[700], + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 16), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + disabledBackgroundColor: Colors.grey[300], // Ajouté pour le style désactivé + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + _cart.isEmpty ? Icons.shopping_cart_outlined : Icons.preview, + size: 20, + ), + const SizedBox(width: 8), + Text( + _cart.isEmpty + ? "Sélectionner des articles" + : "Valider l'ajout (${_cart.length})", // CHANGÉ LE TEXTE + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget buildCategoryButton(String label, int id) { + final selected = _selectedCategory == id; + return Expanded( + child: GestureDetector( + onTap: () => changeCategory(id), + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: selected ? Colors.green[700] : Colors.grey[100], + border: Border( + bottom: BorderSide( + color: selected ? Colors.green[700]! : Colors.transparent, + width: 2, + ), + ), + ), + child: Center( + child: Text( + label, + style: TextStyle( + fontWeight: selected ? FontWeight.bold : FontWeight.normal, + color: selected ? Colors.white : Colors.black87, + ), + ), + ), + ), + ), + ); + } +} + +// Modal pour ajouter au panier (même que MenuPage) +class AddToCartModal extends StatefulWidget { + final dynamic item; + final Function(dynamic, int, String) onAddToCart; + + const AddToCartModal({ + Key? key, + required this.item, + required this.onAddToCart, + }) : super(key: key); + + @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: [ + Expanded( + child: 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, + ), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/pages/commande_item_validation.dart b/lib/pages/commande_item_validation.dart new file mode 100644 index 0000000..b7fa273 --- /dev/null +++ b/lib/pages/commande_item_validation.dart @@ -0,0 +1,727 @@ +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; + +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 + + const ValidateAddItemsPage({ + Key? key, + required this.commandeId, + required this.numeroCommande, + required this.newItems, + required this.commandeDetails, + }) : super(key: key); + + @override + State createState() => _ValidateAddItemsPageState(); +} + +class _ValidateAddItemsPageState extends State { + List _newCartItems = []; + bool _isValidating = false; + + @override + void initState() { + super.initState(); + _processNewItems(); + } + + void _processNewItems() { + // Grouper les nouveaux articles identiques + Map groupedItems = {}; + + for (var item in widget.newItems) { + String key = "${item['id']}_${item['notes'] ?? ''}"; + + if (groupedItems.containsKey(key)) { + groupedItems[key]!.quantity++; + } else { + groupedItems[key] = CartItemModel( + id: item['id'], + nom: item['nom'] ?? 'Article', + prix: _parsePrice(item['prix']), + quantity: 1, + notes: item['notes'] ?? '', + ); + } + } + + setState(() { + _newCartItems = groupedItems.values.toList(); + }); + } + + double _parsePrice(dynamic prix) { + if (prix == null) return 0.0; + if (prix is num) return prix.toDouble(); + if (prix is String) return double.tryParse(prix) ?? 0.0; + return 0.0; + } + + void _updateQuantity(int index, int newQuantity) { + setState(() { + if (newQuantity > 0) { + _newCartItems[index].quantity = newQuantity; + } else { + _newCartItems.removeAt(index); + } + }); + } + + void _removeItem(int index) { + setState(() { + _newCartItems.removeAt(index); + }); + } + + // Calculer le total des nouveaux articles + double _calculateNewItemsTotal() { + return _newCartItems.fold( + 0.0, + (sum, item) => sum + (item.prix * item.quantity), + ); + } + + // Calculer le total des articles existants + double _calculateExistingItemsTotal() { + 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']); + int quantite = item['quantite'] ?? 1; + total += prix * quantite; + } + return total; + } + + // Calculer le total général + double _calculateGrandTotal() { + return _calculateExistingItemsTotal() + _calculateNewItemsTotal(); + } + + int _getTotalNewArticles() { + return _newCartItems.fold(0, (sum, item) => sum + item.quantity); + } + + int _getTotalExistingArticles() { + if (widget.commandeDetails == null || widget.commandeDetails!['items'] == null) { + return 0; + } + return widget.commandeDetails!['items'].fold(0, (sum, item) => sum + (item['quantite'] ?? 1)); + } + + void _showConfirmationDialog() { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: Text( + 'Confirmer l\'ajout', + style: TextStyle(fontWeight: FontWeight.bold), + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Êtes-vous sûr de vouloir ajouter ces articles à la commande ?'), + SizedBox(height: 16), + Text( + 'Récapitulatif:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + Text('• Commande: ${widget.numeroCommande}'), + Text('• Nouveaux articles: ${_getTotalNewArticles()}'), + Text('• Nouveau total: ${_calculateGrandTotal().toStringAsFixed(2)} MGA'), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text('Annuler', style: TextStyle(color: Colors.grey[600])), + ), + ElevatedButton( + 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'), + ), + ], + ); + }, + ); + } + + Future _validateAddItems() async { + if (_newCartItems.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Aucun article à ajouter'), + backgroundColor: Colors.orange, + ), + ); + return; + } + + setState(() { + _isValidating = true; + }); + + 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'); + + if (response.statusCode == 200 || response.statusCode == 201) { + final responseData = json.decode(response.body); + print(responseData); + + if (responseData['success'] == true) { + _showSuccessDialog(); + } else { + throw Exception('API returned success: false'); + } + } else { + throw Exception('Failed to add items: ${response.statusCode}'); + } + } catch (e) { + _showErrorDialog('Erreur lors de l\'ajout: $e'); + } finally { + setState(() { + _isValidating = false; + }); + } + } + + void _showSuccessDialog() { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext context) { + return AlertDialog( + title: Row( + children: [ + Icon(Icons.check_circle, color: Colors.green[700]), + SizedBox(width: 8), + Text('Articles ajoutés'), + ], + ), + content: Text( + 'Les nouveaux articles ont été ajoutés à la commande avec succès !', + ), + actions: [ + ElevatedButton( + onPressed: () { + // Retourner à la page précédente avec un indicateur de succès + Navigator.of(context).pop(); + Navigator.of(context).pop(true); + }, + child: Text('OK'), + ), + ], + ); + }, + ); + } + + void _showErrorDialog(String message) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Row( + children: [ + Icon(Icons.error, color: Colors.red), + SizedBox(width: 8), + Text('Erreur'), + ], + ), + content: Text(message), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: Text('OK'), + ), + ], + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.grey[50], + appBar: AppBar( + backgroundColor: Colors.white, + elevation: 0, + leading: IconButton( + onPressed: () => Navigator.pop(context), + icon: Icon(Icons.arrow_back, color: Colors.black), + ), + title: Text( + 'Valider l\'ajout', + style: TextStyle( + color: Colors.black, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text( + 'Annuler', + style: TextStyle(color: Colors.black, fontSize: 16), + ), + ), + SizedBox(width: 16), + ], + ), + body: Column( + children: [ + // Header avec infos commande et totaux + Container( + width: double.infinity, + padding: EdgeInsets.all(20), + color: Colors.white, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Commande: ${widget.numeroCommande}', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + 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), + ], + + Text( + 'Nouveaux articles à ajouter: ${_getTotalNewArticles()}', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.green[700], + ), + ), + ], + ), + ), + + // 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: 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, + ), + 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}', + 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 + Text( + '${(item.prix * item.quantity).toStringAsFixed(2)} MGA', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.green[700], + ), + ), + ], + ), + ], + ), + ); + }, + ), + ), + + // Récapitulatif final + 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 final', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + ), + SizedBox(height: 16), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Articles existants:', style: TextStyle(fontSize: 16)), + Text( + _getTotalExistingArticles().toString(), + style: TextStyle(fontSize: 16), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Sous-total existant:', style: TextStyle(fontSize: 16)), + Text( + '${_calculateExistingItemsTotal().toStringAsFixed(2)} MGA', + style: TextStyle(fontSize: 16), + ), + ], + ), + SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Nouveaux articles:', style: TextStyle(fontSize: 16, color: Colors.green[700])), + Text( + _getTotalNewArticles().toString(), + style: TextStyle(fontSize: 16, color: Colors.green[700]), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + 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]), + ), + ], + ), + SizedBox(height: 16), + Divider(), + SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Total général:', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + Text( + '${_calculateGrandTotal().toStringAsFixed(2)} MGA', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.green[700], + ), + ), + ], + ), + SizedBox(height: 20), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: _newCartItems.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], + ), + 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( + 'Ajout en cours...', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ] else ...[ + Icon(Icons.add_shopping_cart, size: 20), + SizedBox(width: 8), + Text( + _newCartItems.isEmpty + ? 'Aucun article à ajouter' + : 'Ajouter à la commande (${_getTotalNewArticles()})', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ], + ], + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} + +// Réutiliser la même classe CartItemModel +class CartItemModel { + final int id; + final String nom; + final double prix; + int quantity; + final String notes; + + CartItemModel({ + required this.id, + required this.nom, + required this.prix, + 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 8828413..25d5980 100644 --- a/lib/pages/commandes_screen.dart +++ b/lib/pages/commandes_screen.dart @@ -4,6 +4,8 @@ import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; +import 'commande_item_screen.dart'; + class OrdersManagementScreen extends StatefulWidget { const OrdersManagementScreen({super.key}); @@ -620,12 +622,23 @@ class OrderCard extends StatelessWidget { ), ), 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), - ), + 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}', + ), + ), + ); + }, + ), + ], ), const SizedBox(height: 8), @@ -817,133 +830,6 @@ 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 @@ -1018,6 +904,8 @@ class Order { items: orderItems, ); } + + get numero => null; } class OrderItem { diff --git a/lib/pages/menus_screen.dart b/lib/pages/menus_screen.dart index bb78a2c..283101e 100644 --- a/lib/pages/menus_screen.dart +++ b/lib/pages/menus_screen.dart @@ -1,500 +1,555 @@ -// TODO Implement this library. +import 'dart:convert'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; -import 'dart:convert'; +import './PlatEdit_screen.dart'; -class MenuPage extends StatefulWidget { - final int? tableId; - final int? personne; - - const MenuPage({Key? key, required this.tableId, required this.personne}) - : super(key: key); +class PlatsManagementScreen extends StatefulWidget { + const PlatsManagementScreen({super.key}); @override - State createState() => _MenuPageState(); + State createState() => _PlatsManagementScreenState(); } -class _MenuPageState extends State { - int? _selectedCategory; - List _categories = []; - List _menus = []; - List _cart = []; +class _PlatsManagementScreenState extends State { + final _baseUrl = 'https://restaurant.careeracademy.mg/api'; + List plats = []; + List categories = []; + String search = ''; + int? selectedCategoryId; + String disponibilite = ''; + bool isLoading = true; + + final TextEditingController _searchController = TextEditingController(); @override void initState() { super.initState(); - fetchCategories(); + _fetchCategories(); + _fetchPlats(); } - Future fetchCategories() async { + // Fetch categories + 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 { - print("Erreur API catégories: ${response.statusCode}"); - } - } catch (e) { - print("Exception fetchCategories: $e"); - } + final res = await http.get(Uri.parse('$_baseUrl/menu-categories')); + final data = json.decode(res.body); + setState(() { + categories = + (data['data']['categories'] as List) + .map((item) => MenuCategory.fromJson(item)) + .toList(); + }); + } catch (_) {} } - Future fetchMenus(int categoryId) async { + // Fetch plats + Future _fetchPlats() async { + setState(() => isLoading = true); try { - final url = Uri.parse( - "https://restaurant.careeracademy.mg/api/menus/category/$categoryId?disponible=true", + final uri = Uri.parse('$_baseUrl/menus').replace( + queryParameters: { + if (search.isNotEmpty) 'search': search, + if (selectedCategoryId != null) + 'category_id': selectedCategoryId.toString(), + }, ); - 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 { - print("Erreur API menus: ${response.statusCode}"); + final res = await http.get(uri); + final data = json.decode(res.body); + setState(() { + plats = + (data['data']['menus'] as List) + .map((item) => MenuPlat.fromJson(item)) + .toList(); + isLoading = false; + }); + if (kDebugMode) { + // print('fetched plat here: $plats items'); } } catch (e) { - print("Exception fetchMenus: $e"); + setState(() => isLoading = false); + if (kDebugMode) print("Error: $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); - } - }); + Future _deletePlat(int id) async { + final res = await http.delete(Uri.parse('$_baseUrl/menus/$id')); + if (res.statusCode == 200) + _fetchPlats(); + else { + if (kDebugMode) print("Error deleting plat: ${res.body}"); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Des commandes sont liées à ce plat.')), + ); + } } - void showAddToCartModal(dynamic item) { + void _showEditPlatDialog(MenuPlat plat) { showDialog( context: context, - builder: (BuildContext context) { - return AddToCartModal(item: item, onAddToCart: addToCart); - }, + builder: + (_) => EditPlatDialog( + plat: plat, + onPlatUpdated: _fetchPlats, + categories: categories, + ), ); } - /// 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( + backgroundColor: const Color(0xfffcfbf9), appBar: AppBar( - title: Text("Menu"), + elevation: 0, + title: const Text( + 'Gestion des plats', + style: TextStyle(fontWeight: FontWeight.bold), + ), + backgroundColor: Colors.transparent, + foregroundColor: Colors.black87, actions: [ - IconButton( - onPressed: () { - if (_selectedCategory != null) fetchMenus(_selectedCategory!); - }, - icon: Icon(Icons.refresh), + Padding( + padding: const EdgeInsets.only(right: 16.0), + child: ElevatedButton.icon( + onPressed: + () => navigateToCreate( + context, + categories, + () => {_fetchPlats()}, + ), + icon: const Icon(Icons.add, size: 18), + label: const Text('Nouveau plat'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green[700], + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric( + horizontal: 18, + vertical: 12, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + ), + ), ), ], ), 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: TextStyle(fontSize: 16), + Card( + margin: const EdgeInsets.symmetric(horizontal: 24, vertical: 18), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 18, horizontal: 18), + child: Row( + children: [ + const Icon(Icons.filter_alt_outlined, size: 22), + const SizedBox(width: 10), + Expanded( + child: TextField( + controller: _searchController, + onChanged: (value) { + setState(() => search = value); + _fetchPlats(); + }, + decoration: const InputDecoration( + hintText: "Rechercher un plat...", + border: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(8)), + borderSide: BorderSide.none, + ), + filled: true, + fillColor: Color(0xFFF7F7F7), + isDense: true, + contentPadding: EdgeInsets.symmetric( + vertical: 10, + horizontal: 14, + ), + ), + ), + ), + const SizedBox(width: 10), + // Catégories + DropdownButton( + value: selectedCategoryId, + hint: const Text("Toutes les catégories"), + items: [ + const DropdownMenuItem( + value: null, + child: Text("Toutes les catégories"), + ), + ...categories.map( + (cat) => DropdownMenuItem( + value: cat.id, + child: Text(cat.nom), + ), + ), + ], + onChanged: (v) { + setState(() => selectedCategoryId = v); + _fetchPlats(); + }, + ), + ], + ), ), ), - if (_categories.isNotEmpty) - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: - _categories.map((cat) { - return buildCategoryButton(cat['nom'], cat['id']); - }).toList(), - ) + if (isLoading) + const Expanded(child: Center(child: CircularProgressIndicator())) else - Center(child: CircularProgressIndicator()), - Expanded( - child: - _menus.isNotEmpty - ? ListView.builder( - itemCount: _menus.length, - itemBuilder: (context, index) { - final item = _menus[index]; - return Card( - margin: 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, + Expanded( + child: ListView.builder( + itemCount: plats.length, + padding: const EdgeInsets.symmetric(horizontal: 24), + itemBuilder: (ctx, i) { + final p = plats[i]; + return Card( + margin: const EdgeInsets.only(bottom: 20), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 16, + horizontal: 18, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 2, + child: Text( + p.nom, + style: const TextStyle( + fontWeight: FontWeight.w600, + fontSize: 18, ), ), - title: Text( - item['nom'] ?? 'Nom non disponible', - style: TextStyle(fontWeight: FontWeight.bold), + ), + Expanded( + flex: 6, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + p.commentaire ?? "", + style: const TextStyle(fontSize: 15), + ), + if (p.ingredients != null && + p.ingredients!.isNotEmpty) + Padding( + padding: const EdgeInsets.symmetric( + vertical: 3.0, + ), + child: Text( + p.ingredients!, + style: const TextStyle( + color: Colors.grey, + fontSize: 13, + ), + ), + ), + Row( + children: [ + if (p.category != null) + CategoryChip( + label: p.category!.nom, + color: Colors.black, + ) + else + const CategoryChip( + label: "Catégorie", + color: Colors.black, + ), + ], + ), + ], ), - subtitle: Text(item['commentaire'] ?? ''), - trailing: Text( - "${formatPrix(item['prix'])} MGA", - style: TextStyle( - color: Colors.green[700], - fontWeight: FontWeight.bold, - fontSize: 16, - ), + ), + Expanded( + flex: 2, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + "${p.prix.toStringAsFixed(2)} MGA", + style: const TextStyle( + fontWeight: FontWeight.bold, + color: Colors.green, + fontSize: 20, + ), + ), + const SizedBox(width: 10), + SizedBox( + height: 38, + width: 38, + child: IconButton( + icon: const Icon( + Icons.edit, + color: Colors.black54, + ), + onPressed: () => _showEditPlatDialog(p), + ), + ), + SizedBox( + height: 38, + width: 38, + child: IconButton( + icon: const Icon( + Icons.delete, + color: Colors.redAccent, + ), + onPressed: () async { + final confirm = await showDialog( + context: context, + builder: + (_) => AlertDialog( + title: const Text( + 'Supprimer ce plat ?', + ), + content: Text( + 'Supprimer ${p.nom} ', + ), + actions: [ + TextButton( + onPressed: + () => Navigator.pop( + context, + false, + ), + child: const Text("Annuler"), + ), + ElevatedButton( + onPressed: + () => Navigator.pop( + context, + true, + ), + style: + ElevatedButton.styleFrom( + backgroundColor: + Colors.red, + ), + child: const Text( + "Supprimer", + style: TextStyle( + color: Colors.white, + ), + ), + ), + ], + ), + ); + if (confirm == true) _deletePlat(p.id); + }, + ), + ), + ], ), ), - ); - }, - ) - : Center(child: Text("Aucun menu disponible")), - ), - Container( - width: double.infinity, - padding: EdgeInsets.symmetric(vertical: 12), - color: Colors.green[700], - child: Center( - child: TextButton( - onPressed: () { - // TODO: Naviguer vers la page panier + ], + ), + ), + ); }, - child: Text( - "Voir le panier (${_cart.length})", - style: 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: 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, - ), - ), - ), - ), - ), +class MenuPlat { + final int id; + final String nom; + final String? commentaire; + final double prix; + final String? ingredients; + final MenuCategory? category; // single category + + MenuPlat({ + required this.id, + required this.nom, + this.commentaire, + required this.prix, + this.ingredients, + this.category, + }); + + factory MenuPlat.fromJson(Map json) { + double parsePrix(dynamic p) { + if (p is int) return p.toDouble(); + if (p is double) return p; + if (p is String) return double.tryParse(p) ?? 0; + return 0; + } + + return MenuPlat( + id: json['id'], + nom: json['nom'], + commentaire: json['commentaire'], + prix: parsePrix(json['prix']), + ingredients: json['ingredients'], + category: + json['category'] != null + ? MenuCategory.fromJson(json['category']) + : null, ); } + + get disponible => null; +} + +class CategoryChip extends StatelessWidget { + final String label; + final Color color; + const CategoryChip({super.key, required this.label, required this.color}); + @override + Widget build(BuildContext context) => Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + // ignore: deprecated_member_use + color: color.withOpacity(0.16), + ), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + child: Text( + label, + style: TextStyle(color: color, fontWeight: FontWeight.w600, fontSize: 13), + ), + ); } -// Modal pour ajouter au panier -class AddToCartModal extends StatefulWidget { - final dynamic item; - final Function(dynamic, int, String) onAddToCart; +Future navigateToCreate( + BuildContext context, + List categories, + Function()? onSaved, +) async { + await Navigator.push( + context, + MaterialPageRoute( + builder: (_) => PlatEditPage(categories: categories, onSaved: onSaved), + ), + ); +} - const AddToCartModal({ - Key? key, - required this.item, - required this.onAddToCart, - }) : super(key: key); +class EditPlatDialog extends StatefulWidget { + final MenuPlat plat; + final List categories; + final VoidCallback onPlatUpdated; + const EditPlatDialog({ + super.key, + required this.plat, + required this.onPlatUpdated, + required this.categories, + }); @override - State createState() => _AddToCartModalState(); + State createState() => _EditPlatDialogState(); } -class _AddToCartModalState extends State { - int _quantity = 1; - final TextEditingController _notesController = TextEditingController(); +class _EditPlatDialogState extends State { + late String nom; + late String commentaire; + late String ingredients; + late double prix; + // late bool disponible; + MenuCategory? cat; + final _formKey = GlobalKey(); - 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"; + @override + void initState() { + super.initState(); + nom = widget.plat.nom; + commentaire = widget.plat.commentaire ?? ''; + ingredients = widget.plat.ingredients ?? ''; + prix = widget.plat.prix; + var disponible = widget.plat.disponible; + // cat = (widget.plat.categories) as MenuCategory?; + cat = widget.plat.category; } - 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; + Future submit() async { + if (!_formKey.currentState!.validate()) return; + final res = await http.put( + Uri.parse( + 'https://restaurant.careeracademy.mg/api/menus/${widget.plat.id}', + ), + headers: {'Content-Type': 'application/json'}, + body: json.encode({ + "nom": nom, + "commentaire": commentaire, + "ingredients": ingredients, + "prix": prix, + "categorie_id": cat?.id, + }), + ); + if (res.statusCode == 200) { + widget.onPlatUpdated(); + // ignore: use_build_context_synchronously + Navigator.pop(context); } - return prix * _quantity; } @override - Widget build(BuildContext context) { - return Dialog( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - child: Container( - padding: EdgeInsets.all(20), - constraints: BoxConstraints(maxWidth: 400), + Widget build(BuildContext context) => AlertDialog( + title: const Text("Éditer le plat"), + content: Form( + key: _formKey, + child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Header - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - widget.item['nom'] ?? 'Menu', - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - IconButton( - onPressed: () => Navigator.of(context).pop(), - icon: Icon(Icons.close), - padding: EdgeInsets.zero, - constraints: BoxConstraints(), - ), - ], - ), - 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], - ), - ), - 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]), - ), - SizedBox(height: 16), - - // Prix unitaire - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - 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], - ), - ), - ], - ), - SizedBox(height: 20), - - // Quantité - Text( - "Quantité", - style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), + TextFormField( + initialValue: nom, + onChanged: (v) => nom = v, + decoration: const InputDecoration(labelText: "Nom"), + validator: (v) => (v?.isEmpty ?? true) ? "Obligatoire" : null, ), - SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - IconButton( - onPressed: - _quantity > 1 - ? () { - setState(() { - _quantity--; - }); - } - : null, - icon: Icon(Icons.remove), - style: IconButton.styleFrom( - backgroundColor: Colors.grey[200], - ), - ), - SizedBox(width: 20), - Text( - _quantity.toString(), - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - SizedBox(width: 20), - IconButton( - onPressed: () { - setState(() { - _quantity++; - }); - }, - icon: Icon(Icons.add), - style: IconButton.styleFrom( - backgroundColor: Colors.grey[200], - ), - ), - ], + TextFormField( + initialValue: commentaire, + onChanged: (v) => commentaire = v, + decoration: const InputDecoration(labelText: "Commentaire"), ), - SizedBox(height: 20), - - // Notes - Text( - "Notes (optionnel)", - style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), + TextFormField( + initialValue: ingredients, + onChanged: (v) => ingredients = v, + decoration: const InputDecoration(labelText: "Ingrédients"), ), - 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: EdgeInsets.all(12), + TextFormField( + initialValue: prix.toString(), + onChanged: (v) => prix = double.tryParse(v) ?? 0, + decoration: const InputDecoration(labelText: "Prix"), + keyboardType: const TextInputType.numberWithOptions( + decimal: true, ), ), - SizedBox(height: 24), - - // Total et bouton d'ajout - Container( - width: double.infinity, - padding: EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.grey[50], - borderRadius: BorderRadius.circular(12), - ), - child: Column( - children: [ - 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, - color: Colors.green[700], - ), - ), - ], - ), - 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: Duration(seconds: 2), - ), - ); - }, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.green[700], - foregroundColor: Colors.white, - padding: EdgeInsets.symmetric(vertical: 16), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - ), - child: Text( - "Ajouter au panier", - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ], - ), + DropdownButton( + hint: const Text("Catégorie"), + value: cat, + isExpanded: true, + items: + widget.categories + .toSet() + .map( + (c) => DropdownMenuItem(value: c, child: Text(c.nom)), + ) + .toList(), + onChanged: (v) => setState(() => cat = v), ), ], ), ), - ); - } + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text("Annuler"), + ), + ElevatedButton(onPressed: submit, child: const Text("Enregistrer")), + ], + ); } diff --git a/lib/pages/tables.dart b/lib/pages/tables.dart index 2b58088..389e631 100644 --- a/lib/pages/tables.dart +++ b/lib/pages/tables.dart @@ -306,10 +306,7 @@ class _TablesScreenState extends State { crossAxisCount: crossAxisCount, crossAxisSpacing: 12, mainAxisSpacing: 12, - childAspectRatio: - isDesktop - ? 1.7 - : 2.1, + childAspectRatio: isDesktop ? 1.7 : 2.1, ), itemCount: tables.length, itemBuilder: (context, index) { diff --git a/lib/widgets/bottom_navigation.dart b/lib/widgets/bottom_navigation.dart index 256ae4d..6e1d5a4 100644 --- a/lib/widgets/bottom_navigation.dart +++ b/lib/widgets/bottom_navigation.dart @@ -155,12 +155,12 @@ class AppBottomNavigation extends StatelessWidget { const SizedBox(width: 20), GestureDetector( - onTap: () => onItemTapped(3), + onTap: () => onItemTapped(4), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: - selectedIndex == 3 + selectedIndex == 4 ? Colors.green.shade700 : Colors.transparent, borderRadius: BorderRadius.circular(20), @@ -169,23 +169,23 @@ class AppBottomNavigation extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Icon( - Icons.restaurant_menu, + Icons.restaurant_menu, color: - selectedIndex == 3 + selectedIndex == 4 ? Colors.white : Colors.grey.shade600, size: 16, ), const SizedBox(width: 6), Text( - 'Menu', + 'Plats', style: TextStyle( color: - selectedIndex == 3 + selectedIndex == 4 ? Colors.white : Colors.grey.shade600, fontWeight: - selectedIndex == 3 + selectedIndex == 4 ? FontWeight.w500 : FontWeight.normal, ), @@ -220,4 +220,4 @@ class AppBottomNavigation extends StatelessWidget { ], ); } -} \ No newline at end of file +}