// TODO Implement this library. import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; class OrdersManagementScreen extends StatefulWidget { const OrdersManagementScreen({super.key}); @override State createState() => _OrdersManagementScreenState(); } class _OrdersManagementScreenState extends State { List orders = []; bool isLoading = true; String? errorMessage; final String baseUrl = 'https://restaurant.careeracademy.mg/api'; @override void initState() { super.initState(); loadOrders(); } Future loadOrders() async { try { setState(() { isLoading = true; errorMessage = null; }); // Get all orders with filtering for active ones only final response = await http.get( Uri.parse('$baseUrl/commandes'), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, ); if (response.statusCode == 200) { final responseData = json.decode(response.body); if (responseData['success'] == true) { final List commandesData = responseData['data']['commandes']; setState(() { orders = commandesData .map((orderData) => Order.fromJson(orderData)) .toList(); isLoading = false; }); } else { throw Exception('API returned success: false'); } } else { throw Exception('Failed to load orders: ${response.statusCode}'); } } catch (e) { setState(() { isLoading = false; errorMessage = 'Erreur de chargement: $e'; }); if (kDebugMode) { print('Error loading orders: $e'); } } } Future loadKitchenOrders() async { try { final response = await http.get( Uri.parse('$baseUrl/commandes/kitchen'), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, ); if (response.statusCode == 200) { json.decode(response.body); // Handle kitchen orders if (kDebugMode) { print('Kitchen orders loaded'); } } } catch (e) { if (kDebugMode) { print('Error loading kitchen orders: $e'); } } } Future loadOrderStats() async { try { final response = await http.get( Uri.parse('$baseUrl/commandes/stats'), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, ); if (response.statusCode == 200) { json.decode(response.body); // Handle stats if (kDebugMode) { print('Order stats loaded'); } } } catch (e) { print('Error loading stats: $e'); } } Future getOrderById(int orderId) async { try { final response = await http.get( Uri.parse('$baseUrl/commandes/$orderId'), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, ); if (response.statusCode == 200) { final responseData = json.decode(response.body); if (responseData['success'] == true) { return Order.fromJson(responseData['data']); } } } catch (e) { print('Error getting order: $e'); } return null; } Future updateOrderStatus( Order order, String newStatus, { String? modePaiement, }) async { try { final Map updateData = {'statut': newStatus}; if (modePaiement != null) { updateData['mode_paiement'] = modePaiement; } final response = await http.put( Uri.parse('$baseUrl/commandes/${order.id}/status'), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, body: json.encode(updateData), ); if (response.statusCode == 200) { final responseData = json.decode(response.body); if (responseData['success'] == true) { setState(() { order.statut = newStatus; order.modePaiement = modePaiement; order.updatedAt = DateTime.now(); if (newStatus == "payee") { order.dateService = DateTime.now(); } }); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Commande mise à jour avec succès'), backgroundColor: Colors.green, ), ); // Remove from active orders list if status changed to completed if (newStatus == "payee" || newStatus == "annulee") { loadOrders(); // Refresh the list } } else { throw Exception('API returned success: false'); } } else { throw Exception('Failed to update order: ${response.statusCode}'); } } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Erreur lors de la mise à jour: $e'), backgroundColor: Colors.red, ), ); print('Error updating order: $e'); } } Future createNewOrder() async { // Sample order creation - you can customize this try { final Map newOrderData = { 'client_id': 1, 'table_id': 1, 'reservation_id': 1, 'serveur': 'Marie', 'commentaires': 'Nouvelle commande', 'items': [ {'menu_id': 1, 'quantite': 2, 'commentaires': 'Bien cuit'}, {'menu_id': 2, 'quantite': 1, 'commentaires': ''}, ], }; final response = await http.post( Uri.parse('$baseUrl/commandes'), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, body: json.encode(newOrderData), ); if (response.statusCode == 201) { final responseData = json.decode(response.body); if (responseData['success'] == true) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Nouvelle commande créée avec succès'), backgroundColor: Colors.green, ), ); loadOrders(); // Refresh the list } } } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Erreur lors de la création: $e'), backgroundColor: Colors.red, ), ); print('Error creating order: $e'); } } Future deleteOrder(Order order) async { try { final response = await http.delete( Uri.parse('$baseUrl/commandes/${order.id}'), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, ); if (response.statusCode == 200) { final responseData = json.decode(response.body); if (responseData['success'] == true) { setState(() { orders.remove(order); }); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Commande supprimée avec succès'), backgroundColor: Colors.green, ), ); } } } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Erreur lors de la suppression: $e'), backgroundColor: Colors.red, ), ); print('Error deleting order: $e'); } } List get activeOrders { return orders .where( (order) => order.statut == "en_attente" || order.statut == "en_preparation", ) .toList(); } void processPayment(Order order) { showDialog( context: context, builder: (BuildContext context) { String selectedPaymentMethod = 'carte'; return StatefulBuilder( builder: (context, setState) { return AlertDialog( title: const Text('Mettre en caisse'), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Table ${order.tableId} - ${order.totalTtc.toStringAsFixed(2)} MGA', ), const SizedBox(height: 16), const Text('Mode de paiement:'), const SizedBox(height: 8), DropdownButton( value: selectedPaymentMethod, isExpanded: true, items: const [ DropdownMenuItem( value: 'carte', child: Text('Carte bancaire'), ), DropdownMenuItem( value: 'especes', child: Text('Espèces'), ), DropdownMenuItem(value: 'cheque', child: Text('Chèque')), ], onChanged: (value) { setState(() { selectedPaymentMethod = value!; }); }, ), ], ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Annuler'), ), ElevatedButton( onPressed: () { Navigator.of(context).pop(); updateOrderStatus( order, "payee", modePaiement: selectedPaymentMethod, ); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.green, ), child: const Text( 'Confirmer le paiement', style: TextStyle(color: Colors.white), ), ), ], ); }, ); }, ); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xFFF8F9FA), appBar: AppBar( backgroundColor: Colors.white, elevation: 1, title: const Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Gestion des commandes', style: TextStyle( color: Colors.black87, fontSize: 20, fontWeight: FontWeight.bold, ), ), Text( 'Suivez et gérez toutes les commandes', style: TextStyle( color: Colors.grey, fontSize: 14, fontWeight: FontWeight.normal, ), ), ], ), toolbarHeight: 80, actions: [ IconButton( icon: const Icon(Icons.kitchen, color: Colors.grey), onPressed: loadKitchenOrders, tooltip: 'Commandes cuisine', ), IconButton( icon: const Icon(Icons.bar_chart, color: Colors.grey), onPressed: loadOrderStats, tooltip: 'Statistiques', ), IconButton( icon: const Icon(Icons.add, color: Colors.green), onPressed: createNewOrder, tooltip: 'Nouvelle commande', ), IconButton( icon: const Icon(Icons.refresh, color: Colors.grey), onPressed: loadOrders, tooltip: 'Actualiser', ), // IconButton( // icon: const Icon(Icons.logout, color: Colors.grey), // onPressed: () { // Navigator.of(context).pop(); // }, // ), ], ), body: Padding( padding: const EdgeInsets.all(24.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Active orders header Row( children: [ const Icon(Icons.access_time, size: 20, color: Colors.black87), const SizedBox(width: 8), Text( 'Commandes actives (${activeOrders.length})', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.black87, ), ), ], ), const SizedBox(height: 16), // Content Expanded(child: _buildContent()), ], ), ), ); } Widget _buildContent() { if (isLoading) { return const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ CircularProgressIndicator(color: Colors.green), SizedBox(height: 16), Text( 'Chargement des commandes...', style: TextStyle(color: Colors.grey), ), ], ), ); } if (errorMessage != null) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.error_outline, size: 64, color: Colors.red), const SizedBox(height: 16), Text( errorMessage!, style: const TextStyle(fontSize: 16, color: Colors.red), textAlign: TextAlign.center, ), const SizedBox(height: 16), ElevatedButton( onPressed: loadOrders, child: const Text('Réessayer'), ), ], ), ); } if (activeOrders.isEmpty) { return const Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.restaurant_menu, size: 64, color: Colors.grey), SizedBox(height: 16), Text( 'Aucune commande active', style: TextStyle(fontSize: 18, color: Colors.grey), ), ], ), ); } return RefreshIndicator( onRefresh: loadOrders, child: ListView.builder( itemCount: activeOrders.length, itemBuilder: (context, index) { final order = activeOrders[index]; return OrderCard( order: order, onStatusUpdate: updateOrderStatus, onProcessPayment: processPayment, onDelete: deleteOrder, onViewDetails: () => getOrderById(order.id), ); }, ), ); } } class OrderCard extends StatelessWidget { final Order order; final Function(Order, String, {String? modePaiement}) onStatusUpdate; final Function(Order) onProcessPayment; final Function(Order) onDelete; final VoidCallback onViewDetails; const OrderCard({ Key? key, required this.order, required this.onStatusUpdate, required this.onProcessPayment, required this.onDelete, required this.onViewDetails, }) : super(key: key); 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'; } Color _getStatusColor(String status) { switch (status) { case 'en_attente': return Colors.green; case 'en_preparation': return Colors.orange; case 'servie': return Colors.blue; case 'payee': return Colors.grey; default: return Colors.grey; } } String _getStatusText(String status) { switch (status) { case 'en_attente': return 'En cours'; case 'en_preparation': return 'En préparation'; case 'servie': return 'Servie'; case 'payee': return 'Payée'; default: return status; } } @override Widget build(BuildContext context) { return Container( margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey.shade200), ), child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header row Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ GestureDetector( onTap: onViewDetails, child: Text( 'Table ${order.tableId}', style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87, ), ), ), Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 4, ), decoration: BoxDecoration( color: _getStatusColor(order.statut), borderRadius: BorderRadius.circular(12), ), child: Text( _getStatusText(order.statut), style: const TextStyle( color: Colors.white, fontSize: 12, fontWeight: FontWeight.w500, ), ), ), ], ), const SizedBox(height: 8), // Time and details Text( _formatTime(order.dateCommande), style: const TextStyle(color: Colors.grey, fontSize: 14), ), const SizedBox(height: 8), // Order number and server Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( order.numeroCommande, style: const TextStyle(fontSize: 12, color: Colors.black54), ), Text( 'Serveur: ${order.serveur}', style: const TextStyle(fontSize: 12, color: Colors.black54), ), ], ), const SizedBox(height: 16), // Order items placeholder if (order.items.isNotEmpty) ...order.items.map( (item) => Padding( padding: const EdgeInsets.only(bottom: 4), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '${item.quantite}x ${item.menuId}', // You might want to resolve menu name style: const TextStyle( fontSize: 14, color: Colors.black87, ), ), Text( '${(item.quantite * 8.00).toStringAsFixed(2)} MGA', // Placeholder price style: const TextStyle( fontSize: 14, color: Colors.black87, ), ), ], ), ), ) else const Text( 'Détails de la commande...', style: TextStyle(fontSize: 14, color: Colors.black87), ), const SizedBox(height: 12), const Divider(), const SizedBox(height: 8), // Total row Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Total', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black87, ), ), Text( '${order.totalTtc.toStringAsFixed(2)} MGA', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.green, ), ), ], ), const SizedBox(height: 16), // Action buttons if (order.statut == 'en_attente' || order.statut == 'en_preparation') Row( children: [ Expanded( flex: 3, child: ElevatedButton.icon( onPressed: () => onProcessPayment(order), icon: const Icon( Icons.point_of_sale, color: Colors.white, size: 18, ), label: const Text( 'Mettre en caisse', style: TextStyle( color: Colors.white, fontWeight: FontWeight.w500, ), ), style: ElevatedButton.styleFrom( backgroundColor: Colors.green, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(6), ), elevation: 0, ), ), ), const SizedBox(width: 8), if (order.statut == 'en_attente') Expanded( child: ElevatedButton( onPressed: () => onStatusUpdate(order, 'en_preparation'), style: ElevatedButton.styleFrom( backgroundColor: Colors.orange, padding: const EdgeInsets.symmetric(vertical: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(6), ), ), child: const Text( 'Préparer', style: TextStyle(color: Colors.white, fontSize: 12), ), ), ), const SizedBox(width: 8), Container( decoration: BoxDecoration( border: Border.all(color: Colors.red.shade200), borderRadius: BorderRadius.circular(6), ), child: IconButton( onPressed: () { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Supprimer la commande'), content: const Text( 'Êtes-vous sûr de vouloir supprimer cette commande ?', ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Annuler'), ), ElevatedButton( onPressed: () { Navigator.pop(context); onDelete(order); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.red, ), child: const Text( 'Supprimer', style: TextStyle(color: Colors.white), ), ), ], ), ); }, icon: Icon( Icons.delete, color: Colors.red.shade400, size: 18, ), ), ), ], ), ], ), ), ); } } // Updated Order model to include items class Order { final int id; final int clientId; final int tableId; final int? reservationId; final String numeroCommande; String statut; final double totalHt; final double totalTva; final double totalTtc; String? modePaiement; final String? commentaires; final String serveur; final DateTime dateCommande; DateTime? dateService; final DateTime createdAt; DateTime updatedAt; final List items; Order({ required this.id, required this.clientId, required this.tableId, this.reservationId, required this.numeroCommande, required this.statut, required this.totalHt, required this.totalTva, required this.totalTtc, this.modePaiement, this.commentaires, required this.serveur, required this.dateCommande, this.dateService, required this.createdAt, required this.updatedAt, this.items = const [], }); factory Order.fromJson(Map json) { List orderItems = []; if (json['items'] != null) { orderItems = (json['items'] as List) .map((item) => OrderItem.fromJson(item)) .toList(); } return Order( id: json['id'], clientId: json['client_id'], tableId: json['table_id'], reservationId: json['reservation_id'], numeroCommande: json['numero_commande'], statut: json['statut'], totalHt: double.parse(json['total_ht'].toString()), totalTva: double.parse(json['total_tva'].toString()), totalTtc: double.parse(json['total_ttc'].toString()), modePaiement: json['mode_paiement'], commentaires: json['commentaires'], serveur: json['serveur'], dateCommande: DateTime.parse(json['date_commande']), dateService: json['date_service'] != null ? DateTime.parse(json['date_service']) : null, createdAt: DateTime.parse(json['created_at']), updatedAt: DateTime.parse(json['updated_at']), items: orderItems, ); } } class OrderItem { final int menuId; final int quantite; final String? commentaires; OrderItem({required this.menuId, required this.quantite, this.commentaires}); factory OrderItem.fromJson(Map json) { return OrderItem( menuId: json['menu_id'], quantite: json['quantite'], commentaires: json['commentaires'], ); } }