diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index e2d9cf5..ae82cd3 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -8,7 +8,8 @@ plugins { android { namespace = "com.example.my_app" compileSdk = flutter.compileSdkVersion - ndkVersion = flutter.ndkVersion + ndkVersion = "26.3.11579264" + compileOptions { sourceCompatibility = JavaVersion.VERSION_11 diff --git a/ios/build/ios/XCBuildData/PIFCache/workspace/WORKSPACE@v11_hash=(null)_subobjects=b2e12b7ce127ba825ffc2656889f5368-json b/ios/build/ios/XCBuildData/PIFCache/workspace/WORKSPACE@v11_hash=(null)_subobjects=b2e12b7ce127ba825ffc2656889f5368-json new file mode 100644 index 0000000..54ba11d --- /dev/null +++ b/ios/build/ios/XCBuildData/PIFCache/workspace/WORKSPACE@v11_hash=(null)_subobjects=b2e12b7ce127ba825ffc2656889f5368-json @@ -0,0 +1 @@ +{"guid":"dc4b70c03e8043e50e38f2068887b1d4","name":"Pods","path":"/Users/rabarisonmimistephanethannio/Documents/DEV/guycom_finale/ios/Pods/Pods.xcodeproj/project.xcworkspace","projects":["PROJECT@v11_mod=7e9f9a517e1b730b3eb5b9aa5a52f2df_hash=bfdfe7dc352907fc980b868725387e98plugins=1OJSG6M1FOV3XYQCBH7Z29RZ0FPR9XDE1"]} \ No newline at end of file diff --git a/lib/Components/AddClient.dart b/lib/Components/AddClient.dart index f952650..e4a4ea2 100644 --- a/lib/Components/AddClient.dart +++ b/lib/Components/AddClient.dart @@ -4,22 +4,21 @@ import 'package:youmazgestion/Models/client.dart'; import '../Services/stock_managementDatabase.dart'; - class ClientFormController extends GetxController { final _formKey = GlobalKey(); - + // Controllers pour les champs final _nomController = TextEditingController(); final _prenomController = TextEditingController(); final _emailController = TextEditingController(); final _telephoneController = TextEditingController(); final _adresseController = TextEditingController(); - + // Variables observables pour la recherche var suggestedClients = [].obs; var isSearching = false.obs; var selectedClient = Rxn(); - + @override void onClose() { _nomController.dispose(); @@ -29,14 +28,14 @@ class ClientFormController extends GetxController { _adresseController.dispose(); super.onClose(); } - + // Méthode pour rechercher les clients existants Future searchClients(String query) async { if (query.length < 2) { suggestedClients.clear(); return; } - + isSearching.value = true; try { final clients = await AppDatabase.instance.suggestClients(query); @@ -48,7 +47,7 @@ class ClientFormController extends GetxController { isSearching.value = false; } } - + // Méthode pour remplir automatiquement le formulaire void fillFormWithClient(Client client) { selectedClient.value = client; @@ -59,7 +58,7 @@ class ClientFormController extends GetxController { _adresseController.text = client.adresse ?? ''; suggestedClients.clear(); } - + // Méthode pour vider le formulaire void clearForm() { selectedClient.value = null; @@ -70,14 +69,14 @@ class ClientFormController extends GetxController { _adresseController.clear(); suggestedClients.clear(); } - + // Méthode pour valider et soumettre Future submitForm() async { if (!_formKey.currentState!.validate()) return; - + try { Client clientToUse; - + if (selectedClient.value != null) { // Utiliser le client existant clientToUse = selectedClient.value!; @@ -88,17 +87,18 @@ class ClientFormController extends GetxController { prenom: _prenomController.text.trim(), email: _emailController.text.trim(), telephone: _telephoneController.text.trim(), - adresse: _adresseController.text.trim().isEmpty ? null : _adresseController.text.trim(), + adresse: _adresseController.text.trim().isEmpty + ? null + : _adresseController.text.trim(), dateCreation: DateTime.now(), ); - + clientToUse = await AppDatabase.instance.createOrGetClient(newClient); } - + // Procéder avec la commande Get.back(); _submitOrderWithClient(clientToUse); - } catch (e) { Get.snackbar( 'Erreur', @@ -108,7 +108,7 @@ class ClientFormController extends GetxController { ); } } - + void _submitOrderWithClient(Client client) { // Votre logique existante pour soumettre la commande // avec le client fourni @@ -116,9 +116,10 @@ class ClientFormController extends GetxController { } // Widget pour le formulaire avec auto-completion +// ignore: unused_element void _showClientFormDialog() { final controller = Get.put(ClientFormController()); - + Get.dialog( AlertDialog( title: Row( @@ -155,7 +156,7 @@ void _showClientFormDialog() { // Section de recherche rapide _buildSearchSection(controller), const SizedBox(height: 16), - + // Indicateur client sélectionné Obx(() { if (controller.selectedClient.value != null) { @@ -168,7 +169,8 @@ void _showClientFormDialog() { ), child: Row( children: [ - Icon(Icons.check_circle, color: Colors.green.shade600), + Icon(Icons.check_circle, + color: Colors.green.shade600), const SizedBox(width: 8), Expanded( child: Text( @@ -186,12 +188,13 @@ void _showClientFormDialog() { return const SizedBox.shrink(); }), const SizedBox(height: 12), - + // Champs du formulaire _buildTextFormField( controller: controller._nomController, label: 'Nom', - validator: (value) => value?.isEmpty ?? true ? 'Veuillez entrer un nom' : null, + validator: (value) => + value?.isEmpty ?? true ? 'Veuillez entrer un nom' : null, onChanged: (value) { if (controller.selectedClient.value != null) { controller.selectedClient.value = null; @@ -199,11 +202,13 @@ void _showClientFormDialog() { }, ), const SizedBox(height: 12), - + _buildTextFormField( controller: controller._prenomController, label: 'Prénom', - validator: (value) => value?.isEmpty ?? true ? 'Veuillez entrer un prénom' : null, + validator: (value) => value?.isEmpty ?? true + ? 'Veuillez entrer un prénom' + : null, onChanged: (value) { if (controller.selectedClient.value != null) { controller.selectedClient.value = null; @@ -211,14 +216,16 @@ void _showClientFormDialog() { }, ), const SizedBox(height: 12), - + _buildTextFormField( controller: controller._emailController, label: 'Email', keyboardType: TextInputType.emailAddress, validator: (value) { - if (value?.isEmpty ?? true) return 'Veuillez entrer un email'; - if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value!)) { + // if (value?.isEmpty ?? true) return 'Veuillez entrer un email'; + if (value?.isEmpty ?? true) return null; + if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$') + .hasMatch(value!)) { return 'Email invalide'; } return null; @@ -232,12 +239,14 @@ void _showClientFormDialog() { }, ), const SizedBox(height: 12), - + _buildTextFormField( controller: controller._telephoneController, label: 'Téléphone', keyboardType: TextInputType.phone, - validator: (value) => value?.isEmpty ?? true ? 'Veuillez entrer un téléphone' : null, + validator: (value) => value?.isEmpty ?? true + ? 'Veuillez entrer un téléphone' + : null, onChanged: (value) { if (controller.selectedClient.value != null) { controller.selectedClient.value = null; @@ -247,12 +256,14 @@ void _showClientFormDialog() { }, ), const SizedBox(height: 12), - + _buildTextFormField( controller: controller._adresseController, label: 'Adresse', maxLines: 2, - validator: (value) => value?.isEmpty ?? true ? 'Veuillez entrer une adresse' : null, + validator: (value) => value?.isEmpty ?? true + ? 'Veuillez entrer une adresse' + : null, onChanged: (value) { if (controller.selectedClient.value != null) { controller.selectedClient.value = null; @@ -260,9 +271,9 @@ void _showClientFormDialog() { }, ), const SizedBox(height: 12), - + _buildCommercialDropdown(), - + // Liste des suggestions Obx(() { if (controller.isSearching.value) { @@ -271,11 +282,11 @@ void _showClientFormDialog() { child: Center(child: CircularProgressIndicator()), ); } - + if (controller.suggestedClients.isEmpty) { return const SizedBox.shrink(); } - + return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -288,8 +299,9 @@ void _showClientFormDialog() { ), ), const SizedBox(height: 8), - ...controller.suggestedClients.map((client) => - _buildClientSuggestionTile(client, controller), + ...controller.suggestedClients.map( + (client) => + _buildClientSuggestionTile(client, controller), ), ], ); @@ -349,7 +361,8 @@ Widget _buildSearchSection(ClientFormController controller) { } // Widget pour afficher une suggestion de client -Widget _buildClientSuggestionTile(Client client, ClientFormController controller) { +Widget _buildClientSuggestionTile( + Client client, ClientFormController controller) { return Card( margin: const EdgeInsets.only(bottom: 8), child: ListTile( @@ -414,4 +427,4 @@ Widget _buildTextFormField({ Widget _buildCommercialDropdown() { // Votre implémentation existante return Container(); // Remplacez par votre code existant -} \ No newline at end of file +} diff --git a/lib/Components/QrScan.dart b/lib/Components/QrScan.dart index 0ad9243..bcaf4b1 100644 --- a/lib/Components/QrScan.dart +++ b/lib/Components/QrScan.dart @@ -25,13 +25,14 @@ class _ScanQRPageState extends State { } void _initializeController() { - if (!isMobile) { - setState(() { - _hasError = true; - _errorMessage = "Le scanner QR n'est pas disponible sur cette plateforme."; - }); - return; - } + if (!isMobile) { + setState(() { + _hasError = true; + _errorMessage = + "Le scanner QR n'est pas disponible sur cette plateforme."; + }); + return; + } try { cameraController = MobileScannerController( detectionSpeed: DetectionSpeed.noDuplicates, @@ -61,22 +62,25 @@ class _ScanQRPageState extends State { return Scaffold( appBar: AppBar( title: const Text('Scanner QR Code'), - actions: _hasError ? [] : [ - if (cameraController != null) ...[ - IconButton( - color: Colors.white, - icon: const Icon(Icons.flash_on, color: Colors.white), - iconSize: 32.0, - onPressed: () => cameraController!.toggleTorch(), - ), - IconButton( - color: Colors.white, - icon: const Icon(Icons.flip_camera_ios, color: Colors.white), - iconSize: 32.0, - onPressed: () => cameraController!.switchCamera(), - ), - ], - ], + actions: _hasError + ? [] + : [ + if (cameraController != null) ...[ + IconButton( + color: Colors.white, + icon: const Icon(Icons.flash_on, color: Colors.white), + iconSize: 32.0, + onPressed: () => cameraController!.toggleTorch(), + ), + IconButton( + color: Colors.white, + icon: + const Icon(Icons.flip_camera_ios, color: Colors.white), + iconSize: 32.0, + onPressed: () => cameraController!.switchCamera(), + ), + ], + ], ), body: _hasError ? _buildErrorWidget() : _buildScannerWidget(), ); @@ -150,7 +154,8 @@ class _ScanQRPageState extends State { children: [ const Icon(Icons.error, size: 64, color: Colors.red), const SizedBox(height: 16), - Text('Erreur: ${error.errorDetails?.message ?? 'Erreur inconnue'}'), + Text( + 'Erreur: ${error.errorDetails?.message ?? 'Erreur inconnue'}'), const SizedBox(height: 16), ElevatedButton( onPressed: () => _initializeController(), @@ -236,19 +241,25 @@ class QrScannerOverlay extends CustomPainter { ..style = PaintingStyle.stroke; // Coins du scanner - _drawCorner(canvas, borderPaint, areaLeft, areaTop, borderLength, true, true); - _drawCorner(canvas, borderPaint, areaLeft + areaSize, areaTop, borderLength, false, true); - _drawCorner(canvas, borderPaint, areaLeft, areaTop + areaSize, borderLength, true, false); - _drawCorner(canvas, borderPaint, areaLeft + areaSize, areaTop + areaSize, borderLength, false, false); + _drawCorner( + canvas, borderPaint, areaLeft, areaTop, borderLength, true, true); + _drawCorner(canvas, borderPaint, areaLeft + areaSize, areaTop, borderLength, + false, true); + _drawCorner(canvas, borderPaint, areaLeft, areaTop + areaSize, borderLength, + true, false); + _drawCorner(canvas, borderPaint, areaLeft + areaSize, areaTop + areaSize, + borderLength, false, false); } - void _drawCorner(Canvas canvas, Paint paint, double x, double y, double length, bool isLeft, bool isTop) { + void _drawCorner(Canvas canvas, Paint paint, double x, double y, + double length, bool isLeft, bool isTop) { final double horizontalStart = isLeft ? x : x - length; final double horizontalEnd = isLeft ? x + length : x; final double verticalStart = isTop ? y : y - length; final double verticalEnd = isTop ? y + length : y; - canvas.drawLine(Offset(horizontalStart, y), Offset(horizontalEnd, y), paint); + canvas.drawLine( + Offset(horizontalStart, y), Offset(horizontalEnd, y), paint); canvas.drawLine(Offset(x, verticalStart), Offset(x, verticalEnd), paint); } @@ -256,4 +267,4 @@ class QrScannerOverlay extends CustomPainter { bool shouldRepaint(covariant CustomPainter oldDelegate) { return false; } -} \ No newline at end of file +} diff --git a/lib/Views/demande_sortie_personnelle_page.dart b/lib/Views/demande_sortie_personnelle_page.dart index e395637..54963c5 100644 --- a/lib/Views/demande_sortie_personnelle_page.dart +++ b/lib/Views/demande_sortie_personnelle_page.dart @@ -5,31 +5,33 @@ import 'package:youmazgestion/Components/appDrawer.dart'; import 'package:youmazgestion/Services/stock_managementDatabase.dart'; import 'package:youmazgestion/controller/userController.dart'; import '../Models/produit.dart'; +import 'package:mobile_scanner/mobile_scanner.dart'; class DemandeSortiePersonnellePage extends StatefulWidget { const DemandeSortiePersonnellePage({super.key}); @override - _DemandeSortiePersonnellePageState createState() => _DemandeSortiePersonnellePageState(); + _DemandeSortiePersonnellePageState createState() => + _DemandeSortiePersonnellePageState(); } -class _DemandeSortiePersonnellePageState extends State - with TickerProviderStateMixin { +class _DemandeSortiePersonnellePageState + extends State with TickerProviderStateMixin { final AppDatabase _database = AppDatabase.instance; final UserController _userController = Get.find(); - + final _formKey = GlobalKey(); final _quantiteController = TextEditingController(text: '1'); final _motifController = TextEditingController(); final _notesController = TextEditingController(); final _searchController = TextEditingController(); - + Product? _selectedProduct; List _products = []; List _filteredProducts = []; bool _isLoading = false; bool _isSearching = false; - + late AnimationController _animationController; late Animation _fadeAnimation; late Animation _slideAnimation; @@ -44,14 +46,66 @@ class _DemandeSortiePersonnellePageState extends State(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), ); - _slideAnimation = Tween(begin: const Offset(0, 0.3), end: Offset.zero).animate( + _slideAnimation = + Tween(begin: const Offset(0, 0.3), end: Offset.zero).animate( CurvedAnimation(parent: _animationController, curve: Curves.easeOutCubic), ); - + _loadProducts(); _searchController.addListener(_filterProducts); } + void _scanQrOrBarcode() async { + // Open the mobile scanner as a modal widget + await showDialog( + context: context, + builder: (context) { + return AlertDialog( + content: Container( + width: double.maxFinite, + height: 400, // Adjust according to your needs + child: MobileScanner( + onDetect: (barcodeCapture) { + String scanResult = barcodeCapture.rawValue ?? ''; + Navigator.of(context).pop(); // Close dialog after scanning + + if (scanResult.isEmpty) return; + + setState(() { + _searchController.text = scanResult; // Show scanned text + }); + + // Assume IMEI is always 15 digits, adjust if necessary + Product? found; + if (scanResult.length == 15 && + int.tryParse(scanResult) != null) { + found = + _products.firstWhereOrNull((p) => p.imei == scanResult); + } else { + found = _products + .firstWhereOrNull((p) => p.reference == scanResult); + } + + if (found != null) { + setState(() { + _selectedProduct = found; + _filteredProducts = [found!]; + }); + } else { + _showErrorSnackbar('Aucun produit trouvé avec ce code.'); + setState(() { + _filteredProducts = []; + _selectedProduct = null; + }); + } + }, + ), + ), + ); + }, + ); + } + void _filterProducts() { final query = _searchController.text.toLowerCase(); setState(() { @@ -62,7 +116,7 @@ class _DemandeSortiePersonnellePageState extends State 0 ? _userController.pointDeVenteId : null, - notes: _notesController.text.trim().isNotEmpty ? _notesController.text.trim() : null, + pointDeVenteId: _userController.pointDeVenteId > 0 + ? _userController.pointDeVenteId + : null, + notes: _notesController.text.trim().isNotEmpty + ? _notesController.text.trim() + : null, ); - _showSuccessSnackbar('Votre demande de sortie personnelle a été soumise pour approbation'); + _showSuccessSnackbar( + 'Votre demande de sortie personnelle a été soumise pour approbation'); // Réinitialiser le formulaire avec animation _resetForm(); @@ -144,55 +204,58 @@ class _DemandeSortiePersonnellePageState extends State _showConfirmationDialog() async { return await showDialog( - context: context, - builder: (context) => AlertDialog( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - title: Row( - children: [ - Icon(Icons.help_outline, color: Colors.orange.shade700), - const SizedBox(width: 8), - const Text('Confirmer la demande'), - ], - ), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text('Êtes-vous sûr de vouloir soumettre cette demande ?'), - const SizedBox(height: 16), - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.grey.shade50, - borderRadius: BorderRadius.circular(8), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Produit: ${_selectedProduct?.name}'), - Text('Quantité: ${_quantiteController.text}'), - Text('Motif: ${_motifController.text}'), - ], - ), + context: context, + builder: (context) => AlertDialog( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + title: Row( + children: [ + Icon(Icons.help_outline, color: Colors.orange.shade700), + const SizedBox(width: 8), + const Text('Confirmer la demande'), + ], ), - ], - ), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(false), - child: const Text('Annuler'), - ), - ElevatedButton( - onPressed: () => Navigator.of(context).pop(true), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.orange.shade700, - foregroundColor: Colors.white, + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Êtes-vous sûr de vouloir soumettre cette demande ?'), + const SizedBox(height: 16), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.grey.shade50, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Produit: ${_selectedProduct?.name}'), + Text('Quantité: ${_quantiteController.text}'), + Text('Motif: ${_motifController.text}'), + ], + ), + ), + ], ), - child: const Text('Confirmer'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: const Text('Annuler'), + ), + ElevatedButton( + onPressed: () => Navigator.of(context).pop(true), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.orange.shade700, + foregroundColor: Colors.white, + ), + child: const Text('Confirmer'), + ), + ], ), - ], - ), - ) ?? false; + ) ?? + false; } void _showSuccessSnackbar(String message) { @@ -203,7 +266,9 @@ class _DemandeSortiePersonnellePageState extends State (_selectedProduct!.stock ?? 0)) { + if (_selectedProduct != null && + quantite > (_selectedProduct!.stock ?? 0)) { return 'Quantité supérieure au stock disponible'; } return null; @@ -538,7 +620,8 @@ class _DemandeSortiePersonnellePageState extends State 0) - _buildInfoRow(Icons.store, 'Point de vente', _userController.pointDeVenteDesignation), - _buildInfoRow(Icons.calendar_today, 'Date', DateTime.now().toLocal().toString().split(' ')[0]), + _buildInfoRow(Icons.store, 'Point de vente', + _userController.pointDeVenteDesignation), + _buildInfoRow(Icons.calendar_today, 'Date', + DateTime.now().toLocal().toString().split(' ')[0]), ], ), ); @@ -721,4 +807,8 @@ class _DemandeSortiePersonnellePageState extends State null; +} diff --git a/lib/Views/historique.dart b/lib/Views/historique.dart index e48d92d..b364590 100644 --- a/lib/Views/historique.dart +++ b/lib/Views/historique.dart @@ -1,275 +1,224 @@ -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: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}); + 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 + _HistoriquePageState createState() => _HistoriquePageState(); + } - @override - void initState() { - super.initState(); - _loadCommandes(); + class _HistoriquePageState extends State { + final AppDatabase _appDatabase = AppDatabase.instance; - // Listeners pour les filtres - _searchController.addListener(_filterCommandes); - _searchClientController.addListener(_filterCommandes); - _searchCommandeIdController.addListener(_filterCommandes); - } + // 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; - Future _loadCommandes() async { - setState(() { - _isLoading = true; - }); + @override + void initState() { + super.initState(); + _loadCommandes(); + + // Listeners pour les filtres + _searchController.addListener(_filterCommandes); + _searchClientController.addListener(_filterCommandes); + _searchCommandeIdController.addListener(_filterCommandes); + } - try { - final commandes = await _appDatabase.getCommandes(); + Future _loadCommandes() async { setState(() { - _commandes.clear(); - _commandes.addAll(commandes); - _filteredCommandes.clear(); - _filteredCommandes.addAll(commandes); - _isLoading = false; + _isLoading = true; }); - } catch (e) { - setState(() { - _isLoading = false; - }); - Get.snackbar( - 'Erreur', - 'Impossible de charger les commandes: ${e.toString()}', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red, - colorText: Colors.white, - ); + + 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(), - ), - ); + 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(); + 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(); + // 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); + setState(() { + _filteredCommandes.clear(); - bool matchesAmount = true; - if (_minAmount != null && commande.montantTotal < _minAmount!) { - matchesAmount = false; - } - if (_maxAmount != null && commande.montantTotal > _maxAmount!) { - matchesAmount = false; - } + 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); + 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; - } + 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(); - } + // 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(); - } + // 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 + // 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: [ - 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, + 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, ), ), ], ), - ] else ...[ - // Version mobile - champs empilés + const SizedBox(height: 16), + + // Champ de recherche générale TextField( - controller: _searchClientController, + controller: _searchController, decoration: InputDecoration( - labelText: 'Client', - prefixIcon: const Icon(Icons.person), + labelText: 'Recherche', + prefixIcon: const Icon(Icons.search), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), @@ -278,556 +227,607 @@ class _HistoriquePageState extends State { ), ), const SizedBox(height: 12), - TextField( - controller: _searchCommandeIdController, + + 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: 'ID Commande', - prefixIcon: const Icon(Icons.confirmation_number), + labelText: 'Statut', + prefixIcon: const Icon(Icons.assignment), 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, + items: [ + const DropdownMenuItem( + value: null, + child: Text('Tous les statuts'), ), - 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 + ...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, ), - ), - ), - 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 + 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 + ), ), ), - ), - ], - ), - - 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), + 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: 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, + ], + ), + + 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(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, + + 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); + 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, + 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: [ - _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), + padding: const EdgeInsets.all(8), 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, + color: Colors.blue.shade100, + borderRadius: BorderRadius.circular(8), ), + child: Icon(Icons.receipt_long, color: Colors.blue.shade700), ), + const SizedBox(width: 12), Text( - '${commande.montantTotal.toStringAsFixed(2)} MGA', + 'Commande #${commande.id}', style: const TextStyle( - fontSize: 16, + fontSize: 18, // Taille réduite pour mobile fontWeight: FontWeight.bold, - color: Colors.green, ), ), ], ), + IconButton( + icon: const Icon(Icons.close), + onPressed: () => Get.back(), + ), ], ), - ), - - 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 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, ), - leading: Container( - width: 40, - height: 40, - decoration: BoxDecoration( - color: Colors.blue.shade50, - borderRadius: BorderRadius.circular(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, ), - 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, + 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), + ); + }, + ), + ), + + 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, - ); - } + 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, + 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'; + 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: 8), - Text( - 'Modifiez vos critères de recherche', - style: TextStyle( - fontSize: 14, - color: Colors.grey.shade500, + 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), + // 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), + ), ), - 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, + ), + ), + ], + ), ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + Column( + crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( - 'Commande #${commande.id}', - style: const TextStyle( + '${commande.montantTotal.toStringAsFixed(2)} MGA', + style: TextStyle( fontWeight: FontWeight.bold, - fontSize: 16, + color: Colors.green.shade700, + fontSize: isMobile ? 14 : 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, + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: _getStatutColor(commande.statut).withOpacity(0.2), + borderRadius: BorderRadius.circular(12), ), - ), - Text( - DateFormat('dd/MM/yyyy').format(commande.dateCommande), - style: TextStyle( - fontSize: 12, - color: Colors.grey.shade600, + child: Text( + _getStatutText(commande.statut), + style: TextStyle( + color: _getStatutColor(commande.statut), + fontWeight: FontWeight.bold, + fontSize: isMobile ? 10 : 12, + ), ), ), ], ), - ), - 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) {