Add equipment management features (and qr generation support)
This commit is contained in:
		
							
								
								
									
										133
									
								
								em2rp/lib/views/equipment_form/brand_model_selector.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								em2rp/lib/views/equipment_form/brand_model_selector.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:em2rp/providers/equipment_provider.dart'; | ||||
|  | ||||
| class BrandModelSelector extends StatefulWidget { | ||||
|   final TextEditingController brandController; | ||||
|   final TextEditingController modelController; | ||||
|   final ValueChanged<String?>? onBrandChanged; | ||||
|   final List<String> filteredModels; | ||||
|   final String? selectedBrand; | ||||
|   final Function(List<String>) onModelsChanged; | ||||
|  | ||||
|   const BrandModelSelector({ | ||||
|     super.key, | ||||
|     required this.brandController, | ||||
|     required this.modelController, | ||||
|     this.onBrandChanged, | ||||
|     required this.filteredModels, | ||||
|     required this.selectedBrand, | ||||
|     required this.onModelsChanged, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   State<BrandModelSelector> createState() => _BrandModelSelectorState(); | ||||
| } | ||||
|  | ||||
| class _BrandModelSelectorState extends State<BrandModelSelector> { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Row( | ||||
|       children: [ | ||||
|         Expanded( | ||||
|           child: Consumer<EquipmentProvider>( | ||||
|             builder: (context, provider, child) { | ||||
|               return Autocomplete<String>( | ||||
|                 initialValue: TextEditingValue(text: widget.brandController.text), | ||||
|                 optionsBuilder: (TextEditingValue textEditingValue) { | ||||
|                   if (textEditingValue.text.isEmpty) { | ||||
|                     return provider.brands; | ||||
|                   } | ||||
|                   return provider.brands.where((String brand) { | ||||
|                     return brand.toLowerCase().contains( | ||||
|                       textEditingValue.text.toLowerCase(), | ||||
|                     ); | ||||
|                   }); | ||||
|                 }, | ||||
|                 onSelected: (String selection) async { | ||||
|                   widget.brandController.text = selection; | ||||
|                   widget.onBrandChanged?.call(selection); | ||||
|                   final equipmentProvider = Provider.of<EquipmentProvider>(context, listen: false); | ||||
|                   final models = await equipmentProvider.loadModelsByBrand(selection); | ||||
|                   widget.onModelsChanged(models); | ||||
|                 }, | ||||
|                 fieldViewBuilder: (context, controller, focusNode, onEditingComplete) { | ||||
|                   if (controller.text != widget.brandController.text) { | ||||
|                     controller.text = widget.brandController.text; | ||||
|                   } | ||||
|                   return TextFormField( | ||||
|                     controller: controller, | ||||
|                     focusNode: focusNode, | ||||
|                     decoration: const InputDecoration( | ||||
|                       labelText: 'Marque *', | ||||
|                       border: OutlineInputBorder(), | ||||
|                       prefixIcon: Icon(Icons.business), | ||||
|                       helperText: 'Champ obligatoire', | ||||
|                     ), | ||||
|                     onChanged: (value) async { | ||||
|                       widget.brandController.text = value; | ||||
|                       widget.modelController.clear(); | ||||
|                       widget.onBrandChanged?.call(value.isNotEmpty ? value : null); | ||||
|                       if (value.isNotEmpty) { | ||||
|                         final equipmentProvider = Provider.of<EquipmentProvider>(context, listen: false); | ||||
|                         final models = await equipmentProvider.loadModelsByBrand(value); | ||||
|                         widget.onModelsChanged(models); | ||||
|                       } else { | ||||
|                         widget.onModelsChanged([]); | ||||
|                       } | ||||
|                     }, | ||||
|                   ); | ||||
|                 }, | ||||
|               ); | ||||
|             }, | ||||
|           ), | ||||
|         ), | ||||
|         const SizedBox(width: 16), | ||||
|         Expanded( | ||||
|           child: Autocomplete<String>( | ||||
|             initialValue: TextEditingValue(text: widget.modelController.text), | ||||
|             optionsBuilder: (TextEditingValue textEditingValue) { | ||||
|               if (widget.selectedBrand == null || widget.selectedBrand!.isEmpty) { | ||||
|                 return const Iterable<String>.empty(); | ||||
|               } | ||||
|               if (textEditingValue.text.isEmpty) { | ||||
|                 return widget.filteredModels; | ||||
|               } | ||||
|               return widget.filteredModels.where((String model) { | ||||
|                 return model.toLowerCase().contains( | ||||
|                   textEditingValue.text.toLowerCase(), | ||||
|                 ); | ||||
|               }); | ||||
|             }, | ||||
|             onSelected: (String selection) { | ||||
|               widget.modelController.text = selection; | ||||
|             }, | ||||
|             fieldViewBuilder: (context, controller, focusNode, onEditingComplete) { | ||||
|               if (controller.text != widget.modelController.text) { | ||||
|                 controller.text = widget.modelController.text; | ||||
|               } | ||||
|               return TextFormField( | ||||
|                 controller: controller, | ||||
|                 focusNode: focusNode, | ||||
|                 enabled: widget.selectedBrand != null && widget.selectedBrand!.isNotEmpty, | ||||
|                 decoration: InputDecoration( | ||||
|                   labelText: 'Modèle *', | ||||
|                   border: const OutlineInputBorder(), | ||||
|                   prefixIcon: const Icon(Icons.inventory_2), | ||||
|                   hintText: widget.selectedBrand == null || widget.selectedBrand!.isEmpty | ||||
|                       ? 'Marque requise' | ||||
|                       : 'Saisissez le modèle', | ||||
|                   helperText: 'Champ obligatoire', | ||||
|                 ), | ||||
|                 onChanged: (value) { | ||||
|                   widget.modelController.text = value; | ||||
|                 }, | ||||
|               ); | ||||
|             }, | ||||
|           ), | ||||
|         ), | ||||
|       ], | ||||
|     ); | ||||
|   } | ||||
| } | ||||
|  | ||||
							
								
								
									
										26
									
								
								em2rp/lib/views/equipment_form/id_generator.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								em2rp/lib/views/equipment_form/id_generator.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:em2rp/services/equipment_service.dart'; | ||||
|  | ||||
| class EquipmentIdGenerator { | ||||
|   static String generate({required String brand, required String model, int? number}) { | ||||
|     final brandTrim = brand.trim().replaceAll(' ', '_'); | ||||
|     final modelTrim = model.trim().replaceAll(' ', '_'); | ||||
|     if (brandTrim.isEmpty && modelTrim.isEmpty) { | ||||
|       return 'EQ-${DateTime.now().millisecondsSinceEpoch}${number != null ? '_$number' : ''}'; | ||||
|     } | ||||
|     final brandPrefix = brandTrim.length >= 4 ? brandTrim.substring(0, 4) : brandTrim; | ||||
|     String baseId = modelTrim.isNotEmpty ? '${brandPrefix}_$modelTrim' : (brandPrefix.isNotEmpty ? brandPrefix : 'EQ'); | ||||
|     if (number != null) { | ||||
|       baseId += '_#$number'; | ||||
|     } | ||||
|     return baseId; | ||||
|   } | ||||
|  | ||||
|   static Future<String> ensureUniqueId(String baseId, EquipmentService service) async { | ||||
|     if (await service.isIdUnique(baseId)) { | ||||
|       return baseId; | ||||
|     } | ||||
|     return '${baseId}_${DateTime.now().millisecondsSinceEpoch}'; | ||||
|   } | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 ElPoyo
					ElPoyo