Browse Source

tsy haiko hoe inona no nataoko teto

master
Stephane 4 months ago
parent
commit
646efaad64
  1. 14
      lib/pages/PlatEdit_screen.dart
  2. 53
      lib/pages/commandes_screen.dart
  3. 102
      lib/pages/menu.dart
  4. 8
      lib/pages/menus_screen.dart

14
lib/pages/PlatEdit_screen.dart

@ -21,6 +21,20 @@ class MenuPlat {
required this.disponible, required this.disponible,
required this.categories, required this.categories,
}); });
factory MenuPlat.fromJson(Map<String, dynamic> json) {
return MenuPlat(
id: json['id'],
nom: json['nom'],
commentaire: json['commentaire'],
prix: (json['prix'] as num).toDouble(),
imageUrl: json['image_url'],
disponible: json['disponible'] ?? true,
categories:
(json['categories'] as List)
.map((c) => MenuCategory.fromJson(c))
.toList(),
);
}
} }
class MenuCategory { class MenuCategory {

53
lib/pages/commandes_screen.dart

@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'dart:convert'; import 'dart:convert';
import './PlatEdit_screen.dart';
class OrdersManagementScreen extends StatefulWidget { class OrdersManagementScreen extends StatefulWidget {
const OrdersManagementScreen({super.key}); const OrdersManagementScreen({super.key});
@ -16,11 +17,18 @@ class _OrdersManagementScreenState extends State<OrdersManagementScreen> {
bool isLoading = true; bool isLoading = true;
String? errorMessage; String? errorMessage;
final String baseUrl = 'https://restaurant.careeracademy.mg/api'; final String baseUrl = 'https://restaurant.careeracademy.mg/api';
Map<int, MenuPlat>? menuMap;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
loadOrders(); loadOrders();
loadMenuMap();
}
Future<void> loadMenuMap() async {
final loadedMenuMap = await fetchMenuMap();
menuMap = loadedMenuMap;
} }
Future<void> loadOrders() async { Future<void> loadOrders() async {
@ -521,6 +529,7 @@ class _OrdersManagementScreenState extends State<OrdersManagementScreen> {
onProcessPayment: processPayment, onProcessPayment: processPayment,
onDelete: deleteOrder, onDelete: deleteOrder,
onViewDetails: () => getOrderById(order.id), onViewDetails: () => getOrderById(order.id),
menuMap: menuMap,
); );
}, },
), ),
@ -528,6 +537,30 @@ class _OrdersManagementScreenState extends State<OrdersManagementScreen> {
} }
} }
/// Récupère la liste des menus et la stocke dans un [Map]
/// la clé est l'ID du menu et la valeur est le [MenuPlat] correspondant.
///
/// Si la requête échoue, lève une [Exception] avec un message d'erreur.
Future<Map<int, MenuPlat>> fetchMenuMap() async {
final response = await http.get(
Uri.parse('https://restaurant.careeracademy.mg/api/menus'),
);
if (response.statusCode == 200) {
final List<dynamic> menuList = json.decode(response.body);
final Map<int, MenuPlat> menuMap = {};
for (var menu in menuList) {
final menuPlat = MenuPlat.fromJson(menu);
menuMap[menuPlat.id] = menuPlat;
}
return menuMap;
} else {
throw Exception('Échec de la récupération des menus');
}
}
class OrderCard extends StatelessWidget { class OrderCard extends StatelessWidget {
final Order order; final Order order;
final Function(Order, String, {String? modePaiement}) onStatusUpdate; final Function(Order, String, {String? modePaiement}) onStatusUpdate;
@ -535,14 +568,19 @@ class OrderCard extends StatelessWidget {
final Function(Order) onDelete; final Function(Order) onDelete;
final VoidCallback onViewDetails; final VoidCallback onViewDetails;
const OrderCard({ // Add menuMap field to have access to menu names
Key? key,
// late Future<Map<int, MenuPlat>> menuMapFuture = fetchMenuMap();
final Map<int, MenuPlat> menuMap;
OrderCard({
super.key,
required this.order, required this.order,
required this.onStatusUpdate, required this.onStatusUpdate,
required this.onProcessPayment, required this.onProcessPayment,
required this.onDelete, required this.onDelete,
required this.onViewDetails, required this.onViewDetails,
}) : super(key: key); required this.menuMap, // Require menuMap in constructor
});
String _formatTime(DateTime dateTime) { String _formatTime(DateTime dateTime) {
return '${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}${dateTime.day.toString().padLeft(2, '0')}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.year} •1 personne'; return '${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}${dateTime.day.toString().padLeft(2, '0')}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.year} •1 personne';
@ -661,7 +699,7 @@ class OrderCard extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'${item.quantite}x ${item.menuId}', // You might want to resolve menu name '${item.quantite}x ${item.getNomById(menuMap)}',
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
color: Colors.black87, color: Colors.black87,
@ -904,4 +942,9 @@ class OrderItem {
commentaires: json['commentaires'], commentaires: json['commentaires'],
); );
} }
// Pass a Map of menus to get the name
String getNomById(Map<int, MenuPlat> menuMap) {
return menuMap[menuId]?.nom ?? 'Menu Item $menuId';
}
} }

