import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:em2rp/models/equipment_model.dart'; import 'package:em2rp/providers/equipment_provider.dart'; import 'package:em2rp/providers/local_user_provider.dart'; import 'package:em2rp/services/equipment_service.dart'; import 'package:em2rp/utils/colors.dart'; import 'package:em2rp/views/widgets/nav/custom_app_bar.dart'; import 'package:intl/intl.dart'; import 'package:em2rp/views/equipment_form/brand_model_selector.dart'; import 'package:em2rp/views/equipment_form/id_generator.dart'; class EquipmentFormPage extends StatefulWidget { final EquipmentModel? equipment; const EquipmentFormPage({super.key, this.equipment}); @override State createState() => _EquipmentFormPageState(); } class _EquipmentFormPageState extends State { final _formKey = GlobalKey(); final EquipmentService _equipmentService = EquipmentService(); // Controllers final TextEditingController _identifierController = TextEditingController(); final TextEditingController _brandController = TextEditingController(); final TextEditingController _modelController = TextEditingController(); final TextEditingController _purchasePriceController = TextEditingController(); final TextEditingController _rentalPriceController = TextEditingController(); final TextEditingController _totalQuantityController = TextEditingController(); final TextEditingController _criticalThresholdController = TextEditingController(); final TextEditingController _notesController = TextEditingController(); final TextEditingController _quantityToAddController = TextEditingController(text: '1'); // State variables EquipmentCategory _selectedCategory = EquipmentCategory.other; EquipmentStatus _selectedStatus = EquipmentStatus.available; DateTime? _purchaseDate; DateTime? _lastMaintenanceDate; DateTime? _nextMaintenanceDate; List _selectedParentBoxIds = []; List _availableBoxes = []; bool _isLoading = false; bool _isLoadingBoxes = true; bool _addMultiple = false; String? _selectedBrand; List _filteredModels = []; @override void initState() { super.initState(); _loadAvailableBoxes(); WidgetsBinding.instance.addPostFrameCallback((_) { final provider = Provider.of(context, listen: false); provider.loadBrands(); provider.loadModels(); }); if (widget.equipment != null) { _populateFields(); } } void _populateFields() { final equipment = widget.equipment!; _identifierController.text = equipment.id; _brandController.text = equipment.brand ?? ''; _selectedBrand = equipment.brand; _modelController.text = equipment.model ?? ''; _selectedCategory = equipment.category; _selectedStatus = equipment.status; _purchasePriceController.text = equipment.purchasePrice?.toStringAsFixed(2) ?? ''; _rentalPriceController.text = equipment.rentalPrice?.toStringAsFixed(2) ?? ''; _totalQuantityController.text = equipment.totalQuantity?.toString() ?? ''; _criticalThresholdController.text = equipment.criticalThreshold?.toString() ?? ''; _purchaseDate = equipment.purchaseDate; _lastMaintenanceDate = equipment.lastMaintenanceDate; _nextMaintenanceDate = equipment.nextMaintenanceDate; _selectedParentBoxIds = List.from(equipment.parentBoxIds); _notesController.text = equipment.notes ?? ''; if (_selectedBrand != null && _selectedBrand!.isNotEmpty) { _loadFilteredModels(_selectedBrand!); } } Future _loadAvailableBoxes() async { try { final boxes = await _equipmentService.getBoxes(); setState(() { _availableBoxes = boxes; _isLoadingBoxes = false; }); } catch (e) { setState(() { _isLoadingBoxes = false; }); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Erreur lors du chargement des boîtes : $e')), ); } } } Future _loadFilteredModels(String brand) async { try { final equipmentProvider = Provider.of(context, listen: false); final models = await equipmentProvider.loadModelsByBrand(brand); setState(() { _filteredModels = models; }); } catch (e) { setState(() { _filteredModels = []; }); } } @override void dispose() { _identifierController.dispose(); _brandController.dispose(); _modelController.dispose(); _purchasePriceController.dispose(); _rentalPriceController.dispose(); _totalQuantityController.dispose(); _criticalThresholdController.dispose(); _notesController.dispose(); _quantityToAddController.dispose(); super.dispose(); } bool get _isConsumable => _selectedCategory == EquipmentCategory.consumable || _selectedCategory == EquipmentCategory.cable; @override Widget build(BuildContext context) { final localUserProvider = Provider.of(context); final hasManagePermission = localUserProvider.hasPermission('manage_equipment'); final isEditing = widget.equipment != null; return Scaffold( appBar: CustomAppBar( title: isEditing ? 'Modifier l\'équipement' : 'Nouvel équipement', ), body: _isLoading ? const Center(child: CircularProgressIndicator()) : SingleChildScrollView( padding: const EdgeInsets.all(24.0), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Identifiant (généré ou saisi) TextFormField( controller: _identifierController, decoration: InputDecoration( labelText: 'Identifiant *', border: const OutlineInputBorder(), prefixIcon: const Icon(Icons.tag), hintText: isEditing ? null : 'Laissez vide pour générer automatiquement', helperText: isEditing ? 'Non modifiable' : 'Format auto: {Marque4Chars}_{Modèle}', ), enabled: !isEditing, ), const SizedBox(height: 16), // Case à cocher "Ajouter plusieurs" (uniquement en mode création) if (!isEditing) ...[ Row( children: [ Expanded( flex: 2, child: CheckboxListTile( title: const Text('Ajouter plusieurs équipements'), subtitle: const Text('Créer plusieurs équipements numérotés'), value: _addMultiple, contentPadding: EdgeInsets.zero, onChanged: (bool? value) { setState(() { _addMultiple = value ?? false; }); }, ), ), if (_addMultiple) ...[ const SizedBox(width: 16), Expanded( child: TextFormField( controller: _quantityToAddController, decoration: const InputDecoration( labelText: 'Quantité ou range', border: OutlineInputBorder(), prefixIcon: Icon(Icons.plus_one), hintText: '5 ou 6-18', helperText: 'Ex: 5 ou 6-18', ), keyboardType: TextInputType.text, validator: (value) { if (_addMultiple) { if (value == null || value.isEmpty) return 'Requis'; // Vérifier si c'est un nombre simple ou une range if (value.contains('-')) { final parts = value.split('-'); if (parts.length != 2) return 'Format invalide'; final start = int.tryParse(parts[0].trim()); final end = int.tryParse(parts[1].trim()); if (start == null || end == null) return 'Nombres invalides'; if (start >= end) return 'Le début doit être < fin'; if (end - start > 100) return 'Max 100 équipements'; } else { final num = int.tryParse(value); if (num == null || num < 1 || num > 100) return '1-100'; } } return null; }, ), ), ], ], ), const SizedBox(height: 16), ], // Sélecteur Marque/Modèle BrandModelSelector( brandController: _brandController, modelController: _modelController, selectedBrand: _selectedBrand, filteredModels: _filteredModels, onBrandChanged: (brand) { setState(() { _selectedBrand = brand; }); if (brand != null && brand.isNotEmpty) { _loadFilteredModels(brand); } else { setState(() { _filteredModels = []; }); } }, onModelsChanged: (models) { setState(() { _filteredModels = models; }); }, ), const SizedBox(height: 16), // Catégorie et Statut Row( children: [ Expanded( child: DropdownButtonFormField( value: _selectedCategory, decoration: const InputDecoration( labelText: 'Catégorie *', border: OutlineInputBorder(), prefixIcon: Icon(Icons.category), ), items: EquipmentCategory.values.map((category) { return DropdownMenuItem( value: category, child: Text(_getCategoryLabel(category)), ); }).toList(), onChanged: (value) { if (value != null) { setState(() { _selectedCategory = value; }); } }, ), ), // Afficher le statut uniquement si ce n'est pas un consommable ou câble if (!_isConsumable) ...[ const SizedBox(width: 16), Expanded( child: DropdownButtonFormField( value: _selectedStatus, decoration: const InputDecoration( labelText: 'Statut *', border: OutlineInputBorder(), prefixIcon: Icon(Icons.info), ), items: EquipmentStatus.values.map((status) { return DropdownMenuItem( value: status, child: Text(_getStatusLabel(status)), ); }).toList(), onChanged: (value) { if (value != null) { setState(() { _selectedStatus = value; }); } }, ), ), ], ], ), const SizedBox(height: 16), // Prix if (hasManagePermission) ...[ Row( children: [ Expanded( child: TextFormField( controller: _purchasePriceController, decoration: const InputDecoration( labelText: 'Prix d\'achat (€)', border: OutlineInputBorder(), prefixIcon: Icon(Icons.euro), ), keyboardType: const TextInputType.numberWithOptions(decimal: true), inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,2}'))], ), ), const SizedBox(width: 16), Expanded( child: TextFormField( controller: _rentalPriceController, decoration: const InputDecoration( labelText: 'Prix de location (€)', border: OutlineInputBorder(), prefixIcon: Icon(Icons.attach_money), ), keyboardType: const TextInputType.numberWithOptions(decimal: true), inputFormatters: [FilteringTextInputFormatter.allow(RegExp(r'^\d+\.?\d{0,2}'))], ), ), ], ), const SizedBox(height: 16), ], // Quantités pour consommables if (_isConsumable) ...[ const Divider(), const Text('Gestion des quantités', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), const SizedBox(height: 16), Row( children: [ Expanded( child: TextFormField( controller: _totalQuantityController, decoration: const InputDecoration( labelText: 'Quantité totale', border: OutlineInputBorder(), prefixIcon: Icon(Icons.inventory), ), keyboardType: TextInputType.number, inputFormatters: [FilteringTextInputFormatter.digitsOnly], ), ), const SizedBox(width: 16), Expanded( child: TextFormField( controller: _criticalThresholdController, decoration: const InputDecoration( labelText: 'Seuil critique', border: OutlineInputBorder(), prefixIcon: Icon(Icons.warning), ), keyboardType: TextInputType.number, inputFormatters: [FilteringTextInputFormatter.digitsOnly], ), ), ], ), const SizedBox(height: 16), ], // Boîtes parentes const Divider(), const Text('Boîtes parentes', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), const SizedBox(height: 8), _isLoadingBoxes ? const Center(child: CircularProgressIndicator()) : _buildParentBoxesSelector(), const SizedBox(height: 16), // Dates const Divider(), const Text('Dates', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), const SizedBox(height: 16), _buildDateField(label: 'Date d\'achat', icon: Icons.shopping_cart, value: _purchaseDate, onTap: () => _selectDate(context, 'purchase')), const SizedBox(height: 16), _buildDateField(label: 'Dernière maintenance', icon: Icons.build, value: _lastMaintenanceDate, onTap: () => _selectDate(context, 'lastMaintenance')), const SizedBox(height: 16), _buildDateField(label: 'Prochaine maintenance', icon: Icons.event, value: _nextMaintenanceDate, onTap: () => _selectDate(context, 'nextMaintenance')), const SizedBox(height: 16), // Notes const Divider(), TextFormField( controller: _notesController, decoration: const InputDecoration( labelText: 'Notes', border: OutlineInputBorder(), prefixIcon: Icon(Icons.notes), ), maxLines: 3, ), const SizedBox(height: 24), // Boutons Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Annuler'), ), const SizedBox(width: 16), ElevatedButton( onPressed: _saveEquipment, style: ElevatedButton.styleFrom( backgroundColor: AppColors.rouge, padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), ), child: Text(isEditing ? 'Enregistrer' : 'Créer', style: const TextStyle(color: Colors.white)), ), ], ), ], ), ), ), ); } Widget _buildParentBoxesSelector() { if (_availableBoxes.isEmpty) { return const Card( child: Padding( padding: EdgeInsets.all(16.0), child: Text('Aucune boîte disponible'), ), ); } return Card( child: Column( children: _availableBoxes.map((box) { final isSelected = _selectedParentBoxIds.contains(box.id); return CheckboxListTile( title: Text(box.name), subtitle: box.model != null ? Text('Modèle: {box.model}') : null, value: isSelected, onChanged: (bool? value) { setState(() { if (value == true) { _selectedParentBoxIds.add(box.id); } else { _selectedParentBoxIds.remove(box.id); } }); }, ); }).toList(), ), ); } Widget _buildDateField({required String label, required IconData icon, required DateTime? value, required VoidCallback onTap}) { return InkWell( onTap: onTap, child: InputDecorator( decoration: InputDecoration( labelText: label, border: const OutlineInputBorder(), prefixIcon: Icon(icon), suffixIcon: value != null ? IconButton( icon: const Icon(Icons.clear), onPressed: () { setState(() { if (label.contains('achat')) { _purchaseDate = null; } else if (label.contains('Dernière')) { _lastMaintenanceDate = null; } else if (label.contains('Prochaine')) { _nextMaintenanceDate = null; } }); }, ) : null, ), child: Text( value != null ? DateFormat('dd/MM/yyyy').format(value) : 'Sélectionner une date', style: TextStyle(color: value != null ? Colors.black : Colors.grey), ), ), ); } Future _selectDate(BuildContext context, String field) async { final DateTime? picked = await showDatePicker( context: context, initialDate: DateTime.now(), firstDate: DateTime(2000), lastDate: DateTime(2100), ); if (picked != null) { setState(() { switch (field) { case 'purchase': _purchaseDate = picked; break; case 'lastMaintenance': _lastMaintenanceDate = picked; break; case 'nextMaintenance': _nextMaintenanceDate = picked; break; } }); } } Future _saveEquipment() async { if (!_formKey.currentState!.validate()) return; setState(() => _isLoading = true); try { final equipmentProvider = Provider.of(context, listen: false); final isEditing = widget.equipment != null; int? availableQuantity; if (_isConsumable && _totalQuantityController.text.isNotEmpty) { final totalQuantity = int.parse(_totalQuantityController.text); if (isEditing && widget.equipment!.availableQuantity != null) { availableQuantity = widget.equipment!.availableQuantity; } else { availableQuantity = totalQuantity; } } // Validation marque/modèle obligatoires String brand = _brandController.text.trim(); String model = _modelController.text.trim(); if (brand.isEmpty || model.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('La marque et le modèle sont obligatoires')), ); return; } // Génération d'identifiant si vide List ids = []; List numbers = []; if (!isEditing && _identifierController.text.isEmpty) { // Gérer la range ou nombre simple final quantityText = _quantityToAddController.text.trim(); if (_addMultiple && quantityText.contains('-')) { // Range: ex "6-18" final parts = quantityText.split('-'); final start = int.parse(parts[0].trim()); final end = int.parse(parts[1].trim()); for (int i = start; i <= end; i++) { numbers.add(i); } } else if (_addMultiple) { // Nombre simple final nbToAdd = int.tryParse(quantityText) ?? 1; for (int i = 1; i <= nbToAdd; i++) { numbers.add(i); } } // Générer les IDs if (numbers.isEmpty) { String baseId = EquipmentIdGenerator.generate(brand: brand, model: model, number: null); String uniqueId = await EquipmentIdGenerator.ensureUniqueId(baseId, _equipmentService); ids.add(uniqueId); } else { for (final num in numbers) { String baseId = EquipmentIdGenerator.generate(brand: brand, model: model, number: num); String uniqueId = await EquipmentIdGenerator.ensureUniqueId(baseId, _equipmentService); ids.add(uniqueId); } } } else { ids.add(_identifierController.text.trim()); } // Création des équipements for (final id in ids) { final now = DateTime.now(); final equipment = EquipmentModel( id: id, name: id, // Utilisation de l'identifiant comme nom brand: brand, model: model, category: _selectedCategory, status: _selectedStatus, purchasePrice: _purchasePriceController.text.isNotEmpty ? double.tryParse(_purchasePriceController.text) : null, rentalPrice: _rentalPriceController.text.isNotEmpty ? double.tryParse(_rentalPriceController.text) : null, totalQuantity: _isConsumable ? int.tryParse(_totalQuantityController.text) : null, criticalThreshold: _isConsumable ? int.tryParse(_criticalThresholdController.text) : null, purchaseDate: _purchaseDate, lastMaintenanceDate: _lastMaintenanceDate, nextMaintenanceDate: _nextMaintenanceDate, parentBoxIds: _selectedParentBoxIds, notes: _notesController.text, createdAt: isEditing ? (widget.equipment?.createdAt ?? now) : now, updatedAt: now, availableQuantity: availableQuantity, ); if (isEditing) { await equipmentProvider.updateEquipment( equipment.id, equipment.toMap(), ); } else { await equipmentProvider.addEquipment(equipment); } } if (mounted) { Navigator.pop(context, true); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Erreur lors de l\'enregistrement : $e')), ); } } finally { if (mounted) setState(() => _isLoading = false); } } // Correction des enums dans _getCategoryLabel String _getCategoryLabel(EquipmentCategory category) { switch (category) { case EquipmentCategory.lighting: return 'Lumière'; case EquipmentCategory.sound: return 'Son'; case EquipmentCategory.video: return 'Vidéo'; case EquipmentCategory.effect: return 'Effet'; case EquipmentCategory.structure: return 'Structure'; case EquipmentCategory.consumable: return 'Consommable'; case EquipmentCategory.cable: return 'Câble'; case EquipmentCategory.other: default: return 'Autre'; } } // Correction des enums dans _getStatusLabel String _getStatusLabel(EquipmentStatus status) { switch (status) { case EquipmentStatus.available: return 'Disponible'; case EquipmentStatus.inUse: return 'En prestation'; case EquipmentStatus.rented: return 'Loué'; case EquipmentStatus.lost: return 'Perdu'; case EquipmentStatus.outOfService: return 'HS'; case EquipmentStatus.maintenance: return 'Maintenance'; default: return 'Autre'; } } }