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 with TickerProviderStateMixin { late AnimationController _animationController; List _cardAnimationControllers = []; List 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 _headers = { 'Content-Type': 'application/json', 'Accept': 'application/json', }; @override void initState() { super.initState(); _animationController = AnimationController( duration: Duration(milliseconds: 1000), vsync: this, ); _loadCommandes(); } Future _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 data = []; // Gestion améliorée de la réponse if (responseBody is Map) { 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) { final dataMap = responseBody['data'] as Map; 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) { data = commandesValue; } else if (commandesValue != null) { data = [commandesValue]; } // Pagination if (dataMap.containsKey('pagination')) { final pagination = dataMap['pagination'] as Map?; 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) { 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) { 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 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) { 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 _buildCondensedPagination() { List 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(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? 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 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? _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 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) { 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 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()); }