import ' package:flutter/material.dart ' ;
import ' package:http/http.dart ' as http ;
import ' dart:convert ' ;
class OrderHistoryPage extends StatefulWidget {
@ override
_OrderHistoryPageState createState ( ) = > _OrderHistoryPageState ( ) ;
}
class _OrderHistoryPageState extends State < OrderHistoryPage >
with TickerProviderStateMixin {
late AnimationController _animationController ;
List < AnimationController > _cardAnimationControllers = [ ] ;
List < CommandeData > commandes = [ ] ;
bool isLoading = true ;
String ? error ;
// Informations d'affichage et pagination
int totalItems = 0 ;
int currentPage = 1 ;
int totalPages = 1 ;
final int itemsPerPage = 10 ; // Nombre d'éléments par page
final String baseUrl = ' https://restaurant.careeracademy.mg ' ;
final Map < String , String > _headers = {
' Content-Type ' : ' application/json ' ,
' Accept ' : ' application/json ' ,
} ;
@ override
void initState ( ) {
super . initState ( ) ;
_animationController = AnimationController (
duration: Duration ( milliseconds: 1000 ) ,
vsync: this ,
) ;
_loadCommandes ( ) ;
}
Future < void > _loadCommandes ( { int page = 1 } ) async {
try {
setState ( ( ) {
isLoading = true ;
error = null ;
} ) ;
// Ajouter les paramètres de pagination à l'URL
final uri = Uri . parse ( ' $ baseUrl /api/commandes ' ) . replace ( queryParameters: {
' statut ' : ' payee ' ,
' page ' : page . toString ( ) ,
' limit ' : itemsPerPage . toString ( ) ,
} ) ;
final response = await http . get ( uri , headers: _headers ) ;
print ( ' === DÉBUT DEBUG RESPONSE === ' ) ;
print ( ' Status Code: ${ response . statusCode } ' ) ;
print ( ' Response Body: ${ response . body } ' ) ;
if ( response . statusCode = = 200 ) {
final dynamic responseBody = json . decode ( response . body ) ;
print ( ' === PARSED RESPONSE === ' ) ;
print ( ' Type: ${ responseBody . runtimeType } ' ) ;
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 ( ( ) {
commandes = parsedCommandes ;
isLoading = false ;
} ) ;
// Initialiser les animations après avoir mis à jour l'état
_initializeAnimations ( ) ;
_startAnimations ( ) ;
} else {
print ( ' ERROR: HTTP ${ response . statusCode } : ${ response . reasonPhrase } ' ) ;
setState ( ( ) {
error = ' Erreur HTTP ${ response . statusCode } : ${ response . reasonPhrase } ' ;
isLoading = false ;
} ) ;
}
} catch ( e , stackTrace ) {
print ( ' === ERREUR GÉNÉRALE === ' ) ;
print ( ' Erreur: $ e ' ) ;
print ( ' Stack trace: $ stackTrace ' ) ;
setState ( ( ) {
error = ' Erreur de connexion: $ e ' ;
isLoading = false ;
} ) ;
}
}
// 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 ( ) {
// Disposer les anciens contrôleurs
for ( var controller in _cardAnimationControllers ) {
controller . dispose ( ) ;
}
_cardAnimationControllers = List . generate (
commandes . length ,
( index ) = > AnimationController (
duration: Duration ( milliseconds: 600 ) ,
vsync: this ,
) ,
) ;
}
void _startAnimations ( ) async {
if ( ! mounted ) return ;
_animationController . forward ( ) ;
for ( int i = 0 ; i < _cardAnimationControllers . length ; i + + ) {
await Future . delayed ( Duration ( milliseconds: 150 ) ) ;
if ( mounted & & i < _cardAnimationControllers . length ) {
_cardAnimationControllers [ i ] . forward ( ) ;
}
}
}
@ override
void dispose ( ) {
_animationController . dispose ( ) ;
for ( var controller in _cardAnimationControllers ) {
controller . dispose ( ) ;
}
super . dispose ( ) ;
}
@ override
Widget build ( BuildContext context ) {
return Scaffold (
backgroundColor: Colors . white ,
appBar: AppBar (
title: Text ( ' Historique des commandes ' ) ,
backgroundColor: Colors . white ,
foregroundColor: Colors . black ,
elevation: 0 ,
) ,
body: RefreshIndicator (
onRefresh: ( ) = > _loadCommandes ( page: currentPage ) ,
child: Column (
children: [
_buildHeader ( ) ,
Expanded (
child: _buildContent ( ) ,
) ,
if ( totalPages > 1 ) _buildPagination ( ) ,
] ,
) ,
) ,
) ;
}
Widget _buildHeader ( ) {
return AnimatedBuilder (
animation: _animationController ,
builder: ( context , child ) {
return Transform . translate (
offset: Offset ( 0 , - 50 * ( 1 - _animationController . value ) ) ,
child: Opacity (
opacity: _animationController . value ,
child: Container (
width: double . infinity ,
margin: EdgeInsets . symmetric ( horizontal: 20 , vertical: 5 ) ,
padding: EdgeInsets . all ( 10 ) ,
decoration: BoxDecoration (
color: Colors . white ,
borderRadius: BorderRadius . circular ( 12 ) ,
boxShadow: [
BoxShadow (
color: Colors . grey . withOpacity ( 0.08 ) ,
blurRadius: 6 ,
offset: Offset ( 0 , 2 ) ,
) ,
] ,
) ,
child: Column (
children: [
Text (
' Historique des commandes payées ' ,
style: TextStyle (
fontSize: 18 ,
fontWeight: FontWeight . bold ,
color: Color ( 0xFF2c3e50 ) ,
) ,
textAlign: TextAlign . center ,
) ,
SizedBox ( height: 2 ) ,
Text (
' Consultez toutes les commandes qui ont été payées ' ,
style: TextStyle (
fontSize: 12 ,
color: Colors . grey . shade600 ,
) ,
textAlign: TextAlign . center ,
) ,
if ( totalItems > 0 )
Padding (
padding: EdgeInsets . only ( top: 4 ) ,
child: Text (
totalPages > 1
? ' $ totalItems commande ${ totalItems > 1 ? ' s ' : ' ' } • Page $ currentPage / $ totalPages '
: ' $ totalItems commande ${ totalItems > 1 ? ' s ' : ' ' } trouvée ${ totalItems > 1 ? ' s ' : ' ' } ' ,
style: TextStyle (
fontSize: 10 ,
color: Colors . grey . shade500 ,
fontWeight: FontWeight . w500 ,
) ,
textAlign: TextAlign . center ,
) ,
) ,
] ,
) ,
) ,
) ,
) ;
} ,
) ;
}
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 ( ) {
if ( isLoading ) {
return Center (
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 ) ,
) ,
] ,
) ,
) ;
}
if ( error ! = null ) {
return Center (
child: Column (
mainAxisAlignment: MainAxisAlignment . center ,
children: [
Icon ( Icons . error_outline , size: 64 , color: Colors . grey ) ,
SizedBox ( height: 16 ) ,
Padding (
padding: EdgeInsets . symmetric ( horizontal: 20 ) ,
child: Text (
error ! ,
style: TextStyle ( color: Colors . grey ) ,
textAlign: TextAlign . center ,
) ,
) ,
SizedBox ( height: 16 ) ,
ElevatedButton (
onPressed: ( ) = > _loadCommandes ( page: currentPage ) ,
child: Text ( ' Réessayer ' ) ,
style: ElevatedButton . styleFrom (
backgroundColor: Color ( 0xFF4CAF50 ) ,
foregroundColor: Colors . white ,
) ,
) ,
] ,
) ,
) ;
}
if ( commandes . isEmpty ) {
return Center (
child: Column (
mainAxisAlignment: MainAxisAlignment . center ,
children: [
Icon ( Icons . restaurant_menu , size: 64 , color: Colors . grey ) ,
SizedBox ( height: 16 ) ,
Text (
currentPage > 1
? ' Aucune commande sur cette page '
: ' Aucune commande payée ' ,
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 ,
) ,
) ,
] ,
] ,
) ,
) ;
}
return ListView . builder (
padding: EdgeInsets . symmetric ( horizontal: 20 ) ,
itemCount: commandes . length ,
itemBuilder: ( context , index ) {
return _buildOrderCard ( commandes [ index ] , index ) ;
} ,
) ;
}
Widget _buildOrderCard ( CommandeData commande , int index ) {
if ( index > = _cardAnimationControllers . length ) {
return SizedBox . shrink ( ) ;
}
return AnimatedBuilder (
animation: _cardAnimationControllers [ index ] ,
builder: ( context , child ) {
return Transform . translate (
offset: Offset ( 0 , 50 * ( 1 - _cardAnimationControllers [ index ] . value ) ) ,
child: Opacity (
opacity: _cardAnimationControllers [ index ] . value ,
child: Container (
margin: EdgeInsets . only ( bottom: 12 ) ,
child: Material (
elevation: 8 ,
borderRadius: BorderRadius . circular ( 15 ) ,
child: InkWell (
borderRadius: BorderRadius . circular ( 15 ) ,
onTap: ( ) {
// Action au tap
} ,
child: Container (
decoration: BoxDecoration (
borderRadius: BorderRadius . circular ( 15 ) ,
color: Colors . white ,
border: Border (
left: BorderSide (
color: Color ( 0xFF4CAF50 ) ,
width: 3 ,
) ,
) ,
) ,
child: Column (
children: [
_buildOrderHeader ( commande ) ,
_buildOrderItems ( commande ) ,
_buildOrderFooter ( commande ) ,
] ,
) ,
) ,
) ,
) ,
) ,
) ,
) ;
} ,
) ;
}
Widget _buildOrderHeader ( CommandeData commande ) {
return Container (
padding: EdgeInsets . all ( 12 ) ,
decoration: BoxDecoration (
border: Border (
bottom: BorderSide (
color: Colors . grey . shade200 ,
width: 1 ,
) ,
) ,
) ,
child: Row (
children: [
Container (
width: 35 ,
height: 35 ,
decoration: BoxDecoration (
gradient: LinearGradient (
colors: [ Color ( 0xFF667eea ) , Color ( 0xFF764ba2 ) ] ,
) ,
borderRadius: BorderRadius . circular ( 8 ) ,
) ,
child: Center (
child: Text (
commande . getTableShortName ( ) ,
style: TextStyle (
color: Colors . white ,
fontWeight: FontWeight . bold ,
fontSize: 10 ,
) ,
) ,
) ,
) ,
SizedBox ( width: 8 ) ,
Expanded (
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Text (
commande . tablename ? ? ' Table inconnue ' ,
style: TextStyle (
fontSize: 14 ,
fontWeight: FontWeight . w600 ,
color: Color ( 0xFF2c3e50 ) ,
) ,
) ,
SizedBox ( height: 2 ) ,
Text (
commande . numeroCommande ? ? ' N/A ' ,
style: TextStyle (
fontSize: 10 ,
color: Colors . grey ,
fontWeight: FontWeight . w500 ,
) ,
) ,
SizedBox ( height: 2 ) ,
Row (
children: [
Icon ( Icons . calendar_today , size: 12 , color: Colors . grey ) ,
SizedBox ( width: 3 ) ,
Text (
commande . dateCommande ! = null
? _formatDateTime ( commande . dateCommande ! )
: ' Date inconnue ' ,
style: TextStyle ( color: Colors . grey , fontSize: 10 ) ,
) ,
SizedBox ( width: 8 ) ,
Icon ( Icons . person , size: 12 , color: Colors . grey ) ,
SizedBox ( width: 3 ) ,
Text (
commande . serveur ? ? ' Serveur inconnu ' ,
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 ) ,
) ,
] ,
) ,
] ,
) ,
) ,
Container (
padding: EdgeInsets . symmetric ( horizontal: 8 , vertical: 4 ) ,
decoration: BoxDecoration (
gradient: LinearGradient (
colors: [ Color ( 0xFF4CAF50 ) , Color ( 0xFF388E3C ) ] ,
) ,
borderRadius: BorderRadius . circular ( 10 ) ,
) ,
child: Row (
mainAxisSize: MainAxisSize . min ,
children: [
Icon (
Icons . check_circle ,
color: Colors . white ,
size: 10 ,
) ,
SizedBox ( width: 3 ) ,
Text (
' PAYÉE ' ,
style: TextStyle (
color: Colors . white ,
fontWeight: FontWeight . w600 ,
fontSize: 8 ,
letterSpacing: 0.3 ,
) ,
) ,
] ,
) ,
) ,
] ,
) ,
) ;
}
Widget _buildOrderItems ( CommandeData commande ) {
return Container (
padding: EdgeInsets . all ( 10 ) ,
child: Column (
children: ( commande . items ? ? [ ] ) . map ( ( item ) = > _buildOrderItem ( item ) ) . toList ( ) ,
) ,
) ;
}
Widget _buildOrderItem ( CommandeItem item ) {
return Container (
padding: EdgeInsets . symmetric ( vertical: 6 ) ,
decoration: BoxDecoration (
border: Border (
bottom: BorderSide (
color: Colors . grey . shade100 ,
width: 1 ,
) ,
) ,
) ,
child: Row (
children: [
Container (
width: 32 ,
height: 32 ,
decoration: BoxDecoration (
gradient: LinearGradient (
colors: [ Color ( 0xFFffeaa7 ) , Color ( 0xFFfdcb6e ) ] ,
) ,
borderRadius: BorderRadius . circular ( 8 ) ,
) ,
child: Center (
child: Text (
_getMenuIcon ( item . menuNom ) ,
style: TextStyle ( fontSize: 14 ) ,
) ,
) ,
) ,
SizedBox ( width: 8 ) ,
Expanded (
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Text (
item . menuNom ,
style: TextStyle (
fontSize: 12 ,
fontWeight: FontWeight . w600 ,
color: Color ( 0xFF2c3e50 ) ,
) ,
) ,
if ( item . commentaires ! = null & & item . commentaires ! . isNotEmpty )
Text (
item . commentaires ! ,
style: TextStyle (
fontSize: 9 ,
color: Colors . grey . shade600 ,
fontStyle: FontStyle . italic ,
) ,
) ,
Text (
' ${ item . quantite } × ${ _formatPrice ( item . prixUnitaire ) } ' ,
style: TextStyle (
fontSize: 10 ,
color: Colors . grey ,
) ,
) ,
] ,
) ,
) ,
Text (
_formatPrice ( item . totalItem ) ,
style: TextStyle (
fontSize: 13 ,
fontWeight: FontWeight . bold ,
color: Color ( 0xFF4CAF50 ) ,
) ,
) ,
] ,
) ,
) ;
}
Widget _buildOrderFooter ( CommandeData commande ) {
return Container (
padding: EdgeInsets . all ( 10 ) ,
decoration: BoxDecoration (
color: Colors . grey . shade50 ,
borderRadius: BorderRadius . only (
bottomLeft: Radius . circular ( 15 ) ,
bottomRight: Radius . circular ( 15 ) ,
) ,
) ,
child: Row (
mainAxisAlignment: MainAxisAlignment . spaceBetween ,
children: [
Row (
children: [
Container (
width: 28 ,
height: 28 ,
decoration: BoxDecoration (
gradient: LinearGradient (
colors: [ Color ( 0xFF4CAF50 ) , Color ( 0xFF388E3C ) ] ,
) ,
borderRadius: BorderRadius . circular ( 6 ) ,
) ,
child: Center (
child: Icon (
_getPaymentIcon ( commande . modePaiement ) ,
color: Colors . white ,
size: 14 ,
) ,
) ,
) ,
SizedBox ( width: 6 ) ,
Column (
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
Text (
_getPaymentMethodText ( commande . modePaiement ) ,
style: TextStyle (
fontSize: 10 ,
color: Colors . grey . shade600 ,
) ,
) ,
if ( ( commande . totalTva ? ? 0 ) > 0 )
Text (
' TVA: ${ _formatPrice ( commande . totalTva ? ? 0 ) } ' ,
style: TextStyle (
fontSize: 9 ,
color: Colors . grey . shade500 ,
) ,
) ,
] ,
) ,
] ,
) ,
Container (
padding: EdgeInsets . symmetric ( horizontal: 12 , vertical: 6 ) ,
decoration: BoxDecoration (
color: Color ( 0xFF4CAF50 ) . withOpacity ( 0.1 ) ,
borderRadius: BorderRadius . circular ( 15 ) ,
) ,
child: Row (
mainAxisSize: MainAxisSize . min ,
children: [
Icon (
Icons . attach_money ,
color: Color ( 0xFF4CAF50 ) ,
size: 14 ,
) ,
Text (
_formatPrice ( commande . totalTtc ? ? 0 ) ,
style: TextStyle (
fontSize: 14 ,
fontWeight: FontWeight . bold ,
color: Color ( 0xFF4CAF50 ) ,
) ,
) ,
] ,
) ,
) ,
] ,
) ,
) ;
}
String _formatDateTime ( DateTime dateTime ) {
return ' ${ dateTime . day . toString ( ) . padLeft ( 2 , ' 0 ' ) } / ${ dateTime . month . toString ( ) . padLeft ( 2 , ' 0 ' ) } / ${ dateTime . year } à ${ dateTime . hour . toString ( ) . padLeft ( 2 , ' 0 ' ) } : ${ dateTime . minute . toString ( ) . padLeft ( 2 , ' 0 ' ) } ' ;
}
String _formatPrice ( double priceInCents ) {
return ' ${ ( priceInCents / 100 ) . toStringAsFixed ( 2 ) } Ar ' ;
}
String _getMenuIcon ( String menuNom ) {
String lowerName = menuNom . toLowerCase ( ) ;
if ( lowerName . contains ( ' pizza ' ) ) return ' 🍕 ' ;
if ( lowerName . contains ( ' steak ' ) | | lowerName . contains ( ' steack ' ) ) return ' 🥩 ' ;
if ( lowerName . contains ( ' frite ' ) ) return ' 🍟 ' ;
if ( lowerName . contains ( ' salade ' ) ) return ' 🥗 ' ;
if ( lowerName . contains ( ' soupe ' ) ) return ' 🍲 ' ;
if ( lowerName . contains ( ' burger ' ) ) return ' 🍔 ' ;
if ( lowerName . contains ( ' poisson ' ) ) return ' 🐟 ' ;
if ( lowerName . contains ( ' poulet ' ) ) return ' 🍗 ' ;
if ( lowerName . contains ( ' pâtes ' ) | | lowerName . contains ( ' pasta ' ) ) return ' 🍝 ' ;
if ( lowerName . contains ( ' dessert ' ) | | lowerName . contains ( ' gâteau ' ) ) return ' 🍰 ' ;
if ( lowerName . contains ( ' boisson ' ) ) return ' 🥤 ' ;
return ' 🍽️ ' ;
}
IconData _getPaymentIcon ( String ? method ) {
if ( method = = null ) return Icons . help_outline ;
switch ( method . toLowerCase ( ) ) {
case ' especes ' :
case ' cash ' :
return Icons . money ;
case ' carte ' :
case ' card ' :
return Icons . credit_card ;
case ' mobile ' :
case ' paypal ' :
return Icons . smartphone ;
default :
return Icons . payment ;
}
}
String _getPaymentMethodText ( String ? method ) {
if ( method = = null ) return ' Non défini ' ;
switch ( method . toLowerCase ( ) ) {
case ' especes ' :
case ' cash ' :
return ' Espèces ' ;
case ' carte ' :
case ' card ' :
return ' Carte ' ;
case ' mobile ' :
case ' paypal ' :
return ' Mobile ' ;
default :
return method ;
}
}
}
// Modèles de données avec gestion des valeurs nulles et debug amélioré
class CommandeData {
final int ? id ;
final int ? clientId ;
final int ? tableId ;
final int ? reservationId ;
final String ? numeroCommande ;
final String ? statut ;
final double ? totalHt ;
final double ? totalTva ;
final double ? totalTtc ;
final String ? modePaiement ;
final String ? commentaires ;
final String ? serveur ;
final DateTime ? dateCommande ;
final DateTime ? datePaiement ;
final DateTime ? createdAt ;
final DateTime ? updatedAt ;
final List < CommandeItem > ? items ;
final String ? tablename ;
CommandeData ( {
this . id ,
this . clientId ,
this . tableId ,
this . reservationId ,
this . numeroCommande ,
this . statut ,
this . totalHt ,
this . totalTva ,
this . totalTtc ,
this . modePaiement ,
this . commentaires ,
this . serveur ,
this . dateCommande ,
this . datePaiement ,
this . createdAt ,
this . updatedAt ,
this . items ,
this . tablename ,
} ) ;
factory CommandeData . fromJson ( Map < String , dynamic > json ) {
try {
// 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 ' ] ,
tableId: json [ ' table_id ' ] ,
reservationId: json [ ' reservation_id ' ] ,
numeroCommande: numeroCommande ,
statut: json [ ' statut ' ] ? . toString ( ) ,
totalHt: _parseDouble ( json [ ' total_ht ' ] ) ,
totalTva: _parseDouble ( json [ ' total_tva ' ] ) ,
totalTtc: totalTtc ,
modePaiement: modePaiement ,
commentaires: json [ ' commentaires ' ] ? . toString ( ) ,
serveur: serveur ,
dateCommande: dateCommande ,
datePaiement: datePaiement ,
createdAt: _parseDateTime ( json [ ' created_at ' ] ) ,
updatedAt: _parseDateTime ( json [ ' updated_at ' ] ) ,
items: items ,
tablename: tablename ,
) ;
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 ( ) {
final name = tablename ? ? ' Table ' ;
if ( name . toLowerCase ( ) . contains ( ' caisse ' ) ) return ' C ' ;
if ( name . toLowerCase ( ) . contains ( ' terrasse ' ) ) return ' T ' ;
RegExp regExp = RegExp ( r'\d+' ) ;
Match ? match = regExp . firstMatch ( name ) ;
if ( match ! = null ) {
return ' T ${ match . group ( 0 ) } ' ;
}
return name . isNotEmpty ? name . substring ( 0 , 1 ) . toUpperCase ( ) : ' T ' ;
}
}
class CommandeItem {
final int id ;
final int commandeId ;
final int menuId ;
final int quantite ;
final double prixUnitaire ;
final double totalItem ;
final String ? commentaires ;
final String statut ;
final DateTime ? createdAt ;
final DateTime ? updatedAt ;
final String menuNom ;
final String menuDescription ;
final double menuPrixActuel ;
final String tablename ;
CommandeItem ( {
required this . id ,
required this . commandeId ,
required this . menuId ,
required this . quantite ,
required this . prixUnitaire ,
required this . totalItem ,
this . commentaires ,
required this . statut ,
this . createdAt ,
this . updatedAt ,
required this . menuNom ,
required this . menuDescription ,
required this . menuPrixActuel ,
required this . tablename ,
} ) ;
factory CommandeItem . fromJson ( Map < String , dynamic > json ) {
try {
print ( ' === PARSING COMMANDE ITEM === ' ) ;
print ( ' JSON item: $ json ' ) ;
// Debug chaque champ
final id = json [ ' id ' ] ? ? 0 ;
print ( ' ID: ${ json [ ' id ' ] } -> $ id ' ) ;
final commandeId = json [ ' commande_id ' ] ? ? 0 ;
print ( ' Commande ID: ${ json [ ' commande_id ' ] } -> $ commandeId ' ) ;
final menuId = json [ ' menu_id ' ] ? ? 0 ;
print ( ' Menu ID: ${ json [ ' menu_id ' ] } -> $ menuId ' ) ;
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 ;
}
}
}
class MyApp extends StatelessWidget {
@ override
Widget build ( BuildContext context ) {
return MaterialApp (
theme: ThemeData (
primarySwatch: Colors . blue ,
visualDensity: VisualDensity . adaptivePlatformDensity ,
) ,
home: OrderHistoryPage ( ) ,
debugShowCheckedModeBanner: false ,
) ;
}
}
void main ( ) {
runApp ( MyApp ( ) ) ;
}