diff --git a/lib/Services/stock_managementDatabase.dart b/lib/Services/stock_managementDatabase.dart index 5ff4784..628a1e3 100644 --- a/lib/Services/stock_managementDatabase.dart +++ b/lib/Services/stock_managementDatabase.dart @@ -806,24 +806,12 @@ class AppDatabase { Future> getCommandes() async { final db = await database; - final result = await db.query(''' - SELECT - c.*, - cl.nom as clientNom, - cl.prenom as clientPrenom, - cl.email as clientEmail, - u.nom as commandeurNom, - u.prenom as commandeurPrenom, - pdv.nom as pointDeVenteNom, - pdv.id as pointDeVenteId - FROM commandes c - LEFT JOIN clients cl ON c.clientId = cl.id - LEFT JOIN users u ON c.commandeurId = u.id - LEFT JOIN points_de_vente pdv ON u.point_de_vente_id = pdv.id - ORDER BY c.dateCommande DESC - '''); - + SELECT c.*, cl.nom as clientNom, cl.prenom as clientPrenom, cl.email as clientEmail + FROM commandes c + LEFT JOIN clients cl ON c.clientId = cl.id + ORDER BY c.dateCommande DESC + '''); return result.map((row) => Commande.fromMap(row.fields)).toList(); } diff --git a/lib/Views/historique.dart b/lib/Views/historique.dart index e775fec..5351930 100644 --- a/lib/Views/historique.dart +++ b/lib/Views/historique.dart @@ -1,261 +1,320 @@ - 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'; - import 'package:youmazgestion/controller/userController.dart'; - class HistoriquePage extends StatefulWidget { - const HistoriquePage({super.key}); - - @override - _HistoriquePageState createState() => _HistoriquePageState(); - } +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'; +import 'package:youmazgestion/controller/userController.dart'; - class _HistoriquePageState extends State { - final AppDatabase _appDatabase = AppDatabase.instance; - - // Listes pour les commandes - final List _commandes = []; - final List _filteredCommandes = []; - - List> _pointsDeVente = []; - String? _selectedPointDeVente; - final UserController _userController = Get.find(); - - 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(); - _loadPointsDeVenteWithDefault(); - - // Listeners pour les filtres - _searchController.addListener(_filterCommandes); - _searchClientController.addListener(_filterCommandes); - _searchCommandeIdController.addListener(_filterCommandes); - } +class HistoriquePage extends StatefulWidget { + const HistoriquePage({super.key}); - -Future _loadPointsDeVenteWithDefault() async { - try { - print(_userController.userId); - final points = await _appDatabase.getPointsDeVente(); - setState(() { - _pointsDeVente = points; + @override + _HistoriquePageState createState() => _HistoriquePageState(); +} - if (points.isNotEmpty) { - if (_userController.pointDeVenteId > 0) { - final userPointDeVente = points.firstWhere( - (point) => point['id'] == _userController.pointDeVenteId, - orElse: () => {}, - ); +class _HistoriquePageState extends State { + final AppDatabase _appDatabase = AppDatabase.instance; - if (userPointDeVente.isNotEmpty) { - _selectedPointDeVente = userPointDeVente['nom'] as String; - } else { - _selectedPointDeVente = points[0]['nom'] as String; - } - } else { - _selectedPointDeVente = points[0]['nom'] as String; - } - } - }); - } catch (e) { - Get.snackbar('Erreur', 'Impossible de charger les points de vente: $e'); - print('❌ Erreur chargement points de vente: $e'); - } -} + // Listes pour les commandes + final List _commandes = []; + final List _filteredCommandes = []; - Future _loadCommandes() async { - setState(() { - _isLoading = true; - }); + List> _pointsDeVente = []; + String? _selectedPointDeVente; + final UserController _userController = Get.find(); - 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, - ); - } - } + bool _isLoading = true; + DateTimeRange? _dateRange; - 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(), - ), - ); + // Contrôleurs pour les filtres + final TextEditingController _searchController = TextEditingController(); + final TextEditingController _searchClientController = TextEditingController(); + final TextEditingController _searchCommandeIdController = + TextEditingController(); - if (picked != null) { - setState(() { - _dateRange = picked; - }); - _filterCommandes(); - } - } + // Variables de filtre + StatutCommande? _selectedStatut; + bool _showOnlyToday = false; + double? _minAmount; + double? _maxAmount; + + @override + void initState() { + super.initState(); + _loadCommandes(); + _loadPointsDeVenteWithDefault(); - // Méthode pour filtrer les commandes - void _filterCommandes() { - final searchText = _searchController.text.toLowerCase(); - final clientQuery = _searchClientController.text.toLowerCase(); - final commandeIdQuery = _searchCommandeIdController.text.toLowerCase(); + // Listeners pour les filtres + _searchController.addListener(_filterCommandes); + _searchClientController.addListener(_filterCommandes); + _searchCommandeIdController.addListener(_filterCommandes); + } + Future _loadPointsDeVenteWithDefault() async { + try { + print(_userController.userId); + final points = await _appDatabase.getPointsDeVente(); 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; - } + _pointsDeVente = points; + + if (points.isNotEmpty) { + if (_userController.pointDeVenteId > 0) { + final userPointDeVente = points.firstWhere( + (point) => point['id'] == _userController.pointDeVenteId, + orElse: () => {}, + ); - if (matchesSearch && matchesClient && matchesCommandeId && - matchesStatut && matchesDate && matchesToday && matchesAmount) { - _filteredCommandes.add(commande); + if (userPointDeVente.isNotEmpty) { + _selectedPointDeVente = userPointDeVente['nom'] as String; + } else { + _selectedPointDeVente = points[0]['nom'] as String; + } + } else { + _selectedPointDeVente = points[0]['nom'] as String; } } }); + } catch (e) { + Get.snackbar('Erreur', 'Impossible de charger les points de vente: $e'); + print('❌ Erreur chargement points de vente: $e'); } + } - bool _isToday(DateTime date) { - final now = DateTime.now(); - return date.year == now.year && - date.month == now.month && - date.day == now.day; - } + Future _loadCommandes() async { + setState(() { + _isLoading = true; + }); + + try { + final commandes = await _appDatabase.getCommandes(); - // Toggle filtre aujourd'hui - void _toggleTodayFilter() { setState(() { - _showOnlyToday = !_showOnlyToday; + _commandes.clear(); + _commandes.addAll(commandes); + _filteredCommandes.clear(); + _filteredCommandes.addAll(commandes); + _isLoading = false; }); - _filterCommandes(); + } 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(), + ), + ); - // Réinitialiser les filtres - void _clearFilters() { + if (picked != null) { setState(() { - _searchController.clear(); - _searchClientController.clear(); - _searchCommandeIdController.clear(); - _selectedStatut = null; - _dateRange = null; - _showOnlyToday = false; - _minAmount = null; - _maxAmount = null; + _dateRange = picked; }); _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: [ + // 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: [ - 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), + 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 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(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, ), ), ], ), - const SizedBox(height: 16), - - // Champ de recherche générale + ] else ...[ + // Version mobile - champs empilés TextField( - controller: _searchController, + controller: _searchClientController, decoration: InputDecoration( - labelText: 'Recherche', - prefixIcon: const Icon(Icons.search), + labelText: 'Client', + prefixIcon: const Icon(Icons.person), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), @@ -264,615 +323,578 @@ Future _loadPointsDeVenteWithDefault() async { ), ), 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, + TextField( + controller: _searchCommandeIdController, decoration: InputDecoration( - labelText: 'Statut', - prefixIcon: const Icon(Icons.assignment), + labelText: 'ID Commande', + prefixIcon: const Icon(Icons.confirmation_number), 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(); - }, + keyboardType: TextInputType.number, ), - 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 - ), - ), - ), - ], + ], + 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, ), - - 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), - ), - ], - ), - ), + items: [ + const DropdownMenuItem( + value: null, + child: Text('Tous les statuts'), ), - - const SizedBox(height: 8), - - // Compteur de résultats - Container( - padding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 8 + ...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), + ), ), - decoration: BoxDecoration( - color: Colors.blue.shade50, - borderRadius: BorderRadius.circular(20), + 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), + ), ), - child: Text( - '${_filteredCommandes.length} commande(s)', - style: TextStyle( - color: Colors.blue.shade700, - fontWeight: FontWeight.w600, - fontSize: isMobile ? 12 : 14, + ], + ), + + 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, + 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), + _buildDetailRow( + 'PV', '${_selectedPointDeVente} ', Icons.person), 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.all(8), + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 4), decoration: BoxDecoration( - color: Colors.blue.shade100, - borderRadius: BorderRadius.circular(8), + 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, + ), ), - child: Icon(Icons.receipt_long, color: Colors.blue.shade700), ), - const SizedBox(width: 12), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ Text( - 'Commande #${commande.id}', + 'Total:', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.grey.shade700, + ), + ), + Text( + '${commande.montantTotal.toStringAsFixed(2)} MGA', style: const TextStyle( - fontSize: 18, // Taille réduite pour mobile + fontSize: 16, fontWeight: FontWeight.bold, + color: Colors.green, ), ), ], ), - 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), - _buildDetailRow('Client', '${_selectedPointDeVente} ', Icons.person), - 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: 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, ), - ), - ), - ], - ), - 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), + leading: Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: Colors.blue.shade50, + borderRadius: BorderRadius.circular(8), ), - 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, - ), + 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), - ), + ), + ); + }, + ), + ), + + 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é'), ), + 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, ), - ), ], ), - ); - } + ), + isScrollControlled: true, + ); + } - 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'; - } + 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; - } + 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, - ); - } + 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, - ), + // 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, - ), + ), + 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; - // print(commande.commandeurId); - print(_userController.userId); - - 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), - ), + // Widget pour l'item de commande (adapté pour mobile) + Widget _buildCommandeListItem(Commande commande) { + final isMobile = MediaQuery.of(context).size.width < 600; + // print(commande.commandeurId); + print(_userController.userId); + + 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), ), - 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( - '${_selectedPointDeVente}', - style: const TextStyle( - fontWeight: FontWeight.w500, - fontSize: 14, - ), - ), - Text( - DateFormat('dd/MM/yyyy').format(commande.dateCommande), - style: TextStyle( - fontSize: 12, - color: Colors.grey.shade600, - ), - ), - ], - ), + child: Icon( + Icons.shopping_cart, + size: isMobile ? 20 : 24, + color: _getStatutColor(commande.statut), ), - Column( - crossAxisAlignment: CrossAxisAlignment.end, + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '${commande.montantTotal.toStringAsFixed(2)} MGA', - style: TextStyle( + 'Commande #${commande.id}', + style: const TextStyle( fontWeight: FontWeight.bold, - color: Colors.green.shade700, - fontSize: isMobile ? 14 : 16, + fontSize: 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), + Text( + '${commande.clientNom} ${commande.clientPrenom}', + style: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 14, ), - child: Text( - _getStatutText(commande.statut), - style: TextStyle( - color: _getStatutColor(commande.statut), - fontWeight: FontWeight.bold, - fontSize: isMobile ? 10 : 12, - ), + ), + Text( + 'PV: ${_selectedPointDeVente}', + 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', @@ -888,11 +910,12 @@ Future _loadPointsDeVenteWithDefault() async { 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), + padding: + const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), child: ElevatedButton.icon( icon: const Icon(Icons.filter_alt), label: const Text('Filtres'), @@ -902,7 +925,7 @@ Future _loadPointsDeVenteWithDefault() async { isScrollControlled: true, builder: (context) => SingleChildScrollView( padding: EdgeInsets.only( - bottom: MediaQuery.of(context).viewInsets.bottom), + bottom: MediaQuery.of(context).viewInsets.bottom), child: _buildFilterSection(), ), ); @@ -921,7 +944,8 @@ Future _loadPointsDeVenteWithDefault() async { Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.circular(20), @@ -936,7 +960,7 @@ Future _loadPointsDeVenteWithDefault() async { ), ), ], - + // Liste des commandes Expanded( child: _isLoading @@ -966,4 +990,4 @@ Future _loadPointsDeVenteWithDefault() async { _searchCommandeIdController.dispose(); super.dispose(); } -} \ No newline at end of file +} diff --git a/lib/config/DatabaseConfig.dart b/lib/config/DatabaseConfig.dart index c21e9f7..c5b3d0b 100644 --- a/lib/config/DatabaseConfig.dart +++ b/lib/config/DatabaseConfig.dart @@ -11,7 +11,7 @@ class DatabaseConfig { static const String localDatabase = 'guycom'; // Production (public) MySQL settings - static const String prodHost = '185.70.105.157'; + static const String prodHost = '102.17.52.31'; static const String prodUsername = 'guycom'; static const String prodPassword = '3iV59wjRdbuXAPR'; static const String prodDatabase = 'guycom';