|
|
|
@ -14,6 +14,8 @@ class _OrderHistoryPageState extends State<OrderHistoryPage> |
|
|
|
List<CommandeData> commandes = []; |
|
|
|
bool isLoading = true; |
|
|
|
String? error; |
|
|
|
DateTime? _startDate; |
|
|
|
DateTime? _endDate; |
|
|
|
|
|
|
|
// Informations d'affichage et pagination |
|
|
|
int totalItems = 0; |
|
|
|
@ -37,144 +39,146 @@ class _OrderHistoryPageState extends State<OrderHistoryPage> |
|
|
|
|
|
|
|
_loadCommandes(); |
|
|
|
} |
|
|
|
Future<void> _loadCommandes({int page = 1}) async { |
|
|
|
try { |
|
|
|
setState(() { |
|
|
|
isLoading = true; |
|
|
|
error = null; |
|
|
|
}); |
|
|
|
|
|
|
|
Future<void> _loadCommandes({int page = 1}) async { |
|
|
|
try { |
|
|
|
setState(() { |
|
|
|
isLoading = true; |
|
|
|
error = null; |
|
|
|
}); |
|
|
|
// Construction des paramètres de requête |
|
|
|
Map<String, String> queryParams = { |
|
|
|
'statut': 'payee', |
|
|
|
'page': page.toString(), |
|
|
|
'limit': itemsPerPage.toString(), |
|
|
|
}; |
|
|
|
|
|
|
|
// 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(), |
|
|
|
}); |
|
|
|
// ✅ CORRECTION : Envoyer la date en format ISO avec fuseau horaire UTC |
|
|
|
if (_startDate != null) { |
|
|
|
// Début de journée en UTC (00:00:00) |
|
|
|
final startUtc = DateTime.utc( |
|
|
|
_startDate!.year, |
|
|
|
_startDate!.month, |
|
|
|
_startDate!.day, |
|
|
|
0, 0, 0, |
|
|
|
); |
|
|
|
queryParams['date_start'] = startUtc.toIso8601String(); |
|
|
|
print('📅 Date début (UTC): ${startUtc.toIso8601String()}'); |
|
|
|
} |
|
|
|
|
|
|
|
if (_endDate != null) { |
|
|
|
// Fin de journée en UTC (23:59:59) |
|
|
|
final endUtc = DateTime.utc( |
|
|
|
_endDate!.year, |
|
|
|
_endDate!.month, |
|
|
|
_endDate!.day, |
|
|
|
23, 59, 59, 999, |
|
|
|
); |
|
|
|
queryParams['date_end'] = endUtc.toIso8601String(); |
|
|
|
print('📅 Date fin (UTC): ${endUtc.toIso8601String()}'); |
|
|
|
} |
|
|
|
|
|
|
|
final response = await http.get(uri, headers: _headers); |
|
|
|
|
|
|
|
if (response.statusCode == 200) { |
|
|
|
final dynamic responseBody = json.decode(response.body); |
|
|
|
|
|
|
|
List<dynamic> data = []; |
|
|
|
|
|
|
|
// Gestion améliorée de la réponse |
|
|
|
if (responseBody is Map<String, dynamic>) { |
|
|
|
|
|
|
|
// Structure: {"success": true, "data": {"commandes": [...], "pagination": {...}}} |
|
|
|
if (responseBody.containsKey('data') && responseBody['data'] is Map<String, dynamic>) { |
|
|
|
final dataMap = responseBody['data'] as Map<String, dynamic>; |
|
|
|
if (dataMap.containsKey('commandes')) { |
|
|
|
final commandesValue = dataMap['commandes']; |
|
|
|
|
|
|
|
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; |
|
|
|
} |
|
|
|
} else { |
|
|
|
// Si pas de pagination dans la réponse, calculer approximativement |
|
|
|
totalItems = data.length; |
|
|
|
currentPage = page; |
|
|
|
totalPages = (totalItems / itemsPerPage).ceil(); |
|
|
|
} |
|
|
|
} else { |
|
|
|
totalItems = 0; |
|
|
|
currentPage = 1; |
|
|
|
totalPages = 1; |
|
|
|
} |
|
|
|
} else if (responseBody.containsKey('commandes')) { |
|
|
|
// Fallback: commandes directement dans responseBody |
|
|
|
final commandesValue = responseBody['commandes']; |
|
|
|
|
|
|
|
if (commandesValue is List<dynamic>) { |
|
|
|
data = commandesValue; |
|
|
|
} else if (commandesValue != null) { |
|
|
|
data = [commandesValue]; |
|
|
|
} |
|
|
|
totalItems = data.length; |
|
|
|
currentPage = page; |
|
|
|
totalPages = (totalItems / itemsPerPage).ceil(); |
|
|
|
} else { |
|
|
|
totalItems = 0; |
|
|
|
currentPage = 1; |
|
|
|
totalPages = 1; |
|
|
|
// Debug: Afficher l'URL complète |
|
|
|
final uri = Uri.parse('$baseUrl/api/commandes').replace(queryParameters: queryParams); |
|
|
|
print('🌐 URL appelée: $uri'); |
|
|
|
|
|
|
|
final response = await http.get(uri, headers: _headers); |
|
|
|
|
|
|
|
print('📡 Status code: ${response.statusCode}'); |
|
|
|
|
|
|
|
if (response.statusCode == 200) { |
|
|
|
final dynamic responseBody = json.decode(response.body); |
|
|
|
List<dynamic> data = []; |
|
|
|
|
|
|
|
// Gestion selon le format de réponse |
|
|
|
if (responseBody is Map<String, dynamic>) { |
|
|
|
final dataMap = responseBody['data'] as Map<String, dynamic>?; |
|
|
|
|
|
|
|
if (dataMap != null) { |
|
|
|
final commandesValue = dataMap['commandes']; |
|
|
|
if (commandesValue is List) { |
|
|
|
data = commandesValue; |
|
|
|
} else if (commandesValue != null) { |
|
|
|
data = [commandesValue]; |
|
|
|
} |
|
|
|
} else if (responseBody is List<dynamic>) { |
|
|
|
data = responseBody; |
|
|
|
totalItems = data.length; |
|
|
|
currentPage = page; |
|
|
|
totalPages = (totalItems / itemsPerPage).ceil(); |
|
|
|
} else { |
|
|
|
throw Exception('Format de réponse inattendu: ${responseBody.runtimeType}'); |
|
|
|
|
|
|
|
// Pagination |
|
|
|
final pagination = dataMap['pagination'] as Map<String, dynamic>?; |
|
|
|
currentPage = pagination?['currentPage'] ?? page; |
|
|
|
totalPages = pagination?['totalPages'] ?? (data.length / itemsPerPage).ceil(); |
|
|
|
totalItems = pagination?['totalItems'] ?? data.length; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 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]; |
|
|
|
|
|
|
|
if (item is Map<String, dynamic>) { |
|
|
|
item.forEach((key, value) { |
|
|
|
}); |
|
|
|
|
|
|
|
final commandeData = CommandeData.fromJson(item); |
|
|
|
|
|
|
|
if (commandeData.items != null) { |
|
|
|
for (int j = 0; j < commandeData.items!.length; j++) { |
|
|
|
final commandeItem = commandeData.items![j]; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
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 |
|
|
|
} else if (responseBody is List) { |
|
|
|
data = responseBody; |
|
|
|
totalItems = data.length; |
|
|
|
currentPage = page; |
|
|
|
totalPages = (totalItems / itemsPerPage).ceil(); |
|
|
|
} else { |
|
|
|
throw Exception('Format de réponse inattendu: ${responseBody.runtimeType}'); |
|
|
|
} |
|
|
|
|
|
|
|
// Parsing sécurisé des commandes |
|
|
|
List<CommandeData> parsedCommandes = []; |
|
|
|
for (int i = 0; i < data.length; i++) { |
|
|
|
try { |
|
|
|
final item = data[i]; |
|
|
|
if (item is Map<String, dynamic>) { |
|
|
|
final commandeData = CommandeData.fromJson(item); |
|
|
|
parsedCommandes.add(commandeData); |
|
|
|
} else { |
|
|
|
print('Item $i invalide: ${item.runtimeType}'); |
|
|
|
} |
|
|
|
} catch (e, stackTrace) { |
|
|
|
print('Erreur parsing item $i: $e'); |
|
|
|
print(stackTrace); |
|
|
|
} |
|
|
|
|
|
|
|
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'); |
|
|
|
|
|
|
|
print('✅ ${parsedCommandes.length} commandes chargées'); |
|
|
|
|
|
|
|
setState(() { |
|
|
|
commandes = parsedCommandes; |
|
|
|
isLoading = false; |
|
|
|
}); |
|
|
|
|
|
|
|
_initializeAnimations(); |
|
|
|
_startAnimations(); |
|
|
|
} else { |
|
|
|
setState(() { |
|
|
|
error = 'Erreur de connexion: $e'; |
|
|
|
error = 'Erreur HTTP ${response.statusCode}: ${response.reasonPhrase}'; |
|
|
|
isLoading = false; |
|
|
|
}); |
|
|
|
print('Erreur HTTP ${response.statusCode}: ${response.reasonPhrase}'); |
|
|
|
} |
|
|
|
} catch (e, stackTrace) { |
|
|
|
print('=== ERREUR GÉNÉRALE ==='); |
|
|
|
print('Erreur: $e'); |
|
|
|
print('Stack trace: $stackTrace'); |
|
|
|
setState(() { |
|
|
|
error = 'Erreur de connexion: $e'; |
|
|
|
isLoading = false; |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
Future<void> _selectDate(BuildContext context, bool isStart) async { |
|
|
|
final DateTime? picked = await showDatePicker( |
|
|
|
context: context, |
|
|
|
initialDate: isStart ? (_startDate ?? DateTime.now()) : (_endDate ?? DateTime.now()), |
|
|
|
firstDate: DateTime(2020), |
|
|
|
lastDate: DateTime(2100), |
|
|
|
); |
|
|
|
if (picked != null) { |
|
|
|
setState(() { |
|
|
|
if (isStart) { |
|
|
|
_startDate = picked; |
|
|
|
} else { |
|
|
|
_endDate = picked; |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Fonction pour aller à la page suivante |
|
|
|
void _goToNextPage() { |
|
|
|
@ -258,73 +262,151 @@ class _OrderHistoryPageState extends State<OrderHistoryPage> |
|
|
|
), |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
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), |
|
|
|
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), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
child: Column( |
|
|
|
children: [ |
|
|
|
Text( |
|
|
|
'Historique des commandes payées', |
|
|
|
style: TextStyle( |
|
|
|
fontSize: 18, |
|
|
|
fontWeight: FontWeight.bold, |
|
|
|
color: Color(0xFF2c3e50), |
|
|
|
), |
|
|
|
textAlign: TextAlign.center, |
|
|
|
textAlign: TextAlign.center, |
|
|
|
), |
|
|
|
SizedBox(height: 2), |
|
|
|
Text( |
|
|
|
'Consultez toutes les commandes qui ont été payées', |
|
|
|
style: TextStyle( |
|
|
|
fontSize: 12, |
|
|
|
color: Colors.grey.shade600, |
|
|
|
), |
|
|
|
SizedBox(height: 2), |
|
|
|
Text( |
|
|
|
'Consultez toutes les commandes qui ont été payées', |
|
|
|
style: TextStyle( |
|
|
|
fontSize: 12, |
|
|
|
color: Colors.grey.shade600, |
|
|
|
textAlign: TextAlign.center, |
|
|
|
), |
|
|
|
SizedBox(height: 6), |
|
|
|
// Barre des filtres |
|
|
|
Row( |
|
|
|
mainAxisAlignment: MainAxisAlignment.center, |
|
|
|
children: [ |
|
|
|
ElevatedButton( |
|
|
|
onPressed: () => _selectDate(context, true), |
|
|
|
child: Text(_startDate == null |
|
|
|
? 'Date début' |
|
|
|
: 'Début: ${_startDate!.day}/${_startDate!.month}/${_startDate!.year}'), |
|
|
|
style: ElevatedButton.styleFrom( |
|
|
|
backgroundColor: Color(0xFF4CAF50), |
|
|
|
foregroundColor: Colors.white, |
|
|
|
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), |
|
|
|
textStyle: TextStyle(fontSize: 10), |
|
|
|
), |
|
|
|
), |
|
|
|
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, |
|
|
|
SizedBox(width: 5), |
|
|
|
ElevatedButton( |
|
|
|
onPressed: () => _selectDate(context, false), |
|
|
|
child: Text(_endDate == null |
|
|
|
? 'Date fin' |
|
|
|
: 'Fin: ${_endDate!.day}/${_endDate!.month}/${_endDate!.year}'), |
|
|
|
style: ElevatedButton.styleFrom( |
|
|
|
backgroundColor: Color(0xFF4CAF50), |
|
|
|
foregroundColor: Colors.white, |
|
|
|
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), |
|
|
|
textStyle: TextStyle(fontSize: 10), |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
SizedBox(width: 5), |
|
|
|
ElevatedButton( |
|
|
|
onPressed: () => _loadCommandes(page: 1), |
|
|
|
child: Text('Filtrer'), |
|
|
|
style: ElevatedButton.styleFrom( |
|
|
|
backgroundColor: Colors.orange, |
|
|
|
foregroundColor: Colors.white, |
|
|
|
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), |
|
|
|
textStyle: TextStyle(fontSize: 10), |
|
|
|
), |
|
|
|
), |
|
|
|
SizedBox(width: 5), |
|
|
|
ElevatedButton( |
|
|
|
onPressed: () { |
|
|
|
setState(() { |
|
|
|
_startDate = null; |
|
|
|
_endDate = null; |
|
|
|
}); |
|
|
|
_loadCommandes(page: 1); |
|
|
|
}, |
|
|
|
child: Text('Réinitialiser'), |
|
|
|
style: ElevatedButton.styleFrom( |
|
|
|
backgroundColor: Colors.grey, |
|
|
|
foregroundColor: Colors.white, |
|
|
|
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), |
|
|
|
textStyle: TextStyle(fontSize: 10), |
|
|
|
), |
|
|
|
), |
|
|
|
SizedBox(width: 5), |
|
|
|
ElevatedButton( |
|
|
|
onPressed: () { |
|
|
|
final today = DateTime.now(); |
|
|
|
setState(() { |
|
|
|
_startDate = DateTime(today.year, today.month, today.day); |
|
|
|
_endDate = DateTime(today.year, today.month, today.day, 23, 59, 59); |
|
|
|
}); |
|
|
|
_loadCommandes(page: 1); |
|
|
|
}, |
|
|
|
child: Text('Aujourd’hui'), |
|
|
|
style: ElevatedButton.styleFrom( |
|
|
|
backgroundColor: Colors.blue, |
|
|
|
foregroundColor: Colors.white, |
|
|
|
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), |
|
|
|
textStyle: TextStyle(fontSize: 10), |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
SizedBox(height: 4), |
|
|
|
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( |
|
|
|
@ -462,91 +544,106 @@ class _OrderHistoryPageState extends State<OrderHistoryPage> |
|
|
|
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), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
); |
|
|
|
} |
|
|
|
Widget _buildContent() { |
|
|
|
if (isLoading) { |
|
|
|
return const Center( |
|
|
|
child: CircularProgressIndicator(color: Color(0xFF4CAF50)), |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
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 (error != null) { |
|
|
|
return Center( |
|
|
|
child: Text(error!, style: TextStyle(color: Colors.red)), |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
if (commandes.isEmpty) { |
|
|
|
return const Center( |
|
|
|
child: Text("Aucune commande trouvée"), |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
// ✅ Afficher les données sous forme de tableau |
|
|
|
return SingleChildScrollView( |
|
|
|
scrollDirection: Axis.horizontal, |
|
|
|
child: ConstrainedBox( |
|
|
|
constraints: BoxConstraints(minWidth: MediaQuery.of(context).size.width * 0.9), // 1.5x la largeur écran |
|
|
|
child: SingleChildScrollView( |
|
|
|
child: _buildTableView(), |
|
|
|
), |
|
|
|
), |
|
|
|
); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
Widget _buildTableView() { |
|
|
|
return DataTable( |
|
|
|
columnSpacing: 20, |
|
|
|
headingRowColor: MaterialStateProperty.all(const Color(0xFF4CAF50)), |
|
|
|
headingTextStyle: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold), |
|
|
|
dataRowHeight: 48, |
|
|
|
columns: const [ |
|
|
|
DataColumn(label: Text("N°")), |
|
|
|
DataColumn(label: Text("Table")), |
|
|
|
DataColumn(label: Text("Total")), |
|
|
|
DataColumn(label: Text("Détails")), // Nouvelle colonne pour le bouton |
|
|
|
], |
|
|
|
rows: commandes.map((commande) { |
|
|
|
return DataRow( |
|
|
|
cells: [ |
|
|
|
DataCell(Text(commande.numeroCommande ?? '-')), |
|
|
|
DataCell(Text(commande.tablename ?? '-')), |
|
|
|
DataCell(Text(_formatPrice(commande.totalTtc ?? 0))), |
|
|
|
DataCell( |
|
|
|
IconButton( |
|
|
|
icon: Icon(Icons.info, color: Color(0xFF4CAF50)), |
|
|
|
tooltip: 'Voir les détails', |
|
|
|
onPressed: () { |
|
|
|
_showCommandeDetails(commande); |
|
|
|
}, |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
); |
|
|
|
} |
|
|
|
}).toList(), |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
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, |
|
|
|
), |
|
|
|
// Exemple de fonction pour afficher les détails dans un dialog |
|
|
|
void _showCommandeDetails(CommandeData commande) { |
|
|
|
showDialog( |
|
|
|
context: context, |
|
|
|
builder: (context) { |
|
|
|
return AlertDialog( |
|
|
|
title: Text('Détails commande ${commande.numeroCommande ?? ""}'), |
|
|
|
content: SingleChildScrollView( |
|
|
|
child: Column( |
|
|
|
crossAxisAlignment: CrossAxisAlignment.start, |
|
|
|
children: [ |
|
|
|
Text('Table: ${commande.tablename ?? "-"}'), |
|
|
|
Text( |
|
|
|
'Date de paiement: ${commande.datePaiement != null ? _formatDateTime(commande.datePaiement!) : "-"}', |
|
|
|
), |
|
|
|
Text('Total TTC: ${_formatPrice(commande.totalTtc ?? 0)}'), |
|
|
|
SizedBox(height: 10), |
|
|
|
const Text('Articles:', style: TextStyle(fontWeight: FontWeight.bold)), |
|
|
|
...?commande.items?.map((item) => Text( |
|
|
|
'${item.quantite} × ${item.menuNom} - ${_formatPrice(item.totalItem)}')), |
|
|
|
], |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
actions: [ |
|
|
|
TextButton( |
|
|
|
onPressed: () => Navigator.pop(context), |
|
|
|
child: const Text('Fermer'), |
|
|
|
), |
|
|
|
], |
|
|
|
); |
|
|
|
} |
|
|
|
}, |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
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) { |
|
|
|
@ -1052,20 +1149,19 @@ class CommandeData { |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
static DateTime? _parseDateTime(dynamic value) { |
|
|
|
if (value == null) return null; |
|
|
|
if (value is String) { |
|
|
|
try { |
|
|
|
final result = DateTime.parse(value); |
|
|
|
return result; |
|
|
|
} catch (e) { |
|
|
|
print('Erreur parsing date: $value - $e'); |
|
|
|
return null; |
|
|
|
} |
|
|
|
static DateTime? _parseDateTime(dynamic value) { |
|
|
|
if (value == null) return null; |
|
|
|
if (value is String) { |
|
|
|
try { |
|
|
|
return DateTime.parse(value).toLocal(); // converti en heure locale |
|
|
|
} catch (e) { |
|
|
|
print('Erreur parsing date: $value - $e'); |
|
|
|
return null; |
|
|
|
} |
|
|
|
print('Impossible de parser en datetime: $value'); |
|
|
|
return null; |
|
|
|
} |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static List<CommandeItem>? _parseItems(dynamic value) { |
|
|
|
|
|
|
|
|