import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; import 'package:youmazgestion/Components/app_bar.dart'; import 'package:youmazgestion/Components/appDrawer.dart'; import 'package:youmazgestion/Models/client.dart'; import 'package:youmazgestion/Services/stock_managementDatabase.dart'; class HistoriquePage extends StatefulWidget { const HistoriquePage({super.key}); @override _HistoriquePageState createState() => _HistoriquePageState(); } class _HistoriquePageState extends State { final AppDatabase _appDatabase = AppDatabase.instance; // Listes pour les commandes final List _commandes = []; final List _filteredCommandes = []; bool _isLoading = true; DateTimeRange? _dateRange; // Contrôleurs pour les filtres final TextEditingController _searchController = TextEditingController(); final TextEditingController _searchClientController = TextEditingController(); final TextEditingController _searchCommandeIdController = TextEditingController(); // Variables de filtre StatutCommande? _selectedStatut; bool _showOnlyToday = false; double? _minAmount; double? _maxAmount; @override void initState() { super.initState(); _loadCommandes(); // Listeners pour les filtres _searchController.addListener(_filterCommandes); _searchClientController.addListener(_filterCommandes); _searchCommandeIdController.addListener(_filterCommandes); } Future _loadCommandes() async { setState(() { _isLoading = true; }); try { final commandes = await _appDatabase.getCommandes(); setState(() { _commandes.clear(); _commandes.addAll(commandes); _filteredCommandes.clear(); _filteredCommandes.addAll(commandes); _isLoading = false; }); } catch (e) { setState(() { _isLoading = false; }); Get.snackbar( 'Erreur', 'Impossible de charger les commandes: ${e.toString()}', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red, colorText: Colors.white, ); } } Future _selectDateRange(BuildContext context) async { final DateTimeRange? picked = await showDateRangePicker( context: context, firstDate: DateTime(2020), lastDate: DateTime.now().add(const Duration(days: 365)), initialDateRange: _dateRange ?? DateTimeRange( start: DateTime.now().subtract(const Duration(days: 30)), end: DateTime.now(), ), ); if (picked != null) { setState(() { _dateRange = picked; }); _filterCommandes(); } } // Méthode pour filtrer les commandes void _filterCommandes() { final searchText = _searchController.text.toLowerCase(); final clientQuery = _searchClientController.text.toLowerCase(); final commandeIdQuery = _searchCommandeIdController.text.toLowerCase(); setState(() { _filteredCommandes.clear(); for (var commande in _commandes) { bool matchesSearch = searchText.isEmpty || commande.clientNom!.toLowerCase().contains(searchText) || commande.clientPrenom!.toLowerCase().contains(searchText) || commande.id.toString().contains(searchText); bool matchesClient = clientQuery.isEmpty || commande.clientNom!.toLowerCase().contains(clientQuery) || commande.clientPrenom!.toLowerCase().contains(clientQuery); bool matchesCommandeId = commandeIdQuery.isEmpty || commande.id.toString().contains(commandeIdQuery); bool matchesStatut = _selectedStatut == null || commande.statut == _selectedStatut; bool matchesDate = true; if (_dateRange != null) { final date = commande.dateCommande; matchesDate = date.isAfter(_dateRange!.start) && date.isBefore(_dateRange!.end.add(const Duration(days: 1))); } bool matchesToday = !_showOnlyToday || _isToday(commande.dateCommande); bool matchesAmount = true; if (_minAmount != null && commande.montantTotal < _minAmount!) { matchesAmount = false; } if (_maxAmount != null && commande.montantTotal > _maxAmount!) { matchesAmount = false; } if (matchesSearch && matchesClient && matchesCommandeId && matchesStatut && matchesDate && matchesToday && matchesAmount) { _filteredCommandes.add(commande); } } }); } bool _isToday(DateTime date) { final now = DateTime.now(); return date.year == now.year && date.month == now.month && date.day == now.day; } // Toggle filtre aujourd'hui void _toggleTodayFilter() { setState(() { _showOnlyToday = !_showOnlyToday; }); _filterCommandes(); } // Réinitialiser les filtres void _clearFilters() { setState(() { _searchController.clear(); _searchClientController.clear(); _searchCommandeIdController.clear(); _selectedStatut = null; _dateRange = null; _showOnlyToday = false; _minAmount = null; _maxAmount = null; }); _filterCommandes(); } // Widget pour la section des filtres (adapté pour mobile) Widget _buildFilterSection() { final isMobile = MediaQuery.of(context).size.width < 600; return Card( elevation: 2, margin: const EdgeInsets.only(bottom: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.filter_list, color: Colors.blue.shade700), const SizedBox(width: 8), const Text( 'Filtres', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Color.fromARGB(255, 9, 56, 95), ), ), const Spacer(), TextButton.icon( onPressed: _clearFilters, icon: const Icon(Icons.clear, size: 18), label: isMobile ? const SizedBox() : const Text('Réinitialiser'), style: TextButton.styleFrom( foregroundColor: Colors.grey.shade600, ), ), ], ), const SizedBox(height: 16), // Champ de recherche générale TextField( controller: _searchController, decoration: InputDecoration( labelText: 'Recherche', prefixIcon: const Icon(Icons.search), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), filled: true, fillColor: Colors.grey.shade50, ), ), const SizedBox(height: 12), if (!isMobile) ...[ // Version desktop - champs sur la même ligne Row( children: [ Expanded( child: TextField( controller: _searchClientController, decoration: InputDecoration( labelText: 'Client', prefixIcon: const Icon(Icons.person), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), filled: true, fillColor: Colors.grey.shade50, ), ),), const SizedBox(width: 12), Expanded( child: TextField( controller: _searchCommandeIdController, decoration: InputDecoration( labelText: 'ID Commande', prefixIcon: const Icon(Icons.confirmation_number), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), filled: true, fillColor: Colors.grey.shade50, ), keyboardType: TextInputType.number, ), ), ], ), ] else ...[ // Version mobile - champs empilés TextField( controller: _searchClientController, decoration: InputDecoration( labelText: 'Client', prefixIcon: const Icon(Icons.person), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), filled: true, fillColor: Colors.grey.shade50, ), ), const SizedBox(height: 12), TextField( controller: _searchCommandeIdController, decoration: InputDecoration( labelText: 'ID Commande', prefixIcon: const Icon(Icons.confirmation_number), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), filled: true, fillColor: Colors.grey.shade50, ), keyboardType: TextInputType.number, ), ], const SizedBox(height: 12), // Dropdown pour le statut DropdownButtonFormField( value: _selectedStatut, decoration: InputDecoration( labelText: 'Statut', prefixIcon: const Icon(Icons.assignment), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), filled: true, fillColor: Colors.grey.shade50, ), items: [ const DropdownMenuItem( value: null, child: Text('Tous les statuts'), ), ...StatutCommande.values.map((StatutCommande statut) { return DropdownMenuItem( value: statut, child: Text(_getStatutText(statut)), ); }).toList(), ], onChanged: (StatutCommande? newValue) { setState(() { _selectedStatut = newValue; }); _filterCommandes(); }, ), const SizedBox(height: 16), // Boutons de filtre rapide - adaptés pour mobile Wrap( spacing: 8, runSpacing: 8, children: [ ElevatedButton.icon( onPressed: _toggleTodayFilter, icon: Icon( _showOnlyToday ? Icons.today : Icons.calendar_today, size: 20, ), label: Text(_showOnlyToday ? isMobile ? 'Toutes dates' : 'Toutes les dates' : isMobile ? 'Aujourd\'hui' : 'Aujourd\'hui seulement'), style: ElevatedButton.styleFrom( backgroundColor: _showOnlyToday ? Colors.green.shade600 : Colors.blue.shade600, foregroundColor: Colors.white, padding: EdgeInsets.symmetric( horizontal: isMobile ? 12 : 16, vertical: 8 ), ), ), ElevatedButton.icon( onPressed: () => _selectDateRange(context), icon: const Icon(Icons.date_range, size: 20), label: Text(_dateRange != null ? isMobile ? 'Période' : 'Période sélectionnée' : isMobile ? 'Période' : 'Choisir période'), style: ElevatedButton.styleFrom( backgroundColor: _dateRange != null ? Colors.orange.shade600 : Colors.grey.shade600, foregroundColor: Colors.white, padding: EdgeInsets.symmetric( horizontal: isMobile ? 12 : 16, vertical: 8 ), ), ), ], ), if (_dateRange != null) Padding( padding: const EdgeInsets.only(top: 8), child: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.orange.shade50, borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.date_range, size: 16, color: Colors.orange.shade700), const SizedBox(width: 4), Text( '${DateFormat('dd/MM/yyyy').format(_dateRange!.start)} - ${DateFormat('dd/MM/yyyy').format(_dateRange!.end)}', style: TextStyle( fontSize: isMobile ? 10 : 12, color: Colors.orange.shade700, fontWeight: FontWeight.w600, ), ), const SizedBox(width: 4), GestureDetector( onTap: () { setState(() { _dateRange = null; }); _filterCommandes(); }, child: Icon(Icons.close, size: 16, color: Colors.orange.shade700), ), ], ), ), ), const SizedBox(height: 8), // Compteur de résultats Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 8 ), decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.circular(20), ), child: Text( '${_filteredCommandes.length} commande(s)', style: TextStyle( color: Colors.blue.shade700, fontWeight: FontWeight.w600, fontSize: isMobile ? 12 : 14, ), ), ), ], ), ), ); } void _showCommandeDetails(Commande commande) async { final details = await _appDatabase.getDetailsCommande(commande.id!); final client = await _appDatabase.getClientById(commande.clientId); Get.bottomSheet( Container( padding: const EdgeInsets.all(16), height: MediaQuery.of(context).size.height * 0.85, // Plus grand sur mobile decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.blue.shade100, borderRadius: BorderRadius.circular(8), ), child: Icon(Icons.receipt_long, color: Colors.blue.shade700), ), const SizedBox(width: 12), Text( 'Commande #${commande.id}', style: const TextStyle( fontSize: 18, // Taille réduite pour mobile fontWeight: FontWeight.bold, ), ), ], ), IconButton( icon: const Icon(Icons.close), onPressed: () => Get.back(), ), ], ), const Divider(), // Informations de la commande - version compacte pour mobile Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey.shade50, borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildDetailRow('Client', '${client?.nom} ${client?.prenom}', Icons.person), _buildDetailRow('Date', DateFormat('dd/MM/yyyy à HH:mm').format(commande.dateCommande), Icons.calendar_today), Row( children: [ Icon(Icons.assignment, size: 16, color: Colors.grey.shade600), const SizedBox(width: 8), const Text('Statut: ', style: TextStyle(fontWeight: FontWeight.w500)), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: _getStatutColor(commande.statut).withOpacity(0.2), borderRadius: BorderRadius.circular(12), ), child: Text( _getStatutText(commande.statut), style: TextStyle( color: _getStatutColor(commande.statut), fontWeight: FontWeight.bold, fontSize: 12, ), ), ), ], ), const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Total:', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.grey.shade700, ), ), Text( '${commande.montantTotal.toStringAsFixed(2)} MGA', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.green, ), ), ], ), ], ), ), const SizedBox(height: 12), const Text( 'Articles:', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), Expanded( child: details.isEmpty ? const Center( child: Text('Aucun détail disponible'), ) : ListView.builder( itemCount: details.length, itemBuilder: (context, index) { final detail = details[index]; return Card( margin: const EdgeInsets.symmetric(vertical: 4), elevation: 1, child: ListTile( contentPadding: const EdgeInsets.symmetric( horizontal: 12, vertical: 8, ), leading: Container( width: 40, height: 40, decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.circular(8), ), child: const Icon(Icons.shopping_bag, size: 20), ), title: Text( detail.produitNom ?? 'Produit inconnu', style: const TextStyle(fontWeight: FontWeight.w500), ), subtitle: Text( '${detail.quantite} x ${detail.prixUnitaire.toStringAsFixed(2)} MGA', ), trailing: Text( '${detail.sousTotal.toStringAsFixed(2)} MGA', style: TextStyle( fontWeight: FontWeight.bold, color: Colors.blue.shade800, ), ), ), ); }, ), ), if (commande.statut == StatutCommande.enAttente) SizedBox( width: double.infinity, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.blue.shade800, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(vertical: 14), // Plus compact shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), onPressed: () => _updateStatutCommande(commande.id!), child: const Text('Marquer comme confirmé'), ), ), ], ), ), isScrollControlled: true, ); } Widget _buildDetailRow(String label, String value, IconData icon) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: Row( children: [ Icon(icon, size: 16, color: Colors.grey.shade600), const SizedBox(width: 8), Text('$label: ', style: const TextStyle(fontWeight: FontWeight.w500)), Expanded( child: Text( value, overflow: TextOverflow.ellipsis, maxLines: 1, ), ), ], ), ); } String _getStatutText(StatutCommande statut) { switch (statut) { case StatutCommande.enAttente: return 'En attente'; case StatutCommande.confirmee: return 'Confirmée'; case StatutCommande.annulee: return 'Annulée'; default: return 'Inconnu'; } } Color _getStatutColor(StatutCommande statut) { switch (statut) { case StatutCommande.enAttente: return Colors.orange; case StatutCommande.confirmee: return Colors.green; case StatutCommande.annulee: return Colors.red; default: return Colors.grey; } } Future _updateStatutCommande(int commandeId) async { try { await _appDatabase.updateStatutCommande( commandeId, StatutCommande.confirmee); Get.back(); // Ferme le bottom sheet _loadCommandes(); Get.snackbar( 'Succès', 'Statut de la commande mis à jour', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.green, colorText: Colors.white, ); } catch (e) { Get.snackbar( 'Erreur', 'Impossible de mettre à jour le statut: ${e.toString()}', snackPosition: SnackPosition.BOTTOM, backgroundColor: Colors.red, colorText: Colors.white, ); } } // Widget pour l'état vide Widget _buildEmptyState() { return Center( child: Padding( padding: const EdgeInsets.all(32.0), child: Column( children: [ Icon( Icons.receipt_long_outlined, size: 64, color: Colors.grey.shade400, ), const SizedBox(height: 16), Text( 'Aucune commande trouvée', style: TextStyle( fontSize: 18, fontWeight: FontWeight.w500, color: Colors.grey.shade600, ), ), const SizedBox(height: 8), Text( 'Modifiez vos critères de recherche', style: TextStyle( fontSize: 14, color: Colors.grey.shade500, ), ), ], ), ), ); } // Widget pour l'item de commande (adapté pour mobile) Widget _buildCommandeListItem(Commande commande) { final isMobile = MediaQuery.of(context).size.width < 600; return Card( margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), child: InkWell( borderRadius: BorderRadius.circular(8), onTap: () => _showCommandeDetails(commande), child: Padding( padding: const EdgeInsets.all(12.0), child: Row( children: [ Container( width: isMobile ? 40 : 50, height: isMobile ? 40 : 50, decoration: BoxDecoration( color: _getStatutColor(commande.statut).withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Icon( Icons.shopping_cart, size: isMobile ? 20 : 24, color: _getStatutColor(commande.statut), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Commande #${commande.id}', style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), ), const SizedBox(height: 4), Text( '${commande.clientNom} ${commande.clientPrenom}', style: const TextStyle( fontWeight: FontWeight.w500, fontSize: 14, ), ), Text( '${commande.clientNom} ${commande.clientPrenom}', style: const TextStyle( fontWeight: FontWeight.w500, fontSize: 14, ), ), Text( DateFormat('dd/MM/yyyy').format(commande.dateCommande), style: TextStyle( fontSize: 12, color: Colors.grey.shade600, ), ), ], ), ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( '${commande.montantTotal.toStringAsFixed(2)} MGA', style: TextStyle( fontWeight: FontWeight.bold, color: Colors.green.shade700, fontSize: isMobile ? 14 : 16, ), ), const SizedBox(height: 4), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: _getStatutColor(commande.statut).withOpacity(0.2), borderRadius: BorderRadius.circular(12), ), child: Text( _getStatutText(commande.statut), style: TextStyle( color: _getStatutColor(commande.statut), fontWeight: FontWeight.bold, fontSize: isMobile ? 10 : 12, ), ), ), ], ), ], ), ), ), ); } @override Widget build(BuildContext context) { final isMobile = MediaQuery.of(context).size.width < 600; return Scaffold( appBar: CustomAppBar( title: 'Historique', actions: [ IconButton( icon: const Icon(Icons.refresh), onPressed: _loadCommandes, ), ], ), drawer: CustomDrawer(), body: Column( children: [ // Section des filtres - toujours visible mais plus compacte sur mobile if (!isMobile) _buildFilterSection(), // Sur mobile, on ajoute un bouton pour afficher les filtres dans un modal if (isMobile) ...[ Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), child: ElevatedButton.icon( icon: const Icon(Icons.filter_alt), label: const Text('Filtres'), onPressed: () { showModalBottomSheet( context: context, isScrollControlled: true, builder: (context) => SingleChildScrollView( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom), child: _buildFilterSection(), ), ); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.blue.shade700, foregroundColor: Colors.white, minimumSize: const Size(double.infinity, 48), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), ), ), // Compteur de résultats visible en haut sur mobile Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.circular(20), ), child: Text( '${_filteredCommandes.length} commande(s)', style: TextStyle( color: Colors.blue.shade700, fontWeight: FontWeight.w600, ), ), ), ), ], // Liste des commandes Expanded( child: _isLoading ? const Center( child: CircularProgressIndicator(), ) : _filteredCommandes.isEmpty ? _buildEmptyState() : ListView.builder( padding: const EdgeInsets.all(16.0), itemCount: _filteredCommandes.length, itemBuilder: (context, index) { final commande = _filteredCommandes[index]; return _buildCommandeListItem(commande); }, ), ), ], ), ); } @override void dispose() { _searchController.dispose(); _searchClientController.dispose(); _searchCommandeIdController.dispose(); super.dispose(); } }