Browse Source

ajout panier

master
Stephane 4 months ago
parent
commit
a23321274a
  1. 530
      lib/pages/commande_item_validation.dart
  2. 121
      lib/pages/commandes_screen.dart

530
lib/pages/commande_item_validation.dart

@ -6,7 +6,8 @@ class ValidateAddItemsPage extends StatefulWidget {
final int commandeId;
final String numeroCommande;
final List<dynamic> newItems; // Nouveaux articles à ajouter
final Map<String, dynamic>? commandeDetails; // Détails de la commande existante
final Map<String, dynamic>?
commandeDetails; // Détails de la commande existante
const ValidateAddItemsPage({
Key? key,
@ -88,14 +89,17 @@ class _ValidateAddItemsPageState extends State<ValidateAddItemsPage> {
// Calculer le total des articles existants
double _calculateExistingItemsTotal() {
if (widget.commandeDetails == null || widget.commandeDetails!['items'] == null) {
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']);
double prix = _parsePrice(
item['menu_prix_actuel'] ?? item['prix_unitaire'],
);
int quantite = item['quantite'] ?? 1;
total += prix * quantite;
}
@ -112,10 +116,14 @@ class _ValidateAddItemsPageState extends State<ValidateAddItemsPage> {
}
int _getTotalExistingArticles() {
if (widget.commandeDetails == null || widget.commandeDetails!['items'] == null) {
if (widget.commandeDetails == null ||
widget.commandeDetails!['items'] == null) {
return 0;
}
return widget.commandeDetails!['items'].fold(0, (sum, item) => sum + (item['quantite'] ?? 1));
return widget.commandeDetails!['items'].fold(
0,
(sum, item) => sum + (item['quantite'] ?? 1),
);
}
void _showConfirmationDialog() {
@ -132,7 +140,9 @@ class _ValidateAddItemsPageState extends State<ValidateAddItemsPage> {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Êtes-vous sûr de vouloir ajouter ces articles à la commande ?'),
Text(
'Êtes-vous sûr de vouloir ajouter ces articles à la commande ?',
),
SizedBox(height: 16),
Text(
'Récapitulatif:',
@ -140,7 +150,9 @@ class _ValidateAddItemsPageState extends State<ValidateAddItemsPage> {
),
Text('• Commande: ${widget.numeroCommande}'),
Text('• Nouveaux articles: ${_getTotalNewArticles()}'),
Text('• Nouveau total: ${_calculateGrandTotal().toStringAsFixed(2)} MGA'),
Text(
'• Nouveau total: ${_calculateGrandTotal().toStringAsFixed(2)} MGA',
),
],
),
actions: [
@ -149,26 +161,30 @@ class _ValidateAddItemsPageState extends State<ValidateAddItemsPage> {
child: Text('Annuler', style: TextStyle(color: Colors.grey[600])),
),
ElevatedButton(
onPressed: _isValidating
? null
: () {
Navigator.of(context).pop();
_validateAddItems();
},
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<Color>(Colors.white),
),
)
: Text('Confirmer'),
child:
_isValidating
? SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Colors.white,
),
),
)
: Text('Confirmer'),
),
],
);
@ -193,36 +209,36 @@ class _ValidateAddItemsPageState extends State<ValidateAddItemsPage> {
try {
// Préparer les données des nouveaux articles
// 1. Construire la liste des items sans commande_id à lintérieur
List<Map<String, dynamic>> 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');
// 1. Construire la liste des items sans commande_id à lintérieur
List<Map<String, dynamic>> 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);
@ -351,58 +367,57 @@ print('✅ Réponse backend : $responseData');
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<Widget>((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),
],
// 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<Widget>((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(
@ -417,152 +432,168 @@ print('✅ Réponse backend : $responseData');
// 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:
_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],
),
],
),
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,
),
],
),
)
: 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,
IconButton(
onPressed: () => _removeItem(index),
icon: Icon(
Icons.delete_outline,
color: Colors.red,
),
constraints: BoxConstraints(),
padding: EdgeInsets.zero,
),
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}',
'${item.prix.toStringAsFixed(2)} MGA l\'unité',
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
if (item.notes.isNotEmpty) ...[
SizedBox(height: 8),
Text(
'${(item.prix * item.quantity).toStringAsFixed(2)} MGA',
'Notes: ${item.notes}',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.green[700],
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
@ -577,10 +608,7 @@ print('✅ Réponse backend : $responseData');
children: [
Text(
'Récapitulatif final',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
SizedBox(height: 16),
Row(
@ -596,7 +624,10 @@ print('✅ Réponse backend : $responseData');
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Sous-total existant:', style: TextStyle(fontSize: 16)),
Text(
'Sous-total existant:',
style: TextStyle(fontSize: 16),
),
Text(
'${_calculateExistingItemsTotal().toStringAsFixed(2)} MGA',
style: TextStyle(fontSize: 16),
@ -607,7 +638,10 @@ print('✅ Réponse backend : $responseData');
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Nouveaux articles:', style: TextStyle(fontSize: 16, color: Colors.green[700])),
Text(
'Nouveaux articles:',
style: TextStyle(fontSize: 16, color: Colors.green[700]),
),
Text(
_getTotalNewArticles().toString(),
style: TextStyle(fontSize: 16, color: Colors.green[700]),
@ -617,7 +651,10 @@ print('✅ Réponse backend : $responseData');
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Sous-total nouveaux:', style: TextStyle(fontSize: 16, color: Colors.green[700])),
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]),
@ -651,9 +688,10 @@ print('✅ Réponse backend : $responseData');
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _newCartItems.isNotEmpty && !_isValidating
? _showConfirmationDialog
: null,
onPressed:
_newCartItems.isNotEmpty && !_isValidating
? _showConfirmationDialog
: null,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green[700],
foregroundColor: Colors.white,
@ -672,7 +710,9 @@ print('✅ Réponse backend : $responseData');
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
valueColor: AlwaysStoppedAnimation<Color>(
Colors.white,
),
),
),
SizedBox(width: 8),

121
lib/pages/commandes_screen.dart

@ -140,8 +140,6 @@ class _OrdersManagementScreenState extends State<OrdersManagementScreen> {
return null;
}
Future<void> updateOrderStatus(
Order order,
String newStatus, {
@ -289,9 +287,6 @@ class _OrdersManagementScreenState extends State<OrdersManagementScreen> {
}
}
List<Order> get activeOrders {
return orders
.where(
@ -347,7 +342,6 @@ class _OrdersManagementScreenState extends State<OrdersManagementScreen> {
style: TextStyle(color: Colors.white),
),
),
],
);
},
@ -356,22 +350,23 @@ class _OrdersManagementScreenState extends State<OrdersManagementScreen> {
);
}
Future<void> updateTableStatus(int tableId, String newStatus) async {
const String apiUrl = 'https://restaurant.careeracademy.mg/api/tables'; // adapte lURL si besoin
Future<void> updateTableStatus(int tableId, String newStatus) async {
const String apiUrl =
'https://restaurant.careeracademy.mg/api/tables'; // adapte lURL si besoin
final response = await http.put(
Uri.parse('$apiUrl/$tableId'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'status': newStatus}),
);
final response = await http.put(
Uri.parse('$apiUrl/$tableId'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'status': newStatus}),
);
if (response.statusCode != 200) {
print('Erreur lors de la mise à jour du statut de la table');
throw Exception('Erreur: ${response.body}');
} else {
print('✅ Table $tableId mise à jour en $newStatus');
if (response.statusCode != 200) {
print('Erreur lors de la mise à jour du statut de la table');
throw Exception('Erreur: ${response.body}');
} else {
print('✅ Table $tableId mise à jour en $newStatus');
}
}
}
@override
Widget build(BuildContext context) {
@ -602,44 +597,37 @@ class OrderCard extends StatelessWidget {
// Header row
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 4,
),
decoration: BoxDecoration(
color: _getStatusColor(order.statut),
borderRadius: BorderRadius.circular(12),
),
child: Text(
_getStatusText(order.statut),
style: const TextStyle(
color: Colors.white,
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
children: [
GestureDetector(
onTap: onViewDetails,
child: Text(
'Table ${order.tableId}',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
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: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AddItemsToOrderPage(
commandeId: order.id,
numeroCommande: order.numero ?? 'Commande #${order.id}',
),
),
);
},
),
],
),
),
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),
@ -731,7 +719,8 @@ class OrderCard extends StatelessWidget {
order.statut == 'en_preparation')
Row(
children: [
if (order.statut == 'en_attente')
if (order.statut == 'en_attente' ||
order.statut == 'en_preparation')
Expanded(
child: ElevatedButton(
onPressed:
@ -741,22 +730,28 @@ class OrderCard extends StatelessWidget {
MaterialPageRoute(
builder:
(context) => AddItemsToOrderPage(
commandId: order.id,
commandeId: order.id,
numeroCommande: order.numeroCommande,
),
),
),
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
backgroundColor: const Color.fromARGB(
255,
0,
76,
255,
),
padding: const EdgeInsets.symmetric(vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
),
child: const Text(
'Préparer',
style: TextStyle(color: Colors.white, fontSize: 12),
child: const Icon(
Icons.add,
color: Colors.white,
size: 16,
),
),
),
@ -860,8 +855,6 @@ class OrderCard extends StatelessWidget {
}
}
// Updated Order model to include items
class Order {
final int id;

Loading…
Cancel
Save