import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:em2rp/models/maintenance_model.dart'; import 'package:em2rp/models/equipment_model.dart'; import 'package:em2rp/providers/maintenance_provider.dart'; import 'package:em2rp/providers/equipment_provider.dart'; import 'package:intl/intl.dart'; import 'package:em2rp/utils/colors.dart'; import 'package:uuid/uuid.dart'; /// Page de formulaire pour créer ou modifier une maintenance class MaintenanceFormPage extends StatefulWidget { final MaintenanceModel? maintenance; final List? initialEquipmentIds; const MaintenanceFormPage({ super.key, this.maintenance, this.initialEquipmentIds, }); @override State createState() => _MaintenanceFormPageState(); } class _MaintenanceFormPageState extends State { final _formKey = GlobalKey(); final _nameController = TextEditingController(); final _descriptionController = TextEditingController(); final _costController = TextEditingController(); final _notesController = TextEditingController(); MaintenanceType _selectedType = MaintenanceType.preventive; DateTime _scheduledDate = DateTime.now(); final List _selectedEquipmentIds = []; bool _isLoading = false; bool get _isEditing => widget.maintenance != null; @override void initState() { super.initState(); if (_isEditing) { _nameController.text = widget.maintenance!.name; _descriptionController.text = widget.maintenance!.description; _selectedType = widget.maintenance!.type; _scheduledDate = widget.maintenance!.scheduledDate; _selectedEquipmentIds.addAll(widget.maintenance!.equipmentIds); if (widget.maintenance!.cost != null) { _costController.text = widget.maintenance!.cost!.toStringAsFixed(2); } if (widget.maintenance!.notes != null) { _notesController.text = widget.maintenance!.notes!; } } else if (widget.initialEquipmentIds != null) { // Pré-remplir avec les équipements fournis _selectedEquipmentIds.addAll(widget.initialEquipmentIds!); } // Charger les équipements WidgetsBinding.instance.addPostFrameCallback((_) { context.read().ensureLoaded(); }); } @override void dispose() { _nameController.dispose(); _descriptionController.dispose(); _costController.dispose(); _notesController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(_isEditing ? 'Modifier la maintenance' : 'Nouvelle maintenance'), backgroundColor: AppColors.bleuFonce, ), body: Form( key: _formKey, child: ListView( padding: const EdgeInsets.all(16), children: [ // Nom TextFormField( controller: _nameController, decoration: const InputDecoration( labelText: 'Nom de la maintenance *', hintText: 'Ex: Révision annuelle', prefixIcon: Icon(Icons.title), border: OutlineInputBorder(), ), validator: (value) { if (value == null || value.trim().isEmpty) { return 'Le nom est requis'; } return null; }, ), const SizedBox(height: 16), // Type DropdownButtonFormField( initialValue: _selectedType, decoration: const InputDecoration( labelText: 'Type de maintenance *', prefixIcon: Icon(Icons.category), border: OutlineInputBorder(), ), items: MaintenanceType.values.map((type) { final info = _getMaintenanceTypeInfo(type); return DropdownMenuItem( value: type, child: Row( children: [ Icon(info.$2, size: 20, color: info.$3), const SizedBox(width: 8), Text(info.$1), ], ), ); }).toList(), onChanged: (value) { if (value != null) { setState(() { _selectedType = value; }); } }, ), const SizedBox(height: 16), // Date planifiée InkWell( onTap: _selectDate, child: InputDecorator( decoration: const InputDecoration( labelText: 'Date planifiée *', prefixIcon: Icon(Icons.event), border: OutlineInputBorder(), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(DateFormat('dd/MM/yyyy').format(_scheduledDate)), const Icon(Icons.arrow_drop_down), ], ), ), ), const SizedBox(height: 16), // Équipements _buildEquipmentSelector(), const SizedBox(height: 16), // Description TextFormField( controller: _descriptionController, decoration: const InputDecoration( labelText: 'Description *', hintText: 'Détails de l\'opération à effectuer', prefixIcon: Icon(Icons.description), border: OutlineInputBorder(), alignLabelWithHint: true, ), maxLines: 4, validator: (value) { if (value == null || value.trim().isEmpty) { return 'La description est requise'; } return null; }, ), const SizedBox(height: 16), // Coût estimé TextFormField( controller: _costController, decoration: const InputDecoration( labelText: 'Coût estimé (€)', hintText: 'Ex: 150.00', prefixIcon: Icon(Icons.euro), border: OutlineInputBorder(), ), keyboardType: TextInputType.number, validator: (value) { if (value != null && value.isNotEmpty) { if (double.tryParse(value) == null) { return 'Coût invalide'; } } return null; }, ), const SizedBox(height: 16), // Notes TextFormField( controller: _notesController, decoration: const InputDecoration( labelText: 'Notes', hintText: 'Informations complémentaires', prefixIcon: Icon(Icons.notes), border: OutlineInputBorder(), alignLabelWithHint: true, ), maxLines: 3, ), const SizedBox(height: 24), // Bouton sauvegarder ElevatedButton.icon( onPressed: _isLoading ? null : _saveMaintenance, icon: _isLoading ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white), ) : const Icon(Icons.save), label: Text(_isEditing ? 'Mettre à jour' : 'Créer la maintenance'), style: ElevatedButton.styleFrom( backgroundColor: AppColors.bleuFonce, padding: const EdgeInsets.symmetric(vertical: 16), ), ), ], ), ), ); } Widget _buildEquipmentSelector() { return Consumer( builder: (context, equipmentProvider, _) { // Filtrer uniquement les équipements final availableEquipment = equipmentProvider.allEquipment .cast() .toList(); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ InputDecorator( decoration: InputDecoration( labelText: 'Équipements concernés *', prefixIcon: const Icon(Icons.inventory), border: const OutlineInputBorder(), errorText: _selectedEquipmentIds.isEmpty ? 'Sélectionnez au moins un équipement' : null, ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (_selectedEquipmentIds.isEmpty) const Text( 'Aucun équipement sélectionné', style: TextStyle(color: Colors.grey), ) else Wrap( spacing: 8, runSpacing: 8, children: _selectedEquipmentIds.map((id) { final equipment = availableEquipment.firstWhere( (eq) => eq.id == id, orElse: () => EquipmentModel( id: id, name: 'Inconnu', category: EquipmentCategory.other, status: EquipmentStatus.available, maintenanceIds: [], createdAt: DateTime.now(), updatedAt: DateTime.now(), ), ); return Chip( label: Text(equipment.name), deleteIcon: const Icon(Icons.close, size: 18), onDeleted: () { setState(() { _selectedEquipmentIds.remove(id); }); }, ); }).toList(), ), const SizedBox(height: 8), OutlinedButton.icon( onPressed: () => _showEquipmentPicker(availableEquipment), icon: const Icon(Icons.add), label: const Text('Ajouter des équipements'), ), ], ), ), ], ); }, ); } Future _showEquipmentPicker(List availableEquipment) async { final selectedIds = await showDialog>( context: context, builder: (context) => _EquipmentPickerDialog( availableEquipment: availableEquipment, initialSelectedIds: _selectedEquipmentIds, ), ); if (selectedIds != null) { setState(() { _selectedEquipmentIds.clear(); _selectedEquipmentIds.addAll(selectedIds); }); } } Future _selectDate() async { final date = await showDatePicker( context: context, initialDate: _scheduledDate, firstDate: DateTime.now().subtract(const Duration(days: 365)), lastDate: DateTime.now().add(const Duration(days: 365 * 5)), locale: const Locale('fr', 'FR'), ); if (date != null) { setState(() { _scheduledDate = date; }); } } Future _saveMaintenance() async { if (!_formKey.currentState!.validate()) { return; } if (_selectedEquipmentIds.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Veuillez sélectionner au moins un équipement'), backgroundColor: Colors.orange, ), ); return; } setState(() { _isLoading = true; }); try { final cost = _costController.text.trim().isNotEmpty ? double.tryParse(_costController.text.trim()) : null; final notes = _notesController.text.trim().isNotEmpty ? _notesController.text.trim() : null; if (_isEditing) { // Mise à jour await context.read().updateMaintenance( widget.maintenance!.id, { 'name': _nameController.text.trim(), 'description': _descriptionController.text.trim(), 'type': maintenanceTypeToString(_selectedType), 'scheduledDate': _scheduledDate, 'equipmentIds': _selectedEquipmentIds, 'cost': cost, 'notes': notes, 'updatedAt': DateTime.now(), }, ); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Maintenance mise à jour avec succès'), backgroundColor: Colors.green, ), ); } } else { // Création final maintenance = MaintenanceModel( id: const Uuid().v4(), equipmentIds: _selectedEquipmentIds, type: _selectedType, scheduledDate: _scheduledDate, name: _nameController.text.trim(), description: _descriptionController.text.trim(), cost: cost, notes: notes, createdAt: DateTime.now(), updatedAt: DateTime.now(), ); await context.read().createMaintenance(maintenance); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Maintenance créée avec succès'), backgroundColor: Colors.green, ), ); } } if (mounted) { Navigator.pop(context, true); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Erreur: $e'), backgroundColor: Colors.red, ), ); } } finally { if (mounted) { setState(() { _isLoading = false; }); } } } (String, IconData, Color) _getMaintenanceTypeInfo(MaintenanceType type) { switch (type) { case MaintenanceType.preventive: return ('Préventive', Icons.schedule, Colors.blue); case MaintenanceType.corrective: return ('Corrective', Icons.build, Colors.orange); case MaintenanceType.inspection: return ('Inspection', Icons.search, Colors.purple); } } } /// Dialog pour sélectionner plusieurs équipements class _EquipmentPickerDialog extends StatefulWidget { final List availableEquipment; final List initialSelectedIds; const _EquipmentPickerDialog({ required this.availableEquipment, required this.initialSelectedIds, }); @override State<_EquipmentPickerDialog> createState() => _EquipmentPickerDialogState(); } class _EquipmentPickerDialogState extends State<_EquipmentPickerDialog> { late List _selectedIds; String _searchQuery = ''; @override void initState() { super.initState(); _selectedIds = List.from(widget.initialSelectedIds); } @override Widget build(BuildContext context) { final filteredEquipment = widget.availableEquipment.where((eq) { if (_searchQuery.isEmpty) return true; return eq.name.toLowerCase().contains(_searchQuery.toLowerCase()) || eq.id.toLowerCase().contains(_searchQuery.toLowerCase()); }).toList(); return AlertDialog( title: const Text('Sélectionner des équipements'), content: SizedBox( width: double.maxFinite, child: Column( mainAxisSize: MainAxisSize.min, children: [ // Barre de recherche TextField( decoration: const InputDecoration( labelText: 'Rechercher', prefixIcon: Icon(Icons.search), border: OutlineInputBorder(), ), onChanged: (value) { setState(() { _searchQuery = value; }); }, ), const SizedBox(height: 16), // Compteur Text( '${_selectedIds.length} équipement(s) sélectionné(s)', style: const TextStyle(fontWeight: FontWeight.bold), ), const SizedBox(height: 8), // Liste des équipements Expanded( child: filteredEquipment.isEmpty ? const Center(child: Text('Aucun équipement trouvé')) : ListView.builder( shrinkWrap: true, itemCount: filteredEquipment.length, itemBuilder: (context, index) { final equipment = filteredEquipment[index]; final isSelected = _selectedIds.contains(equipment.id); return CheckboxListTile( value: isSelected, onChanged: (selected) { setState(() { if (selected == true) { _selectedIds.add(equipment.id); } else { _selectedIds.remove(equipment.id); } }); }, title: Text(equipment.name), subtitle: Text( '${equipment.id} • ${_getCategoryLabel(equipment.category)}', style: TextStyle(fontSize: 12, color: Colors.grey[600]), ), secondary: Icon( _getCategoryIcon(equipment.category), color: AppColors.bleuFonce, ), ); }, ), ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Annuler'), ), ElevatedButton( onPressed: () => Navigator.pop(context, _selectedIds), style: ElevatedButton.styleFrom(backgroundColor: AppColors.bleuFonce), child: const Text('Valider'), ), ], ); } String _getCategoryLabel(EquipmentCategory category) { switch (category) { case EquipmentCategory.sound: return 'Son'; case EquipmentCategory.lighting: return 'Lumière'; case EquipmentCategory.video: return 'Vidéo'; case EquipmentCategory.structure: return 'Structure'; case EquipmentCategory.effect: return 'Effets'; case EquipmentCategory.cable: return 'Câblage'; case EquipmentCategory.consumable: return 'Consommable'; case EquipmentCategory.vehicle: return 'Véhicule'; case EquipmentCategory.backline: return 'Backline'; case EquipmentCategory.other: return 'Autre'; } } IconData _getCategoryIcon(EquipmentCategory category) { switch (category) { case EquipmentCategory.sound: return Icons.volume_up; case EquipmentCategory.lighting: return Icons.lightbulb; case EquipmentCategory.video: return Icons.videocam; case EquipmentCategory.structure: return Icons.construction; case EquipmentCategory.effect: return Icons.auto_awesome; case EquipmentCategory.cable: return Icons.cable; case EquipmentCategory.consumable: return Icons.inventory_2; case EquipmentCategory.vehicle: return Icons.local_shipping; case EquipmentCategory.backline: return Icons.queue_music; case EquipmentCategory.other: return Icons.category; } } }