From c71f663346746b765752ecf8a345751df2d0d80e Mon Sep 17 00:00:00 2001 From: Stephane Date: Fri, 30 May 2025 10:50:18 +0300 Subject: [PATCH] FCFA to MGA --- lib/Components/panier.dart | 4 +- lib/Views/HandleProduct.dart | 1145 ++++++++++++++-------------- lib/Views/bilanMois.dart | 2 +- lib/Views/listCommandeHistory.dart | 4 +- lib/Views/ticketPage.dart | 30 +- lib/accueil.dart | 147 ++-- lib/main.dart | 15 +- 7 files changed, 700 insertions(+), 647 deletions(-) diff --git a/lib/Components/panier.dart b/lib/Components/panier.dart index 93e3e23..0fa211c 100644 --- a/lib/Components/panier.dart +++ b/lib/Components/panier.dart @@ -53,7 +53,7 @@ class PanierPage extends StatelessWidget { trailing: Row( mainAxisSize: MainAxisSize.min, children: [ - Text('${product.price.toStringAsFixed(2)} fcfa'), + Text('${product.price.toStringAsFixed(2)} MGA'), const SizedBox(width: 8), Text('x $quantity'), const SizedBox(width: 8), @@ -78,7 +78,7 @@ class PanierPage extends StatelessWidget { ), const SizedBox(height: 16), Text( - 'Total: ${calculateTotalPrice().toStringAsFixed(2)} fcfa', + 'Total: ${calculateTotalPrice().toStringAsFixed(2)} MGA', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, diff --git a/lib/Views/HandleProduct.dart b/lib/Views/HandleProduct.dart index f2af6af..8867e41 100644 --- a/lib/Views/HandleProduct.dart +++ b/lib/Views/HandleProduct.dart @@ -13,7 +13,6 @@ import '../Components/app_bar.dart'; import '../Models/produit.dart'; import '../Services/productDatabase.dart'; - class ProductManagementPage extends StatefulWidget { const ProductManagementPage({super.key}); @@ -32,7 +31,13 @@ class _ProductManagementPageState extends State { // Catégories prédéfinies pour l'ajout de produits final List _predefinedCategories = [ - 'Sucré', 'Salé', 'Jus', 'Gateaux', 'Snacks', 'Boissons', 'Non catégorisé' + 'Sucré', + 'Salé', + 'Jus', + 'Gateaux', + 'Snacks', + 'Boissons', + 'Non catégorisé' ]; @override @@ -48,427 +53,431 @@ class _ProductManagementPageState extends State { super.dispose(); } - - - - - - //====================================================================================================== // Ajoutez ces variables à la classe _ProductManagementPageState -bool _isImporting = false; -double _importProgress = 0.0; -String _importStatusText = ''; + bool _isImporting = false; + double _importProgress = 0.0; + String _importStatusText = ''; // Ajoutez ces méthodes à la classe _ProductManagementPageState -void _resetImportState() { - setState(() { - _isImporting = false; - _importProgress = 0.0; - _importStatusText = ''; - }); -} + void _resetImportState() { + setState(() { + _isImporting = false; + _importProgress = 0.0; + _importStatusText = ''; + }); + } -void _showExcelCompatibilityError() { - Get.dialog( - AlertDialog( - title: const Text('Fichier Excel incompatible'), - content: const Text( - 'Ce fichier Excel contient des éléments qui ne sont pas compatibles avec notre système d\'importation.\n\n' - 'Solutions recommandées :\n' - '• Téléchargez notre modèle Excel et copiez-y vos données\n' - '• Ou exportez votre fichier en format simple: Classeur Excel .xlsx depuis Excel\n' - '• Ou créez un nouveau fichier Excel simple sans formatage complexe' - ), - actions: [ - TextButton( - onPressed: () => Get.back(), - child: const Text('Annuler'), - ), - TextButton( - onPressed: () { - Get.back(); - _downloadExcelTemplate(); - }, - child: const Text('Télécharger modèle'), - style: TextButton.styleFrom( - backgroundColor: Colors.green, - foregroundColor: Colors.white, + void _showExcelCompatibilityError() { + Get.dialog( + AlertDialog( + title: const Text('Fichier Excel incompatible'), + content: const Text( + 'Ce fichier Excel contient des éléments qui ne sont pas compatibles avec notre système d\'importation.\n\n' + 'Solutions recommandées :\n' + '• Téléchargez notre modèle Excel et copiez-y vos données\n' + '• Ou exportez votre fichier en format simple: Classeur Excel .xlsx depuis Excel\n' + '• Ou créez un nouveau fichier Excel simple sans formatage complexe'), + actions: [ + TextButton( + onPressed: () => Get.back(), + child: const Text('Annuler'), ), - ), - ], - ), - ); -} - -Future _downloadExcelTemplate() async { - try { - final excel = Excel.createExcel(); - excel.delete('Sheet1'); - excel.copy('Sheet1', 'Produits'); - excel.delete('Sheet1'); - - final sheet = excel['Produits']; - - final headers = ['Nom', 'Prix', 'Catégorie', 'Description', 'Stock']; - for (int i = 0; i < headers.length; i++) { - final cell = sheet.cell(CellIndex.indexByColumnRow(columnIndex: i, rowIndex: 0)); - cell.value = headers[i]; - cell.cellStyle = CellStyle( - bold: true, - backgroundColorHex: '#E8F4FD', - ); - } - - final examples = [ - ['Croissant', '1.50', 'Sucré', 'Délicieux croissant beurré', '20'], - ['Sandwich jambon', '4.00', 'Salé', 'Sandwich fait maison', '15'], - ['Jus d\'orange', '2.50', 'Jus', 'Jus d\'orange frais', '30'], - ['Gâteau chocolat', '18.00', 'Gateaux', 'Gâteau au chocolat portion 8 personnes', '5'], - ]; - - for (int row = 0; row < examples.length; row++) { - for (int col = 0; col < examples[row].length; col++) { - final cell = sheet.cell(CellIndex.indexByColumnRow(columnIndex: col, rowIndex: row + 1)); - cell.value = examples[row][col]; - } - } - - sheet.setColWidth(0, 20); - sheet.setColWidth(1, 10); - sheet.setColWidth(2, 15); - sheet.setColWidth(3, 30); - sheet.setColWidth(4, 10); - - final bytes = excel.save(); - - if (bytes == null) { - Get.snackbar('Erreur', 'Impossible de créer le fichier modèle'); - return; - } - - final String? outputFile = await FilePicker.platform.saveFile( - fileName: 'modele_import_produits.xlsx', - allowedExtensions: ['xlsx'], - type: FileType.custom, + TextButton( + onPressed: () { + Get.back(); + _downloadExcelTemplate(); + }, + child: const Text('Télécharger modèle'), + style: TextButton.styleFrom( + backgroundColor: Colors.green, + foregroundColor: Colors.white, + ), + ), + ], + ), ); - - if (outputFile != null) { - try { - await File(outputFile).writeAsBytes(bytes); - Get.snackbar( - 'Succès', - 'Modèle téléchargé avec succès\n$outputFile', - duration: const Duration(seconds: 4), - backgroundColor: Colors.green, - colorText: Colors.white, + } + + Future _downloadExcelTemplate() async { + try { + final excel = Excel.createExcel(); + excel.delete('Sheet1'); + excel.copy('Sheet1', 'Produits'); + excel.delete('Sheet1'); + + final sheet = excel['Produits']; + + final headers = ['Nom', 'Prix', 'Catégorie', 'Description', 'Stock']; + for (int i = 0; i < headers.length; i++) { + final cell = + sheet.cell(CellIndex.indexByColumnRow(columnIndex: i, rowIndex: 0)); + cell.value = headers[i]; + cell.cellStyle = CellStyle( + bold: true, + backgroundColorHex: '#E8F4FD', ); - } catch (e) { - Get.snackbar('Erreur', 'Impossible d\'écrire le fichier: $e'); } - } - } catch (e) { - Get.snackbar('Erreur', 'Erreur lors de la création du modèle: $e'); - debugPrint('Erreur création modèle Excel: $e'); - } -} -Future _importFromExcel() async { - try { - final result = await FilePicker.platform.pickFiles( - type: FileType.custom, - allowedExtensions: ['xlsx', 'xls','csv'], - allowMultiple: false, - ); + final examples = [ + ['Croissant', '1.50', 'Sucré', 'Délicieux croissant beurré', '20'], + ['Sandwich jambon', '4.00', 'Salé', 'Sandwich fait maison', '15'], + ['Jus d\'orange', '2.50', 'Jus', 'Jus d\'orange frais', '30'], + [ + 'Gâteau chocolat', + '18.00', + 'Gateaux', + 'Gâteau au chocolat portion 8 personnes', + '5' + ], + ]; - if (result == null || result.files.isEmpty) { - Get.snackbar('Annulé', 'Aucun fichier sélectionné'); - return; - } + for (int row = 0; row < examples.length; row++) { + for (int col = 0; col < examples[row].length; col++) { + final cell = sheet.cell( + CellIndex.indexByColumnRow(columnIndex: col, rowIndex: row + 1)); + cell.value = examples[row][col]; + } + } - setState(() { - _isImporting = true; - _importProgress = 0.0; - _importStatusText = 'Lecture du fichier...'; - }); + sheet.setColWidth(0, 20); + sheet.setColWidth(1, 10); + sheet.setColWidth(2, 15); + sheet.setColWidth(3, 30); + sheet.setColWidth(4, 10); - final file = File(result.files.single.path!); - - if (!await file.exists()) { - _resetImportState(); - Get.snackbar('Erreur', 'Le fichier sélectionné n\'existe pas'); - return; - } + final bytes = excel.save(); - setState(() { - _importProgress = 0.1; - _importStatusText = 'Vérification du fichier...'; - }); + if (bytes == null) { + Get.snackbar('Erreur', 'Impossible de créer le fichier modèle'); + return; + } - final bytes = await file.readAsBytes(); - - if (bytes.isEmpty) { - _resetImportState(); - Get.snackbar('Erreur', 'Le fichier Excel est vide'); - return; - } + final String? outputFile = await FilePicker.platform.saveFile( + fileName: 'modele_import_produits.xlsx', + allowedExtensions: ['xlsx'], + type: FileType.custom, + ); - setState(() { - _importProgress = 0.2; - _importStatusText = 'Décodage du fichier Excel...'; - }); + if (outputFile != null) { + try { + await File(outputFile).writeAsBytes(bytes); + Get.snackbar( + 'Succès', + 'Modèle téléchargé avec succès\n$outputFile', + duration: const Duration(seconds: 4), + backgroundColor: Colors.green, + colorText: Colors.white, + ); + } catch (e) { + Get.snackbar('Erreur', 'Impossible d\'écrire le fichier: $e'); + } + } + } catch (e) { + Get.snackbar('Erreur', 'Erreur lors de la création du modèle: $e'); + debugPrint('Erreur création modèle Excel: $e'); + } + } - Excel excel; + Future _importFromExcel() async { try { + final result = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ['xlsx', 'xls', 'csv'], + allowMultiple: false, + ); + + if (result == null || result.files.isEmpty) { + Get.snackbar('Annulé', 'Aucun fichier sélectionné'); + return; + } + setState(() { _isImporting = true; _importProgress = 0.0; - _importStatusText = 'Initialisation...'; + _importStatusText = 'Lecture du fichier...'; }); - await Future.delayed(Duration(milliseconds: 50)); - excel = Excel.decodeBytes(bytes); - } catch (e) { - _resetImportState(); - debugPrint('Erreur décodage Excel: $e'); - - if (e.toString().contains('styles') || e.toString().contains('Damaged')) { - _showExcelCompatibilityError(); - return; - } else { - Get.snackbar('Erreur', 'Impossible de lire le fichier Excel. Format non supporté.'); + final file = File(result.files.single.path!); + + if (!await file.exists()) { + _resetImportState(); + Get.snackbar('Erreur', 'Le fichier sélectionné n\'existe pas'); return; } - } - if (excel.tables.isEmpty) { - _resetImportState(); - Get.snackbar('Erreur', 'Le fichier Excel ne contient aucune feuille'); - return; - } - - setState(() { - _importProgress = 0.3; - _importStatusText = 'Analyse des données...'; - }); + setState(() { + _importProgress = 0.1; + _importStatusText = 'Vérification du fichier...'; + }); - int successCount = 0; - int errorCount = 0; - List errorMessages = []; + final bytes = await file.readAsBytes(); - final sheetName = excel.tables.keys.first; - final sheet = excel.tables[sheetName]!; - - if (sheet.rows.isEmpty) { - _resetImportState(); - Get.snackbar('Erreur', 'La feuille Excel est vide'); - return; - } + if (bytes.isEmpty) { + _resetImportState(); + Get.snackbar('Erreur', 'Le fichier Excel est vide'); + return; + } - final totalRows = sheet.rows.length - 1; - - setState(() { - _importStatusText = 'Importation en cours... (0/$totalRows)'; - }); + setState(() { + _importProgress = 0.2; + _importStatusText = 'Décodage du fichier Excel...'; + }); - for (var i = 1; i < sheet.rows.length; i++) { + Excel excel; try { - final currentProgress = 0.3 + (0.6 * (i - 1) / totalRows); setState(() { - _importProgress = currentProgress; - _importStatusText = 'Importation en cours... (${i - 1}/$totalRows)'; + _isImporting = true; + _importProgress = 0.0; + _importStatusText = 'Initialisation...'; }); - await Future.delayed(const Duration(milliseconds: 10)); - - final row = sheet.rows[i]; - - if (row.isEmpty || row.length < 2) { - errorCount++; - errorMessages.add('Ligne ${i + 1}: Données insuffisantes'); - continue; + await Future.delayed(Duration(milliseconds: 50)); + excel = Excel.decodeBytes(bytes); + } catch (e) { + _resetImportState(); + debugPrint('Erreur décodage Excel: $e'); + + if (e.toString().contains('styles') || + e.toString().contains('Damaged')) { + _showExcelCompatibilityError(); + return; + } else { + Get.snackbar('Erreur', + 'Impossible de lire le fichier Excel. Format non supporté.'); + return; } + } - String? nameValue; - String? priceValue; - - if (row[0]?.value != null) { - nameValue = row[0]!.value.toString().trim(); - } - - if (row[1]?.value != null) { - priceValue = row[1]!.value.toString().trim(); - } - - if (nameValue == null || nameValue.isEmpty) { - errorCount++; - errorMessages.add('Ligne ${i + 1}: Nom du produit manquant'); - continue; - } + if (excel.tables.isEmpty) { + _resetImportState(); + Get.snackbar('Erreur', 'Le fichier Excel ne contient aucune feuille'); + return; + } - if (priceValue == null || priceValue.isEmpty) { - errorCount++; - errorMessages.add('Ligne ${i + 1}: Prix manquant'); - continue; - } + setState(() { + _importProgress = 0.3; + _importStatusText = 'Analyse des données...'; + }); - final name = nameValue; - final price = double.tryParse(priceValue.replaceAll(',', '.')); + int successCount = 0; + int errorCount = 0; + List errorMessages = []; - if (price == null || price <= 0) { - errorCount++; - errorMessages.add('Ligne ${i + 1}: Prix invalide ($priceValue)'); - continue; - } + final sheetName = excel.tables.keys.first; + final sheet = excel.tables[sheetName]!; + + if (sheet.rows.isEmpty) { + _resetImportState(); + Get.snackbar('Erreur', 'La feuille Excel est vide'); + return; + } + + final totalRows = sheet.rows.length - 1; + + setState(() { + _importStatusText = 'Importation en cours... (0/$totalRows)'; + }); + + for (var i = 1; i < sheet.rows.length; i++) { + try { + final currentProgress = 0.3 + (0.6 * (i - 1) / totalRows); + setState(() { + _importProgress = currentProgress; + _importStatusText = 'Importation en cours... (${i - 1}/$totalRows)'; + }); + + await Future.delayed(const Duration(milliseconds: 10)); - String category = 'Non catégorisé'; - if (row.length > 2 && row[2]?.value != null) { - final categoryValue = row[2]!.value.toString().trim(); - if (categoryValue.isNotEmpty) { - category = categoryValue; + final row = sheet.rows[i]; + + if (row.isEmpty || row.length < 2) { + errorCount++; + errorMessages.add('Ligne ${i + 1}: Données insuffisantes'); + continue; } - } - String description = ''; - if (row.length > 3 && row[3]?.value != null) { - description = row[3]!.value.toString().trim(); - } + String? nameValue; + String? priceValue; - int stock = 0; - if (row.length > 4 && row[4]?.value != null) { - final stockStr = row[4]!.value.toString().trim(); - stock = int.tryParse(stockStr) ?? 0; - } + if (row[0]?.value != null) { + nameValue = row[0]!.value.toString().trim(); + } - String reference = _generateUniqueReference(); - var existingProduct = await _productDatabase.getProductByReference(reference); - while (existingProduct != null) { - reference = _generateUniqueReference(); - existingProduct = await _productDatabase.getProductByReference(reference); - } - - final product = Product( - name: name, - price: price, - image: '', - category: category, - description: description, - stock: stock, - qrCode: '', - reference: reference, - ); + if (row[1]?.value != null) { + priceValue = row[1]!.value.toString().trim(); + } - setState(() { - _importStatusText = 'Génération QR Code... (${i - 1}/$totalRows)'; - }); - - final qrPath = await _generateAndSaveQRCode(reference); - product.qrCode = qrPath; + if (nameValue == null || nameValue.isEmpty) { + errorCount++; + errorMessages.add('Ligne ${i + 1}: Nom du produit manquant'); + continue; + } - await _productDatabase.createProduct(product); - successCount++; - - } catch (e) { - errorCount++; - errorMessages.add('Ligne ${i + 1}: Erreur de traitement - $e'); - debugPrint('Erreur ligne ${i + 1}: $e'); + if (priceValue == null || priceValue.isEmpty) { + errorCount++; + errorMessages.add('Ligne ${i + 1}: Prix manquant'); + continue; + } + + final name = nameValue; + final price = double.tryParse(priceValue.replaceAll(',', '.')); + + if (price == null || price <= 0) { + errorCount++; + errorMessages.add('Ligne ${i + 1}: Prix invalide ($priceValue)'); + continue; + } + + String category = 'Non catégorisé'; + if (row.length > 2 && row[2]?.value != null) { + final categoryValue = row[2]!.value.toString().trim(); + if (categoryValue.isNotEmpty) { + category = categoryValue; + } + } + + String description = ''; + if (row.length > 3 && row[3]?.value != null) { + description = row[3]!.value.toString().trim(); + } + + int stock = 0; + if (row.length > 4 && row[4]?.value != null) { + final stockStr = row[4]!.value.toString().trim(); + stock = int.tryParse(stockStr) ?? 0; + } + + String reference = _generateUniqueReference(); + var existingProduct = + await _productDatabase.getProductByReference(reference); + while (existingProduct != null) { + reference = _generateUniqueReference(); + existingProduct = + await _productDatabase.getProductByReference(reference); + } + + final product = Product( + name: name, + price: price, + image: '', + category: category, + description: description, + stock: stock, + qrCode: '', + reference: reference, + ); + + setState(() { + _importStatusText = 'Génération QR Code... (${i - 1}/$totalRows)'; + }); + + final qrPath = await _generateAndSaveQRCode(reference); + product.qrCode = qrPath; + + await _productDatabase.createProduct(product); + successCount++; + } catch (e) { + errorCount++; + errorMessages.add('Ligne ${i + 1}: Erreur de traitement - $e'); + debugPrint('Erreur ligne ${i + 1}: $e'); + } } - } - setState(() { - _importProgress = 1.0; - _importStatusText = 'Finalisation...'; - }); + setState(() { + _importProgress = 1.0; + _importStatusText = 'Finalisation...'; + }); + + await Future.delayed(const Duration(milliseconds: 500)); - await Future.delayed(const Duration(milliseconds: 500)); + _resetImportState(); - _resetImportState(); + String message = '$successCount produits importés avec succès'; + if (errorCount > 0) { + message += ', $errorCount erreurs'; - String message = '$successCount produits importés avec succès'; - if (errorCount > 0) { - message += ', $errorCount erreurs'; - - if (errorMessages.length <= 5) { - message += ':\n${errorMessages.join('\n')}'; + if (errorMessages.length <= 5) { + message += ':\n${errorMessages.join('\n')}'; + } } - } - Get.snackbar( - 'Importation terminée', - message, - duration: const Duration(seconds: 6), - colorText: Colors.white, - backgroundColor: successCount > 0 ? Colors.green : Colors.orange, - ); - - // Recharger la liste des produits après importation - _loadProducts(); - - } catch (e) { - _resetImportState(); - Get.snackbar('Erreur', 'Erreur lors de l\'importation Excel: $e'); - debugPrint('Erreur générale import Excel: $e'); + Get.snackbar( + 'Importation terminée', + message, + duration: const Duration(seconds: 6), + colorText: Colors.white, + backgroundColor: successCount > 0 ? Colors.green : Colors.orange, + ); + + // Recharger la liste des produits après importation + _loadProducts(); + } catch (e) { + _resetImportState(); + Get.snackbar('Erreur', 'Erreur lors de l\'importation Excel: $e'); + debugPrint('Erreur générale import Excel: $e'); + } } -} // Ajoutez ce widget dans votre méthode build, par exemple dans la partie supérieure -Widget _buildImportProgressIndicator() { - if (!_isImporting) return const SizedBox.shrink(); - - return Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.blue.shade50, - borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.blue.shade200), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Importation en cours...', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.blue.shade800, + Widget _buildImportProgressIndicator() { + if (!_isImporting) return const SizedBox.shrink(); + + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.blue.shade50, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.blue.shade200), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Importation en cours...', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.blue.shade800, + ), ), - ), - const SizedBox(height: 8), - LinearProgressIndicator( - value: _importProgress, - backgroundColor: Colors.blue.shade100, - valueColor: AlwaysStoppedAnimation(Colors.blue.shade600), - ), - const SizedBox(height: 8), - Text( - _importStatusText, - style: TextStyle( - fontSize: 14, - color: Colors.blue.shade700, + const SizedBox(height: 8), + LinearProgressIndicator( + value: _importProgress, + backgroundColor: Colors.blue.shade100, + valueColor: AlwaysStoppedAnimation(Colors.blue.shade600), ), - ), - const SizedBox(height: 8), - Text( - '${(_importProgress * 100).round()}%', - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Colors.blue.shade600, + const SizedBox(height: 8), + Text( + _importStatusText, + style: TextStyle( + fontSize: 14, + color: Colors.blue.shade700, + ), ), - ), - ], - ), - ); -} + const SizedBox(height: 8), + Text( + '${(_importProgress * 100).round()}%', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Colors.blue.shade600, + ), + ), + ], + ), + ); + } + //============================================================================================================================= Future _loadProducts() async { setState(() => _isLoading = true); - + try { await _productDatabase.initDatabase(); final products = await _productDatabase.getProducts(); final categories = await _productDatabase.getCategories(); - + setState(() { _products = products; _filteredProducts = products; @@ -483,16 +492,16 @@ Widget _buildImportProgressIndicator() { void _filterProducts() { final query = _searchController.text.toLowerCase(); - + setState(() { _filteredProducts = _products.where((product) { final matchesSearch = product.name.toLowerCase().contains(query) || product.description!.toLowerCase().contains(query) || product.reference!.toLowerCase().contains(query); - - final matchesCategory = _selectedCategory == 'Tous' || + + final matchesCategory = _selectedCategory == 'Tous' || product.category == _selectedCategory; - + return matchesSearch && matchesCategory; }).toList(); }); @@ -508,7 +517,7 @@ Widget _buildImportProgressIndicator() { // Méthode pour générer et sauvegarder le QR Code Future _generateAndSaveQRCode(String reference) async { final qrUrl = 'https://stock.guycom.mg/$reference'; - + final validation = QrValidator.validate( data: qrUrl, version: QrVersions.auto, @@ -529,9 +538,10 @@ Widget _buildImportProgressIndicator() { final directory = await getApplicationDocumentsDirectory(); final path = '${directory.path}/$reference.png'; - + try { - final picData = await painter.toImageData(2048, format: ImageByteFormat.png); + final picData = + await painter.toImageData(2048, format: ImageByteFormat.png); if (picData != null) { await File(path).writeAsBytes(picData.buffer.asUint8List()); } else { @@ -550,8 +560,9 @@ Widget _buildImportProgressIndicator() { final stockController = TextEditingController(); final descriptionController = TextEditingController(); final imageController = TextEditingController(); - - String selectedCategory = _predefinedCategories.last; // 'Non catégorisé' par défaut + + String selectedCategory = + _predefinedCategories.last; // 'Non catégorisé' par défaut File? pickedImage; String? qrPreviewData; String? currentReference; @@ -579,7 +590,8 @@ Widget _buildImportProgressIndicator() { color: Colors.green.shade100, borderRadius: BorderRadius.circular(8), ), - child: Icon(Icons.add_shopping_cart, color: Colors.green.shade700), + child: + Icon(Icons.add_shopping_cart, color: Colors.green.shade700), ), const SizedBox(width: 12), const Text('Ajouter un produit'), @@ -605,11 +617,13 @@ Widget _buildImportProgressIndicator() { ), child: Row( children: [ - Icon(Icons.info, color: Colors.red.shade600, size: 16), + Icon(Icons.info, + color: Colors.red.shade600, size: 16), const SizedBox(width: 8), const Text( 'Les champs marqués d\'un * sont obligatoires', - style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500), + style: TextStyle( + fontSize: 12, fontWeight: FontWeight.w500), ), ], ), @@ -633,16 +647,17 @@ Widget _buildImportProgressIndicator() { }, ), const SizedBox(height: 16), - + // Prix et Stock sur la même ligne Row( children: [ Expanded( child: TextField( controller: priceController, - keyboardType: const TextInputType.numberWithOptions(decimal: true), + keyboardType: const TextInputType.numberWithOptions( + decimal: true), decoration: InputDecoration( - labelText: 'Prix (FCFA) *', + labelText: 'Prix (MGA) *', border: const OutlineInputBorder(), prefixIcon: const Icon(Icons.attach_money), filled: true, @@ -667,12 +682,14 @@ Widget _buildImportProgressIndicator() { ], ), const SizedBox(height: 16), - + // Catégorie DropdownButtonFormField( value: selectedCategory, - items: _predefinedCategories.map((category) => - DropdownMenuItem(value: category, child: Text(category))).toList(), + items: _predefinedCategories + .map((category) => DropdownMenuItem( + value: category, child: Text(category))) + .toList(), onChanged: (value) { setDialogState(() => selectedCategory = value!); }, @@ -685,7 +702,7 @@ Widget _buildImportProgressIndicator() { ), ), const SizedBox(height: 16), - + // Description TextField( controller: descriptionController, @@ -699,7 +716,7 @@ Widget _buildImportProgressIndicator() { ), ), const SizedBox(height: 16), - + // Section Image Container( padding: const EdgeInsets.all(12), @@ -741,10 +758,13 @@ Widget _buildImportProgressIndicator() { const SizedBox(width: 8), ElevatedButton.icon( onPressed: () async { - final result = await FilePicker.platform.pickFiles(type: FileType.image); - if (result != null && result.files.single.path != null) { + final result = await FilePicker.platform + .pickFiles(type: FileType.image); + if (result != null && + result.files.single.path != null) { setDialogState(() { - pickedImage = File(result.files.single.path!); + pickedImage = + File(result.files.single.path!); imageController.text = pickedImage!.path; }); } @@ -758,7 +778,7 @@ Widget _buildImportProgressIndicator() { ], ), const SizedBox(height: 12), - + // Aperçu de l'image if (pickedImage != null) Center( @@ -767,11 +787,13 @@ Widget _buildImportProgressIndicator() { width: 100, decoration: BoxDecoration( borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.grey.shade300), + border: + Border.all(color: Colors.grey.shade300), ), child: ClipRRect( borderRadius: BorderRadius.circular(8), - child: Image.file(pickedImage!, fit: BoxFit.cover), + child: Image.file(pickedImage!, + fit: BoxFit.cover), ), ), ), @@ -779,7 +801,7 @@ Widget _buildImportProgressIndicator() { ), ), const SizedBox(height: 16), - + // Aperçu QR Code if (qrPreviewData != null) Container( @@ -793,7 +815,8 @@ Widget _buildImportProgressIndicator() { children: [ Row( children: [ - Icon(Icons.qr_code_2, color: Colors.green.shade700), + Icon(Icons.qr_code_2, + color: Colors.green.shade700), const SizedBox(width: 8), Text( 'Aperçu du QR Code', @@ -823,7 +846,8 @@ Widget _buildImportProgressIndicator() { const SizedBox(height: 8), Text( 'Réf: $currentReference', - style: const TextStyle(fontSize: 10, color: Colors.grey), + style: const TextStyle( + fontSize: 10, color: Colors.grey), ), ], ), @@ -844,25 +868,28 @@ Widget _buildImportProgressIndicator() { final name = nameController.text.trim(); final price = double.tryParse(priceController.text.trim()) ?? 0.0; final stock = int.tryParse(stockController.text.trim()) ?? 0; - + if (name.isEmpty || price <= 0) { Get.snackbar('Erreur', 'Nom et prix sont obligatoires'); return; } - + try { // Générer une référence unique et vérifier son unicité - String finalReference = currentReference ?? _generateUniqueReference(); - var existingProduct = await _productDatabase.getProductByReference(finalReference); - + String finalReference = + currentReference ?? _generateUniqueReference(); + var existingProduct = await _productDatabase + .getProductByReference(finalReference); + while (existingProduct != null) { finalReference = _generateUniqueReference(); - existingProduct = await _productDatabase.getProductByReference(finalReference); + existingProduct = await _productDatabase + .getProductByReference(finalReference); } - + // Générer le QR code final qrPath = await _generateAndSaveQRCode(finalReference); - + final product = Product( name: name, price: price, @@ -873,11 +900,11 @@ Widget _buildImportProgressIndicator() { qrCode: qrPath, reference: finalReference, ); - + await _productDatabase.createProduct(product); Get.back(); Get.snackbar( - 'Succès', + 'Succès', 'Produit ajouté avec succès!\nRéférence: $finalReference', backgroundColor: Colors.green, colorText: Colors.white, @@ -904,7 +931,7 @@ Widget _buildImportProgressIndicator() { void _showQRCode(Product product) { final qrUrl = 'https://stock.guycom.mg/${product.reference}'; - + Get.dialog( AlertDialog( title: Row( @@ -969,7 +996,7 @@ Widget _buildImportProgressIndicator() { Clipboard.setData(ClipboardData(text: qrUrl)); Get.back(); Get.snackbar( - 'Copié', + 'Copié', 'URL copiée dans le presse-papiers', backgroundColor: Colors.green, colorText: Colors.white, @@ -988,14 +1015,17 @@ Widget _buildImportProgressIndicator() { void _editProduct(Product product) { final nameController = TextEditingController(text: product.name); - final priceController = TextEditingController(text: product.price.toString()); - final stockController = TextEditingController(text: product.stock.toString()); - final descriptionController = TextEditingController(text: product.description ?? ''); + final priceController = + TextEditingController(text: product.price.toString()); + final stockController = + TextEditingController(text: product.stock.toString()); + final descriptionController = + TextEditingController(text: product.description ?? ''); final imageController = TextEditingController(text: product.image); - + String selectedCategory = product.category; File? pickedImage; - + Get.dialog( AlertDialog( title: const Text('Modifier le produit'), @@ -1013,17 +1043,16 @@ Widget _buildImportProgressIndicator() { ), ), const SizedBox(height: 16), - TextField( controller: priceController, - keyboardType: const TextInputType.numberWithOptions(decimal: true), + keyboardType: + const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration( labelText: 'Prix*', border: OutlineInputBorder(), ), ), const SizedBox(height: 16), - TextField( controller: stockController, keyboardType: TextInputType.number, @@ -1033,7 +1062,6 @@ Widget _buildImportProgressIndicator() { ), ), const SizedBox(height: 16), - StatefulBuilder( builder: (context, setDialogState) { return Column( @@ -1053,11 +1081,14 @@ Widget _buildImportProgressIndicator() { const SizedBox(width: 8), ElevatedButton( onPressed: () async { - final result = await FilePicker.platform.pickFiles(type: FileType.image); - if (result != null && result.files.single.path != null) { + final result = await FilePicker.platform + .pickFiles(type: FileType.image); + if (result != null && + result.files.single.path != null) { if (context.mounted) { setDialogState(() { - pickedImage = File(result.files.single.path!); + pickedImage = + File(result.files.single.path!); imageController.text = pickedImage!.path; }); } @@ -1068,7 +1099,6 @@ Widget _buildImportProgressIndicator() { ], ), const SizedBox(height: 16), - if (pickedImage != null || product.image!.isNotEmpty) Container( height: 100, @@ -1082,16 +1112,19 @@ Widget _buildImportProgressIndicator() { child: pickedImage != null ? Image.file(pickedImage!, fit: BoxFit.cover) : (product.image!.isNotEmpty - ? Image.file(File(product.image!), fit: BoxFit.cover) + ? Image.file(File(product.image!), + fit: BoxFit.cover) : const Icon(Icons.image, size: 50)), ), ), const SizedBox(height: 16), - DropdownButtonFormField( value: selectedCategory, - items: _categories.skip(1).map((category) => - DropdownMenuItem(value: category, child: Text(category))).toList(), + items: _categories + .skip(1) + .map((category) => DropdownMenuItem( + value: category, child: Text(category))) + .toList(), onChanged: (value) { if (context.mounted) { setDialogState(() => selectedCategory = value!); @@ -1103,7 +1136,6 @@ Widget _buildImportProgressIndicator() { ), ), const SizedBox(height: 16), - TextField( controller: descriptionController, maxLines: 3, @@ -1130,12 +1162,12 @@ Widget _buildImportProgressIndicator() { final name = nameController.text.trim(); final price = double.tryParse(priceController.text.trim()) ?? 0.0; final stock = int.tryParse(stockController.text.trim()) ?? 0; - + if (name.isEmpty || price <= 0) { Get.snackbar('Erreur', 'Nom et prix sont obligatoires'); return; } - + final updatedProduct = Product( id: product.id, name: name, @@ -1147,12 +1179,12 @@ Widget _buildImportProgressIndicator() { qrCode: product.qrCode, reference: product.reference, ); - + try { await _productDatabase.updateProduct(updatedProduct); Get.back(); Get.snackbar( - 'Succès', + 'Succès', 'Produit modifié avec succès', backgroundColor: Colors.green, colorText: Colors.white, @@ -1186,7 +1218,7 @@ Widget _buildImportProgressIndicator() { await _productDatabase.deleteProduct(product.id); Get.back(); Get.snackbar( - 'Succès', + 'Succès', 'Produit supprimé avec succès', backgroundColor: Colors.green, colorText: Colors.white, @@ -1197,7 +1229,8 @@ Widget _buildImportProgressIndicator() { Get.snackbar('Erreur', 'Suppression échouée: $e'); } }, - child: const Text('Supprimer', style: TextStyle(color: Colors.white)), + child: + const Text('Supprimer', style: TextStyle(color: Colors.white)), ), ], ), @@ -1233,7 +1266,7 @@ Widget _buildImportProgressIndicator() { ), ), const SizedBox(width: 16), - + // Informations du produit Expanded( child: Column( @@ -1259,7 +1292,8 @@ Widget _buildImportProgressIndicator() { Row( children: [ Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 2), decoration: BoxDecoration( color: Colors.blue.shade100, borderRadius: BorderRadius.circular(12), @@ -1300,7 +1334,7 @@ Widget _buildImportProgressIndicator() { ], ), ), - + // Actions Column( children: [ @@ -1315,7 +1349,7 @@ Widget _buildImportProgressIndicator() { tooltip: 'Modifier', ), IconButton( - onPressed: () => _deleteProduct(product), + onPressed: () => _deleteProduct(product), icon: const Icon(Icons.delete, color: Colors.red), tooltip: 'Supprimer', ), @@ -1330,136 +1364,139 @@ Widget _buildImportProgressIndicator() { @override Widget build(BuildContext context) { return Scaffold( - appBar: const CustomAppBar(title: 'Gestion des produits'), - drawer: CustomDrawer(), - floatingActionButton: Column( - mainAxisSize: MainAxisSize.min, - children: [ - FloatingActionButton( - heroTag: 'importBtn', - onPressed: _isImporting ? null : _importFromExcel, - mini: true, - child: const Icon(Icons.upload), - backgroundColor: Colors.blue, - foregroundColor: Colors.white, - ), - const SizedBox(height: 8), - FloatingActionButton.extended( - heroTag: 'addBtn', - onPressed: _showAddProductDialog, - icon: const Icon(Icons.add), - label: const Text('Ajouter'), - backgroundColor: Colors.green, - foregroundColor: Colors.white, - ), - ], - ), - body: Column( - children: [ - // Barre de recherche et filtres - Container( - padding: const EdgeInsets.all(16), - color: Colors.grey.shade100, - child: Column( - children: [ - // Ajoutez cette Row pour les boutons d'import - Row( - children: [ - Expanded( - child: ElevatedButton.icon( - onPressed: _isImporting ? null : _importFromExcel, - icon: const Icon(Icons.upload), - label: const Text('Importer depuis Excel'), - ), - ), - const SizedBox(width: 10), - TextButton( - onPressed: _isImporting ? null : _downloadExcelTemplate, - child: const Text('Modèle'), - ), - ], - ), - const SizedBox(height: 16), - - // Barre de recherche existante - Row( - children: [ - Expanded( - child: TextField( - controller: _searchController, - decoration: InputDecoration( - labelText: 'Rechercher...', - prefixIcon: const Icon(Icons.search), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), - filled: true, - fillColor: Colors.white, + appBar: const CustomAppBar(title: 'Gestion des produits'), + drawer: CustomDrawer(), + floatingActionButton: Column( + mainAxisSize: MainAxisSize.min, + children: [ + FloatingActionButton( + heroTag: 'importBtn', + onPressed: _isImporting ? null : _importFromExcel, + mini: true, + child: const Icon(Icons.upload), + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + ), + const SizedBox(height: 8), + FloatingActionButton.extended( + heroTag: 'addBtn', + onPressed: _showAddProductDialog, + icon: const Icon(Icons.add), + label: const Text('Ajouter'), + backgroundColor: Colors.green, + foregroundColor: Colors.white, + ), + ], + ), + body: Column( + children: [ + // Barre de recherche et filtres + Container( + padding: const EdgeInsets.all(16), + color: Colors.grey.shade100, + child: Column( + children: [ + // Ajoutez cette Row pour les boutons d'import + Row( + children: [ + Expanded( + child: ElevatedButton.icon( + onPressed: _isImporting ? null : _importFromExcel, + icon: const Icon(Icons.upload), + label: const Text('Importer depuis Excel'), ), ), - ), - const SizedBox(width: 16), - Container( - padding: const EdgeInsets.symmetric(horizontal: 12), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8), - border: Border.all(color: Colors.grey.shade300), + const SizedBox(width: 10), + TextButton( + onPressed: _isImporting ? null : _downloadExcelTemplate, + child: const Text('Modèle'), ), - child: DropdownButton( - value: _selectedCategory, - items: _categories.map((category) => - DropdownMenuItem(value: category, child: Text(category))).toList(), - onChanged: (value) { - setState(() { - _selectedCategory = value!; - _filterProducts(); - }); - }, - underline: const SizedBox(), - hint: const Text('Catégorie'), + ], + ), + const SizedBox(height: 16), + + // Barre de recherche existante + Row( + children: [ + Expanded( + child: TextField( + controller: _searchController, + decoration: InputDecoration( + labelText: 'Rechercher...', + prefixIcon: const Icon(Icons.search), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + filled: true, + fillColor: Colors.white, + ), + ), ), - ), - ], - ), - const SizedBox(height: 12), - - // Indicateur de progression d'importation - _buildImportProgressIndicator(), - - // Compteur de produits - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - '${_filteredProducts.length} produit(s) trouvé(s)', - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Colors.grey, + const SizedBox(width: 16), + Container( + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + border: Border.all(color: Colors.grey.shade300), + ), + child: DropdownButton( + value: _selectedCategory, + items: _categories + .map((category) => DropdownMenuItem( + value: category, child: Text(category))) + .toList(), + onChanged: (value) { + setState(() { + _selectedCategory = value!; + _filterProducts(); + }); + }, + underline: const SizedBox(), + hint: const Text('Catégorie'), + ), ), - ), - if (_searchController.text.isNotEmpty || _selectedCategory != 'Tous') - TextButton.icon( - onPressed: () { - setState(() { - _searchController.clear(); - _selectedCategory = 'Tous'; - _filterProducts(); - }); - }, - icon: const Icon(Icons.clear, size: 16), - label: const Text('Réinitialiser'), - style: TextButton.styleFrom( - foregroundColor: Colors.orange, + ], + ), + const SizedBox(height: 12), + + // Indicateur de progression d'importation + _buildImportProgressIndicator(), + + // Compteur de produits + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '${_filteredProducts.length} produit(s) trouvé(s)', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.grey, ), ), - ], - ), - ], + if (_searchController.text.isNotEmpty || + _selectedCategory != 'Tous') + TextButton.icon( + onPressed: () { + setState(() { + _searchController.clear(); + _selectedCategory = 'Tous'; + _filterProducts(); + }); + }, + icon: const Icon(Icons.clear, size: 16), + label: const Text('Réinitialiser'), + style: TextButton.styleFrom( + foregroundColor: Colors.orange, + ), + ), + ], + ), + ], + ), ), - ), - + // Liste des produits Expanded( child: _isLoading @@ -1476,9 +1513,9 @@ Widget _buildImportProgressIndicator() { ), const SizedBox(height: 16), Text( - _products.isEmpty - ? 'Aucun produit enregistré' - : 'Aucun produit trouvé pour cette recherche', + _products.isEmpty + ? 'Aucun produit enregistré' + : 'Aucun produit trouvé pour cette recherche', style: TextStyle( fontSize: 18, color: Colors.grey.shade600, @@ -1488,8 +1525,8 @@ Widget _buildImportProgressIndicator() { const SizedBox(height: 8), Text( _products.isEmpty - ? 'Commencez par ajouter votre premier produit' - : 'Essayez de modifier vos critères de recherche', + ? 'Commencez par ajouter votre premier produit' + : 'Essayez de modifier vos critères de recherche', style: TextStyle( fontSize: 14, color: Colors.grey.shade500, @@ -1529,4 +1566,4 @@ Widget _buildImportProgressIndicator() { ), ); } -} \ No newline at end of file +} diff --git a/lib/Views/bilanMois.dart b/lib/Views/bilanMois.dart index 71a94a7..b2e66c3 100644 --- a/lib/Views/bilanMois.dart +++ b/lib/Views/bilanMois.dart @@ -38,7 +38,7 @@ class _BilanMoisState extends State { children: [ _buildInfoCard( title: 'Chiffre réalisé', - value: '${controller.totalSum.value.toStringAsFixed(2)} fcfa', + value: '${controller.totalSum.value.toStringAsFixed(2)} MGA', color: Colors.green, icon: Icons.monetization_on, ), diff --git a/lib/Views/listCommandeHistory.dart b/lib/Views/listCommandeHistory.dart index 401e9c5..9e2b418 100644 --- a/lib/Views/listCommandeHistory.dart +++ b/lib/Views/listCommandeHistory.dart @@ -112,7 +112,7 @@ class HistoryDetailPage extends StatelessWidget { ), ), Text( - 'Total Somme: $totalSum fcfa', + 'Total Somme: $totalSum MGA', style: const TextStyle( fontSize: 18.0, fontWeight: FontWeight.bold, @@ -197,7 +197,7 @@ class HistoryDetailPage extends StatelessWidget { ), ], ), - subtitle: Text('Total: ${order.totalPrice} fcfa'), + subtitle: Text('Total: ${order.totalPrice} MGA'), trailing: Text('Date: ${order.dateTime}'), leading: Text('vendeur: ${order.user}'), onTap: () { diff --git a/lib/Views/ticketPage.dart b/lib/Views/ticketPage.dart index d04f606..5e0a8fd 100644 --- a/lib/Views/ticketPage.dart +++ b/lib/Views/ticketPage.dart @@ -86,11 +86,11 @@ class TicketPage extends StatelessWidget { width: 1, ), PosColumn( - text: '${product.price.toStringAsFixed(2)} fcfa', + text: '${product.price.toStringAsFixed(2)} MGA', width: 1, ), PosColumn( - text: '${productTotal.toStringAsFixed(2)} fcfa', + text: '${productTotal.toStringAsFixed(2)} MGA', width: 1, ), ]); @@ -104,7 +104,7 @@ class TicketPage extends StatelessWidget { styles: const PosStyles(align: PosAlign.left, bold: true), ), PosColumn( - text: '${totalCartPrice.toStringAsFixed(2)} fcfa', + text: '${totalCartPrice.toStringAsFixed(2)} MGA', width: 1, styles: const PosStyles(align: PosAlign.left, bold: true), ), @@ -116,7 +116,7 @@ class TicketPage extends StatelessWidget { styles: const PosStyles(align: PosAlign.left), ), PosColumn( - text: '${amountPaid.toStringAsFixed(2)} fcfa', + text: '${amountPaid.toStringAsFixed(2)} MGA', width: 1, styles: const PosStyles(align: PosAlign.left), ), @@ -128,7 +128,7 @@ class TicketPage extends StatelessWidget { styles: const PosStyles(align: PosAlign.left), ), PosColumn( - text: '${(amountPaid - totalCartPrice).toStringAsFixed(2)} fcfa', + text: '${(amountPaid - totalCartPrice).toStringAsFixed(2)} MGA', width: 1, styles: const PosStyles(align: PosAlign.left), ), @@ -179,8 +179,8 @@ class TicketPage extends StatelessWidget { return [ product.name, quantity.toString(), - '${product.price.toStringAsFixed(2)} fcfa', - '${productTotal.toStringAsFixed(2)} fcfa', + '${product.price.toStringAsFixed(2)} MGA', + '${productTotal.toStringAsFixed(2)} MGA', ]; }).toList(), ], @@ -194,7 +194,7 @@ class TicketPage extends StatelessWidget { pw.Text('Total :', style: pw.TextStyle( fontSize: 18, fontWeight: pw.FontWeight.bold)), - pw.Text('${totalCartPrice.toStringAsFixed(2)} fcfa', + pw.Text('${totalCartPrice.toStringAsFixed(2)} MGA', style: pw.TextStyle( fontSize: 18, fontWeight: pw.FontWeight.bold)), ], @@ -207,7 +207,7 @@ class TicketPage extends StatelessWidget { children: [ pw.Text('Somme remise :', style: const pw.TextStyle(fontSize: 16)), - pw.Text('${amountPaid.toStringAsFixed(2)} fcfa', + pw.Text('${amountPaid.toStringAsFixed(2)} MGA', style: const pw.TextStyle(fontSize: 16)), ], ), @@ -218,7 +218,7 @@ class TicketPage extends StatelessWidget { pw.Text('Somme rendue :', style: const pw.TextStyle(fontSize: 16)), pw.Text( - '${(amountPaid - totalCartPrice).toStringAsFixed(2)} fcfa', + '${(amountPaid - totalCartPrice).toStringAsFixed(2)} MGA', style: const pw.TextStyle(fontSize: 16)), ], ), @@ -387,14 +387,14 @@ class TicketPage extends StatelessWidget { TableCell( child: Center( child: Text( - '${product.price.toStringAsFixed(2)} fcfa', + '${product.price.toStringAsFixed(2)} MGA', ), ), ), TableCell( child: Center( child: Text( - '${productTotal.toStringAsFixed(2)} fcfa', + '${productTotal.toStringAsFixed(2)} MGA', ), ), ), @@ -421,7 +421,7 @@ class TicketPage extends StatelessWidget { ), ), Text( - '${totalOrderAmount.toStringAsFixed(2)} fcfa', + '${totalOrderAmount.toStringAsFixed(2)} MGA', style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -446,7 +446,7 @@ class TicketPage extends StatelessWidget { ), ), Text( - '${amountPaid.toStringAsFixed(2)} fcfa', + '${amountPaid.toStringAsFixed(2)} MGA', style: const TextStyle( fontSize: 16, ), @@ -464,7 +464,7 @@ class TicketPage extends StatelessWidget { ), ), Text( - '${change.toStringAsFixed(2)} fcfa', + '${change.toStringAsFixed(2)} MGA', style: const TextStyle( fontSize: 16, ), diff --git a/lib/accueil.dart b/lib/accueil.dart index 8112462..8ea257c 100644 --- a/lib/accueil.dart +++ b/lib/accueil.dart @@ -177,8 +177,7 @@ class _AccueilPageState extends State { void showTicketPage() { Get.offAll(TicketPage( businessName: 'Youmaz', - businessAddress: - 'quartier escale, Diourbel, Sénégal, en face de Sonatel', + businessAddress: 'quartier escale, Diourbel, Sénégal, en face de Sonatel', businessPhoneNumber: '77 446 92 68', cartItems: selectedProducts, totalCartPrice: calculateTotalPrice(), @@ -192,37 +191,38 @@ class _AccueilPageState extends State { appBar: CustomAppBar( title: "Accueil", subtitle: Text('Bienvenue $username ! (Rôle: $role)', - style: const TextStyle(color: Colors.white70, fontSize: 14)), + style: const TextStyle(color: Colors.white70, fontSize: 14)), ), drawer: CustomDrawer(), body: ParticleBackground( child: Container( decoration: const BoxDecoration( gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [Colors.white, Color.fromARGB(255, 4, 54, 95)]), + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Colors.white, Color.fromARGB(255, 4, 54, 95)]), ), child: FutureBuilder>>( future: productsFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center( - child: CircularProgressIndicator( - valueColor: AlwaysStoppedAnimation( - Color.fromARGB(255, 4, 54, 95),), - )); + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation( + Color.fromARGB(255, 4, 54, 95), + ), + )); } else if (snapshot.hasError) { return const Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.error, color: Colors.red, size: 48), - SizedBox(height: 16), - Text("Erreur de chargement des produits", + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.error, color: Colors.red, size: 48), + SizedBox(height: 16), + Text("Erreur de chargement des produits", style: TextStyle(fontSize: 16, color: Colors.white)), - ], - )); + ], + )); } else if (snapshot.hasData) { final productsByCategory = snapshot.data!; final categories = productsByCategory.keys.toList(); @@ -233,15 +233,13 @@ class _AccueilPageState extends State { Expanded( flex: 3, child: Container( - padding:const EdgeInsets.all(8), + padding: const EdgeInsets.all(8), decoration: BoxDecoration( - color: Colors.white.withOpacity(0.9), borderRadius: const BorderRadius.only( topRight: Radius.circular(20), + ), ), - - ), child: ListView.builder( itemCount: categories.length, itemBuilder: (context, index) { @@ -252,8 +250,9 @@ class _AccueilPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - margin: const EdgeInsets.symmetric(vertical: 8), - padding:const EdgeInsets.all(12), + margin: + const EdgeInsets.symmetric(vertical: 8), + padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Color.fromARGB(255, 4, 54, 95), borderRadius: BorderRadius.circular(12), @@ -261,7 +260,8 @@ class _AccueilPageState extends State { BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 4, - offset: Offset(0, 2),) + offset: Offset(0, 2), + ) ], ), child: Center( @@ -300,17 +300,16 @@ class _AccueilPageState extends State { ); }, ), + ), + + // Section panier ), - - // Section panier - - - ), - Expanded(flex: 1, + Expanded( + flex: 1, child: Container( decoration: BoxDecoration( color: Colors.grey[200], - borderRadius:const BorderRadius.only( + borderRadius: const BorderRadius.only( topLeft: Radius.circular(20), ), boxShadow: [ @@ -331,7 +330,7 @@ class _AccueilPageState extends State { color: Color.fromARGB(255, 4, 54, 95), borderRadius: BorderRadius.circular(12), ), - child:const Text( + child: const Text( 'Panier', style: TextStyle( fontSize: 20, @@ -342,22 +341,23 @@ class _AccueilPageState extends State { ), ), SizedBox(height: 16), - + // Liste des produits dans le panier Expanded( child: selectedProducts.isEmpty ? const Center( child: Column( - mainAxisAlignment: MainAxisAlignment.center, + mainAxisAlignment: + MainAxisAlignment.center, children: [ Icon(Icons.shopping_cart, - size: 48, color: Colors.grey), + size: 48, color: Colors.grey), SizedBox(height: 16), Text( "Votre panier est vide", style: TextStyle( - fontSize: 16, - color: Colors.grey), + fontSize: 16, + color: Colors.grey), ), ], ), @@ -365,39 +365,49 @@ class _AccueilPageState extends State { : ListView.builder( itemCount: selectedProducts.length, itemBuilder: (context, index) { - final cartItem = selectedProducts[index]; + final cartItem = + selectedProducts[index]; return Card( - margin: EdgeInsets.symmetric(vertical: 4), + margin: + EdgeInsets.symmetric(vertical: 4), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10), + borderRadius: + BorderRadius.circular(10), ), elevation: 2, child: ListTile( - contentPadding: EdgeInsets.symmetric( - horizontal: 12, vertical: 4), - leading: Icon(Icons.shopping_basket, - color: Color.fromARGB(255, 4, 54, 95),), + contentPadding: + EdgeInsets.symmetric( + horizontal: 12, + vertical: 4), + leading: Icon( + Icons.shopping_basket, + color: Color.fromARGB( + 255, 4, 54, 95), + ), title: Text( cartItem.product.name, style: const TextStyle( - fontWeight: FontWeight.bold), + fontWeight: FontWeight.bold), ), subtitle: Text( - '${NumberFormat('#,##0').format(cartItem.product.price)} FCFA x ${cartItem.quantity}', - style:const TextStyle(fontSize: 14), + '${NumberFormat('#,##0').format(cartItem.product.price)} MGA x ${cartItem.quantity}', + style: + const TextStyle(fontSize: 14), ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ Text( '${NumberFormat('#,##0').format(cartItem.product.price * cartItem.quantity)}', - style:const TextStyle( - fontWeight: FontWeight.bold), + style: const TextStyle( + fontWeight: + FontWeight.bold), ), - const SizedBox(width: 8), + const SizedBox(width: 8), IconButton( icon: const Icon(Icons.delete, - color: Colors.red), + color: Colors.red), onPressed: () { setState(() { selectedProducts @@ -412,7 +422,7 @@ class _AccueilPageState extends State { }, ), ), - + // Total et paiement Container( padding: EdgeInsets.all(12), @@ -430,18 +440,20 @@ class _AccueilPageState extends State { child: Column( children: [ Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ - const Text('Total:', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold)), + const Text('Total:', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold)), Text( '${NumberFormat('#,##0.00').format(calculateTotalPrice())} FCFA', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, - color: Color.fromARGB(255, 4, 54, 95),), + color: Color.fromARGB(255, 4, 54, 95), + ), ), ], ), @@ -460,7 +472,8 @@ class _AccueilPageState extends State { ), onChanged: (value) { setState(() { - amountPaid = double.tryParse(value) ?? 0; + amountPaid = + double.tryParse(value) ?? 0; }); }, ), @@ -468,14 +481,15 @@ class _AccueilPageState extends State { ElevatedButton.icon( style: ElevatedButton.styleFrom( backgroundColor: Colors.green, - padding: EdgeInsets.symmetric(vertical: 16), + padding: + EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(10), ), ), onPressed: saveOrderToDatabase, - icon:const Icon(Icons.check_circle), - label:const Text( + icon: const Icon(Icons.check_circle), + label: const Text( 'Valider la commande', style: TextStyle( fontSize: 16, @@ -488,13 +502,14 @@ class _AccueilPageState extends State { ), ], ), - ),) + ), + ) ], ); } else { return const Center( child: Text("Aucun produit disponible", - style: TextStyle(color: Colors.white)), + style: TextStyle(color: Colors.white)), ); } }, @@ -509,4 +524,4 @@ class _AccueilPageState extends State { _amountController.dispose(); super.dispose(); } -} \ No newline at end of file +} diff --git a/lib/main.dart b/lib/main.dart index 4df3e92..f9c7d18 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,20 +8,21 @@ import 'package:logging/logging.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - + try { // Initialiser les bases de données une seule fois - // await AppDatabase.instance.deleteDatabaseFile(); - //await ProductDatabase.instance.deleteDatabaseFile(); + // await AppDatabase.instance.deleteDatabaseFile(); + // await ProductDatabase.instance.deleteDatabaseFile(); await ProductDatabase.instance.initDatabase(); await AppDatabase.instance.initDatabase(); - + // Afficher les informations de la base (pour debug) await AppDatabase.instance.printDatabaseInfo(); - Get.put(UserController()); // Ajoute ce code AVANT tout accès au UserController + Get.put( + UserController()); // Ajoute ce code AVANT tout accès au UserController setupLogger(); - + runApp(const GetMaterialApp( debugShowCheckedModeBanner: false, home: MyApp(), @@ -44,4 +45,4 @@ void setupLogger() { Logger.root.onRecord.listen((record) { print('${record.level.name}: ${record.time}: ${record.message}'); }); -} \ No newline at end of file +}