import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:em2rp/utils/colors.dart'; import 'package:em2rp/models/container_model.dart'; import 'package:em2rp/models/equipment_model.dart'; import 'package:em2rp/providers/container_provider.dart'; import 'package:em2rp/providers/equipment_provider.dart'; import 'package:em2rp/utils/id_generator.dart'; class ContainerFormPage extends StatefulWidget { final ContainerModel? container; const ContainerFormPage({super.key, this.container}); @override State createState() => _ContainerFormPageState(); } class _ContainerFormPageState extends State { final _formKey = GlobalKey(); // Controllers final _nameController = TextEditingController(); final _idController = TextEditingController(); final _weightController = TextEditingController(); final _lengthController = TextEditingController(); final _widthController = TextEditingController(); final _heightController = TextEditingController(); final _notesController = TextEditingController(); // Form fields ContainerType _selectedType = ContainerType.flightCase; EquipmentStatus _selectedStatus = EquipmentStatus.available; bool _autoGenerateId = true; final Set _selectedEquipmentIds = {}; bool _isEditing = false; @override void initState() { super.initState(); if (widget.container != null) { _isEditing = true; _loadContainerData(); } } void _loadContainerData() { final container = widget.container!; _nameController.text = container.name; _idController.text = container.id; _selectedType = container.type; _selectedStatus = container.status; _weightController.text = container.weight?.toString() ?? ''; _lengthController.text = container.length?.toString() ?? ''; _widthController.text = container.width?.toString() ?? ''; _heightController.text = container.height?.toString() ?? ''; _notesController.text = container.notes ?? ''; _selectedEquipmentIds.addAll(container.equipmentIds); _autoGenerateId = false; } void _updateIdFromName() { if (_autoGenerateId && !_isEditing) { final name = _nameController.text; if (name.isNotEmpty) { final baseId = IdGenerator.generateContainerId( type: _selectedType, name: name, ); _idController.text = baseId; } } } void _updateIdFromType() { if (_autoGenerateId && !_isEditing) { final name = _nameController.text; if (name.isNotEmpty) { final baseId = IdGenerator.generateContainerId( type: _selectedType, name: name, ); _idController.text = baseId; } } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(_isEditing ? 'Modifier Container' : 'Nouveau Container'), backgroundColor: AppColors.rouge, foregroundColor: Colors.white, ), body: Form( key: _formKey, child: ListView( padding: const EdgeInsets.all(24), children: [ // Nom TextFormField( controller: _nameController, decoration: const InputDecoration( labelText: 'Nom du container *', hintText: 'ex: Flight Case Beam 7R', border: OutlineInputBorder(), prefixIcon: Icon(Icons.label), ), onChanged: (_) => _updateIdFromName(), validator: (value) { if (value == null || value.isEmpty) { return 'Veuillez entrer un nom'; } return null; }, ), const SizedBox(height: 16), // ID Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: TextFormField( controller: _idController, decoration: const InputDecoration( labelText: 'Identifiant *', hintText: 'ex: FLIGHTCASE_BEAM', border: OutlineInputBorder(), prefixIcon: Icon(Icons.qr_code), ), enabled: !_autoGenerateId || _isEditing, validator: (value) { if (value == null || value.isEmpty) { return 'Veuillez entrer un identifiant'; } final validation = IdGenerator.validateContainerId(value); return validation; }, ), ), if (!_isEditing) ...[ const SizedBox(width: 8), IconButton( icon: Icon( _autoGenerateId ? Icons.lock : Icons.lock_open, color: _autoGenerateId ? AppColors.rouge : Colors.grey, ), tooltip: _autoGenerateId ? 'Génération automatique' : 'Saisie manuelle', onPressed: () { setState(() { _autoGenerateId = !_autoGenerateId; if (_autoGenerateId) { _updateIdFromName(); } }); }, ), ], ], ), const SizedBox(height: 16), // Type DropdownButtonFormField( value: _selectedType, decoration: const InputDecoration( labelText: 'Type de container *', border: OutlineInputBorder(), prefixIcon: Icon(Icons.category), ), items: ContainerType.values.map((type) { return DropdownMenuItem( value: type, child: Text(type.label), ); }).toList(), onChanged: (value) { if (value != null) { setState(() { _selectedType = value; _updateIdFromType(); }); } }, ), const SizedBox(height: 16), // Statut DropdownButtonFormField( value: _selectedStatus, decoration: const InputDecoration( labelText: 'Statut *', border: OutlineInputBorder(), prefixIcon: Icon(Icons.info), ), items: [ EquipmentStatus.available, EquipmentStatus.inUse, EquipmentStatus.maintenance, EquipmentStatus.outOfService, ].map((status) { String label; switch (status) { case EquipmentStatus.available: label = 'Disponible'; break; case EquipmentStatus.inUse: label = 'En prestation'; break; case EquipmentStatus.maintenance: label = 'En maintenance'; break; case EquipmentStatus.outOfService: label = 'Hors service'; break; default: label = 'Autre'; } return DropdownMenuItem( value: status, child: Text(label), ); }).toList(), onChanged: (value) { if (value != null) { setState(() { _selectedStatus = value; }); } }, ), const SizedBox(height: 24), // Section Caractéristiques physiques Text( 'Caractéristiques physiques', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), ), const Divider(), const SizedBox(height: 16), // Poids TextFormField( controller: _weightController, decoration: const InputDecoration( labelText: 'Poids à vide (kg)', hintText: 'ex: 15.5', border: OutlineInputBorder(), prefixIcon: Icon(Icons.scale), ), keyboardType: const TextInputType.numberWithOptions(decimal: true), validator: (value) { if (value != null && value.isNotEmpty) { if (double.tryParse(value) == null) { return 'Veuillez entrer un nombre valide'; } } return null; }, ), const SizedBox(height: 16), // Dimensions Row( children: [ Expanded( child: TextFormField( controller: _lengthController, decoration: const InputDecoration( labelText: 'Longueur (cm)', border: OutlineInputBorder(), ), keyboardType: TextInputType.numberWithOptions(decimal: true), validator: (value) { if (value != null && value.isNotEmpty) { if (double.tryParse(value) == null) { return 'Nombre invalide'; } } return null; }, ), ), const SizedBox(width: 8), Expanded( child: TextFormField( controller: _widthController, decoration: const InputDecoration( labelText: 'Largeur (cm)', border: OutlineInputBorder(), ), keyboardType: TextInputType.numberWithOptions(decimal: true), validator: (value) { if (value != null && value.isNotEmpty) { if (double.tryParse(value) == null) { return 'Nombre invalide'; } } return null; }, ), ), const SizedBox(width: 8), Expanded( child: TextFormField( controller: _heightController, decoration: const InputDecoration( labelText: 'Hauteur (cm)', border: OutlineInputBorder(), ), keyboardType: TextInputType.numberWithOptions(decimal: true), validator: (value) { if (value != null && value.isNotEmpty) { if (double.tryParse(value) == null) { return 'Nombre invalide'; } } return null; }, ), ), ], ), const SizedBox(height: 24), // Section Équipements Text( 'Équipements dans ce container', style: Theme.of(context).textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), ), const Divider(), const SizedBox(height: 16), // Liste des équipements sélectionnés if (_selectedEquipmentIds.isNotEmpty) Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '${_selectedEquipmentIds.length} équipement(s) sélectionné(s)', style: const TextStyle(fontWeight: FontWeight.bold), ), const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 8, children: _selectedEquipmentIds.map((id) { return Chip( label: Text(id), deleteIcon: const Icon(Icons.close, size: 18), onDeleted: () { setState(() { _selectedEquipmentIds.remove(id); }); }, ); }).toList(), ), ], ), ) else Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(8), color: Colors.grey.shade50, ), child: const Center( child: Text( 'Aucun équipement sélectionné', style: TextStyle(color: Colors.grey), ), ), ), const SizedBox(height: 12), // Bouton pour ajouter des équipements OutlinedButton.icon( onPressed: _selectEquipment, icon: const Icon(Icons.add), label: const Text('Ajouter des équipements'), style: OutlinedButton.styleFrom( minimumSize: const Size(double.infinity, 48), ), ), const SizedBox(height: 24), // Notes TextFormField( controller: _notesController, decoration: const InputDecoration( labelText: 'Notes', hintText: 'Informations additionnelles...', border: OutlineInputBorder(), prefixIcon: Icon(Icons.notes), ), maxLines: 3, ), const SizedBox(height: 32), // Boutons Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Annuler'), ), const SizedBox(width: 16), ElevatedButton.icon( onPressed: _saveContainer, icon: const Icon(Icons.save, color: Colors.white), label: Text( _isEditing ? 'Mettre à jour' : 'Créer', style: const TextStyle(color: Colors.white), ), style: ElevatedButton.styleFrom( backgroundColor: AppColors.rouge, padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 12, ), ), ), ], ), ], ), ), ); } Future _selectEquipment() async { final equipmentProvider = context.read(); await showDialog( context: context, builder: (context) => _EquipmentSelectorDialog( selectedIds: _selectedEquipmentIds, equipmentProvider: equipmentProvider, ), ); setState(() {}); } Future _isIdUnique(String id) async { final provider = context.read(); final container = await provider.getContainerById(id); return container == null; } Future _saveContainer() async { if (!_formKey.currentState!.validate()) { return; } try { final containerProvider = context.read(); if (_isEditing) { await _updateContainer(containerProvider); } else { await _createSingleContainer(containerProvider); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Erreur: $e')), ); } } } Future _createSingleContainer(ContainerProvider provider) async { final baseId = _idController.text.trim(); // Vérifier l'unicité de l'ID directement String uniqueId = baseId; if (!await _isIdUnique(baseId)) { uniqueId = '${baseId}_${DateTime.now().millisecondsSinceEpoch}'; } final container = ContainerModel( id: uniqueId, name: _nameController.text.trim(), type: _selectedType, status: _selectedStatus, equipmentIds: _selectedEquipmentIds.toList(), weight: _weightController.text.isNotEmpty ? double.tryParse(_weightController.text) : null, length: _lengthController.text.isNotEmpty ? double.tryParse(_lengthController.text) : null, width: _widthController.text.isNotEmpty ? double.tryParse(_widthController.text) : null, height: _heightController.text.isNotEmpty ? double.tryParse(_heightController.text) : null, notes: _notesController.text.trim().isNotEmpty ? _notesController.text.trim() : null, createdAt: DateTime.now(), updatedAt: DateTime.now(), ); await provider.createContainer(container); // Mettre à jour les parentBoxIds des équipements for (final equipmentId in _selectedEquipmentIds) { try { await provider.addEquipmentToContainer( containerId: uniqueId, equipmentId: equipmentId, ); } catch (e) { print('Erreur lors de l\'ajout de l\'équipement $equipmentId: $e'); } } if (mounted) { Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Container créé avec succès')), ); } } Future _updateContainer(ContainerProvider provider) async { final container = widget.container!; await provider.updateContainer(container.id, { 'name': _nameController.text.trim(), 'type': containerTypeToString(_selectedType), 'status': equipmentStatusToString(_selectedStatus), 'equipmentIds': _selectedEquipmentIds.toList(), 'weight': _weightController.text.isNotEmpty ? double.tryParse(_weightController.text) : null, 'length': _lengthController.text.isNotEmpty ? double.tryParse(_lengthController.text) : null, 'width': _widthController.text.isNotEmpty ? double.tryParse(_widthController.text) : null, 'height': _heightController.text.isNotEmpty ? double.tryParse(_heightController.text) : null, 'notes': _notesController.text.trim().isNotEmpty ? _notesController.text.trim() : null, }); // Gérer les équipements ajoutés final addedEquipment = _selectedEquipmentIds.difference(container.equipmentIds.toSet()); for (final equipmentId in addedEquipment) { try { await provider.addEquipmentToContainer( containerId: container.id, equipmentId: equipmentId, ); } catch (e) { print('Erreur lors de l\'ajout de l\'équipement $equipmentId: $e'); } } // Gérer les équipements retirés final removedEquipment = container.equipmentIds.toSet().difference(_selectedEquipmentIds); for (final equipmentId in removedEquipment) { try { await provider.removeEquipmentFromContainer( containerId: container.id, equipmentId: equipmentId, ); } catch (e) { print('Erreur lors du retrait de l\'équipement $equipmentId: $e'); } } if (mounted) { Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Container mis à jour avec succès')), ); } } @override void dispose() { _nameController.dispose(); _idController.dispose(); _weightController.dispose(); _lengthController.dispose(); _widthController.dispose(); _heightController.dispose(); _notesController.dispose(); super.dispose(); } } /// Widget de dialogue pour sélectionner les équipements class _EquipmentSelectorDialog extends StatefulWidget { final Set selectedIds; final EquipmentProvider equipmentProvider; const _EquipmentSelectorDialog({ required this.selectedIds, required this.equipmentProvider, }); @override State<_EquipmentSelectorDialog> createState() => _EquipmentSelectorDialogState(); } class _EquipmentSelectorDialogState extends State<_EquipmentSelectorDialog> { final TextEditingController _searchController = TextEditingController(); EquipmentCategory? _filterCategory; String _searchQuery = ''; @override void dispose() { _searchController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Dialog( child: Container( width: MediaQuery.of(context).size.width * 0.8, height: MediaQuery.of(context).size.height * 0.8, padding: const EdgeInsets.all(24), child: Column( children: [ // En-tête Row( children: [ const Icon(Icons.inventory, color: AppColors.rouge), const SizedBox(width: 12), const Expanded( child: Text( 'Sélectionner des équipements', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), ), IconButton( icon: const Icon(Icons.close), onPressed: () => Navigator.pop(context), ), ], ), const Divider(), const SizedBox(height: 16), // Barre de recherche TextField( controller: _searchController, decoration: InputDecoration( hintText: 'Rechercher un équipement...', prefixIcon: const Icon(Icons.search), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), suffixIcon: _searchController.text.isNotEmpty ? IconButton( icon: const Icon(Icons.clear), onPressed: () { _searchController.clear(); setState(() { _searchQuery = ''; }); }, ) : null, ), onChanged: (value) { setState(() { _searchQuery = value; }); }, ), const SizedBox(height: 16), // Filtres par catégorie SizedBox( height: 50, child: ListView( scrollDirection: Axis.horizontal, children: [ ChoiceChip( label: const Text('Tout'), selected: _filterCategory == null, onSelected: (selected) { setState(() { _filterCategory = null; }); }, selectedColor: AppColors.rouge, labelStyle: TextStyle( color: _filterCategory == null ? Colors.white : Colors.black, ), ), const SizedBox(width: 8), ...EquipmentCategory.values.map((category) { return Padding( padding: const EdgeInsets.only(right: 8), child: ChoiceChip( label: Text(_getCategoryLabel(category)), selected: _filterCategory == category, onSelected: (selected) { setState(() { _filterCategory = selected ? category : null; }); }, selectedColor: AppColors.rouge, labelStyle: TextStyle( color: _filterCategory == category ? Colors.white : Colors.black, ), ), ); }), ], ), ), const SizedBox(height: 16), // Compteur de sélection Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AppColors.rouge.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Row( children: [ const Icon(Icons.check_circle, color: AppColors.rouge), const SizedBox(width: 8), Text( '${widget.selectedIds.length} équipement(s) sélectionné(s)', style: const TextStyle(fontWeight: FontWeight.bold), ), ], ), ), const SizedBox(height: 16), // Liste des équipements Expanded( child: StreamBuilder>( stream: widget.equipmentProvider.equipmentStream, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } if (snapshot.hasError) { return Center(child: Text('Erreur: ${snapshot.error}')); } var equipment = snapshot.data ?? []; // Filtrer par catégorie if (_filterCategory != null) { equipment = equipment.where((e) => e.category == _filterCategory).toList(); } // Filtrer par recherche if (_searchQuery.isNotEmpty) { final query = _searchQuery.toLowerCase(); equipment = equipment.where((e) { return e.id.toLowerCase().contains(query) || (e.brand?.toLowerCase().contains(query) ?? false) || (e.model?.toLowerCase().contains(query) ?? false); }).toList(); } if (equipment.isEmpty) { return const Center( child: Text('Aucun équipement trouvé'), ); } return ListView.builder( itemCount: equipment.length, itemBuilder: (context, index) { final item = equipment[index]; final isSelected = widget.selectedIds.contains(item.id); return CheckboxListTile( value: isSelected, onChanged: (selected) { setState(() { if (selected == true) { widget.selectedIds.add(item.id); } else { widget.selectedIds.remove(item.id); } }); }, title: Text( item.id, style: const TextStyle(fontWeight: FontWeight.bold), ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (item.brand != null || item.model != null) Text('${item.brand ?? ''} ${item.model ?? ''}'), const SizedBox(height: 4), Text( _getCategoryLabel(item.category), style: TextStyle( fontSize: 12, color: Colors.grey.shade600, ), ), ], ), secondary: Icon( _getCategoryIcon(item.category), color: AppColors.rouge, ), activeColor: AppColors.rouge, ); }, ); }, ), ), // Boutons d'action const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Annuler'), ), const SizedBox(width: 16), ElevatedButton( onPressed: () => Navigator.pop(context), style: ElevatedButton.styleFrom( backgroundColor: AppColors.rouge, ), child: const Text( 'Valider', style: TextStyle(color: Colors.white), ), ), ], ), ], ), ), ); } 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 'Effets'; case EquipmentCategory.structure: return 'Structure'; case EquipmentCategory.consumable: return 'Consommable'; case EquipmentCategory.cable: return 'Câble'; case EquipmentCategory.other: return 'Autre'; } } IconData _getCategoryIcon(EquipmentCategory category) { switch (category) { case EquipmentCategory.lighting: return Icons.lightbulb; case EquipmentCategory.sound: return Icons.speaker; case EquipmentCategory.video: return Icons.videocam; case EquipmentCategory.effect: return Icons.auto_awesome; case EquipmentCategory.structure: return Icons.construction; case EquipmentCategory.consumable: return Icons.inventory; case EquipmentCategory.cable: return Icons.cable; case EquipmentCategory.other: return Icons.category; } } }