Browse Source

handle product

31052025_01
b.razafimandimbihery 6 months ago
parent
commit
fbf4cea2e1
  1. 270
      lib/Views/HandleProduct.dart

270
lib/Views/HandleProduct.dart

@ -13,6 +13,7 @@ import '../Components/app_bar.dart';
import '../Models/produit.dart'; import '../Models/produit.dart';
import '../Services/productDatabase.dart'; import '../Services/productDatabase.dart';
class ProductManagementPage extends StatefulWidget { class ProductManagementPage extends StatefulWidget {
const ProductManagementPage({super.key}); const ProductManagementPage({super.key});
@ -31,13 +32,7 @@ class _ProductManagementPageState extends State<ProductManagementPage> {
// Catégories prédéfinies pour l'ajout de produits // Catégories prédéfinies pour l'ajout de produits
final List<String> _predefinedCategories = [ final List<String> _predefinedCategories = [
'Sucré', 'Sucré', 'Salé', 'Jus', 'Gateaux', 'Snacks', 'Boissons', 'Non catégorisé'
'Salé',
'Jus',
'Gateaux',
'Snacks',
'Boissons',
'Non catégorisé'
]; ];
@override @override
@ -53,6 +48,12 @@ class _ProductManagementPageState extends State<ProductManagementPage> {
super.dispose(); super.dispose();
} }
//====================================================================================================== //======================================================================================================
// Ajoutez ces variables à la classe _ProductManagementPageState // Ajoutez ces variables à la classe _ProductManagementPageState
bool _isImporting = false; bool _isImporting = false;
@ -60,7 +61,6 @@ class _ProductManagementPageState extends State<ProductManagementPage> {
String _importStatusText = ''; String _importStatusText = '';
// Ajoutez ces méthodes à la classe _ProductManagementPageState // Ajoutez ces méthodes à la classe _ProductManagementPageState
void _resetImportState() { void _resetImportState() {
setState(() { setState(() {
_isImporting = false; _isImporting = false;
@ -182,137 +182,6 @@ Future<void> _downloadExcelTemplate() async {
} }
Future<void> _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 = 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,
),
),
],
),
);
}
Future<void> _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,
);
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');
}
}
Future<void> _importFromExcel() async { Future<void> _importFromExcel() async {
try { try {
final result = await FilePicker.platform.pickFiles( final result = await FilePicker.platform.pickFiles(
@ -372,13 +241,11 @@ Future<void> _importFromExcel() async {
_resetImportState(); _resetImportState();
debugPrint('Erreur décodage Excel: $e'); debugPrint('Erreur décodage Excel: $e');
if (e.toString().contains('styles') || if (e.toString().contains('styles') || e.toString().contains('Damaged')) {
e.toString().contains('Damaged')) {
_showExcelCompatibilityError(); _showExcelCompatibilityError();
return; return;
} else { } else {
Get.snackbar('Erreur', Get.snackbar('Erreur', 'Impossible de lire le fichier Excel. Format non supporté.');
'Impossible de lire le fichier Excel. Format non supporté.');
return; return;
} }
} }
@ -483,12 +350,10 @@ Future<void> _importFromExcel() async {
} }
String reference = _generateUniqueReference(); String reference = _generateUniqueReference();
var existingProduct = var existingProduct = await _productDatabase.getProductByReference(reference);
await _productDatabase.getProductByReference(reference);
while (existingProduct != null) { while (existingProduct != null) {
reference = _generateUniqueReference(); reference = _generateUniqueReference();
existingProduct = existingProduct = await _productDatabase.getProductByReference(reference);
await _productDatabase.getProductByReference(reference);
} }
final product = Product( final product = Product(
@ -511,6 +376,7 @@ Future<void> _importFromExcel() async {
await _productDatabase.createProduct(product); await _productDatabase.createProduct(product);
successCount++; successCount++;
} catch (e) { } catch (e) {
errorCount++; errorCount++;
errorMessages.add('Ligne ${i + 1}: Erreur de traitement - $e'); errorMessages.add('Ligne ${i + 1}: Erreur de traitement - $e');
@ -546,6 +412,7 @@ Future<void> _importFromExcel() async {
// Recharger la liste des produits après importation // Recharger la liste des produits après importation
_loadProducts(); _loadProducts();
} catch (e) { } catch (e) {
_resetImportState(); _resetImportState();
Get.snackbar('Erreur', 'Erreur lors de l\'importation Excel: $e'); Get.snackbar('Erreur', 'Erreur lors de l\'importation Excel: $e');
@ -602,7 +469,6 @@ Future<void> _importFromExcel() async {
), ),
); );
} }
//============================================================================================================================= //=============================================================================================================================
Future<void> _loadProducts() async { Future<void> _loadProducts() async {
setState(() => _isLoading = true); setState(() => _isLoading = true);
@ -674,8 +540,7 @@ Future<void> _importFromExcel() async {
final path = '${directory.path}/$reference.png'; final path = '${directory.path}/$reference.png';
try { try {
final picData = final picData = await painter.toImageData(2048, format: ImageByteFormat.png);
await painter.toImageData(2048, format: ImageByteFormat.png);
if (picData != null) { if (picData != null) {
await File(path).writeAsBytes(picData.buffer.asUint8List()); await File(path).writeAsBytes(picData.buffer.asUint8List());
} else { } else {
@ -695,8 +560,7 @@ Future<void> _importFromExcel() async {
final descriptionController = TextEditingController(); final descriptionController = TextEditingController();
final imageController = TextEditingController(); final imageController = TextEditingController();
String selectedCategory = String selectedCategory = _predefinedCategories.last; // 'Non catégorisé' par défaut
_predefinedCategories.last; // 'Non catégorisé' par défaut
File? pickedImage; File? pickedImage;
String? qrPreviewData; String? qrPreviewData;
String? currentReference; String? currentReference;
@ -724,8 +588,7 @@ Future<void> _importFromExcel() async {
color: Colors.green.shade100, color: Colors.green.shade100,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: child: Icon(Icons.add_shopping_cart, color: Colors.green.shade700),
Icon(Icons.add_shopping_cart, color: Colors.green.shade700),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
const Text('Ajouter un produit'), const Text('Ajouter un produit'),
@ -751,13 +614,11 @@ Future<void> _importFromExcel() async {
), ),
child: Row( child: Row(
children: [ children: [
Icon(Icons.info, Icon(Icons.info, color: Colors.red.shade600, size: 16),
color: Colors.red.shade600, size: 16),
const SizedBox(width: 8), const SizedBox(width: 8),
const Text( const Text(
'Les champs marqués d\'un * sont obligatoires', 'Les champs marqués d\'un * sont obligatoires',
style: TextStyle( style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500),
fontSize: 12, fontWeight: FontWeight.w500),
), ),
], ],
), ),
@ -788,10 +649,9 @@ Future<void> _importFromExcel() async {
Expanded( Expanded(
child: TextField( child: TextField(
controller: priceController, controller: priceController,
keyboardType: const TextInputType.numberWithOptions( keyboardType: const TextInputType.numberWithOptions(decimal: true),
decimal: true),
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Prix (MGA) *', labelText: 'Prix (FCFA) *',
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
prefixIcon: const Icon(Icons.attach_money), prefixIcon: const Icon(Icons.attach_money),
filled: true, filled: true,
@ -820,10 +680,8 @@ Future<void> _importFromExcel() async {
// Catégorie // Catégorie
DropdownButtonFormField<String>( DropdownButtonFormField<String>(
value: selectedCategory, value: selectedCategory,
items: _predefinedCategories items: _predefinedCategories.map((category) =>
.map((category) => DropdownMenuItem( DropdownMenuItem(value: category, child: Text(category))).toList(),
value: category, child: Text(category)))
.toList(),
onChanged: (value) { onChanged: (value) {
setDialogState(() => selectedCategory = value!); setDialogState(() => selectedCategory = value!);
}, },
@ -892,13 +750,10 @@ Future<void> _importFromExcel() async {
const SizedBox(width: 8), const SizedBox(width: 8),
ElevatedButton.icon( ElevatedButton.icon(
onPressed: () async { onPressed: () async {
final result = await FilePicker.platform final result = await FilePicker.platform.pickFiles(type: FileType.image);
.pickFiles(type: FileType.image); if (result != null && result.files.single.path != null) {
if (result != null &&
result.files.single.path != null) {
setDialogState(() { setDialogState(() {
pickedImage = pickedImage = File(result.files.single.path!);
File(result.files.single.path!);
imageController.text = pickedImage!.path; imageController.text = pickedImage!.path;
}); });
} }
@ -921,13 +776,11 @@ Future<void> _importFromExcel() async {
width: 100, width: 100,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: border: Border.all(color: Colors.grey.shade300),
Border.all(color: Colors.grey.shade300),
), ),
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
child: Image.file(pickedImage!, child: Image.file(pickedImage!, fit: BoxFit.cover),
fit: BoxFit.cover),
), ),
), ),
), ),
@ -949,8 +802,7 @@ Future<void> _importFromExcel() async {
children: [ children: [
Row( Row(
children: [ children: [
Icon(Icons.qr_code_2, Icon(Icons.qr_code_2, color: Colors.green.shade700),
color: Colors.green.shade700),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
'Aperçu du QR Code', 'Aperçu du QR Code',
@ -980,8 +832,7 @@ Future<void> _importFromExcel() async {
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
'Réf: $currentReference', 'Réf: $currentReference',
style: const TextStyle( style: const TextStyle(fontSize: 10, color: Colors.grey),
fontSize: 10, color: Colors.grey),
), ),
], ],
), ),
@ -1010,15 +861,12 @@ Future<void> _importFromExcel() async {
try { try {
// Générer une référence unique et vérifier son unicité // Générer une référence unique et vérifier son unicité
String finalReference = String finalReference = currentReference ?? _generateUniqueReference();
currentReference ?? _generateUniqueReference(); var existingProduct = await _productDatabase.getProductByReference(finalReference);
var existingProduct = await _productDatabase
.getProductByReference(finalReference);
while (existingProduct != null) { while (existingProduct != null) {
finalReference = _generateUniqueReference(); finalReference = _generateUniqueReference();
existingProduct = await _productDatabase existingProduct = await _productDatabase.getProductByReference(finalReference);
.getProductByReference(finalReference);
} }
// Générer le QR code // Générer le QR code
@ -1149,12 +997,9 @@ Future<void> _importFromExcel() async {
void _editProduct(Product product) { void _editProduct(Product product) {
final nameController = TextEditingController(text: product.name); final nameController = TextEditingController(text: product.name);
final priceController = final priceController = TextEditingController(text: product.price.toString());
TextEditingController(text: product.price.toString()); final stockController = TextEditingController(text: product.stock.toString());
final stockController = final descriptionController = TextEditingController(text: product.description ?? '');
TextEditingController(text: product.stock.toString());
final descriptionController =
TextEditingController(text: product.description ?? '');
final imageController = TextEditingController(text: product.image); final imageController = TextEditingController(text: product.image);
String selectedCategory = product.category; String selectedCategory = product.category;
@ -1177,16 +1022,17 @@ Future<void> _importFromExcel() async {
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
controller: priceController, controller: priceController,
keyboardType: keyboardType: const TextInputType.numberWithOptions(decimal: true),
const TextInputType.numberWithOptions(decimal: true),
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Prix*', labelText: 'Prix*',
border: OutlineInputBorder(), border: OutlineInputBorder(),
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
controller: stockController, controller: stockController,
keyboardType: TextInputType.number, keyboardType: TextInputType.number,
@ -1196,6 +1042,7 @@ Future<void> _importFromExcel() async {
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
StatefulBuilder( StatefulBuilder(
builder: (context, setDialogState) { builder: (context, setDialogState) {
return Column( return Column(
@ -1215,14 +1062,11 @@ Future<void> _importFromExcel() async {
const SizedBox(width: 8), const SizedBox(width: 8),
ElevatedButton( ElevatedButton(
onPressed: () async { onPressed: () async {
final result = await FilePicker.platform final result = await FilePicker.platform.pickFiles(type: FileType.image);
.pickFiles(type: FileType.image); if (result != null && result.files.single.path != null) {
if (result != null &&
result.files.single.path != null) {
if (context.mounted) { if (context.mounted) {
setDialogState(() { setDialogState(() {
pickedImage = pickedImage = File(result.files.single.path!);
File(result.files.single.path!);
imageController.text = pickedImage!.path; imageController.text = pickedImage!.path;
}); });
} }
@ -1233,6 +1077,7 @@ Future<void> _importFromExcel() async {
], ],
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
if (pickedImage != null || product.image!.isNotEmpty) if (pickedImage != null || product.image!.isNotEmpty)
Container( Container(
height: 100, height: 100,
@ -1246,19 +1091,16 @@ Future<void> _importFromExcel() async {
child: pickedImage != null child: pickedImage != null
? Image.file(pickedImage!, fit: BoxFit.cover) ? Image.file(pickedImage!, fit: BoxFit.cover)
: (product.image!.isNotEmpty : (product.image!.isNotEmpty
? Image.file(File(product.image!), ? Image.file(File(product.image!), fit: BoxFit.cover)
fit: BoxFit.cover)
: const Icon(Icons.image, size: 50)), : const Icon(Icons.image, size: 50)),
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
DropdownButtonFormField<String>( DropdownButtonFormField<String>(
value: selectedCategory, value: selectedCategory,
items: _categories items: _categories.skip(1).map((category) =>
.skip(1) DropdownMenuItem(value: category, child: Text(category))).toList(),
.map((category) => DropdownMenuItem(
value: category, child: Text(category)))
.toList(),
onChanged: (value) { onChanged: (value) {
if (context.mounted) { if (context.mounted) {
setDialogState(() => selectedCategory = value!); setDialogState(() => selectedCategory = value!);
@ -1270,6 +1112,7 @@ Future<void> _importFromExcel() async {
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
TextField( TextField(
controller: descriptionController, controller: descriptionController,
maxLines: 3, maxLines: 3,
@ -1363,8 +1206,7 @@ Future<void> _importFromExcel() async {
Get.snackbar('Erreur', 'Suppression échouée: $e'); Get.snackbar('Erreur', 'Suppression échouée: $e');
} }
}, },
child: child: const Text('Supprimer', style: TextStyle(color: Colors.white)),
const Text('Supprimer', style: TextStyle(color: Colors.white)),
), ),
], ],
), ),
@ -1426,8 +1268,7 @@ Future<void> _importFromExcel() async {
Row( Row(
children: [ children: [
Container( Container(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
horizontal: 8, vertical: 2),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.blue.shade100, color: Colors.blue.shade100,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
@ -1576,10 +1417,8 @@ Future<void> _importFromExcel() async {
), ),
child: DropdownButton<String>( child: DropdownButton<String>(
value: _selectedCategory, value: _selectedCategory,
items: _categories items: _categories.map((category) =>
.map((category) => DropdownMenuItem( DropdownMenuItem(value: category, child: Text(category))).toList(),
value: category, child: Text(category)))
.toList(),
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
_selectedCategory = value!; _selectedCategory = value!;
@ -1609,8 +1448,7 @@ Future<void> _importFromExcel() async {
color: Colors.grey, color: Colors.grey,
), ),
), ),
if (_searchController.text.isNotEmpty || if (_searchController.text.isNotEmpty || _selectedCategory != 'Tous')
_selectedCategory != 'Tous')
TextButton.icon( TextButton.icon(
onPressed: () { onPressed: () {
setState(() { setState(() {

Loading…
Cancel
Save