import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:em2rp/models/event_model.dart'; import 'package:em2rp/models/equipment_model.dart'; import 'package:em2rp/models/container_model.dart'; import 'package:em2rp/providers/equipment_provider.dart'; import 'package:em2rp/providers/container_provider.dart'; import 'package:em2rp/providers/event_provider.dart'; import 'package:em2rp/providers/local_user_provider.dart'; import 'package:em2rp/services/data_service.dart'; import 'package:em2rp/services/api_service.dart'; import 'package:em2rp/views/widgets/equipment/equipment_checklist_item.dart' show EquipmentChecklistItem, ChecklistStep; import 'package:em2rp/views/widgets/equipment/container_checklist_item.dart'; import 'package:em2rp/utils/debug_log.dart'; import 'package:em2rp/views/widgets/equipment/missing_equipment_dialog.dart'; import 'package:em2rp/utils/colors.dart'; /// Type d'étape de préparation enum PreparationStep { preparation, // Préparation dépôt loadingOutbound, // Chargement aller unloadingReturn, // Chargement retour (déchargement) return_, // Retour dépôt } /// Page de préparation ou de retour d'un événement class EventPreparationPage extends StatefulWidget { final EventModel initialEvent; const EventPreparationPage({ super.key, required this.initialEvent, }); @override State createState() => _EventPreparationPageState(); } class _EventPreparationPageState extends State with SingleTickerProviderStateMixin { late AnimationController _animationController; late final DataService _dataService; Map _equipmentCache = {}; Map _containerCache = {}; Map _returnedQuantities = {}; // État local des validations (non sauvegardé jusqu'à la validation finale) Map _localValidationState = {}; // NOUVEAU : Gestion des quantités par étape Map _quantitiesAtPreparation = {}; Map _quantitiesAtLoading = {}; Map _quantitiesAtUnloading = {}; Map _quantitiesAtReturn = {}; bool _isLoading = true; bool _isValidating = false; bool _showSuccessAnimation = false; bool _loadSimultaneously = false; // Checkbox "charger en même temps" // Stockage de l'événement actuel late EventModel _currentEvent; // Détermine l'étape actuelle selon le statut de l'événement PreparationStep get _currentStep { final prep = _currentEvent.preparationStatus ?? PreparationStatus.notStarted; final loading = _currentEvent.loadingStatus ?? LoadingStatus.notStarted; final unloading = _currentEvent.unloadingStatus ?? UnloadingStatus.notStarted; final returnStatus = _currentEvent.returnStatus ?? ReturnStatus.notStarted; // Logique stricte : on avance étape par étape // 1. Préparation dépôt if (prep != PreparationStatus.completed) { return PreparationStep.preparation; } // 2. Chargement aller (après préparation complète) if (loading != LoadingStatus.completed) { return PreparationStep.loadingOutbound; } // 3. Chargement retour (après chargement aller complet) if (unloading != UnloadingStatus.completed) { return PreparationStep.unloadingReturn; } // 4. Retour dépôt (après déchargement complet) if (returnStatus != ReturnStatus.completed) { return PreparationStep.return_; } // Tout est terminé, par défaut on retourne à la préparation return PreparationStep.preparation; } @override void initState() { super.initState(); _currentEvent = widget.initialEvent; _dataService = DataService(FirebaseFunctionsApiService()); _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 500), ); // Vérification de sécurité et chargement après le premier frame WidgetsBinding.instance.addPostFrameCallback((_) { if (_isCurrentStepCompleted()) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Cette étape est déjà terminée'), backgroundColor: Colors.orange, ), ); Navigator.of(context).pop(); return; } // Charger les équipements après le premier frame pour éviter setState pendant build _loadEquipmentAndContainers(); }); } /// Vérifie si l'étape actuelle est déjà complétée bool _isCurrentStepCompleted() { switch (_currentStep) { case PreparationStep.preparation: return (_currentEvent.preparationStatus ?? PreparationStatus.notStarted) == PreparationStatus.completed; case PreparationStep.loadingOutbound: return (_currentEvent.loadingStatus ?? LoadingStatus.notStarted) == LoadingStatus.completed; case PreparationStep.unloadingReturn: return (_currentEvent.unloadingStatus ?? UnloadingStatus.notStarted) == UnloadingStatus.completed; case PreparationStep.return_: return (_currentEvent.returnStatus ?? ReturnStatus.notStarted) == ReturnStatus.completed; } } @override void dispose() { _animationController.dispose(); super.dispose(); } Future _loadEquipmentAndContainers() async { setState(() => _isLoading = true); try { final equipmentProvider = context.read(); final containerProvider = context.read(); // S'assurer que les équipements sont chargés await equipmentProvider.ensureLoaded(); await containerProvider.ensureLoaded(); final equipment = await equipmentProvider.equipmentStream.first; final containers = await containerProvider.containersStream.first; for (var eq in _currentEvent.assignedEquipment) { final equipmentItem = equipment.firstWhere( (e) => e.id == eq.equipmentId, orElse: () => EquipmentModel( id: eq.equipmentId, name: 'Équipement inconnu', category: EquipmentCategory.other, status: EquipmentStatus.available, parentBoxIds: [], maintenanceIds: [], createdAt: DateTime.now(), updatedAt: DateTime.now(), ), ); _equipmentCache[eq.equipmentId] = equipmentItem; // Initialiser l'état local de validation depuis l'événement switch (_currentStep) { case PreparationStep.preparation: _localValidationState[eq.equipmentId] = eq.isPrepared; break; case PreparationStep.loadingOutbound: _localValidationState[eq.equipmentId] = eq.isLoaded; break; case PreparationStep.unloadingReturn: _localValidationState[eq.equipmentId] = eq.isUnloaded; break; case PreparationStep.return_: _localValidationState[eq.equipmentId] = eq.isReturned; break; } if ((_currentStep == PreparationStep.return_ || _currentStep == PreparationStep.unloadingReturn) && equipmentItem.hasQuantity) { _returnedQuantities[eq.equipmentId] = eq.quantityAtReturn ?? eq.quantity; } } for (var containerId in _currentEvent.assignedContainers) { final container = containers.firstWhere( (c) => c.id == containerId, orElse: () => ContainerModel( id: containerId, name: 'Conteneur inconnu', type: ContainerType.flightCase, status: EquipmentStatus.available, equipmentIds: [], updatedAt: DateTime.now(), createdAt: DateTime.now(), ), ); _containerCache[containerId] = container; } } catch (e) { DebugLog.error('[EventPreparationPage] Error', e); } finally { setState(() => _isLoading = false); } } /// Basculer l'état de validation d'un équipement (état local uniquement) Future _toggleEquipmentValidation(String equipmentId) async { final currentState = _localValidationState[equipmentId] ?? false; // Si on veut valider (passer de false à true) et que c'était manquant avant if (!currentState && _wasMissingAtPreviousStep(equipmentId)) { final confirmed = await _confirmValidationIfWasMissingBefore(equipmentId); if (!confirmed) { return; // Annulation, ne rien faire } } setState(() { _localValidationState[equipmentId] = !currentState; }); } /// Marquer TOUT comme validé et enregistrer (bouton "Tout confirmer") Future _validateAllAndConfirm() async { // Marquer tout comme validé localement setState(() { for (var eq in _currentEvent.assignedEquipment) { _localValidationState[eq.equipmentId] = true; } }); // Puis enregistrer await _confirmCurrentState(); } /// Enregistrer l'état actuel TEL QUEL (cochés = validés, non cochés = manquants) Future _confirmCurrentState() async { setState(() => _isValidating = true); try { // Déterminer les manquants = équipements NON validés final Map missingAtThisStep = {}; for (var eq in _currentEvent.assignedEquipment) { final isValidated = _localValidationState[eq.equipmentId] ?? false; missingAtThisStep[eq.equipmentId] = !isValidated; // Manquant si pas validé } // Préparer la liste des équipements avec leur nouvel état final updatedEquipment = _currentEvent.assignedEquipment.map((eq) { final isValidated = _localValidationState[eq.equipmentId] ?? false; final isMissing = missingAtThisStep[eq.equipmentId] ?? false; // Récupérer les quantités selon l'étape final qtyAtPrep = _quantitiesAtPreparation[eq.equipmentId]; final qtyAtLoad = _quantitiesAtLoading[eq.equipmentId]; final qtyAtUnload = _quantitiesAtUnloading[eq.equipmentId]; final qtyAtRet = _quantitiesAtReturn[eq.equipmentId]; switch (_currentStep) { case PreparationStep.preparation: if (_loadSimultaneously) { return eq.copyWith( isPrepared: isValidated, isLoaded: isValidated, isMissingAtPreparation: isMissing, isMissingAtLoading: isMissing, // Propager quantityAtPreparation: qtyAtPrep, quantityAtLoading: qtyAtPrep, // Même quantité ); } return eq.copyWith( isPrepared: isValidated, isMissingAtPreparation: isMissing, quantityAtPreparation: qtyAtPrep, ); case PreparationStep.loadingOutbound: return eq.copyWith( isLoaded: isValidated, isMissingAtLoading: isMissing, quantityAtLoading: qtyAtLoad, ); case PreparationStep.unloadingReturn: if (_loadSimultaneously) { return eq.copyWith( isUnloaded: isValidated, isReturned: isValidated, isMissingAtUnloading: isMissing, isMissingAtReturn: isMissing, // Propager quantityAtUnloading: qtyAtUnload, quantityAtReturn: qtyAtRet ?? qtyAtUnload, ); } return eq.copyWith( isUnloaded: isValidated, isMissingAtUnloading: isMissing, quantityAtUnloading: qtyAtUnload, ); case PreparationStep.return_: return eq.copyWith( isReturned: isValidated, isMissingAtReturn: isMissing, quantityAtReturn: qtyAtRet, ); } }).toList(); // Si on est à la dernière étape (retour), vérifier les équipements LOST if (_currentStep == PreparationStep.return_) { await _checkAndMarkLostEquipment(updatedEquipment); } // Mettre à jour Firestore selon l'étape final updateData = { 'assignedEquipment': updatedEquipment.map((e) => e.toMap()).toList(), }; // Ajouter les statuts selon l'étape et la checkbox switch (_currentStep) { case PreparationStep.preparation: updateData['preparationStatus'] = preparationStatusToString(PreparationStatus.completed); if (_loadSimultaneously) { updateData['loadingStatus'] = loadingStatusToString(LoadingStatus.completed); } break; case PreparationStep.loadingOutbound: updateData['loadingStatus'] = loadingStatusToString(LoadingStatus.completed); break; case PreparationStep.unloadingReturn: updateData['unloadingStatus'] = unloadingStatusToString(UnloadingStatus.completed); if (_loadSimultaneously) { updateData['returnStatus'] = returnStatusToString(ReturnStatus.completed); } break; case PreparationStep.return_: updateData['returnStatus'] = returnStatusToString(ReturnStatus.completed); break; } // Sauvegarder dans Firestore via l'API await _dataService.updateEventEquipment( eventId: _currentEvent.id, assignedEquipment: updatedEquipment.map((e) => e.toMap()).toList(), preparationStatus: updateData['preparationStatus'], loadingStatus: updateData['loadingStatus'], unloadingStatus: updateData['unloadingStatus'], returnStatus: updateData['returnStatus'], ); // Mettre à jour les statuts des équipements si nécessaire if (_currentStep == PreparationStep.preparation || (_currentStep == PreparationStep.unloadingReturn && _loadSimultaneously)) { await _updateEquipmentStatuses(updatedEquipment); } // Recharger l'événement depuis le provider final eventProvider = context.read(); // Recharger la liste des événements pour rafraîchir les données final userId = context.read().uid; if (userId != null) { await eventProvider.loadUserEvents(userId, canViewAllEvents: true); } setState(() => _showSuccessAnimation = true); _animationController.forward(); await Future.delayed(const Duration(milliseconds: 500)); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(_getSuccessMessage()), backgroundColor: Colors.green, ), ); await Future.delayed(const Duration(seconds: 2)); Navigator.of(context).pop(true); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Erreur : $e'), backgroundColor: Colors.red, ), ); } } finally { if (mounted) setState(() => _isValidating = false); } } Future _updateEquipmentStatuses(List equipment) async { for (var eq in equipment) { try { final equipmentData = _equipmentCache[eq.equipmentId]; if (equipmentData == null) continue; // Déterminer le nouveau statut EquipmentStatus newStatus; if (eq.isReturned) { newStatus = EquipmentStatus.available; } else if (eq.isPrepared || eq.isLoaded) { newStatus = EquipmentStatus.inUse; } else { continue; // Pas de changement } // Ne mettre à jour que les équipements non quantifiables if (!equipmentData.hasQuantity) { await _dataService.updateEquipmentStatusOnly( equipmentId: eq.equipmentId, status: equipmentStatusToString(newStatus), ); } // Gérer les stocks pour les consommables if (equipmentData.hasQuantity && eq.isReturned && eq.quantityAtReturn != null) { final currentAvailable = equipmentData.availableQuantity ?? 0; await _dataService.updateEquipmentStatusOnly( equipmentId: eq.equipmentId, availableQuantity: currentAvailable + eq.quantityAtReturn!, ); } } catch (e) { // Erreur silencieuse pour ne pas bloquer le processus } } } String _getSuccessMessage() { switch (_currentStep) { case PreparationStep.preparation: return _loadSimultaneously ? 'Préparation dépôt et chargement aller validés !' : 'Préparation dépôt validée !'; case PreparationStep.loadingOutbound: return 'Chargement aller validé !'; case PreparationStep.unloadingReturn: return _loadSimultaneously ? 'Chargement retour et retour dépôt validés !' : 'Chargement retour validé !'; case PreparationStep.return_: return 'Retour dépôt validé !'; } } String _getStepTitle() { switch (_currentStep) { case PreparationStep.preparation: return 'Préparation dépôt'; case PreparationStep.loadingOutbound: return 'Chargement aller'; case PreparationStep.unloadingReturn: return 'Chargement retour'; case PreparationStep.return_: return 'Retour dépôt'; } } String _getValidateAllButtonText() { if (_loadSimultaneously) { return _currentStep == PreparationStep.preparation ? 'Tout confirmer comme préparé et chargé' : 'Tout confirmer comme déchargé et retourné'; } switch (_currentStep) { case PreparationStep.preparation: return 'Tout confirmer comme préparé'; case PreparationStep.loadingOutbound: return 'Tout confirmer comme chargé'; case PreparationStep.unloadingReturn: return 'Tout confirmer comme déchargé'; case PreparationStep.return_: return 'Tout confirmer comme retourné'; } } bool _isStepCompleted() { return _currentEvent.assignedEquipment.every((eq) { return _localValidationState[eq.equipmentId] ?? false; }); } double _getProgress() { if (_currentEvent.assignedEquipment.isEmpty) return 0; return _getValidatedCount() / _currentEvent.assignedEquipment.length; } int _getValidatedCount() { return _currentEvent.assignedEquipment.where((eq) { return _localValidationState[eq.equipmentId] ?? false; }).length; } ChecklistStep _getChecklistStep() { switch (_currentStep) { case PreparationStep.preparation: return ChecklistStep.preparation; case PreparationStep.loadingOutbound: return ChecklistStep.loading; case PreparationStep.unloadingReturn: return ChecklistStep.unloading; case PreparationStep.return_: return ChecklistStep.return_; } } Future _confirm() async { // Vérifier s'il y a des équipements manquants (non cochés localement) final missingEquipmentIds = _currentEvent.assignedEquipment .where((eq) => !(_localValidationState[eq.equipmentId] ?? false)) .map((eq) => eq.equipmentId) .toList(); if (missingEquipmentIds.isEmpty) { // Tout est validé, confirmer directement await _confirmCurrentState(); } else { // Afficher le dialog des manquants final missingEquipmentModels = missingEquipmentIds .map((id) => _equipmentCache[id]) .whereType() .toList(); final missingEventEquipment = _currentEvent.assignedEquipment .where((eq) => missingEquipmentIds.contains(eq.equipmentId)) .toList(); final action = await showDialog( context: context, builder: (context) => MissingEquipmentDialog( missingEquipment: missingEquipmentModels, eventEquipment: missingEventEquipment, isReturnMode: _currentStep == PreparationStep.return_ || _currentStep == PreparationStep.unloadingReturn, ), ); if (action == 'confirm_anyway') { // Confirmer malgré les manquants (enregistrer l'état actuel TEL QUEL) await _confirmCurrentState(); } else if (action == 'mark_as_validated') { // Marquer les manquants comme validés localement for (var equipmentId in missingEquipmentIds) { _localValidationState[equipmentId] = true; } setState(() {}); // Puis confirmer await _confirmCurrentState(); } // Si 'return_to_list', ne rien faire } } /// Valider tous les enfants d'un container void _validateAllContainerChildren(String containerId) { final container = _containerCache[containerId]; if (container == null) return; setState(() { for (final equipmentId in container.equipmentIds) { if (_equipmentCache.containsKey(equipmentId)) { _localValidationState[equipmentId] = true; } } }); } /// Mettre à jour la quantité d'un équipement à l'étape actuelle void _updateEquipmentQuantity(String equipmentId, int newQuantity) { setState(() { switch (_currentStep) { case PreparationStep.preparation: _quantitiesAtPreparation[equipmentId] = newQuantity; break; case PreparationStep.loadingOutbound: _quantitiesAtLoading[equipmentId] = newQuantity; break; case PreparationStep.unloadingReturn: _quantitiesAtUnloading[equipmentId] = newQuantity; break; case PreparationStep.return_: _quantitiesAtReturn[equipmentId] = newQuantity; break; } }); } /// Vérifier si un équipement était manquant à l'étape précédente bool _wasMissingAtPreviousStep(String equipmentId) { final eq = _currentEvent.assignedEquipment.firstWhere( (e) => e.equipmentId == equipmentId, orElse: () => EventEquipment(equipmentId: equipmentId), ); switch (_currentStep) { case PreparationStep.preparation: return false; // Pas d'étape avant case PreparationStep.loadingOutbound: return eq.isMissingAtPreparation; case PreparationStep.unloadingReturn: return eq.isMissingAtLoading; case PreparationStep.return_: return eq.isMissingAtUnloading; } } /// Afficher pop-up de confirmation si l'équipement était manquant avant Future _confirmValidationIfWasMissingBefore(String equipmentId) async { if (!_wasMissingAtPreviousStep(equipmentId)) { return true; // Pas de problème, continuer } final equipment = _equipmentCache[equipmentId]; final result = await showDialog( context: context, builder: (context) => AlertDialog( title: const Row( children: [ Icon(Icons.warning_amber, color: Colors.orange), SizedBox(width: 8), Text('Confirmation'), ], ), content: Text( 'L\'équipement "${equipment?.name ?? equipmentId}" était manquant à l\'étape précédente.\n\n' 'Êtes-vous sûr de le marquer comme présent maintenant ?', ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text('Annuler'), ), ElevatedButton( onPressed: () => Navigator.of(context).pop(true), style: ElevatedButton.styleFrom( backgroundColor: Colors.orange, ), child: const Text('Confirmer'), ), ], ), ); return result ?? false; } /// Vérifier et marquer les équipements LOST (logique intelligente) Future _checkAndMarkLostEquipment(List updatedEquipment) async { for (final eq in updatedEquipment) { final isMissingNow = eq.isMissingAtReturn; if (isMissingNow) { // Vérifier si c'était manquant dès la préparation (étape 0) final wasMissingAtPreparation = eq.isMissingAtPreparation; if (!wasMissingAtPreparation) { // Était présent au départ mais manquant maintenant = LOST try { await _dataService.updateEquipmentStatusOnly( equipmentId: eq.equipmentId, status: EquipmentStatus.lost.toString(), ); DebugLog.info('[EventPreparationPage] Équipement ${eq.equipmentId} marqué comme LOST'); // TODO: Créer une alerte "Équipement perdu" // await _createLostEquipmentAlert(eq.equipmentId); } catch (e) { DebugLog.error('[EventPreparationPage] Erreur marquage LOST ${eq.equipmentId}', e); } } else { // Manquant dès le début = PAS lost, juste manquant DebugLog.info('[EventPreparationPage] Équipement ${eq.equipmentId} manquant depuis le début (pas LOST)'); } } } } @override Widget build(BuildContext context) { final allValidated = _isStepCompleted(); final stepTitle = _getStepTitle(); return Scaffold( appBar: AppBar( title: Text(stepTitle), backgroundColor: AppColors.bleuFonce, ), body: Stack( children: [ _isLoading ? const Center(child: CircularProgressIndicator()) : Column( children: [ Container( padding: const EdgeInsets.all(16), color: Colors.grey.shade100, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( _currentEvent.name, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), Row( children: [ Expanded( child: LinearProgressIndicator( value: _getProgress(), backgroundColor: Colors.grey.shade300, valueColor: AlwaysStoppedAnimation( allValidated ? Colors.green : AppColors.bleuFonce, ), ), ), const SizedBox(width: 12), Text( '${_getValidatedCount()}/${_currentEvent.assignedEquipment.length}', style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), ), ], ), const SizedBox(height: 12), // Checkbox "charger en même temps" (uniquement pour préparation ou chargement retour) if (_currentStep == PreparationStep.preparation || _currentStep == PreparationStep.unloadingReturn) CheckboxListTile( value: _loadSimultaneously, onChanged: (value) { setState(() { _loadSimultaneously = value ?? false; }); }, title: Text( _currentStep == PreparationStep.preparation ? 'Charger en même temps (chargement aller)' : 'Confirmer le retour en même temps (retour dépôt)', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, ), ), dense: true, contentPadding: EdgeInsets.zero, ), const SizedBox(height: 8), ElevatedButton.icon( onPressed: allValidated ? null : _validateAllAndConfirm, icon: const Icon(Icons.check_circle_outline), label: Text( allValidated ? 'Tout est validé !' : _getValidateAllButtonText(), ), style: ElevatedButton.styleFrom( backgroundColor: allValidated ? Colors.green : AppColors.bleuFonce, padding: const EdgeInsets.symmetric(vertical: 12), ), ), ], ), ), Expanded( child: ListView( padding: const EdgeInsets.all(16), children: _buildChecklistItems(), ), ), ], ), if (_showSuccessAnimation) Center( child: ScaleTransition( scale: _animationController, child: Container( padding: const EdgeInsets.all(32), decoration: const BoxDecoration( color: Colors.green, shape: BoxShape.circle, ), child: const Icon( Icons.check, size: 64, color: Colors.white, ), ), ), ), ], ), bottomNavigationBar: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.1), blurRadius: 4, offset: const Offset(0, -2), ), ], ), child: ElevatedButton( onPressed: _isValidating ? null : _confirm, style: ElevatedButton.styleFrom( backgroundColor: allValidated ? Colors.green : AppColors.rouge, padding: const EdgeInsets.symmetric(vertical: 16), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: _isValidating ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Colors.white), ), ) : Text( 'Confirmer ${_getStepTitle().toLowerCase()}', style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), ), ), ); } /// Construit la liste des items de checklist en groupant par containers List _buildChecklistItems() { final List items = []; // Set pour tracker les équipements déjà affichés dans un container final Set equipmentIdsInContainers = {}; // Map des EventEquipment par ID pour accès rapide final Map eventEquipmentsMap = { for (var eq in _currentEvent.assignedEquipment) eq.equipmentId: eq, }; // 1. Afficher les containers avec leurs enfants for (final containerId in _currentEvent.assignedContainers) { final container = _containerCache[containerId]; if (container == null) continue; // Récupérer les équipements enfants de ce container final List childEquipments = []; for (final equipmentId in container.equipmentIds) { final equipment = _equipmentCache[equipmentId]; if (equipment != null && eventEquipmentsMap.containsKey(equipmentId)) { childEquipments.add(equipment); equipmentIdsInContainers.add(equipmentId); } } if (childEquipments.isEmpty) continue; // Vérifier si tous les enfants sont validés final allChildrenValidated = childEquipments.every( (eq) => _localValidationState[eq.id] ?? false, ); // Map des états de validation des enfants final Map childValidationStates = { for (var eq in childEquipments) eq.id: _localValidationState[eq.id] ?? false, }; // Map des enfants manquants à l'étape précédente final Map wasMissingBeforeMap = { for (var eq in childEquipments) eq.id: _wasMissingAtPreviousStep(eq.id), }; items.add( ContainerChecklistItem( container: container, childEquipments: childEquipments, eventEquipmentsMap: eventEquipmentsMap, step: _getChecklistStep(), isValidated: allChildrenValidated, childValidationStates: childValidationStates, onToggleContainer: () => _validateAllContainerChildren(containerId), onToggleChild: (equipmentId) => _toggleEquipmentValidation(equipmentId), onQuantityChanged: (equipmentId, qty) => _updateEquipmentQuantity(equipmentId, qty), wasMissingBeforeMap: wasMissingBeforeMap, ), ); } // 2. Afficher les équipements standalone (pas dans un container) for (final eventEquipment in _currentEvent.assignedEquipment) { // Skip si déjà affiché dans un container if (equipmentIdsInContainers.contains(eventEquipment.equipmentId)) { continue; } final equipment = _equipmentCache[eventEquipment.equipmentId]; if (equipment == null) continue; items.add( EquipmentChecklistItem( equipment: equipment, eventEquipment: eventEquipment, step: _getChecklistStep(), isValidated: _localValidationState[equipment.id] ?? false, onToggle: () => _toggleEquipmentValidation(equipment.id), onQuantityChanged: (qty) => _updateEquipmentQuantity(equipment.id, qty), isChild: false, // Équipement standalone (pas indenté) wasMissingBefore: _wasMissingAtPreviousStep(equipment.id), ), ); } return items; } }