102
lib/pages/menu.dart

@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'dart:convert'; import 'dart:convert';
@ -9,8 +10,7 @@ class MenuPage extends StatefulWidget {
final int tableId; final int tableId;
final int personne; final int personne;
const MenuPage({Key? key, required this.tableId, required this.personne}) const MenuPage({super.key, required this.tableId, required this.personne});
: super(key: key);
@override @override
State<MenuPage> createState() => _MenuPageState(); State<MenuPage> createState() => _MenuPageState();
@ -20,7 +20,7 @@ class _MenuPageState extends State<MenuPage> {
int? _selectedCategory; int? _selectedCategory;
List<dynamic> _categories = []; List<dynamic> _categories = [];
List<dynamic> _menus = []; List<dynamic> _menus = [];
List<dynamic> _cart = []; final List<dynamic> _cart = [];
@override @override
void initState() { void initState() {
@ -49,12 +49,16 @@ class _MenuPageState extends State<MenuPage> {
} }
}); });
} else { } else {
if (kDebugMode) {
print("Erreur API catégories: ${response.statusCode}"); print("Erreur API catégories: ${response.statusCode}");
} }
}
} catch (e) { } catch (e) {
if (kDebugMode) {
print("Exception fetchCategories: $e"); print("Exception fetchCategories: $e");
} }
} }
}
Future<void> fetchMenus(int categoryId) async { Future<void> fetchMenus(int categoryId) async {
try { try {
@ -73,12 +77,16 @@ class _MenuPageState extends State<MenuPage> {
_menus = menusList; _menus = menusList;
}); });
} else { } else {
if (kDebugMode) {
print("Erreur API menus: ${response.statusCode}"); print("Erreur API menus: ${response.statusCode}");
} }
}
} catch (e) { } catch (e) {
if (kDebugMode) {
print("Exception fetchMenus: $e"); print("Exception fetchMenus: $e");
} }
} }
}
void changeCategory(int id) { void changeCategory(int id) {
setState(() { setState(() {
@ -145,13 +153,13 @@ class _MenuPageState extends State<MenuPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text("Menu"), title: const Text("Menu"),
actions: [ actions: [
IconButton( IconButton(
onPressed: () { onPressed: () {
if (_selectedCategory != null) fetchMenus(_selectedCategory!); if (_selectedCategory != null) fetchMenus(_selectedCategory!);
}, },
icon: Icon(Icons.refresh), icon: const Icon(Icons.refresh),
), ),
], ],
), ),
@ -162,7 +170,7 @@ class _MenuPageState extends State<MenuPage> {
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Text( child: Text(
"Table ${widget.tableId}${widget.personne} personne${widget.personne > 1 ? 's' : ''}", "Table ${widget.tableId}${widget.personne} personne${widget.personne > 1 ? 's' : ''}",
style: TextStyle(fontSize: 16), style: const TextStyle(fontSize: 16),
), ),
), ),
if (_categories.isNotEmpty) if (_categories.isNotEmpty)
@ -174,7 +182,7 @@ class _MenuPageState extends State<MenuPage> {
}).toList(), }).toList(),
) )
else else
Center(child: CircularProgressIndicator()), const Center(child: CircularProgressIndicator()),
Expanded( Expanded(
child: child:
_menus.isNotEmpty _menus.isNotEmpty
@ -183,7 +191,7 @@ class _MenuPageState extends State<MenuPage> {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final item = _menus[index]; final item = _menus[index];
return Card( return Card(
margin: EdgeInsets.all(8), margin: const EdgeInsets.all(8),
child: ListTile( child: ListTile(
onTap: onTap:
() => showAddToCartModal( () => showAddToCartModal(
@ -204,7 +212,9 @@ class _MenuPageState extends State<MenuPage> {
), ),
title: Text( title: Text(
item['nom'] ?? 'Nom non disponible', item['nom'] ?? 'Nom non disponible',
style: TextStyle(fontWeight: FontWeight.bold), style: const TextStyle(
fontWeight: FontWeight.bold,
),
), ),
subtitle: Text(item['commentaire'] ?? ''), subtitle: Text(item['commentaire'] ?? ''),
trailing: Text( trailing: Text(
@ -219,18 +229,18 @@ class _MenuPageState extends State<MenuPage> {
); );
}, },
) )
: Center(child: Text("Aucun menu disponible")), : const Center(child: Text("Aucun menu disponible")),
), ),
Container( Container(
width: double.infinity, width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 12), padding: const EdgeInsets.symmetric(vertical: 12),
color: Colors.green[700], color: Colors.green[700],
child: Center( child: Center(
child: TextButton( child: TextButton(
onPressed: _navigateToCart, // Navigation vers la page panier onPressed: _navigateToCart, // Navigation vers la page panier
child: Text( child: Text(
"Voir le panier (${_cart.length})", "Voir le panier (${_cart.length})",
style: TextStyle(color: Colors.white, fontSize: 16), style: const TextStyle(color: Colors.white, fontSize: 16),
), ),
), ),
), ),
@ -246,7 +256,7 @@ class _MenuPageState extends State<MenuPage> {
child: GestureDetector( child: GestureDetector(
onTap: () => changeCategory(id), onTap: () => changeCategory(id),
child: Container( child: Container(
padding: EdgeInsets.symmetric(vertical: 10), padding: const EdgeInsets.symmetric(vertical: 10),
color: selected ? Colors.grey[300] : Colors.grey[100], color: selected ? Colors.grey[300] : Colors.grey[100],
child: Center( child: Center(
child: Text( child: Text(
@ -268,10 +278,10 @@ class AddToCartModal extends StatefulWidget {
final Function(dynamic, int, String) onAddToCart; final Function(dynamic, int, String) onAddToCart;
const AddToCartModal({ const AddToCartModal({
Key? key, super.key,
required this.item, required this.item,
required this.onAddToCart, required this.onAddToCart,
}) : super(key: key); });
@override @override
State<AddToCartModal> createState() => _AddToCartModalState(); State<AddToCartModal> createState() => _AddToCartModalState();
@ -307,8 +317,8 @@ class _AddToCartModalState extends State<AddToCartModal> {
return Dialog( return Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
child: Container( child: Container(
padding: EdgeInsets.all(20), padding: const EdgeInsets.all(20),
constraints: BoxConstraints(maxWidth: 400), constraints: const BoxConstraints(maxWidth: 400),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -319,17 +329,20 @@ class _AddToCartModalState extends State<AddToCartModal> {
children: [ children: [
Text( Text(
widget.item['nom'] ?? 'Menu', widget.item['nom'] ?? 'Menu',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
), ),
IconButton( IconButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
icon: Icon(Icons.close), icon: const Icon(Icons.close),
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
constraints: BoxConstraints(), constraints: const BoxConstraints(),
), ),
], ],
), ),
SizedBox(height: 16), const SizedBox(height: 16),
// Image placeholder // Image placeholder
Container( Container(
@ -345,7 +358,7 @@ class _AddToCartModalState extends State<AddToCartModal> {
color: Colors.green[700], color: Colors.green[700],
), ),
), ),
SizedBox(height: 16), const SizedBox(height: 16),
// Description // Description
if (widget.item['commentaire'] != null && if (widget.item['commentaire'] != null &&
@ -354,13 +367,13 @@ class _AddToCartModalState extends State<AddToCartModal> {
widget.item['commentaire'], widget.item['commentaire'],
style: TextStyle(fontSize: 14, color: Colors.grey[600]), style: TextStyle(fontSize: 14, color: Colors.grey[600]),
), ),
SizedBox(height: 16), const SizedBox(height: 16),
// Prix unitaire // Prix unitaire
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( const Text(
"Prix unitaire", "Prix unitaire",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
), ),
@ -374,14 +387,14 @@ class _AddToCartModalState extends State<AddToCartModal> {
), ),
], ],
), ),
SizedBox(height: 20), const SizedBox(height: 20),
// Quantité // Quantité
Text( const Text(
"Quantité", "Quantité",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
), ),
SizedBox(height: 8), const SizedBox(height: 8),
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
@ -394,38 +407,41 @@ class _AddToCartModalState extends State<AddToCartModal> {
}); });
} }
: null, : null,
icon: Icon(Icons.remove), icon: const Icon(Icons.remove),
style: IconButton.styleFrom( style: IconButton.styleFrom(
backgroundColor: Colors.grey[200], backgroundColor: Colors.grey[200],
), ),
), ),
SizedBox(width: 20), const SizedBox(width: 20),
Text( Text(
_quantity.toString(), _quantity.toString(),
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
), ),
SizedBox(width: 20), ),
const SizedBox(width: 20),
IconButton( IconButton(
onPressed: () { onPressed: () {
setState(() { setState(() {
_quantity++; _quantity++;
}); });
}, },
icon: Icon(Icons.add), icon: const Icon(Icons.add),
style: IconButton.styleFrom( style: IconButton.styleFrom(
backgroundColor: Colors.grey[200], backgroundColor: Colors.grey[200],
), ),
), ),
], ],
), ),
SizedBox(height: 20), const SizedBox(height: 20),
// Notes // Notes
Text( const Text(
"Notes (optionnel)", "Notes (optionnel)",
style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500), style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
), ),
SizedBox(height: 8), const SizedBox(height: 8),
TextField( TextField(
controller: _notesController, controller: _notesController,
maxLines: 3, maxLines: 3,
@ -434,15 +450,15 @@ class _AddToCartModalState extends State<AddToCartModal> {
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
contentPadding: EdgeInsets.all(12), contentPadding: const EdgeInsets.all(12),
), ),
), ),
SizedBox(height: 24), const SizedBox(height: 24),
// Total et bouton d'ajout // Total et bouton d'ajout
Container( Container(
width: double.infinity, width: double.infinity,
padding: EdgeInsets.all(16), padding: const EdgeInsets.all(16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey[50], color: Colors.grey[50],
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
@ -452,7 +468,7 @@ class _AddToCartModalState extends State<AddToCartModal> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text( const Text(
"Total", "Total",
style: TextStyle( style: TextStyle(
fontSize: 18, fontSize: 18,
@ -469,7 +485,7 @@ class _AddToCartModalState extends State<AddToCartModal> {
), ),
], ],
), ),
SizedBox(height: 16), const SizedBox(height: 16),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
child: ElevatedButton( child: ElevatedButton(
@ -488,19 +504,19 @@ class _AddToCartModalState extends State<AddToCartModal> {
"${widget.item['nom']} ajouté au panier", "${widget.item['nom']} ajouté au panier",
), ),
backgroundColor: Colors.green, backgroundColor: Colors.green,
duration: Duration(seconds: 2), duration: const Duration(seconds: 2),
), ),
); );
}, },
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: Colors.green[700], backgroundColor: Colors.green[700],
foregroundColor: Colors.white, foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
), ),
child: Text( child: const Text(
"Ajouter au panier", "Ajouter au panier",
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,

8
lib/pages/menus_screen.dart

@ -74,12 +74,13 @@ class _PlatsManagementScreenState extends State<PlatsManagementScreen> {
Future<void> _deletePlat(int id) async { Future<void> _deletePlat(int id) async {
final res = await http.delete(Uri.parse('$_baseUrl/menus/$id')); final res = await http.delete(Uri.parse('$_baseUrl/menus/$id'));
if (res.statusCode == 200) if (res.statusCode == 200) {
_fetchPlats(); _fetchPlats();
else { } else {
if (kDebugMode) print("Error deleting plat: ${res.body}"); if (kDebugMode) print("Error deleting plat: ${res.body}");
// ignore: use_build_context_synchronously
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Des commandes sont liées à ce plat.')), const SnackBar(content: Text('Des commandes sont liées à ce plat.')),
); );
} }
} }
@ -467,7 +468,6 @@ class _EditPlatDialogState extends State<EditPlatDialog> {
commentaire = widget.plat.commentaire ?? ''; commentaire = widget.plat.commentaire ?? '';
ingredients = widget.plat.ingredients ?? ''; ingredients = widget.plat.ingredients ?? '';
prix = widget.plat.prix; prix = widget.plat.prix;
var disponible = widget.plat.disponible;
// cat = (widget.plat.categories) as MenuCategory?; // cat = (widget.plat.categories) as MenuCategory?;
cat = widget.plat.category; cat = widget.plat.category;
} }

Loading…
Cancel
Save