commit
This commit is contained in:
parent
d8910bb34a
commit
31c3d72a71
@ -4,6 +4,7 @@ import 'package:itrimobe/pages/facture_screen.dart';
|
|||||||
import '../models/command_detail.dart';
|
import '../models/command_detail.dart';
|
||||||
import '../models/payment_method.dart';
|
import '../models/payment_method.dart';
|
||||||
import '../services/restaurant_api_service.dart';
|
import '../services/restaurant_api_service.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
class CaisseScreen extends StatefulWidget {
|
class CaisseScreen extends StatefulWidget {
|
||||||
final String commandeId;
|
final String commandeId;
|
||||||
@ -95,11 +96,20 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
// Navigation vers la facture au lieu du dialog de succès
|
final updateSuccess = await RestaurantApiService.updateCommandeStatus(
|
||||||
|
commandeId: widget.commandeId,
|
||||||
|
newStatus: 'payee',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!updateSuccess) {
|
||||||
|
_showErrorDialog("Paiement effectué, mais échec lors de la mise à jour du statut.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🔄 Redirige vers la facture
|
||||||
Navigator.of(context).pushReplacement(
|
Navigator.of(context).pushReplacement(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder:
|
builder: (context) => FactureScreen(
|
||||||
(context) => FactureScreen(
|
|
||||||
commande: commande!,
|
commande: commande!,
|
||||||
paymentMethod: selectedPaymentMethod!.id,
|
paymentMethod: selectedPaymentMethod!.id,
|
||||||
),
|
),
|
||||||
@ -115,7 +125,8 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
|||||||
setState(() => isProcessingPayment = false);
|
setState(() => isProcessingPayment = false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void _showErrorDialog(String message) {
|
void _showErrorDialog(String message) {
|
||||||
showDialog(
|
showDialog(
|
||||||
@ -148,7 +159,7 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
content: Text(
|
content: Text(
|
||||||
'Le paiement de ${commande!.totalTtc.toStringAsFixed(2)} MGA a été traité avec succès via ${selectedPaymentMethod!.name}.',
|
'Le paiement de ${NumberFormat("#,##0.00", "fr_FR").format(commande!.totalTtc)} MGA a été traité avec succès via ${selectedPaymentMethod!.name}.',
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
@ -233,7 +244,7 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${commande!.totalTtc.toStringAsFixed(2)} MGA',
|
'${NumberFormat("#,##0.00", "fr_FR").format(commande!.totalTtc)} MGA',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@ -302,7 +313,7 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Text(
|
Text(
|
||||||
'${item.totalItem.toStringAsFixed(2)} MGA',
|
'${NumberFormat("#,##0.00", "fr_FR").format(item.totalItem)} MGA',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@ -405,7 +416,7 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
|||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
'${amount.toStringAsFixed(2)} MGA',
|
'${NumberFormat("#,##0.00", "fr_FR").format(amount)} MGA',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
@ -467,7 +478,7 @@ class _CaisseScreenState extends State<CaisseScreen> {
|
|||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
selectedPaymentMethod != null
|
selectedPaymentMethod != null
|
||||||
? 'Payer ${commande?.totalTtc.toStringAsFixed(2)} MGA'
|
? 'Payer ${NumberFormat("#,##0.00", "fr_FR").format(commande?.totalTtc)} MGA'
|
||||||
: 'Sélectionnez une méthode de paiement',
|
: 'Sélectionnez une méthode de paiement',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import 'package:itrimobe/pages/tables.dart';
|
|||||||
import 'package:itrimobe/services/pdf_service.dart';
|
import 'package:itrimobe/services/pdf_service.dart';
|
||||||
|
|
||||||
import '../layouts/main_layout.dart';
|
import '../layouts/main_layout.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
class CartPage extends StatefulWidget {
|
class CartPage extends StatefulWidget {
|
||||||
final int tableId;
|
final int tableId;
|
||||||
@ -122,7 +123,7 @@ class _CartPageState extends State<CartPage> {
|
|||||||
Text('• Table: ${widget.tableId}'),
|
Text('• Table: ${widget.tableId}'),
|
||||||
Text('• Personnes: ${widget.personne}'),
|
Text('• Personnes: ${widget.personne}'),
|
||||||
Text('• Articles: ${_getTotalArticles()}'),
|
Text('• Articles: ${_getTotalArticles()}'),
|
||||||
Text('• Total: ${_calculateTotal().toStringAsFixed(2)} MGA'),
|
Text('• Total: ${NumberFormat("#,##0.00", "fr_FR").format(_calculateTotal())} MGA'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
@ -340,7 +341,7 @@ class _CartPageState extends State<CartPage> {
|
|||||||
Text('Articles: ${_cartItems.length}'),
|
Text('Articles: ${_cartItems.length}'),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Total: ${total.toStringAsFixed(0)} MGA',
|
'Total: ${NumberFormat("#,##0.00", "fr_FR").format(total)} MGA',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@ -612,7 +613,7 @@ class _CartPageState extends State<CartPage> {
|
|||||||
Text('Table ${widget.tablename} libérée'),
|
Text('Table ${widget.tablename} libérée'),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
'Montant: ${total.toStringAsFixed(0)} MGA',
|
'Montant: ${NumberFormat("#,##0.00", "fr_FR").format(total)} MGA',
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
@ -887,7 +888,7 @@ class _CartPageState extends State<CartPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${item.prix.toStringAsFixed(2)} MGA l\'unité',
|
'${NumberFormat("#,##0.00", "fr_FR").format(item.prix)} MGA l\'unité',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: Colors.grey[600],
|
color: Colors.grey[600],
|
||||||
@ -949,7 +950,7 @@ class _CartPageState extends State<CartPage> {
|
|||||||
),
|
),
|
||||||
// Prix total de l'article
|
// Prix total de l'article
|
||||||
Text(
|
Text(
|
||||||
'${(item.prix * item.quantity).toStringAsFixed(2)} MGA',
|
'${NumberFormat("#,##0.00", "fr_FR").format(item.prix * item.quantity)} MGA',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@ -1038,7 +1039,7 @@ class _CartPageState extends State<CartPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${_calculateTotal().toStringAsFixed(2)} MGA',
|
'${NumberFormat("#,##0.00", "fr_FR").format(_calculateTotal())} MGA',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@ -1047,6 +1048,8 @@ class _CartPageState extends State<CartPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// Bouton Valider la commande (toujours visible)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
@ -1101,9 +1104,11 @@ class _CartPageState extends State<CartPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(height: 12),
|
// Espacement conditionnel
|
||||||
|
if (MediaQuery.of(context).size.width >= 768) const SizedBox(height: 12),
|
||||||
|
|
||||||
// Bouton Payer directement
|
// Bouton Payer directement (uniquement sur desktop - largeur >= 768px)
|
||||||
|
if (MediaQuery.of(context).size.width >= 768)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
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 'package:intl/intl.dart';
|
||||||
|
|
||||||
// Import de la page de validation (à ajuster selon votre structure de dossiers)
|
// Import de la page de validation (à ajuster selon votre structure de dossiers)
|
||||||
import 'commande_item_validation.dart';
|
import 'commande_item_validation.dart';
|
||||||
@ -635,7 +636,7 @@ class _AddToCartModalState extends State<AddToCartModal> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
"${calculateTotal().toStringAsFixed(2)} MGA",
|
"${NumberFormat("#,##0.00", "fr_FR").format(calculateTotal())} MGA",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
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 'package:intl/intl.dart';
|
||||||
|
|
||||||
class ValidateAddItemsPage extends StatefulWidget {
|
class ValidateAddItemsPage extends StatefulWidget {
|
||||||
final int commandeId;
|
final int commandeId;
|
||||||
@ -151,7 +152,7 @@ class _ValidateAddItemsPageState extends State<ValidateAddItemsPage> {
|
|||||||
Text('• Commande: ${widget.numeroCommande}'),
|
Text('• Commande: ${widget.numeroCommande}'),
|
||||||
Text('• Nouveaux articles: ${_getTotalNewArticles()}'),
|
Text('• Nouveaux articles: ${_getTotalNewArticles()}'),
|
||||||
Text(
|
Text(
|
||||||
'• Nouveau total: ${_calculateGrandTotal().toStringAsFixed(2)} MGA',
|
'• Nouveau total: ${NumberFormat("#,##0.00", "fr_FR").format(_calculateGrandTotal())} MGA',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -473,7 +474,7 @@ class _ValidateAddItemsPageState extends State<ValidateAddItemsPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${item.prix.toStringAsFixed(2)} MGA l\'unité',
|
'${NumberFormat("#,##0.00", "fr_FR").format(item.prix)} MGA l\'unité',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: Colors.grey[600],
|
color: Colors.grey[600],
|
||||||
@ -535,7 +536,7 @@ class _ValidateAddItemsPageState extends State<ValidateAddItemsPage> {
|
|||||||
),
|
),
|
||||||
// Prix total de l'article
|
// Prix total de l'article
|
||||||
Text(
|
Text(
|
||||||
'${(item.prix * item.quantity).toStringAsFixed(2)} MGA',
|
'${NumberFormat("#,##0.00", "fr_FR").format(item.prix * item.quantity)} MGA',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@ -584,7 +585,7 @@ class _ValidateAddItemsPageState extends State<ValidateAddItemsPage> {
|
|||||||
style: TextStyle(fontSize: 16),
|
style: TextStyle(fontSize: 16),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${_calculateExistingItemsTotal().toStringAsFixed(2)} MGA',
|
'${NumberFormat("#,##0.00", "fr_FR").format(_calculateExistingItemsTotal())} MGA',
|
||||||
style: TextStyle(fontSize: 16),
|
style: TextStyle(fontSize: 16),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -611,7 +612,7 @@ class _ValidateAddItemsPageState extends State<ValidateAddItemsPage> {
|
|||||||
style: TextStyle(fontSize: 16, color: Colors.green[700]),
|
style: TextStyle(fontSize: 16, color: Colors.green[700]),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${_calculateNewItemsTotal().toStringAsFixed(2)} MGA',
|
'${NumberFormat("#,##0.00", "fr_FR").format(_calculateNewItemsTotal())} MGA',
|
||||||
style: TextStyle(fontSize: 16, color: Colors.green[700]),
|
style: TextStyle(fontSize: 16, color: Colors.green[700]),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -630,7 +631,7 @@ class _ValidateAddItemsPageState extends State<ValidateAddItemsPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${_calculateGrandTotal().toStringAsFixed(2)} MGA',
|
'${NumberFormat("#,##0.00", "fr_FR").format(_calculateGrandTotal())} MGA',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
|||||||
@ -3,7 +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 'package:intl/intl.dart';
|
||||||
import 'commande_item_screen.dart';
|
import 'commande_item_screen.dart';
|
||||||
|
|
||||||
class OrdersManagementScreen extends StatefulWidget {
|
class OrdersManagementScreen extends StatefulWidget {
|
||||||
@ -291,7 +291,7 @@ class _OrdersManagementScreenState extends State<OrdersManagementScreen> {
|
|||||||
return orders
|
return orders
|
||||||
.where(
|
.where(
|
||||||
(order) =>
|
(order) =>
|
||||||
order.statut == "en_attente" || order.statut == "en_preparation",
|
order.statut == "en_attente" || order.statut == "en_preparation" || order.statut == "prete",
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
@ -311,7 +311,7 @@ class _OrdersManagementScreenState extends State<OrdersManagementScreen> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'${order.tablename} - ${order.totalTtc.toStringAsFixed(2)} MGA',
|
'${order.tablename} - ${NumberFormat("#,##0.00", "fr_FR").format(order.totalTtc)} MGA',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -545,6 +545,8 @@ class OrderCard extends StatelessWidget {
|
|||||||
return Colors.blue;
|
return Colors.blue;
|
||||||
case 'payee':
|
case 'payee':
|
||||||
return Colors.grey;
|
return Colors.grey;
|
||||||
|
case 'prete':
|
||||||
|
return Colors.grey;
|
||||||
default:
|
default:
|
||||||
return Colors.grey;
|
return Colors.grey;
|
||||||
}
|
}
|
||||||
@ -560,6 +562,8 @@ class OrderCard extends StatelessWidget {
|
|||||||
return 'Servie';
|
return 'Servie';
|
||||||
case 'payee':
|
case 'payee':
|
||||||
return 'Payée';
|
return 'Payée';
|
||||||
|
case 'prete':
|
||||||
|
return 'prête';
|
||||||
default:
|
default:
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
@ -655,7 +659,7 @@ class OrderCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${(item.pu ?? 0) * item.quantite} MGA',
|
'${NumberFormat("#,##0.00", "fr_FR").format((item.pu ?? 0) * item.quantite)} MGA',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: Colors.black87,
|
color: Colors.black87,
|
||||||
@ -688,7 +692,7 @@ class OrderCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${order.totalHt.toStringAsFixed(2)} MGA',
|
'${NumberFormat("#,##0.00", "fr_FR").format(order.totalHt)} MGA',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@ -701,11 +705,11 @@ class OrderCard extends StatelessWidget {
|
|||||||
|
|
||||||
// Action buttons
|
// Action buttons
|
||||||
if (order.statut == 'en_attente' ||
|
if (order.statut == 'en_attente' ||
|
||||||
order.statut == 'en_preparation')
|
order.statut == 'en_preparation' || order.statut == 'prete')
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
if (order.statut == 'en_attente' ||
|
if (order.statut == 'en_attente' ||
|
||||||
order.statut == 'en_preparation')
|
order.statut == 'en_preparation' || order.statut == 'prete')
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed:
|
onPressed:
|
||||||
@ -787,6 +791,25 @@ class OrderCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
|
if (order.statut == 'en_preparation')
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed:
|
||||||
|
() => onStatusUpdate(order, 'prete'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Colors.orange,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'Prête',
|
||||||
|
style: TextStyle(color: Colors.white, fontSize: 12),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border.all(color: Colors.red.shade200),
|
border: Border.all(color: Colors.red.shade200),
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import '../models/command_detail.dart';
|
import '../models/command_detail.dart';
|
||||||
import '../services/pdf_service.dart';
|
import '../services/pdf_service.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
class FactureScreen extends StatefulWidget {
|
class FactureScreen extends StatefulWidget {
|
||||||
final CommandeDetail commande;
|
final CommandeDetail commande;
|
||||||
@ -218,7 +219,7 @@ class _FactureScreenState extends State<FactureScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${(item.prixUnitaire * item.quantite).toStringAsFixed(2)} MGA',
|
'${NumberFormat("#,##0.00", "fr_FR").format(item.prixUnitaire * item.quantite)} AR',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Colors.black87,
|
color: Colors.black87,
|
||||||
@ -246,7 +247,7 @@ class _FactureScreenState extends State<FactureScreen> {
|
|||||||
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${widget.commande.totalTtc.toStringAsFixed(2)} MGA',
|
'${NumberFormat("#,##0.00", "fr_FR").format(widget.commande.totalTtc)} AR',
|
||||||
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@ -267,7 +268,7 @@ class _FactureScreenState extends State<FactureScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _printReceipt() async {
|
void _printReceipt() async {
|
||||||
bool isPrinting;
|
bool isPrinting = false;
|
||||||
setState(() => isPrinting = true);
|
setState(() => isPrinting = true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -345,7 +346,7 @@ class _FactureScreenState extends State<FactureScreen> {
|
|||||||
_showSuccessMessage(
|
_showSuccessMessage(
|
||||||
action == 'print'
|
action == 'print'
|
||||||
? 'Facture envoyée à l\'imprimante ${_getPlatformName()}'
|
? 'Facture envoyée à l\'imprimante ${_getPlatformName()}'
|
||||||
: 'PDF sauvegardé et partagé',
|
: 'PDF sauvegardé avec succès',
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
_showErrorMessage(
|
_showErrorMessage(
|
||||||
|
|||||||
@ -15,13 +15,13 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
bool isLoading = true;
|
bool isLoading = true;
|
||||||
String? error;
|
String? error;
|
||||||
|
|
||||||
// Informations de pagination
|
// Informations d'affichage et pagination
|
||||||
|
int totalItems = 0;
|
||||||
int currentPage = 1;
|
int currentPage = 1;
|
||||||
int totalPages = 1;
|
int totalPages = 1;
|
||||||
int totalItems = 0;
|
final int itemsPerPage = 10; // Nombre d'éléments par page
|
||||||
int itemsPerPage = 10;
|
|
||||||
|
|
||||||
final String baseUrl = 'https://restaurant.careeracademy.mg'; // Remplacez par votre URL
|
final String baseUrl = 'https://restaurant.careeracademy.mg';
|
||||||
final Map<String, String> _headers = {
|
final Map<String, String> _headers = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
@ -38,48 +38,187 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
_loadCommandes();
|
_loadCommandes();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _loadCommandes() async {
|
Future<void> _loadCommandes({int page = 1}) async {
|
||||||
try {
|
try {
|
||||||
setState(() {
|
setState(() {
|
||||||
isLoading = true;
|
isLoading = true;
|
||||||
error = null;
|
error = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
final response = await http.get(
|
// Ajouter les paramètres de pagination à l'URL
|
||||||
Uri.parse('$baseUrl/api/commandes?statut=payee'),
|
final uri = Uri.parse('$baseUrl/api/commandes').replace(queryParameters: {
|
||||||
headers: _headers,
|
'statut': 'payee',
|
||||||
);
|
'page': page.toString(),
|
||||||
|
'limit': itemsPerPage.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
final dynamic responseBody = json.decode(response.body);
|
final response = await http.get(uri, headers: _headers);
|
||||||
print('Réponse getCommandes: ${responseBody}');
|
|
||||||
|
print('=== DÉBUT DEBUG RESPONSE ===');
|
||||||
|
print('Status Code: ${response.statusCode}');
|
||||||
|
print('Response Body: ${response.body}');
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
// Adapter la structure de réponse {data: [...], pagination: {...}}
|
final dynamic responseBody = json.decode(response.body);
|
||||||
final Map<String, dynamic> responseData = json.decode(response.body);
|
print('=== PARSED RESPONSE ===');
|
||||||
final List<dynamic> data = responseData['data'] ?? [];
|
print('Type: ${responseBody.runtimeType}');
|
||||||
final Map<String, dynamic> pagination = responseData['pagination'] ?? {};
|
print('Content: $responseBody');
|
||||||
|
|
||||||
|
List<dynamic> data = [];
|
||||||
|
|
||||||
|
// Gestion améliorée de la réponse
|
||||||
|
if (responseBody is Map<String, dynamic>) {
|
||||||
|
print('=== RESPONSE EST UN MAP ===');
|
||||||
|
print('Keys disponibles: ${responseBody.keys.toList()}');
|
||||||
|
|
||||||
|
// Structure: {"success": true, "data": {"commandes": [...], "pagination": {...}}}
|
||||||
|
if (responseBody.containsKey('data') && responseBody['data'] is Map<String, dynamic>) {
|
||||||
|
final dataMap = responseBody['data'] as Map<String, dynamic>;
|
||||||
|
print('=== DATA MAP TROUVÉ ===');
|
||||||
|
print('Data keys: ${dataMap.keys.toList()}');
|
||||||
|
|
||||||
|
if (dataMap.containsKey('commandes')) {
|
||||||
|
final commandesValue = dataMap['commandes'];
|
||||||
|
print('=== COMMANDES TROUVÉES ===');
|
||||||
|
print('Type commandes: ${commandesValue.runtimeType}');
|
||||||
|
print('Nombre de commandes: ${commandesValue is List ? commandesValue.length : 'pas une liste'}');
|
||||||
|
|
||||||
|
if (commandesValue is List<dynamic>) {
|
||||||
|
data = commandesValue;
|
||||||
|
} else if (commandesValue != null) {
|
||||||
|
data = [commandesValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pagination
|
||||||
|
if (dataMap.containsKey('pagination')) {
|
||||||
|
final pagination = dataMap['pagination'] as Map<String, dynamic>?;
|
||||||
|
if (pagination != null) {
|
||||||
|
currentPage = pagination['currentPage'] ?? page;
|
||||||
|
totalPages = pagination['totalPages'] ?? 1;
|
||||||
|
totalItems = pagination['totalItems'] ?? data.length;
|
||||||
|
print('=== PAGINATION ===');
|
||||||
|
print('Page: $currentPage/$totalPages, Total: $totalItems');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Si pas de pagination dans la réponse, calculer approximativement
|
||||||
|
totalItems = data.length;
|
||||||
|
currentPage = page;
|
||||||
|
totalPages = (totalItems / itemsPerPage).ceil();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
print('=== PAS DE COMMANDES DANS DATA ===');
|
||||||
|
totalItems = 0;
|
||||||
|
currentPage = 1;
|
||||||
|
totalPages = 1;
|
||||||
|
}
|
||||||
|
} else if (responseBody.containsKey('commandes')) {
|
||||||
|
// Fallback: commandes directement dans responseBody
|
||||||
|
final commandesValue = responseBody['commandes'];
|
||||||
|
print('=== COMMANDES DIRECTES ===');
|
||||||
|
|
||||||
|
if (commandesValue is List<dynamic>) {
|
||||||
|
data = commandesValue;
|
||||||
|
} else if (commandesValue != null) {
|
||||||
|
data = [commandesValue];
|
||||||
|
}
|
||||||
|
totalItems = data.length;
|
||||||
|
currentPage = page;
|
||||||
|
totalPages = (totalItems / itemsPerPage).ceil();
|
||||||
|
} else {
|
||||||
|
print('=== STRUCTURE INCONNUE ===');
|
||||||
|
print('Clés disponibles: ${responseBody.keys.toList()}');
|
||||||
|
totalItems = 0;
|
||||||
|
currentPage = 1;
|
||||||
|
totalPages = 1;
|
||||||
|
}
|
||||||
|
} else if (responseBody is List<dynamic>) {
|
||||||
|
print('=== RESPONSE EST UNE LISTE ===');
|
||||||
|
data = responseBody;
|
||||||
|
totalItems = data.length;
|
||||||
|
currentPage = page;
|
||||||
|
totalPages = (totalItems / itemsPerPage).ceil();
|
||||||
|
} else {
|
||||||
|
throw Exception('Format de réponse inattendu: ${responseBody.runtimeType}');
|
||||||
|
}
|
||||||
|
|
||||||
|
print('=== DONNÉES EXTRAITES ===');
|
||||||
|
print('Nombre d\'éléments: ${data.length}');
|
||||||
|
print('Data: $data');
|
||||||
|
|
||||||
|
// Conversion sécurisée avec prints détaillés
|
||||||
|
List<CommandeData> parsedCommandes = [];
|
||||||
|
for (int i = 0; i < data.length; i++) {
|
||||||
|
try {
|
||||||
|
final item = data[i];
|
||||||
|
print('=== ITEM $i ===');
|
||||||
|
print('Type: ${item.runtimeType}');
|
||||||
|
print('Contenu complet: $item');
|
||||||
|
|
||||||
|
if (item is Map<String, dynamic>) {
|
||||||
|
print('--- ANALYSE DES CHAMPS ---');
|
||||||
|
item.forEach((key, value) {
|
||||||
|
print('$key: $value (${value.runtimeType})');
|
||||||
|
});
|
||||||
|
|
||||||
|
final commandeData = CommandeData.fromJson(item);
|
||||||
|
print('--- COMMANDE PARSÉE ---');
|
||||||
|
print('ID: ${commandeData.id}');
|
||||||
|
print('Numéro: ${commandeData.numeroCommande}');
|
||||||
|
print('Table name: ${commandeData.tablename}');
|
||||||
|
print('Serveur: ${commandeData.serveur}');
|
||||||
|
print('Date commande: ${commandeData.dateCommande}');
|
||||||
|
print('Date paiement: ${commandeData.datePaiement}');
|
||||||
|
print('Total TTC: ${commandeData.totalTtc}');
|
||||||
|
print('Mode paiement: ${commandeData.modePaiement}');
|
||||||
|
print('Nombre d\'items: ${commandeData.items?.length ?? 0}');
|
||||||
|
|
||||||
|
if (commandeData.items != null) {
|
||||||
|
print('--- ITEMS DE LA COMMANDE ---');
|
||||||
|
for (int j = 0; j < commandeData.items!.length; j++) {
|
||||||
|
final commandeItem = commandeData.items![j];
|
||||||
|
print('Item $j:');
|
||||||
|
print(' - Menu nom: ${commandeItem.menuNom}');
|
||||||
|
print(' - Quantité: ${commandeItem.quantite}');
|
||||||
|
print(' - Prix unitaire: ${commandeItem.prixUnitaire}');
|
||||||
|
print(' - Total: ${commandeItem.totalItem}');
|
||||||
|
print(' - Commentaires: ${commandeItem.commentaires}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedCommandes.add(commandeData);
|
||||||
|
} else {
|
||||||
|
print('ERROR: Item $i n\'est pas un Map: ${item.runtimeType}');
|
||||||
|
}
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
print('ERROR: Erreur lors du parsing de l\'item $i: $e');
|
||||||
|
print('Stack trace: $stackTrace');
|
||||||
|
// Continue avec les autres items
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print('=== RÉSULTAT FINAL ===');
|
||||||
|
print('Nombre de commandes parsées: ${parsedCommandes.length}');
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
commandes = data.map((json) => CommandeData.fromJson(json)).toList();
|
commandes = parsedCommandes;
|
||||||
|
|
||||||
// Mettre à jour les informations de pagination
|
|
||||||
currentPage = pagination['currentPage'] ?? 1;
|
|
||||||
totalPages = pagination['totalPages'] ?? 1;
|
|
||||||
totalItems = pagination['totalItems'] ?? 0;
|
|
||||||
itemsPerPage = pagination['itemsPerPage'] ?? 10;
|
|
||||||
|
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Initialiser les animations après avoir mis à jour l'état
|
||||||
_initializeAnimations();
|
_initializeAnimations();
|
||||||
_startAnimations();
|
_startAnimations();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
print('ERROR: HTTP ${response.statusCode}: ${response.reasonPhrase}');
|
||||||
setState(() {
|
setState(() {
|
||||||
error = 'Erreur lors du chargement des commandes';
|
error = 'Erreur HTTP ${response.statusCode}: ${response.reasonPhrase}';
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e, stackTrace) {
|
||||||
|
print('=== ERREUR GÉNÉRALE ===');
|
||||||
|
print('Erreur: $e');
|
||||||
|
print('Stack trace: $stackTrace');
|
||||||
setState(() {
|
setState(() {
|
||||||
error = 'Erreur de connexion: $e';
|
error = 'Erreur de connexion: $e';
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
@ -87,7 +226,33 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fonction pour aller à la page suivante
|
||||||
|
void _goToNextPage() {
|
||||||
|
if (currentPage < totalPages) {
|
||||||
|
_loadCommandes(page: currentPage + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour aller à la page précédente
|
||||||
|
void _goToPreviousPage() {
|
||||||
|
if (currentPage > 1) {
|
||||||
|
_loadCommandes(page: currentPage - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fonction pour aller à une page spécifique
|
||||||
|
void _goToPage(int page) {
|
||||||
|
if (page >= 1 && page <= totalPages && page != currentPage) {
|
||||||
|
_loadCommandes(page: page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _initializeAnimations() {
|
void _initializeAnimations() {
|
||||||
|
// Disposer les anciens contrôleurs
|
||||||
|
for (var controller in _cardAnimationControllers) {
|
||||||
|
controller.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
_cardAnimationControllers = List.generate(
|
_cardAnimationControllers = List.generate(
|
||||||
commandes.length,
|
commandes.length,
|
||||||
(index) => AnimationController(
|
(index) => AnimationController(
|
||||||
@ -98,11 +263,13 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _startAnimations() async {
|
void _startAnimations() async {
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
_animationController.forward();
|
_animationController.forward();
|
||||||
|
|
||||||
for (int i = 0; i < _cardAnimationControllers.length; i++) {
|
for (int i = 0; i < _cardAnimationControllers.length; i++) {
|
||||||
await Future.delayed(Duration(milliseconds: 150));
|
await Future.delayed(Duration(milliseconds: 150));
|
||||||
if (mounted) {
|
if (mounted && i < _cardAnimationControllers.length) {
|
||||||
_cardAnimationControllers[i].forward();
|
_cardAnimationControllers[i].forward();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -128,13 +295,14 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
elevation: 0,
|
elevation: 0,
|
||||||
),
|
),
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
onRefresh: _loadCommandes,
|
onRefresh: () => _loadCommandes(page: currentPage),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_buildHeader(),
|
_buildHeader(),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _buildContent(),
|
child: _buildContent(),
|
||||||
),
|
),
|
||||||
|
if (totalPages > 1) _buildPagination(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -188,7 +356,9 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 4),
|
padding: EdgeInsets.only(top: 4),
|
||||||
child: Text(
|
child: Text(
|
||||||
'$totalItems commande${totalItems > 1 ? 's' : ''} • Page $currentPage/$totalPages',
|
totalPages > 1
|
||||||
|
? '$totalItems commande${totalItems > 1 ? 's' : ''} • Page $currentPage/$totalPages'
|
||||||
|
: '$totalItems commande${totalItems > 1 ? 's' : ''} trouvée${totalItems > 1 ? 's' : ''}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
color: Colors.grey.shade500,
|
color: Colors.grey.shade500,
|
||||||
@ -206,10 +376,158 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildPagination() {
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withOpacity(0.1),
|
||||||
|
blurRadius: 4,
|
||||||
|
offset: Offset(0, -2),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
// Bouton Précédent
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: currentPage > 1 ? _goToPreviousPage : null,
|
||||||
|
icon: Icon(Icons.chevron_left, size: 18),
|
||||||
|
label: Text('Précédent'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: currentPage > 1 ? Color(0xFF4CAF50) : Colors.grey.shade300,
|
||||||
|
foregroundColor: currentPage > 1 ? Colors.white : Colors.grey.shade600,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
elevation: currentPage > 1 ? 2 : 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Indicateur de page actuelle avec navigation rapide
|
||||||
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
if (totalPages <= 7)
|
||||||
|
// Afficher toutes les pages si <= 7 pages
|
||||||
|
...List.generate(totalPages, (index) {
|
||||||
|
final pageNum = index + 1;
|
||||||
|
return _buildPageButton(pageNum);
|
||||||
|
})
|
||||||
|
else
|
||||||
|
// Afficher une navigation condensée si > 7 pages
|
||||||
|
..._buildCondensedPagination(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Bouton Suivant
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: currentPage < totalPages ? _goToNextPage : null,
|
||||||
|
icon: Icon(Icons.chevron_right, size: 18),
|
||||||
|
label: Text('Suivant'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: currentPage < totalPages ? Color(0xFF4CAF50) : Colors.grey.shade300,
|
||||||
|
foregroundColor: currentPage < totalPages ? Colors.white : Colors.grey.shade600,
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
elevation: currentPage < totalPages ? 2 : 0,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPageButton(int pageNum) {
|
||||||
|
final isCurrentPage = pageNum == currentPage;
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => _goToPage(pageNum),
|
||||||
|
child: Container(
|
||||||
|
margin: EdgeInsets.symmetric(horizontal: 2),
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isCurrentPage ? Color(0xFF4CAF50) : Colors.transparent,
|
||||||
|
borderRadius: BorderRadius.circular(6),
|
||||||
|
border: Border.all(
|
||||||
|
color: isCurrentPage ? Color(0xFF4CAF50) : Colors.grey.shade300,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
pageNum.toString(),
|
||||||
|
style: TextStyle(
|
||||||
|
color: isCurrentPage ? Colors.white : Colors.grey.shade700,
|
||||||
|
fontWeight: isCurrentPage ? FontWeight.bold : FontWeight.normal,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildCondensedPagination() {
|
||||||
|
List<Widget> pages = [];
|
||||||
|
|
||||||
|
// Toujours afficher la première page
|
||||||
|
pages.add(_buildPageButton(1));
|
||||||
|
|
||||||
|
if (currentPage > 4) {
|
||||||
|
pages.add(Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 4),
|
||||||
|
child: Text('...', style: TextStyle(color: Colors.grey)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Afficher les pages autour de la page actuelle
|
||||||
|
int start = (currentPage - 2).clamp(2, totalPages - 1);
|
||||||
|
int end = (currentPage + 2).clamp(2, totalPages - 1);
|
||||||
|
|
||||||
|
for (int i = start; i <= end; i++) {
|
||||||
|
if (i != 1 && i != totalPages) {
|
||||||
|
pages.add(_buildPageButton(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPage < totalPages - 3) {
|
||||||
|
pages.add(Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 4),
|
||||||
|
child: Text('...', style: TextStyle(color: Colors.grey)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toujours afficher la dernière page si > 1
|
||||||
|
if (totalPages > 1) {
|
||||||
|
pages.add(_buildPageButton(totalPages));
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildContent() {
|
Widget _buildContent() {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return Center(
|
return Center(
|
||||||
child: CircularProgressIndicator(),
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
CircularProgressIndicator(
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFF4CAF50)),
|
||||||
|
),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
'Chargement des commandes...',
|
||||||
|
style: TextStyle(color: Colors.grey.shade600),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,11 +538,22 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
children: [
|
children: [
|
||||||
Icon(Icons.error_outline, size: 64, color: Colors.grey),
|
Icon(Icons.error_outline, size: 64, color: Colors.grey),
|
||||||
SizedBox(height: 16),
|
SizedBox(height: 16),
|
||||||
Text(error!, style: TextStyle(color: Colors.grey)),
|
Padding(
|
||||||
|
padding: EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: Text(
|
||||||
|
error!,
|
||||||
|
style: TextStyle(color: Colors.grey),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
SizedBox(height: 16),
|
SizedBox(height: 16),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: _loadCommandes,
|
onPressed: () => _loadCommandes(page: currentPage),
|
||||||
child: Text('Réessayer'),
|
child: Text('Réessayer'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Color(0xFF4CAF50),
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -236,12 +565,25 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(Icons.payment, size: 64, color: Colors.grey),
|
Icon(Icons.restaurant_menu, size: 64, color: Colors.grey),
|
||||||
SizedBox(height: 16),
|
SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
'Aucune commande payée',
|
currentPage > 1
|
||||||
|
? 'Aucune commande sur cette page'
|
||||||
|
: 'Aucune commande payée',
|
||||||
style: TextStyle(color: Colors.grey, fontSize: 16),
|
style: TextStyle(color: Colors.grey, fontSize: 16),
|
||||||
),
|
),
|
||||||
|
if (currentPage > 1) ...[
|
||||||
|
SizedBox(height: 16),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => _goToPage(1),
|
||||||
|
child: Text('Retour à la première page'),
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: Color(0xFF4CAF50),
|
||||||
|
foregroundColor: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -345,7 +687,7 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
commande.tablename,
|
commande.tablename ?? 'Table inconnue',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@ -354,7 +696,7 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
),
|
),
|
||||||
SizedBox(height: 2),
|
SizedBox(height: 2),
|
||||||
Text(
|
Text(
|
||||||
commande.numeroCommande,
|
commande.numeroCommande ?? 'N/A',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
color: Colors.grey,
|
color: Colors.grey,
|
||||||
@ -367,18 +709,31 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
Icon(Icons.calendar_today, size: 12, color: Colors.grey),
|
Icon(Icons.calendar_today, size: 12, color: Colors.grey),
|
||||||
SizedBox(width: 3),
|
SizedBox(width: 3),
|
||||||
Text(
|
Text(
|
||||||
_formatDateTime(commande.dateCommande),
|
commande.dateCommande != null
|
||||||
|
? _formatDateTime(commande.dateCommande!)
|
||||||
|
: 'Date inconnue',
|
||||||
style: TextStyle(color: Colors.grey, fontSize: 10),
|
style: TextStyle(color: Colors.grey, fontSize: 10),
|
||||||
),
|
),
|
||||||
SizedBox(width: 8),
|
SizedBox(width: 8),
|
||||||
Icon(Icons.person, size: 12, color: Colors.grey),
|
Icon(Icons.person, size: 12, color: Colors.grey),
|
||||||
SizedBox(width: 3),
|
SizedBox(width: 3),
|
||||||
Text(
|
Text(
|
||||||
commande.serveur,
|
commande.serveur ?? 'Serveur inconnu',
|
||||||
style: TextStyle(color: Colors.grey, fontSize: 10),
|
style: TextStyle(color: Colors.grey, fontSize: 10),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
if (commande.datePaiement != null)
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.payment, size: 12, color: Colors.green),
|
||||||
|
SizedBox(width: 3),
|
||||||
|
Text(
|
||||||
|
'Payée: ${_formatDateTime(commande.datePaiement!)}',
|
||||||
|
style: TextStyle(color: Colors.green, fontSize: 10),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -386,7 +741,7 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
colors: [Color(0xFF4CAF50), Color(0xFF45a049)],
|
colors: [Color(0xFF4CAF50), Color(0xFF388E3C)],
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
@ -420,7 +775,7 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
return Container(
|
return Container(
|
||||||
padding: EdgeInsets.all(10),
|
padding: EdgeInsets.all(10),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: commande.items.map((item) => _buildOrderItem(item)).toList(),
|
children: (commande.items ?? []).map((item) => _buildOrderItem(item)).toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -477,7 +832,7 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${item.quantite}x × ${_formatPrice(item.prixUnitaire)}',
|
'${item.quantite} × ${_formatPrice(item.prixUnitaire)}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
color: Colors.grey,
|
color: Colors.grey,
|
||||||
@ -519,7 +874,7 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
height: 28,
|
height: 28,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
colors: [Color(0xFF4CAF50), Color(0xFF45a049)],
|
colors: [Color(0xFF4CAF50), Color(0xFF388E3C)],
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
),
|
),
|
||||||
@ -542,9 +897,9 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
color: Colors.grey.shade600,
|
color: Colors.grey.shade600,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (commande.totalTva > 0)
|
if ((commande.totalTva ?? 0) > 0)
|
||||||
Text(
|
Text(
|
||||||
'TVA: ${_formatPrice(commande.totalTva)}',
|
'TVA: ${_formatPrice(commande.totalTva ?? 0)}',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 9,
|
fontSize: 9,
|
||||||
color: Colors.grey.shade500,
|
color: Colors.grey.shade500,
|
||||||
@ -569,7 +924,7 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
size: 14,
|
size: 14,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
_formatPrice(commande.totalTtc),
|
_formatPrice(commande.totalTtc ?? 0),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@ -589,7 +944,6 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _formatPrice(double priceInCents) {
|
String _formatPrice(double priceInCents) {
|
||||||
// Les prix sont déjà en centimes dans votre API (ex: 20000.00 = 200.00 €)
|
|
||||||
return '${(priceInCents / 100).toStringAsFixed(2)} Ar';
|
return '${(priceInCents / 100).toStringAsFixed(2)} Ar';
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -644,87 +998,178 @@ class _OrderHistoryPageState extends State<OrderHistoryPage>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modèles de données adaptés à votre API
|
// Modèles de données avec gestion des valeurs nulles et debug amélioré
|
||||||
class CommandeData {
|
class CommandeData {
|
||||||
final int id;
|
final int? id;
|
||||||
final int clientId;
|
final int? clientId;
|
||||||
final int tableId;
|
final int? tableId;
|
||||||
final int reservationId;
|
final int? reservationId;
|
||||||
final String numeroCommande;
|
final String? numeroCommande;
|
||||||
final String statut;
|
final String? statut;
|
||||||
final double totalHt;
|
final double? totalHt;
|
||||||
final double totalTva;
|
final double? totalTva;
|
||||||
final double totalTtc;
|
final double? totalTtc;
|
||||||
final String? modePaiement;
|
final String? modePaiement;
|
||||||
final String? commentaires;
|
final String? commentaires;
|
||||||
final String serveur;
|
final String? serveur;
|
||||||
final DateTime dateCommande;
|
final DateTime? dateCommande;
|
||||||
final DateTime? dateService;
|
final DateTime? datePaiement;
|
||||||
final DateTime createdAt;
|
final DateTime? createdAt;
|
||||||
final DateTime updatedAt;
|
final DateTime? updatedAt;
|
||||||
final List<CommandeItem> items;
|
final List<CommandeItem>? items;
|
||||||
final String tablename;
|
final String? tablename;
|
||||||
|
|
||||||
CommandeData({
|
CommandeData({
|
||||||
required this.id,
|
this.id,
|
||||||
required this.clientId,
|
this.clientId,
|
||||||
required this.tableId,
|
this.tableId,
|
||||||
required this.reservationId,
|
this.reservationId,
|
||||||
required this.numeroCommande,
|
this.numeroCommande,
|
||||||
required this.statut,
|
this.statut,
|
||||||
required this.totalHt,
|
this.totalHt,
|
||||||
required this.totalTva,
|
this.totalTva,
|
||||||
required this.totalTtc,
|
this.totalTtc,
|
||||||
this.modePaiement,
|
this.modePaiement,
|
||||||
this.commentaires,
|
this.commentaires,
|
||||||
required this.serveur,
|
this.serveur,
|
||||||
required this.dateCommande,
|
this.dateCommande,
|
||||||
this.dateService,
|
this.datePaiement,
|
||||||
required this.createdAt,
|
this.createdAt,
|
||||||
required this.updatedAt,
|
this.updatedAt,
|
||||||
required this.items,
|
this.items,
|
||||||
required this.tablename,
|
this.tablename,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory CommandeData.fromJson(Map<String, dynamic> json) {
|
factory CommandeData.fromJson(Map<String, dynamic> json) {
|
||||||
return CommandeData(
|
try {
|
||||||
id: json['id'],
|
|
||||||
|
// Parsing avec debug détaillé
|
||||||
|
final id = json['id'];
|
||||||
|
|
||||||
|
final numeroCommande = json['numero_commande']?.toString();
|
||||||
|
|
||||||
|
final tablename = json['tablename']?.toString() ?? json['table_name']?.toString() ?? 'Table inconnue';
|
||||||
|
final serveur = json['serveur']?.toString() ?? json['server']?.toString() ?? 'Serveur inconnu';
|
||||||
|
|
||||||
|
final dateCommande = _parseDateTime(json['date_commande']) ?? _parseDateTime(json['created_at']);
|
||||||
|
|
||||||
|
final datePaiement = _parseDateTime(json['date_paiement']) ?? _parseDateTime(json['date_service']);
|
||||||
|
|
||||||
|
final totalTtc = _parseDouble(json['total_ttc']) ?? _parseDouble(json['total']);
|
||||||
|
|
||||||
|
final modePaiement = json['mode_paiement']?.toString() ?? json['payment_method']?.toString();
|
||||||
|
|
||||||
|
final items = _parseItems(json['items']);
|
||||||
|
|
||||||
|
final result = CommandeData(
|
||||||
|
id: id,
|
||||||
clientId: json['client_id'],
|
clientId: json['client_id'],
|
||||||
tableId: json['table_id'],
|
tableId: json['table_id'],
|
||||||
reservationId: json['reservation_id'],
|
reservationId: json['reservation_id'],
|
||||||
numeroCommande: json['numero_commande'],
|
numeroCommande: numeroCommande,
|
||||||
statut: json['statut'],
|
statut: json['statut']?.toString(),
|
||||||
totalHt: (json['total_ht'] ?? 0).toDouble(),
|
totalHt: _parseDouble(json['total_ht']),
|
||||||
totalTva: (json['total_tva'] ?? 0).toDouble(),
|
totalTva: _parseDouble(json['total_tva']),
|
||||||
totalTtc: (json['total_ttc'] ?? 0).toDouble(),
|
totalTtc: totalTtc,
|
||||||
modePaiement: json['mode_paiement'],
|
modePaiement: modePaiement,
|
||||||
commentaires: json['commentaires'],
|
commentaires: json['commentaires']?.toString(),
|
||||||
serveur: json['serveur'],
|
serveur: serveur,
|
||||||
dateCommande: DateTime.parse(json['date_commande']),
|
dateCommande: dateCommande,
|
||||||
dateService: json['date_service'] != null
|
datePaiement: datePaiement,
|
||||||
? DateTime.parse(json['date_service'])
|
createdAt: _parseDateTime(json['created_at']),
|
||||||
: null,
|
updatedAt: _parseDateTime(json['updated_at']),
|
||||||
createdAt: DateTime.parse(json['created_at']),
|
items: items,
|
||||||
updatedAt: DateTime.parse(json['updated_at']),
|
tablename: tablename,
|
||||||
items: (json['items'] as List)
|
|
||||||
.map((item) => CommandeItem.fromJson(item))
|
|
||||||
.toList(),
|
|
||||||
tablename: json['tablename'] ?? 'Table inconnue',
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
print('=== COMMANDE PARSÉE AVEC SUCCÈS ===');
|
||||||
|
return result;
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
print('=== ERREUR PARSING COMMANDE ===');
|
||||||
|
print('Erreur: $e');
|
||||||
|
print('JSON: $json');
|
||||||
|
print('Stack trace: $stackTrace');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static double? _parseDouble(dynamic value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
if (value is double) return value;
|
||||||
|
if (value is int) return value.toDouble();
|
||||||
|
if (value is String) {
|
||||||
|
final result = double.tryParse(value);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DateTime? _parseDateTime(dynamic value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
if (value is String) {
|
||||||
|
try {
|
||||||
|
final result = DateTime.parse(value);
|
||||||
|
print('String to datetime: "$value" -> $result');
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
print('Erreur parsing date: $value - $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print('Impossible de parser en datetime: $value');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<CommandeItem>? _parseItems(dynamic value) {
|
||||||
|
print('=== PARSING ITEMS ===');
|
||||||
|
print('Items bruts: $value (${value.runtimeType})');
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
print('Items null');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value is! List) {
|
||||||
|
print('Items n\'est pas une liste: ${value.runtimeType}');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<CommandeItem> result = [];
|
||||||
|
for (int i = 0; i < value.length; i++) {
|
||||||
|
print('--- ITEM $i ---');
|
||||||
|
final item = value[i];
|
||||||
|
print('Item brut: $item (${item.runtimeType})');
|
||||||
|
|
||||||
|
if (item is Map<String, dynamic>) {
|
||||||
|
final commandeItem = CommandeItem.fromJson(item);
|
||||||
|
result.add(commandeItem);
|
||||||
|
print('Item parsé: ${commandeItem.menuNom}');
|
||||||
|
} else {
|
||||||
|
print('Item $i n\'est pas un Map');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
print('Total items parsés: ${result.length}');
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
print('Erreur parsing items: $e');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String getTableShortName() {
|
String getTableShortName() {
|
||||||
if (tablename.toLowerCase().contains('caisse')) return 'C';
|
final name = tablename ?? 'Table';
|
||||||
if (tablename.toLowerCase().contains('terrasse')) return 'T';
|
if (name.toLowerCase().contains('caisse')) return 'C';
|
||||||
|
if (name.toLowerCase().contains('terrasse')) return 'T';
|
||||||
|
|
||||||
// Extraire le numéro de la table
|
|
||||||
RegExp regExp = RegExp(r'\d+');
|
RegExp regExp = RegExp(r'\d+');
|
||||||
Match? match = regExp.firstMatch(tablename);
|
Match? match = regExp.firstMatch(name);
|
||||||
if (match != null) {
|
if (match != null) {
|
||||||
return 'T${match.group(0)}';
|
return 'T${match.group(0)}';
|
||||||
}
|
}
|
||||||
|
|
||||||
return tablename.substring(0, 1).toUpperCase();
|
return name.isNotEmpty ? name.substring(0, 1).toUpperCase() : 'T';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -737,8 +1182,8 @@ class CommandeItem {
|
|||||||
final double totalItem;
|
final double totalItem;
|
||||||
final String? commentaires;
|
final String? commentaires;
|
||||||
final String statut;
|
final String statut;
|
||||||
final DateTime createdAt;
|
final DateTime? createdAt;
|
||||||
final DateTime updatedAt;
|
final DateTime? updatedAt;
|
||||||
final String menuNom;
|
final String menuNom;
|
||||||
final String menuDescription;
|
final String menuDescription;
|
||||||
final double menuPrixActuel;
|
final double menuPrixActuel;
|
||||||
@ -753,8 +1198,8 @@ class CommandeItem {
|
|||||||
required this.totalItem,
|
required this.totalItem,
|
||||||
this.commentaires,
|
this.commentaires,
|
||||||
required this.statut,
|
required this.statut,
|
||||||
required this.createdAt,
|
this.createdAt,
|
||||||
required this.updatedAt,
|
this.updatedAt,
|
||||||
required this.menuNom,
|
required this.menuNom,
|
||||||
required this.menuDescription,
|
required this.menuDescription,
|
||||||
required this.menuPrixActuel,
|
required this.menuPrixActuel,
|
||||||
@ -762,31 +1207,82 @@ class CommandeItem {
|
|||||||
});
|
});
|
||||||
|
|
||||||
factory CommandeItem.fromJson(Map<String, dynamic> json) {
|
factory CommandeItem.fromJson(Map<String, dynamic> json) {
|
||||||
return CommandeItem(
|
try {
|
||||||
id: json['id'],
|
print('=== PARSING COMMANDE ITEM ===');
|
||||||
commandeId: json['commande_id'],
|
print('JSON item: $json');
|
||||||
menuId: json['menu_id'],
|
|
||||||
quantite: json['quantite'],
|
// Debug chaque champ
|
||||||
prixUnitaire: (json['prix_unitaire'] ?? 0).toDouble(),
|
final id = json['id'] ?? 0;
|
||||||
totalItem: (json['total_item'] ?? 0).toDouble(),
|
print('ID: ${json['id']} -> $id');
|
||||||
commentaires: json['commentaires'],
|
|
||||||
statut: json['statut'],
|
final commandeId = json['commande_id'] ?? 0;
|
||||||
createdAt: DateTime.parse(json['created_at']),
|
print('Commande ID: ${json['commande_id']} -> $commandeId');
|
||||||
updatedAt: DateTime.parse(json['updated_at']),
|
|
||||||
menuNom: json['menu_nom'],
|
final menuId = json['menu_id'] ?? 0;
|
||||||
menuDescription: json['menu_description'],
|
print('Menu ID: ${json['menu_id']} -> $menuId');
|
||||||
menuPrixActuel: (json['menu_prix_actuel'] ?? 0).toDouble(),
|
|
||||||
tablename: json['tablename'] ?? '',
|
final quantite = json['quantite'] ?? json['quantity'] ?? 0;
|
||||||
|
print('Quantité: ${json['quantite']} / ${json['quantity']} -> $quantite');
|
||||||
|
|
||||||
|
final prixUnitaire = CommandeData._parseDouble(json['prix_unitaire']) ??
|
||||||
|
CommandeData._parseDouble(json['unit_price']) ?? 0.0;
|
||||||
|
print('Prix unitaire: ${json['prix_unitaire']} / ${json['unit_price']} -> $prixUnitaire');
|
||||||
|
|
||||||
|
final totalItem = CommandeData._parseDouble(json['total_item']) ??
|
||||||
|
CommandeData._parseDouble(json['total']) ?? 0.0;
|
||||||
|
print('Total item: ${json['total_item']} / ${json['total']} -> $totalItem');
|
||||||
|
|
||||||
|
final commentaires = json['commentaires']?.toString() ?? json['comments']?.toString();
|
||||||
|
print('Commentaires: ${json['commentaires']} / ${json['comments']} -> $commentaires');
|
||||||
|
|
||||||
|
final statut = json['statut']?.toString() ?? json['status']?.toString() ?? '';
|
||||||
|
final menuNom = json['menu_nom']?.toString() ??
|
||||||
|
json['menu_name']?.toString() ??
|
||||||
|
json['name']?.toString() ?? 'Menu inconnu';
|
||||||
|
|
||||||
|
final menuDescription = json['menu_description']?.toString() ??
|
||||||
|
json['description']?.toString() ?? '';
|
||||||
|
print('Menu description: ${json['menu_description']} / ${json['description']} -> $menuDescription');
|
||||||
|
|
||||||
|
final menuPrixActuel = CommandeData._parseDouble(json['menu_prix_actuel']) ??
|
||||||
|
CommandeData._parseDouble(json['current_price']) ?? 0.0;
|
||||||
|
print('Menu prix actuel: ${json['menu_prix_actuel']} / ${json['current_price']} -> $menuPrixActuel');
|
||||||
|
|
||||||
|
final tablename = json['tablename']?.toString() ??
|
||||||
|
json['table_name']?.toString() ?? '';
|
||||||
|
print('Table name: ${json['tablename']} / ${json['table_name']} -> $tablename');
|
||||||
|
|
||||||
|
final result = CommandeItem(
|
||||||
|
id: id,
|
||||||
|
commandeId: commandeId,
|
||||||
|
menuId: menuId,
|
||||||
|
quantite: quantite,
|
||||||
|
prixUnitaire: prixUnitaire,
|
||||||
|
totalItem: totalItem,
|
||||||
|
commentaires: commentaires,
|
||||||
|
statut: statut,
|
||||||
|
createdAt: CommandeData._parseDateTime(json['created_at']),
|
||||||
|
updatedAt: CommandeData._parseDateTime(json['updated_at']),
|
||||||
|
menuNom: menuNom,
|
||||||
|
menuDescription: menuDescription,
|
||||||
|
menuPrixActuel: menuPrixActuel,
|
||||||
|
tablename: tablename,
|
||||||
);
|
);
|
||||||
|
return result;
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
print('=== ERREUR PARSING ITEM ===');
|
||||||
|
print('Erreur: $e');
|
||||||
|
print('JSON: $json');
|
||||||
|
print('Stack trace: $stackTrace');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage dans votre app principale
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Historique des Commandes',
|
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
primarySwatch: Colors.blue,
|
primarySwatch: Colors.blue,
|
||||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||||
|
|||||||
@ -11,14 +11,15 @@ import 'package:path_provider/path_provider.dart';
|
|||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import '../models/command_detail.dart';
|
import '../models/command_detail.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
class PlatformPrintService {
|
class PlatformPrintService {
|
||||||
// Format spécifique 58mm pour petites imprimantes
|
// Format spécifique 58mm pour petites imprimantes - CENTRÉ POUR L'IMPRESSION
|
||||||
static const PdfPageFormat ticket58mmFormat = PdfPageFormat(
|
static const PdfPageFormat ticket58mmFormat = PdfPageFormat(
|
||||||
58 * PdfPageFormat.mm, // Largeur exacte 58mm
|
48 * PdfPageFormat.mm, // Largeur exacte 58mm
|
||||||
double.infinity, // Hauteur automatique
|
double.infinity, // Hauteur automatique
|
||||||
marginLeft: 1 * PdfPageFormat.mm,
|
marginLeft: 4 * PdfPageFormat.mm, // ✅ Marges équilibrées pour centrer
|
||||||
marginRight: 1 * PdfPageFormat.mm,
|
marginRight: 4 * PdfPageFormat.mm, // ✅ Marges équilibrées pour centrer
|
||||||
marginTop: 2 * PdfPageFormat.mm,
|
marginTop: 2 * PdfPageFormat.mm,
|
||||||
marginBottom: 2 * PdfPageFormat.mm,
|
marginBottom: 2 * PdfPageFormat.mm,
|
||||||
);
|
);
|
||||||
@ -40,15 +41,14 @@ class PlatformPrintService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Générer PDF optimisé pour 58mm
|
// Générer PDF optimisé pour 58mm - VERSION IDENTIQUE À L'ÉCRAN
|
||||||
static Future<Uint8List> _generate58mmTicketPdf({
|
static Future<Uint8List> _generate58mmTicketPdf({
|
||||||
required CommandeDetail commande,
|
required CommandeDetail commande,
|
||||||
required String paymentMethod,
|
required String paymentMethod,
|
||||||
}) async {
|
}) async {
|
||||||
final pdf = pw.Document();
|
final pdf = pw.Document();
|
||||||
|
|
||||||
// Configuration pour 58mm (très petit)
|
const double titleSize = 8;
|
||||||
const double titleSize = 9;
|
|
||||||
const double headerSize = 8;
|
const double headerSize = 8;
|
||||||
const double bodySize = 7;
|
const double bodySize = 7;
|
||||||
const double smallSize = 6;
|
const double smallSize = 6;
|
||||||
@ -56,30 +56,42 @@ class PlatformPrintService {
|
|||||||
|
|
||||||
final restaurantInfo = {
|
final restaurantInfo = {
|
||||||
'nom': 'RESTAURANT ITRIMOBE',
|
'nom': 'RESTAURANT ITRIMOBE',
|
||||||
'adresse': 'Moramanga, Antananarivo',
|
'adresse': 'Moramanga, Madagascar',
|
||||||
'ville': 'Madagascar',
|
'contact': '+261 34 12 34 56',
|
||||||
'contact': '261348415301',
|
|
||||||
'email': 'contact@careeragency.mg',
|
|
||||||
'nif': '4002141594',
|
'nif': '4002141594',
|
||||||
'stat': '10715 33 2025 0 00414',
|
'stat': '10715 33 2025 0 00414',
|
||||||
};
|
};
|
||||||
|
|
||||||
final factureNumber =
|
final factureNumber = 'F${DateTime.now().millisecondsSinceEpoch.toString().substring(7)}';
|
||||||
'T${DateTime.now().millisecondsSinceEpoch.toString().substring(8)}';
|
|
||||||
final dateTime = DateTime.now();
|
final dateTime = DateTime.now();
|
||||||
|
|
||||||
|
String paymentMethodText;
|
||||||
|
switch (paymentMethod) {
|
||||||
|
case 'mvola':
|
||||||
|
paymentMethodText = 'MVola';
|
||||||
|
break;
|
||||||
|
case 'carte':
|
||||||
|
paymentMethodText = 'CB';
|
||||||
|
break;
|
||||||
|
case 'especes':
|
||||||
|
paymentMethodText = 'Espèces';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
paymentMethodText = 'CB';
|
||||||
|
}
|
||||||
|
|
||||||
pdf.addPage(
|
pdf.addPage(
|
||||||
pw.Page(
|
pw.Page(
|
||||||
pageFormat: ticket58mmFormat,
|
pageFormat: ticket58mmFormat,
|
||||||
margin: const pw.EdgeInsets.all(2), // 🔧 Marges minimales
|
margin: const pw.EdgeInsets.all(2),
|
||||||
|
|
||||||
build: (pw.Context context) {
|
build: (pw.Context context) {
|
||||||
return pw.Container(
|
return pw.Container(
|
||||||
width: double.infinity, // 🔧 Forcer la largeur complète
|
width: double.infinity,
|
||||||
child: pw.Column(
|
child: pw.Column(
|
||||||
crossAxisAlignment:
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||||
pw.CrossAxisAlignment.start, // 🔧 Alignement à gauche
|
|
||||||
children: [
|
children: [
|
||||||
// En-tête Restaurant (centré et compact)
|
// TITRE CENTRÉ
|
||||||
pw.Container(
|
pw.Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: pw.Text(
|
child: pw.Text(
|
||||||
@ -92,30 +104,103 @@ class PlatformPrintService {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
pw.SizedBox(height: 2),
|
||||||
|
|
||||||
|
// ADRESSE GAUCHE DÉCALÉE VERS LA GAUCHE (marginRight)
|
||||||
|
pw.Container(
|
||||||
|
width: double.infinity,
|
||||||
|
margin: const pw.EdgeInsets.only(right: 6),
|
||||||
|
child: pw.Text(
|
||||||
|
'Adresse: ${restaurantInfo['adresse']!}',
|
||||||
|
style: pw.TextStyle(fontSize: smallSize),
|
||||||
|
textAlign: pw.TextAlign.left,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// CONTACT GAUCHE DÉCALÉE
|
||||||
|
pw.Container(
|
||||||
|
width: double.infinity,
|
||||||
|
margin: const pw.EdgeInsets.only(right: 8),
|
||||||
|
child: pw.Text(
|
||||||
|
'Contact: ${restaurantInfo['contact']!}',
|
||||||
|
style: pw.TextStyle(fontSize: smallSize),
|
||||||
|
textAlign: pw.TextAlign.left,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// NIF GAUCHE DÉCALÉE
|
||||||
|
pw.Container(
|
||||||
|
width: double.infinity,
|
||||||
|
margin: const pw.EdgeInsets.only(right: 8),
|
||||||
|
child: pw.Text(
|
||||||
|
'NIF: ${restaurantInfo['nif']!}',
|
||||||
|
style: pw.TextStyle(fontSize: smallSize),
|
||||||
|
textAlign: pw.TextAlign.left,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// STAT GAUCHE DÉCALÉE
|
||||||
|
pw.Container(
|
||||||
|
width: double.infinity,
|
||||||
|
margin: const pw.EdgeInsets.only(right: 8),
|
||||||
|
child: pw.Text(
|
||||||
|
'STAT: ${restaurantInfo['stat']!}',
|
||||||
|
style: pw.TextStyle(fontSize: smallSize),
|
||||||
|
textAlign: pw.TextAlign.left,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
pw.SizedBox(height: 3),
|
||||||
|
|
||||||
|
// Ligne de séparation
|
||||||
|
pw.Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: 0.5,
|
||||||
|
color: PdfColors.black,
|
||||||
|
),
|
||||||
|
|
||||||
|
pw.SizedBox(height: 2),
|
||||||
|
|
||||||
|
// FACTURE CENTRÉE
|
||||||
|
pw.Container(
|
||||||
|
width: double.infinity,
|
||||||
|
child: pw.Text(
|
||||||
|
'Facture n° $factureNumber',
|
||||||
|
style: pw.TextStyle(
|
||||||
|
fontSize: bodySize,
|
||||||
|
fontWeight: pw.FontWeight.bold,
|
||||||
|
),
|
||||||
|
textAlign: pw.TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
pw.SizedBox(height: 1),
|
pw.SizedBox(height: 1),
|
||||||
|
|
||||||
|
// DATE CENTRÉE
|
||||||
pw.Container(
|
pw.Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: pw.Text(
|
child: pw.Text(
|
||||||
restaurantInfo['adresse']!,
|
'Date: ${_formatDate(dateTime)} ${_formatTime(dateTime)}',
|
||||||
style: pw.TextStyle(fontSize: smallSize),
|
style: pw.TextStyle(fontSize: smallSize),
|
||||||
textAlign: pw.TextAlign.center,
|
textAlign: pw.TextAlign.center,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// TABLE CENTRÉE
|
||||||
pw.Container(
|
pw.Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: pw.Text(
|
child: pw.Text(
|
||||||
restaurantInfo['ville']!,
|
'Via: ${commande.tablename ?? "N/A"}',
|
||||||
style: pw.TextStyle(fontSize: smallSize),
|
style: pw.TextStyle(fontSize: smallSize),
|
||||||
textAlign: pw.TextAlign.center,
|
textAlign: pw.TextAlign.center,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// PAIEMENT CENTRÉ
|
||||||
pw.Container(
|
pw.Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: pw.Text(
|
child: pw.Text(
|
||||||
'Tel: ${restaurantInfo['contact']!}',
|
'Paiement: $paymentMethodText',
|
||||||
style: pw.TextStyle(fontSize: smallSize),
|
style: pw.TextStyle(fontSize: smallSize),
|
||||||
textAlign: pw.TextAlign.center,
|
textAlign: pw.TextAlign.center,
|
||||||
),
|
),
|
||||||
@ -132,14 +217,21 @@ class PlatformPrintService {
|
|||||||
|
|
||||||
pw.SizedBox(height: 2),
|
pw.SizedBox(height: 2),
|
||||||
|
|
||||||
// Informations ticket
|
// EN-TÊTE DES ARTICLES
|
||||||
pw.Container(
|
pw.Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: pw.Row(
|
child: pw.Row(
|
||||||
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
pw.Text(
|
pw.Text(
|
||||||
'Ticket: $factureNumber',
|
'Qte Designation',
|
||||||
|
style: pw.TextStyle(
|
||||||
|
fontSize: bodySize,
|
||||||
|
fontWeight: pw.FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
pw.Text(
|
||||||
|
'Prix',
|
||||||
style: pw.TextStyle(
|
style: pw.TextStyle(
|
||||||
fontSize: bodySize,
|
fontSize: bodySize,
|
||||||
fontWeight: pw.FontWeight.bold,
|
fontWeight: pw.FontWeight.bold,
|
||||||
@ -149,28 +241,6 @@ class PlatformPrintService {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
pw.SizedBox(height: 1),
|
|
||||||
|
|
||||||
pw.Container(
|
|
||||||
width: double.infinity,
|
|
||||||
child: pw.Row(
|
|
||||||
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
pw.Text(
|
|
||||||
_formatDate(dateTime),
|
|
||||||
style: pw.TextStyle(fontSize: smallSize),
|
|
||||||
),
|
|
||||||
pw.Text(
|
|
||||||
_formatTime(dateTime),
|
|
||||||
style: pw.TextStyle(fontSize: smallSize),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
pw.SizedBox(height: 2),
|
|
||||||
|
|
||||||
// Ligne de séparation
|
|
||||||
pw.Container(
|
pw.Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 0.5,
|
height: 0.5,
|
||||||
@ -179,46 +249,27 @@ class PlatformPrintService {
|
|||||||
|
|
||||||
pw.SizedBox(height: 2),
|
pw.SizedBox(height: 2),
|
||||||
|
|
||||||
// Articles (format très compact)
|
// ARTICLES
|
||||||
...commande.items
|
...commande.items
|
||||||
.map(
|
.map(
|
||||||
(item) => pw.Container(
|
(item) => pw.Container(
|
||||||
width: double.infinity, // 🔧 Largeur complète
|
width: double.infinity,
|
||||||
margin: const pw.EdgeInsets.only(bottom: 1),
|
margin: const pw.EdgeInsets.only(bottom: 1),
|
||||||
child: pw.Column(
|
child: pw.Row(
|
||||||
|
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
||||||
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
crossAxisAlignment: pw.CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
// Nom du plat
|
pw.Expanded(
|
||||||
pw.Container(
|
|
||||||
width: double.infinity,
|
|
||||||
child: pw.Text(
|
child: pw.Text(
|
||||||
'${item.menuNom}',
|
'${item.quantite} ${item.menuNom}',
|
||||||
style: pw.TextStyle(fontSize: bodySize),
|
style: pw.TextStyle(fontSize: smallSize),
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Quantité, prix unitaire et total sur une ligne
|
|
||||||
pw.Container(
|
|
||||||
width: double.infinity,
|
|
||||||
child: pw.Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
pw.MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
pw.Text(
|
pw.Text(
|
||||||
'${item.quantite}x ${item.prixUnitaire.toStringAsFixed(2)}MGA',
|
'${NumberFormat("#,##0.00", "fr_FR").format(item.prixUnitaire * item.quantite)}AR',
|
||||||
style: pw.TextStyle(fontSize: smallSize),
|
style: pw.TextStyle(fontSize: smallSize),
|
||||||
),
|
),
|
||||||
pw.Text(
|
|
||||||
'${(item.prixUnitaire * item.quantite).toStringAsFixed(2)}MGA',
|
|
||||||
style: pw.TextStyle(
|
|
||||||
fontSize: bodySize,
|
|
||||||
fontWeight: pw.FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -236,21 +287,21 @@ class PlatformPrintService {
|
|||||||
|
|
||||||
pw.SizedBox(height: 2),
|
pw.SizedBox(height: 2),
|
||||||
|
|
||||||
// Total
|
// TOTAL
|
||||||
pw.Container(
|
pw.Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: pw.Row(
|
child: pw.Row(
|
||||||
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: pw.MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
pw.Text(
|
pw.Text(
|
||||||
'TOTAL',
|
'Total:',
|
||||||
style: pw.TextStyle(
|
style: pw.TextStyle(
|
||||||
fontSize: titleSize,
|
fontSize: titleSize,
|
||||||
fontWeight: pw.FontWeight.bold,
|
fontWeight: pw.FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
pw.Text(
|
pw.Text(
|
||||||
'${commande.totalTtc.toStringAsFixed(2)}MGA',
|
'${NumberFormat("#,##0.00", "fr_FR").format(commande.totalTtc)}AR',
|
||||||
style: pw.TextStyle(
|
style: pw.TextStyle(
|
||||||
fontSize: titleSize,
|
fontSize: titleSize,
|
||||||
fontWeight: pw.FontWeight.bold,
|
fontWeight: pw.FontWeight.bold,
|
||||||
@ -260,34 +311,13 @@ class PlatformPrintService {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
pw.SizedBox(height: 3),
|
pw.SizedBox(height: 4),
|
||||||
|
|
||||||
// Mode de paiement
|
// MESSAGE FINAL CENTRÉ
|
||||||
pw.Container(
|
pw.Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: pw.Text(
|
child: pw.Text(
|
||||||
'Paiement: ${paymentMethod.toLowerCase()}',
|
'Merci et a bientot !',
|
||||||
style: pw.TextStyle(fontSize: bodySize),
|
|
||||||
textAlign: pw.TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
pw.SizedBox(height: 3),
|
|
||||||
|
|
||||||
// Ligne de séparation
|
|
||||||
pw.Container(
|
|
||||||
width: double.infinity,
|
|
||||||
height: 0.5,
|
|
||||||
color: PdfColors.black,
|
|
||||||
),
|
|
||||||
|
|
||||||
pw.SizedBox(height: 2),
|
|
||||||
|
|
||||||
// Message de remerciement
|
|
||||||
pw.Container(
|
|
||||||
width: double.infinity,
|
|
||||||
child: pw.Text(
|
|
||||||
'Merci de votre visite !',
|
|
||||||
style: pw.TextStyle(
|
style: pw.TextStyle(
|
||||||
fontSize: bodySize,
|
fontSize: bodySize,
|
||||||
fontStyle: pw.FontStyle.italic,
|
fontStyle: pw.FontStyle.italic,
|
||||||
@ -296,27 +326,6 @@ class PlatformPrintService {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
pw.Container(
|
|
||||||
width: double.infinity,
|
|
||||||
child: pw.Text(
|
|
||||||
'A bientôt !',
|
|
||||||
style: pw.TextStyle(fontSize: smallSize),
|
|
||||||
textAlign: pw.TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
pw.SizedBox(height: 3),
|
|
||||||
|
|
||||||
// Code de suivi (optionnel)
|
|
||||||
pw.Container(
|
|
||||||
width: double.infinity,
|
|
||||||
child: pw.Text(
|
|
||||||
'Code: ${factureNumber}',
|
|
||||||
style: pw.TextStyle(fontSize: smallSize),
|
|
||||||
textAlign: pw.TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
pw.SizedBox(height: 4),
|
pw.SizedBox(height: 4),
|
||||||
|
|
||||||
// Ligne de découpe
|
// Ligne de découpe
|
||||||
@ -338,7 +347,8 @@ class PlatformPrintService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return pdf.save();
|
return pdf.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Imprimer ticket 58mm
|
// Imprimer ticket 58mm
|
||||||
static Future<bool> printTicket({
|
static Future<bool> printTicket({
|
||||||
@ -399,20 +409,24 @@ class PlatformPrintService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final fileName =
|
final fileName =
|
||||||
'Ticket_58mm_${commande.numeroCommande}_${DateTime.now().millisecondsSinceEpoch}.pdf';
|
'Facture_${commande.numeroCommande}_${DateTime.now().millisecondsSinceEpoch}.pdf';
|
||||||
final file = File('${directory.path}/$fileName');
|
final file = File('${directory.path}/$fileName');
|
||||||
|
|
||||||
await file.writeAsBytes(pdfData);
|
await file.writeAsBytes(pdfData);
|
||||||
|
|
||||||
|
// ✅ VRAIE SAUVEGARDE au lieu de partage automatique
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
// Sur Android, on peut proposer les deux options
|
||||||
await Share.shareXFiles(
|
await Share.shareXFiles(
|
||||||
[XFile(file.path)],
|
[XFile(file.path)],
|
||||||
subject: 'Ticket ${commande.numeroCommande}',
|
subject: 'Facture ${commande.numeroCommande}',
|
||||||
text: 'Ticket de caisse 58mm',
|
text: 'Facture de restaurant',
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Erreur sauvegarde 58mm: $e');
|
print('Erreur sauvegarde: $e');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -435,7 +449,7 @@ class PlatformPrintService {
|
|||||||
return await printTicket(commande: commande, paymentMethod: paymentMethod);
|
return await printTicket(commande: commande, paymentMethod: paymentMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utilitaires de formatageπ
|
// Utilitaires de formatage
|
||||||
static String _formatDate(DateTime dateTime) {
|
static String _formatDate(DateTime dateTime) {
|
||||||
return '${dateTime.day.toString().padLeft(2, '0')}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.year}';
|
return '${dateTime.day.toString().padLeft(2, '0')}/${dateTime.month.toString().padLeft(2, '0')}/${dateTime.year}';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,33 @@ class RestaurantApiService {
|
|||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static Future<bool> updateCommandeStatus({
|
||||||
|
required String commandeId,
|
||||||
|
required String newStatus,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final response = await http.put(
|
||||||
|
Uri.parse('$baseUrl/api/commandes/$commandeId/status'),
|
||||||
|
headers: _headers,
|
||||||
|
body: json.encode({'statut': newStatus}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200 || response.statusCode == 204) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
print('Erreur updateCommandeStatus: ${response.statusCode} ${response.body}');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print('Exception updateCommandeStatus: $e');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Récupérer les commandes
|
// Récupérer les commandes
|
||||||
|
|
||||||
static Future<List<TableOrder>> getCommandes() async {
|
static Future<List<TableOrder>> getCommandes() async {
|
||||||
try {
|
try {
|
||||||
final response = await http
|
final response = await http
|
||||||
|
|||||||
@ -238,48 +238,48 @@ class AppBottomNavigation extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// const SizedBox(width: 20),
|
const SizedBox(width: 20),
|
||||||
|
|
||||||
// GestureDetector(
|
GestureDetector(
|
||||||
// onTap: () => onItemTapped(6),
|
onTap: () => onItemTapped(6),
|
||||||
// child: Container(
|
child: Container(
|
||||||
// padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
// decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
// color:
|
color:
|
||||||
// selectedIndex == 5
|
selectedIndex == 6
|
||||||
// ? Colors.green.shade700
|
? Colors.green.shade700
|
||||||
// : Colors.transparent,
|
: Colors.transparent,
|
||||||
// borderRadius: BorderRadius.circular(20),
|
borderRadius: BorderRadius.circular(20),
|
||||||
// ),
|
),
|
||||||
// child: Row(
|
child: Row(
|
||||||
// mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
// children: [
|
children: [
|
||||||
// Icon(
|
Icon(
|
||||||
// Icons.payment,
|
Icons.payment,
|
||||||
// color:
|
color:
|
||||||
// selectedIndex == 5
|
selectedIndex == 6
|
||||||
// ? Colors.white
|
? Colors.white
|
||||||
// : Colors.grey.shade600,
|
: Colors.grey.shade600,
|
||||||
// size: 16,
|
size: 16,
|
||||||
// ),
|
),
|
||||||
// const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
// Text(
|
Text(
|
||||||
// 'Historique',
|
'Historique',
|
||||||
// style: TextStyle(
|
style: TextStyle(
|
||||||
// color:
|
color:
|
||||||
// selectedIndex == 5
|
selectedIndex == 6
|
||||||
// ? Colors.white
|
? Colors.white
|
||||||
// : Colors.grey.shade600,
|
: Colors.grey.shade600,
|
||||||
// fontWeight:
|
fontWeight:
|
||||||
// selectedIndex == 5
|
selectedIndex == 6
|
||||||
// ? FontWeight.w500
|
? FontWeight.w500
|
||||||
// : FontWeight.normal,
|
: FontWeight.normal,
|
||||||
// ),
|
),
|
||||||
// ),
|
),
|
||||||
// ],
|
],
|
||||||
// ),
|
),
|
||||||
// ),
|
),
|
||||||
// ),
|
),
|
||||||
|
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
|
|
||||||
|
|||||||
@ -26,6 +26,7 @@ dependencies:
|
|||||||
path_provider: ^2.1.1
|
path_provider: ^2.1.1
|
||||||
share_plus: ^7.2.1
|
share_plus: ^7.2.1
|
||||||
permission_handler: ^11.1.0
|
permission_handler: ^11.1.0
|
||||||
|
intl: ^0.18.1
|
||||||
|
|
||||||
# Dépendances de développement/test
|
# Dépendances de développement/test
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 4.1 KiB |
Loading…
Reference in New Issue
Block a user