|
|
@ -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,14 +48,19 @@ 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; |
|
|
double _importProgress = 0.0; |
|
|
double _importProgress = 0.0; |
|
|
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; |
|
|
@ -195,137 +195,6 @@ Future<void> _importFromExcel() async { |
|
|
return; |
|
|
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 { |
|
|
|
|
|
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(() { |
|
|
setState(() { |
|
|
_isImporting = true; |
|
|
_isImporting = true; |
|
|
_importProgress = 0.0; |
|
|
_importProgress = 0.0; |
|
|
@ -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,15 +412,16 @@ 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'); |
|
|
debugPrint('Erreur générale import 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 |
|
|
// Ajoutez ce widget dans votre méthode build, par exemple dans la partie supérieure |
|
|
Widget _buildImportProgressIndicator() { |
|
|
Widget _buildImportProgressIndicator() { |
|
|
if (!_isImporting) return const SizedBox.shrink(); |
|
|
if (!_isImporting) return const SizedBox.shrink(); |
|
|
|
|
|
|
|
|
return Container( |
|
|
return Container( |
|
|
@ -601,8 +468,7 @@ 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(() { |
|
|
|