From f8f6cfb1025dbad567d070c025360e4fabb26d05 Mon Sep 17 00:00:00 2001 From: ElPoyo Date: Tue, 26 May 2026 14:48:39 +0200 Subject: [PATCH] bugfix: resolve runtime issues - encoding accents, ListView dynamic heights, notifyListeners exceptions during build phase, and layout overflows --- em2rp/lib/providers/equipment_provider.dart | 6 +- em2rp/lib/services/app_initializer.dart | 2 +- em2rp/lib/views/calendar_page.dart | 450 +++++++++--------- em2rp/lib/views/equipment_form_page.dart | 14 +- .../lib/views/equipment_management_page.dart | 7 +- .../event/equipment_selection_dialog.dart | 317 ++++++------ 6 files changed, 399 insertions(+), 397 deletions(-) diff --git a/em2rp/lib/providers/equipment_provider.dart b/em2rp/lib/providers/equipment_provider.dart index cf79f42..ae978aa 100644 --- a/em2rp/lib/providers/equipment_provider.dart +++ b/em2rp/lib/providers/equipment_provider.dart @@ -80,7 +80,7 @@ class EquipmentProvider extends ChangeNotifier { Future loadEquipments() async { print('[EquipmentProvider] Starting to load ALL equipments...'); _isLoading = true; - notifyListeners(); + scheduleMicrotask(notifyListeners); try { _equipment.clear(); @@ -272,7 +272,7 @@ class EquipmentProvider extends ChangeNotifier { _lastVisible = null; _hasMore = true; _isLoading = true; - notifyListeners(); + scheduleMicrotask(notifyListeners); try { await loadNextPage(); @@ -296,7 +296,7 @@ class EquipmentProvider extends ChangeNotifier { _isLoadingMore = true; _isLoading = true; - notifyListeners(); + scheduleMicrotask(notifyListeners); try { final result = await _dataService.getEquipmentsPaginated( diff --git a/em2rp/lib/services/app_initializer.dart b/em2rp/lib/services/app_initializer.dart index f64f685..96c902f 100644 --- a/em2rp/lib/services/app_initializer.dart +++ b/em2rp/lib/services/app_initializer.dart @@ -28,7 +28,7 @@ class AppInitializer with ChangeNotifier { Future initialize() async { if (_isInitialized || _isInitializing) return; _isInitializing = true; - notifyListeners(); + scheduleMicrotask(() => notifyListeners()); try { // Initialiser Firebase diff --git a/em2rp/lib/views/calendar_page.dart b/em2rp/lib/views/calendar_page.dart index c5ca83b..7c23b75 100644 --- a/em2rp/lib/views/calendar_page.dart +++ b/em2rp/lib/views/calendar_page.dart @@ -980,237 +980,243 @@ class _CalendarPageState extends State { ? eventsForSelectedDay[_selectedEventIndex] : null; - // GESTURE DETECTOR pour swipe vertical (plier/déplier) et horizontal (mois) - return GestureDetector( - onVerticalDragEnd: (details) { - if (details.primaryVelocity != null) { - if (details.primaryVelocity! < -200) { - // Swipe vers le haut : plier - setState(() { - _calendarCollapsed = true; - }); - } else if (details.primaryVelocity! > 200) { - // Swipe vers le bas : déplier - setState(() { - _calendarCollapsed = false; - }); - } - } - }, - onHorizontalDragEnd: (details) { - if (details.primaryVelocity != null) { - if (details.primaryVelocity! < -200) { - // Swipe gauche : mois suivant - final newMonth = - DateTime(_focusedDay.year, _focusedDay.month + 1, 1); - setState(() { - _focusedDay = newMonth; - }); - print( - '[CalendarPage] Month changed to ${newMonth.year}-${newMonth.month}'); - _loadCurrentMonthEvents(); - } else if (details.primaryVelocity! > 200) { - // Swipe droite : mois précédent - final newMonth = - DateTime(_focusedDay.year, _focusedDay.month - 1, 1); - setState(() { - _focusedDay = newMonth; - }); - print( - '[CalendarPage] Month changed to ${newMonth.year}-${newMonth.month}'); - _loadCurrentMonthEvents(); - } - } - }, - child: Stack( - children: [ - // Calendrier + détails en dessous - AnimatedPositioned( - duration: const Duration(milliseconds: 400), - curve: Curves.easeInOut, - top: _calendarCollapsed ? -600 : 0, // cache le calendrier en haut - left: 0, - right: 0, - height: _calendarCollapsed ? 0 : null, - child: SizedBox( - height: MediaQuery.of(context).size.height, - child: Column( - children: [ - _buildMonthHeader(context), - if (!_calendarCollapsed) - // Ajout d'un GestureDetector pour swipe horizontal sur le calendrier - GestureDetector( - onHorizontalDragEnd: (details) { - if (details.primaryVelocity != null) { - if (details.primaryVelocity! < -200) { - // Swipe gauche : mois suivant - final newMonth = DateTime( - _focusedDay.year, _focusedDay.month + 1, 1); - setState(() { - _focusedDay = newMonth; - }); - print( - '[CalendarPage] Month changed to ${newMonth.year}-${newMonth.month}'); - _loadCurrentMonthEvents(); - } else if (details.primaryVelocity! > 200) { - // Swipe droite : mois précédent - final newMonth = DateTime( - _focusedDay.year, _focusedDay.month - 1, 1); - setState(() { - _focusedDay = newMonth; - }); - print( - '[CalendarPage] Month changed to ${newMonth.year}-${newMonth.month}'); - _loadCurrentMonthEvents(); - } - } - }, - child: MobileCalendarView( - focusedDay: _focusedDay, - selectedDay: _selectedDay, - events: filteredEvents, - onDaySelected: (day) { - final eventsForDay = filteredEvents - .where((e) => - e.startDateTime.year == day.year && - e.startDateTime.month == day.month && - e.startDateTime.day == day.day) - .toList() - ..sort((a, b) => - a.startDateTime.compareTo(b.startDateTime)); - setState(() { - _selectedDay = day; - _calendarCollapsed = false; - _selectedEventIndex = 0; - _selectedEvent = eventsForDay.isNotEmpty - ? eventsForDay[0] - : null; - }); - }, - ), - ), - Expanded( - child: hasEvents - // Ajout d'un GestureDetector pour swipe horizontal sur le détail événement - ? GestureDetector( - onHorizontalDragEnd: (details) { - if (details.primaryVelocity != null) { - if (details.primaryVelocity! < -200) { - // Swipe gauche : événement suivant - if (_selectedEventIndex < - eventsForSelectedDay.length - 1) { - setState(() { - _selectedEventIndex++; - _selectedEvent = eventsForSelectedDay[ - _selectedEventIndex]; - }); - } - } else if (details.primaryVelocity! > 200) { - // Swipe droite : événement précédent - if (_selectedEventIndex > 0) { - setState(() { - _selectedEventIndex--; - _selectedEvent = eventsForSelectedDay[ - _selectedEventIndex]; - }); - } - } - } - }, - child: EventDetails( - event: eventsForSelectedDay[_selectedEventIndex], - selectedDate: _selectedDay, - events: eventsForSelectedDay, - onSelectEvent: (event, date) { - final idx = eventsForSelectedDay - .indexWhere((e) => e.id == event.id); + return LayoutBuilder( + builder: (context, constraints) { + final maxHeight = constraints.maxHeight; + + // GESTURE DETECTOR pour swipe vertical (plier/déplier) et horizontal (mois) + return GestureDetector( + onVerticalDragEnd: (details) { + if (details.primaryVelocity != null) { + if (details.primaryVelocity! < -200) { + // Swipe vers le haut : plier + setState(() { + _calendarCollapsed = true; + }); + } else if (details.primaryVelocity! > 200) { + // Swipe vers le bas : déplier + setState(() { + _calendarCollapsed = false; + }); + } + } + }, + onHorizontalDragEnd: (details) { + if (details.primaryVelocity != null) { + if (details.primaryVelocity! < -200) { + // Swipe gauche : mois suivant + final newMonth = + DateTime(_focusedDay.year, _focusedDay.month + 1, 1); + setState(() { + _focusedDay = newMonth; + }); + print( + '[CalendarPage] Month changed to ${newMonth.year}-${newMonth.month}'); + _loadCurrentMonthEvents(); + } else if (details.primaryVelocity! > 200) { + // Swipe droite : mois précédent + final newMonth = + DateTime(_focusedDay.year, _focusedDay.month - 1, 1); + setState(() { + _focusedDay = newMonth; + }); + print( + '[CalendarPage] Month changed to ${newMonth.year}-${newMonth.month}'); + _loadCurrentMonthEvents(); + } + } + }, + child: Stack( + children: [ + // Calendrier + détails en dessous + AnimatedPositioned( + duration: const Duration(milliseconds: 400), + curve: Curves.easeInOut, + top: _calendarCollapsed ? -maxHeight : 0, // cache le calendrier en haut + left: 0, + right: 0, + height: _calendarCollapsed ? 0 : null, + child: SizedBox( + height: maxHeight, + child: Column( + children: [ + _buildMonthHeader(context), + if (!_calendarCollapsed) + // Ajout d'un GestureDetector pour swipe horizontal sur le calendrier + GestureDetector( + onHorizontalDragEnd: (details) { + if (details.primaryVelocity != null) { + if (details.primaryVelocity! < -200) { + // Swipe gauche : mois suivant + final newMonth = DateTime( + _focusedDay.year, _focusedDay.month + 1, 1); setState(() { - _selectedEventIndex = idx >= 0 ? idx : 0; - _selectedEvent = event; + _focusedDay = newMonth; }); - }, - ), - ) - : Center( - child: Text( - 'Aucun événement ne démarre à cette date')), - ), - ], - ), - ), - ), - // Vue détail (prend tout l'espace quand calendrier cache) - if (_calendarCollapsed && _selectedDay != null) - AnimatedPositioned( - duration: const Duration(milliseconds: 400), - curve: Curves.easeInOut, - top: _calendarCollapsed ? 0 : 600, - left: 0, - right: 0, - bottom: 0, - child: SizedBox( - height: MediaQuery.of(context).size.height, - child: Column( - children: [ - _buildMonthHeader(context), - Expanded( - child: Stack( - children: [ - if (currentEvent != null) + print( + '[CalendarPage] Month changed to ${newMonth.year}-${newMonth.month}'); + _loadCurrentMonthEvents(); + } else if (details.primaryVelocity! > 200) { + // Swipe droite : mois précédent + final newMonth = DateTime( + _focusedDay.year, _focusedDay.month - 1, 1); + setState(() { + _focusedDay = newMonth; + }); + print( + '[CalendarPage] Month changed to ${newMonth.year}-${newMonth.month}'); + _loadCurrentMonthEvents(); + } + } + }, + child: MobileCalendarView( + focusedDay: _focusedDay, + selectedDay: _selectedDay, + events: filteredEvents, + onDaySelected: (day) { + final eventsForDay = filteredEvents + .where((e) => + e.startDateTime.year == day.year && + e.startDateTime.month == day.month && + e.startDateTime.day == day.day) + .toList() + ..sort((a, b) => + a.startDateTime.compareTo(b.startDateTime)); + setState(() { + _selectedDay = day; + _calendarCollapsed = false; + _selectedEventIndex = 0; + _selectedEvent = eventsForDay.isNotEmpty + ? eventsForDay[0] + : null; + }); + }, + ), + ), + Expanded( + child: hasEvents // Ajout d'un GestureDetector pour swipe horizontal sur le détail événement - GestureDetector( - onHorizontalDragEnd: (details) { - if (details.primaryVelocity != null) { - if (details.primaryVelocity! < -200) { - // Swipe gauche : événement suivant - if (_selectedEventIndex < - eventsForSelectedDay.length - 1) { - setState(() { - _selectedEventIndex++; - _selectedEvent = eventsForSelectedDay[ - _selectedEventIndex]; - }); - } - } else if (details.primaryVelocity! > 200) { - // Swipe droite : événement précédent - if (_selectedEventIndex > 0) { - setState(() { - _selectedEventIndex--; - _selectedEvent = eventsForSelectedDay[ - _selectedEventIndex]; - }); + ? GestureDetector( + onHorizontalDragEnd: (details) { + if (details.primaryVelocity != null) { + if (details.primaryVelocity! < -200) { + // Swipe gauche : événement suivant + if (_selectedEventIndex < + eventsForSelectedDay.length - 1) { + setState(() { + _selectedEventIndex++; + _selectedEvent = eventsForSelectedDay[ + _selectedEventIndex]; + }); + } + } else if (details.primaryVelocity! > 200) { + // Swipe droite : événement précédent + if (_selectedEventIndex > 0) { + setState(() { + _selectedEventIndex--; + _selectedEvent = eventsForSelectedDay[ + _selectedEventIndex]; + }); + } } } - } - }, - child: EventDetails( - event: currentEvent, - selectedDate: _selectedDay, - events: eventsForSelectedDay, - onSelectEvent: (event, date) { - final idx = eventsForSelectedDay - .indexWhere((e) => e.id == event.id); - setState(() { - _selectedEventIndex = idx >= 0 ? idx : 0; - _selectedEvent = event; - }); }, - ), - ), - if (!hasEvents) - const Center( - child: Text( - 'Aucun événement ne démarre à cette date'), - ), - ], + child: EventDetails( + event: eventsForSelectedDay[_selectedEventIndex], + selectedDate: _selectedDay, + events: eventsForSelectedDay, + onSelectEvent: (event, date) { + final idx = eventsForSelectedDay + .indexWhere((e) => e.id == event.id); + setState(() { + _selectedEventIndex = idx >= 0 ? idx : 0; + _selectedEvent = event; + }); + }, + ), + ) + : Center( + child: Text( + 'Aucun événement ne démarre à cette date')), ), - ), - ], + ], + ), ), ), - ), - ], - ), + // Vue détail (prend tout l'espace quand calendrier cache) + if (_calendarCollapsed && _selectedDay != null) + AnimatedPositioned( + duration: const Duration(milliseconds: 400), + curve: Curves.easeInOut, + top: _calendarCollapsed ? 0 : maxHeight, + left: 0, + right: 0, + bottom: 0, + child: SizedBox( + height: maxHeight, + child: Column( + children: [ + _buildMonthHeader(context), + Expanded( + child: Stack( + children: [ + if (currentEvent != null) + // Ajout d'un GestureDetector pour swipe horizontal sur le détail événement + GestureDetector( + onHorizontalDragEnd: (details) { + if (details.primaryVelocity != null) { + if (details.primaryVelocity! < -200) { + // Swipe gauche : événement suivant + if (_selectedEventIndex < + eventsForSelectedDay.length - 1) { + setState(() { + _selectedEventIndex++; + _selectedEvent = eventsForSelectedDay[ + _selectedEventIndex]; + }); + } + } else if (details.primaryVelocity! > 200) { + // Swipe droite : événement précédent + if (_selectedEventIndex > 0) { + setState(() { + _selectedEventIndex--; + _selectedEvent = eventsForSelectedDay[ + _selectedEventIndex]; + }); + } + } + } + }, + child: EventDetails( + event: currentEvent, + selectedDate: _selectedDay, + events: eventsForSelectedDay, + onSelectEvent: (event, date) { + final idx = eventsForSelectedDay + .indexWhere((e) => e.id == event.id); + setState(() { + _selectedEventIndex = idx >= 0 ? idx : 0; + _selectedEvent = event; + }); + }, + ), + ), + if (!hasEvents) + const Center( + child: Text( + 'Aucun événement ne démarre à cette date'), + ), + ], + ), + ), + ], + ), + ), + ), + ], + ), + ); + }, ); } diff --git a/em2rp/lib/views/equipment_form_page.dart b/em2rp/lib/views/equipment_form_page.dart index 7c0b7bb..d4e3c7a 100644 --- a/em2rp/lib/views/equipment_form_page.dart +++ b/em2rp/lib/views/equipment_form_page.dart @@ -57,6 +57,12 @@ class _EquipmentFormPageState extends State { final provider = Provider.of(context, listen: false); provider.loadBrands(); provider.loadModels(); + if (widget.equipment != null) { + if (_selectedBrand != null && _selectedBrand!.isNotEmpty) { + _loadFilteredModels(_selectedBrand!); + } + _loadFilteredSubCategories(_selectedCategory); + } }); if (widget.equipment != null) { _populateFields(); @@ -84,14 +90,6 @@ class _EquipmentFormPageState extends State { }); DebugLog.info('[EquipmentForm] Populating fields for equipment: ${equipment.id}'); - - - if (_selectedBrand != null && _selectedBrand!.isNotEmpty) { - _loadFilteredModels(_selectedBrand!); - } - - // Charger les sous-catégories pour la catégorie sélectionnée - _loadFilteredSubCategories(_selectedCategory); } diff --git a/em2rp/lib/views/equipment_management_page.dart b/em2rp/lib/views/equipment_management_page.dart index cf156a9..b5be32c 100644 --- a/em2rp/lib/views/equipment_management_page.dart +++ b/em2rp/lib/views/equipment_management_page.dart @@ -500,12 +500,7 @@ class _EquipmentManagementPageState extends State return ListView.builder( controller: _scrollController, itemCount: itemCount, - // ✅ prototypeItem utilisé car les cartes ont des hauteurs variables : - // - Les équipements standards (ListTile + margin) font ~88px - // - Les consommables/câbles affichent _buildQuantityDisplay en plus (~30px) - // - prototypeItem permet à Flutter d'optimiser le scroll sans couper les items - prototypeItem: const SizedBox(height: 88), - // ✅ Augmenter le cache pour un scroll plus fluide + // ✅ Augmenter le cache pour un scroll plus fluide (prototypeItem retiré car les hauteurs dynamiques varient selon le type d'équipement) cacheExtent: 500, // Précharger 500px en plus itemBuilder: (context, index) { // Dernier élément = indicateur de chargement diff --git a/em2rp/lib/views/widgets/event/equipment_selection_dialog.dart b/em2rp/lib/views/widgets/event/equipment_selection_dialog.dart index de9ab6d..0cdb28a 100644 --- a/em2rp/lib/views/widgets/event/equipment_selection_dialog.dart +++ b/em2rp/lib/views/widgets/event/equipment_selection_dialog.dart @@ -9,7 +9,7 @@ import 'package:em2rp/services/event_availability_service.dart'; import 'package:em2rp/utils/colors.dart'; import 'package:em2rp/utils/debouncer.dart'; -/// Type de s├®lection dans le dialog +/// Type de sélection dans le dialog enum SelectionType { equipment, container } /// Statut de conflit pour un conteneur @@ -34,18 +34,18 @@ class ContainerConflictInfo { String get description { if (status == ContainerConflictStatus.none) return ''; if (status == ContainerConflictStatus.complete) { - return 'Tous les ├®quipements sont d├®j├á utilis├®s'; + return 'Tous les équipements sont déjà utilisés'; } - return '${conflictingEquipmentIds.length}/$totalChildren ├®quipement(s) d├®j├á utilis├®(s)'; + return '${conflictingEquipmentIds.length}/$totalChildren équipement(s) déjà utilisé(s)'; } } -/// Item s├®lectionn├® (├®quipement ou conteneur) +/// Item sélectionné (équipement ou conteneur) class SelectedItem { final String id; final String name; final SelectionType type; - final int quantity; // Pour consommables/c├óbles + final int quantity; // Pour consommables/câbles SelectedItem({ required this.id, @@ -64,7 +64,7 @@ class SelectedItem { } } -/// Dialog complet de s├®lection de mat├®riel pour un ├®v├®nement +/// Dialog complet de sélection de matériel pour un événement class EquipmentSelectionDialog extends StatefulWidget { final DateTime startDate; final DateTime endDate; @@ -87,34 +87,34 @@ class EquipmentSelectionDialog extends StatefulWidget { class _EquipmentSelectionDialogState extends State { final TextEditingController _searchController = TextEditingController(); - final ScrollController _scrollController = ScrollController(); // Pr├®serve la position de scroll + final ScrollController _scrollController = ScrollController(); // Préserve la position de scroll final DataService _dataService = DataService(apiService); EquipmentCategory? _selectedCategory; Map _selectedItems = {}; - final ValueNotifier _selectionChangeNotifier = ValueNotifier(0); // Pour notifier les changements de s├®lection sans setState + final ValueNotifier _selectionChangeNotifier = ValueNotifier(0); // Pour notifier les changements de sélection sans setState final Map _availableQuantities = {}; // Pour consommables final Map> _recommendedContainers = {}; // Recommandations - final Map> _equipmentConflicts = {}; // Conflits de disponibilit├® (d├®taill├®s) + final Map> _equipmentConflicts = {}; // Conflits de disponibilité (détaillés) final Map _containerConflicts = {}; // Conflits des conteneurs - final Set _expandedContainers = {}; // Conteneurs d├®pli├®s dans la liste + final Set _expandedContainers = {}; // Conteneurs dépliés dans la liste - // NOUVEAU : IDs en conflit r├®cup├®r├®s en batch + // NOUVEAU : IDs en conflit récupérés en batch Set _conflictingEquipmentIds = {}; Set _conflictingContainerIds = {}; - Map _conflictDetails = {}; // D├®tails des conflits par ID - Map _equipmentQuantities = {}; // Infos de quantit├®s pour c├óbles/consommables + Map _conflictDetails = {}; // Détails des conflits par ID + Map _equipmentQuantities = {}; // Infos de quantités pour câbles/consommables bool _isLoadingConflicts = false; String _searchQuery = ''; final _searchDebouncer = Debouncer(); // Nouvelles options d'affichage - bool _showConflictingItems = false; // Afficher les ├®quipements/bo├«tes en conflit + bool _showConflictingItems = false; // Afficher les équipements/boîtes en conflit // NOUVEAU : Lazy loading et pagination - SelectionType _displayType = SelectionType.equipment; // Type affich├® (├®quipements OU containers) + SelectionType _displayType = SelectionType.equipment; // Type affiché (équipements OU containers) bool _isLoadingMore = false; bool _hasMoreEquipments = true; bool _hasMoreContainers = true; @@ -123,7 +123,7 @@ class _EquipmentSelectionDialogState extends State { final List _paginatedEquipments = []; final List _paginatedContainers = []; - // Cache pour ├®viter les rebuilds inutiles + // Cache pour éviter les rebuilds inutiles final List _cachedContainers = []; final List _cachedEquipment = []; @@ -134,7 +134,7 @@ class _EquipmentSelectionDialogState extends State { // Ajouter le listener de scroll pour lazy loading _scrollController.addListener(_onScroll); - // Charger imm├®diatement les donn├®es de mani├¿re asynchrone + // Charger immédiatement les données de manière asynchrone _initializeData(); } @@ -144,7 +144,7 @@ class _EquipmentSelectionDialogState extends State { if (_scrollController.hasClients && _scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 300) { - // Charger la page suivante selon le type affich├® + // Charger la page suivante selon le type affiché if (_displayType == SelectionType.equipment && _hasMoreEquipments) { _loadNextEquipmentPage(); } else if (_displayType == SelectionType.container && _hasMoreContainers) { @@ -153,16 +153,16 @@ class _EquipmentSelectionDialogState extends State { } } - /// Initialise toutes les donn├®es n├®cessaires + /// Initialise toutes les données nécessaires Future _initializeData() async { try { - // 1. Charger les conflits (batch optimis├®) + // 1. Charger les conflits (batch optimisé) await _loadEquipmentConflicts(); - // 2. Initialiser la s├®lection avec le mat├®riel d├®j├á assign├® + // 2. Initialiser la sélection avec le matériel déjà assigné await _initializeAlreadyAssigned(); - // 3. Charger la premi├¿re page selon le type s├®lectionn├® + // 3. Charger la première page selon le type sélectionné if (_displayType == SelectionType.equipment) { await _loadNextEquipmentPage(); } else { @@ -177,7 +177,7 @@ class _EquipmentSelectionDialogState extends State { Future _initializeAlreadyAssigned() async { final Map initialSelection = {}; - // Ajouter les ├®quipements d├®j├á assign├®s + // Ajouter les équipements déjà assignés for (var eq in widget.alreadyAssigned) { initialSelection[eq.equipmentId] = SelectedItem( id: eq.equipmentId, @@ -187,13 +187,13 @@ class _EquipmentSelectionDialogState extends State { ); } - // Ajouter les conteneurs d├®j├á assign├®s + // Ajouter les conteneurs déjà assignés if (widget.alreadyAssignedContainers.isNotEmpty) { try { - // Pour les conteneurs d├®j├á assign├®s, on va les chercher via l'API si n├®cessaire - // ou cr├®er des conteneurs temporaires + // Pour les conteneurs déjà assignés, on va les chercher via l'API si nécessaire + // ou créer des conteneurs temporaires for (var containerId in widget.alreadyAssignedContainers) { - // Chercher dans le cache ou cr├®er un conteneur temporaire + // Chercher dans le cache ou créer un conteneur temporaire final container = _cachedContainers.firstWhere( (c) => c.id == containerId, orElse: () => ContainerModel( @@ -216,7 +216,7 @@ class _EquipmentSelectionDialogState extends State { // Charger le cache des enfants _containerEquipmentCache[containerId] = List.from(container.equipmentIds); - // Ajouter les enfants comme s├®lectionn├®s aussi + // Ajouter les enfants comme sélectionnés aussi for (var equipmentId in container.equipmentIds) { if (!initialSelection.containsKey(equipmentId)) { initialSelection[equipmentId] = SelectedItem( @@ -233,7 +233,7 @@ class _EquipmentSelectionDialogState extends State { } } - // Mettre ├á jour la s├®lection et notifier + // Mettre à jour la sélection et notifier if (mounted && initialSelection.isNotEmpty) { setState(() { _selectedItems = initialSelection; @@ -243,7 +243,7 @@ class _EquipmentSelectionDialogState extends State { } } - /// Charge la page suivante d'├®quipements (lazy loading) + /// Charge la page suivante d'équipements (lazy loading) Future _loadNextEquipmentPage() async { if (_isLoadingMore || !_hasMoreEquipments) return; @@ -304,7 +304,7 @@ class _EquipmentSelectionDialogState extends State { limit: 50, startAfter: _lastContainerId, searchQuery: _searchQuery.isNotEmpty ? _searchQuery : null, - category: _selectedCategory?.name, // Filtre par cat├®gorie d'├®quipements + category: _selectedCategory?.name, // Filtre par catégorie d'équipements sortBy: 'id', sortOrder: 'asc', ); @@ -313,13 +313,13 @@ class _EquipmentSelectionDialogState extends State { DebugLog.info('[EquipmentSelectionDialog] Raw containers data received: ${containersData.length} containers'); - // D'abord, extraire TOUS les ├®quipements + // D'abord, extraire TOUS les équipements final List allEquipmentsToCache = []; for (var data in containersData) { final map = data as Map; final containerId = map['id'] as String; - // Debug: v├®rifier si le champ 'equipment' existe + // Debug: vérifier si le champ 'equipment' existe final hasEquipmentField = map.containsKey('equipment'); final equipmentData = map['equipment']; DebugLog.info('[EquipmentSelectionDialog] Container $containerId: hasEquipmentField=$hasEquipmentField, equipmentData type=${equipmentData?.runtimeType}, count=${equipmentData is List ? equipmentData.length : 0}'); @@ -337,7 +337,7 @@ class _EquipmentSelectionDialogState extends State { DebugLog.info('[EquipmentSelectionDialog] Total equipments extracted from containers: ${allEquipmentsToCache.length}'); - // Cr├®er les containers + // Créer les containers final newContainers = containersData .map((data) { final map = data as Map; @@ -348,7 +348,7 @@ class _EquipmentSelectionDialogState extends State { if (mounted) { setState(() { - // Ajouter tous les ├®quipements au cache DANS le setState + // Ajouter tous les équipements au cache DANS le setState for (var eq in allEquipmentsToCache) { if (!_cachedEquipment.any((e) => e.id == eq.id)) { _cachedEquipment.add(eq); @@ -379,7 +379,7 @@ class _EquipmentSelectionDialogState extends State { } } - /// Recharge depuis le d├®but (appel├® lors d'un changement de filtre/recherche) + /// Recharge depuis le début (appelé lors d'un changement de filtre/recherche) Future _reloadData() async { setState(() { _paginatedEquipments.clear(); @@ -440,7 +440,7 @@ class _EquipmentSelectionDialogState extends State { super.dispose(); } - /// Charge les quantit├®s disponibles pour les consommables/c├óbles d'une liste d'├®quipements + /// Charge les quantités disponibles pour les consommables/câbles d'une liste d'équipements void _loadAvailableQuantities(List equipments) { if (!mounted) return; @@ -450,7 +450,7 @@ class _EquipmentSelectionDialogState extends State { eq.category == EquipmentCategory.cable); for (var eq in consumables) { - // Ne recharger que si on n'a pas d├®j├á la quantit├® + // Ne recharger que si on n'a pas déjà la quantité if (!_availableQuantities.containsKey(eq.id)) { int available = eq.totalQuantity ?? 0; if (_equipmentQuantities.containsKey(eq.id)) { @@ -467,8 +467,8 @@ class _EquipmentSelectionDialogState extends State { } } - /// Charge les conflits de disponibilit├® pour tous les ├®quipements et conteneurs - /// Version optimis├®e : un seul appel API au lieu d'un par ├®quipement + /// Charge les conflits de disponibilité pour tous les équipements et conteneurs + /// Version optimisée : un seul appel API au lieu d'un par équipement Future _loadEquipmentConflicts() async { setState(() => _isLoadingConflicts = true); @@ -477,12 +477,12 @@ class _EquipmentSelectionDialogState extends State { final startTime = DateTime.now(); - // UN SEUL appel API pour r├®cup├®rer TOUS les ├®quipements en conflit + // UN SEUL appel API pour récupérer TOUS les équipements en conflit final result = await _dataService.getConflictingEquipmentIds( startDate: widget.startDate, endDate: widget.endDate, excludeEventId: widget.excludeEventId, - installationTime: 0, // TODO: R├®cup├®rer depuis l'├®v├®nement si n├®cessaire + installationTime: 0, // TODO: Récupérer depuis l'événement si nécessaire disassemblyTime: 0, ); @@ -513,13 +513,13 @@ class _EquipmentSelectionDialogState extends State { _conflictDetails = conflictDetails; _equipmentQuantities = equipmentQuantities; - // Convertir conflictDetails en equipmentConflicts pour l'affichage d├®taill├® + // Convertir conflictDetails en equipmentConflicts pour l'affichage détaillé _equipmentConflicts.clear(); conflictDetails.forEach((itemId, conflicts) { final conflictList = (conflicts as List).map((conflict) { final conflictMap = conflict as Map; - // Cr├®er un EventModel minimal pour le conflit + // Créer un EventModel minimal pour le conflit final conflictEvent = EventModel( id: conflictMap['eventId'] as String, name: conflictMap['eventName'] as String, @@ -551,7 +551,7 @@ class _EquipmentSelectionDialogState extends State { return AvailabilityConflict( equipmentId: itemId, - equipmentName: '', // Sera r├®solu lors de l'affichage + equipmentName: '', // Sera résolu lors de l'affichage conflictingEvent: conflictEvent, overlapDays: overlapDays.clamp(1, 999), ); @@ -561,7 +561,7 @@ class _EquipmentSelectionDialogState extends State { }); } - // Mettre ├á jour les statuts de conteneurs + // Mettre à jour les statuts de conteneurs await _updateContainerConflictStatus(); } catch (e) { @@ -571,14 +571,14 @@ class _EquipmentSelectionDialogState extends State { } } - /// Met ├á jour le statut de conflit des conteneurs bas├® sur les IDs en conflit + /// Met à jour le statut de conflit des conteneurs basé sur les IDs en conflit Future _updateContainerConflictStatus() async { if (!mounted) return; try { - // Utiliser les containers pagin├®s charg├®s + // Utiliser les containers paginés chargés for (var container in _paginatedContainers) { - // V├®rifier si le conteneur lui-m├¬me est en conflit + // Vérifier si le conteneur lui-même est en conflit if (_conflictingContainerIds.contains(container.id)) { _containerConflicts[container.id] = ContainerConflictInfo( status: ContainerConflictStatus.complete, @@ -588,7 +588,7 @@ class _EquipmentSelectionDialogState extends State { continue; } - // V├®rifier si des ├®quipements enfants sont en conflit + // Vérifier si des équipements enfants sont en conflit final conflictingChildren = container.equipmentIds .where((eqId) => _conflictingEquipmentIds.contains(eqId)) .toList(); @@ -610,7 +610,7 @@ class _EquipmentSelectionDialogState extends State { DebugLog.info('[EquipmentSelectionDialog] Total containers with conflicts: ${_containerConflicts.length}'); - // D├®clencher un rebuild pour afficher les changements visuels + // Déclencher un rebuild pour afficher les changements visuels if (mounted) { setState(() {}); } @@ -619,7 +619,7 @@ class _EquipmentSelectionDialogState extends State { } } - /// R├®cup├¿re les d├®tails des conflits pour un ├®quipement/conteneur donn├® + /// Récupère les détails des conflits pour un équipement/conteneur donné List> _getConflictDetailsFor(String id) { final details = _conflictDetails[id]; if (details == null) return []; @@ -631,12 +631,12 @@ class _EquipmentSelectionDialogState extends State { return []; } - /// Construit l'affichage des quantit├®s pour les c├óbles/consommables + /// Construit l'affichage des quantités pour les câbles/consommables Widget _buildQuantityInfo(EquipmentModel equipment) { final quantityInfo = _equipmentQuantities[equipment.id] as Map?; if (quantityInfo == null) { - // Pas d'info de quantit├®, utiliser l'ancien syst├¿me (availableQuantities) + // Pas d'info de quantité, utiliser l'ancien système (availableQuantities) final availableQty = _availableQuantities[equipment.id]; if (availableQty == null) return const SizedBox.shrink(); @@ -687,7 +687,7 @@ class _EquipmentSelectionDialogState extends State { ); } - /// Affiche un dialog avec les d├®tails des r├®servations de quantit├® + /// Affiche un dialog avec les détails des réservations de quantité Future _showQuantityDetailsDialog(EquipmentModel equipment, Map quantityInfo) async { final reservations = quantityInfo['reservations'] as List? ?? []; final totalQuantity = quantityInfo['totalQuantity'] as int? ?? 0; @@ -703,7 +703,7 @@ class _EquipmentSelectionDialogState extends State { const SizedBox(width: 8), Expanded( child: Text( - 'Quantit├®s - ${equipment.name}', + 'Quantités - ${equipment.name}', style: const TextStyle(fontSize: 18), ), ), @@ -715,7 +715,7 @@ class _EquipmentSelectionDialogState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - // R├®sum├® + // Résumé Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( @@ -729,7 +729,7 @@ class _EquipmentSelectionDialogState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'Quantit├® totale :', + 'Quantité totale :', style: TextStyle( fontWeight: FontWeight.bold, color: Colors.grey.shade800, @@ -770,10 +770,10 @@ class _EquipmentSelectionDialogState extends State { ), const SizedBox(height: 16), - // Liste des r├®servations + // Liste des réservations if (reservations.isNotEmpty) ...[ Text( - 'Utilis├® sur ${reservations.length} ├®v├®nement(s) :', + 'Utilisé sur ${reservations.length} événement(s) :', style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14, @@ -786,7 +786,7 @@ class _EquipmentSelectionDialogState extends State { child: Column( children: reservations.map((reservation) { final res = reservation as Map; - final eventName = res['eventName'] as String? ?? '├ëv├®nement inconnu'; + final eventName = res['eventName'] as String? ?? 'Événement inconnu'; final quantity = res['quantity'] as int? ?? 0; final viaContainer = res['viaContainer'] as String?; final viaContainerName = res['viaContainerName'] as String?; @@ -846,21 +846,21 @@ class _EquipmentSelectionDialogState extends State { ); } - /// Recherche les conteneurs recommand├®s pour un ├®quipement - /// NOTE: D├®sactiv├® avec le lazy loading - on ne charge pas tous les containers d'un coup + /// Recherche les conteneurs recommandés pour un équipement + /// NOTE: Désactivé avec le lazy loading - on ne charge pas tous les containers d'un coup Future _findRecommendedContainers(String equipmentId) async { - // D├®sactiv├® pour le moment avec le lazy loading - // On pourrait impl├®menter une API d├®di├®e si n├®cessaire + // Désactivé pour le moment avec le lazy loading + // On pourrait implémenter une API dédiée si nécessaire return; } - /// Obtenir les bo├«tes parentes d'un ├®quipement de mani├¿re synchrone depuis le cache + /// Obtenir les boîtes parentes d'un équipement de manière synchrone depuis le cache List _getParentContainers(String equipmentId) { return _recommendedContainers[equipmentId] ?? []; } void _toggleSelection(String id, String name, SelectionType type, {int? maxQuantity, bool force = false}) async { - // V├®rifier si l'├®quipement est en conflit + // Vérifier si l'équipement est en conflit if (!force && type == SelectionType.equipment && _conflictingEquipmentIds.contains(id)) { // Demander confirmation pour forcer final shouldForce = await _showForceConfirmationDialog(id); @@ -871,16 +871,16 @@ class _EquipmentSelectionDialogState extends State { } if (_selectedItems.containsKey(id)) { - // D├®s├®lectionner + // Désélectionner DebugLog.info('[EquipmentSelectionDialog] Deselecting $type: $id'); DebugLog.info('[EquipmentSelectionDialog] Before deselection, _selectedItems count: ${_selectedItems.length}'); if (type == SelectionType.container) { - // Si c'est un conteneur, d├®s├®lectionner d'abord ses enfants de mani├¿re asynchrone + // Si c'est un conteneur, désélectionner d'abord ses enfants de manière asynchrone await _deselectContainerChildren(id); } - // Mise ├á jour avec setState pour garantir le rebuild + // Mise à jour avec setState pour garantir le rebuild if (mounted) { setState(() { _selectedItems.remove(id); @@ -893,10 +893,10 @@ class _EquipmentSelectionDialogState extends State { // Notifier le changement _selectionChangeNotifier.value++; } else { - // S├®lectionner + // Sélectionner DebugLog.info('[EquipmentSelectionDialog] Selecting $type: $id'); - // Mise ├á jour avec setState pour garantir le rebuild + // Mise à jour avec setState pour garantir le rebuild if (mounted) { setState(() { _selectedItems[id] = SelectedItem( @@ -908,12 +908,12 @@ class _EquipmentSelectionDialogState extends State { }); } - // Si c'est un ├®quipement, chercher les conteneurs recommand├®s + // Si c'est un équipement, chercher les conteneurs recommandés if (type == SelectionType.equipment) { _findRecommendedContainers(id); } - // Si c'est un conteneur, s├®lectionner ses enfants en cascade + // Si c'est un conteneur, sélectionner ses enfants en cascade if (type == SelectionType.container) { await _selectContainerChildren(id); } @@ -923,10 +923,10 @@ class _EquipmentSelectionDialogState extends State { } } - /// S├®lectionner tous les enfants d'un conteneur + /// Sélectionner tous les enfants d'un conteneur Future _selectContainerChildren(String containerId) async { try { - // Chercher le container dans les donn├®es pagin├®es ou le cache + // Chercher le container dans les données paginées ou le cache final container = [..._paginatedContainers, ..._cachedContainers].firstWhere( (c) => c.id == containerId, orElse: () => ContainerModel( @@ -940,13 +940,13 @@ class _EquipmentSelectionDialogState extends State { ), ); - // Mettre ├á jour le cache + // Mettre à jour le cache _containerEquipmentCache[containerId] = List.from(container.equipmentIds); - // S├®lectionner chaque enfant (sans bloquer, car ils sont "compos├®s") + // Sélectionner chaque enfant (sans bloquer, car ils sont "composés") for (var equipmentId in container.equipmentIds) { if (!_selectedItems.containsKey(equipmentId)) { - // Chercher l'├®quipement dans les donn├®es pagin├®es ou le cache + // Chercher l'équipement dans les données paginées ou le cache final eq = [..._paginatedEquipments, ..._cachedEquipment].firstWhere( (e) => e.id == equipmentId, orElse: () => EquipmentModel( @@ -979,10 +979,10 @@ class _EquipmentSelectionDialogState extends State { } } - /// D├®s├®lectionner tous les enfants d'un conteneur + /// Désélectionner tous les enfants d'un conteneur Future _deselectContainerChildren(String containerId) async { try { - // Chercher le container dans les donn├®es pagin├®es ou le cache + // Chercher le container dans les données paginées ou le cache final container = [..._paginatedContainers, ..._cachedContainers].firstWhere( (c) => c.id == containerId, orElse: () => ContainerModel( @@ -1006,7 +1006,7 @@ class _EquipmentSelectionDialogState extends State { // Nettoyer le cache _containerEquipmentCache.remove(containerId); - // Retirer de la liste des conteneurs expand├®s + // Retirer de la liste des conteneurs expandés _expandedContainers.remove(containerId); }); } @@ -1017,7 +1017,7 @@ class _EquipmentSelectionDialogState extends State { } } - /// Affiche un dialog pour confirmer le for├ºage d'un ├®quipement en conflit + /// Affiche un dialog pour confirmer le forçage d'un équipement en conflit Future _showForceConfirmationDialog(String equipmentId) async { final conflicts = _equipmentConflicts[equipmentId] ?? []; @@ -1028,14 +1028,14 @@ class _EquipmentSelectionDialogState extends State { children: [ Icon(Icons.warning, color: Colors.orange), SizedBox(width: 8), - Text('├ëquipement d├®j├á utilis├®'), + Text('Équipement déjà utilisé'), ], ), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('Cet ├®quipement est d├®j├á utilis├® sur ${conflicts.length} ├®v├®nement(s) :'), + Text('Cet équipement est déjà utilisé sur ${conflicts.length} événement(s) :'), const SizedBox(height: 12), ...conflicts.map((conflict) => Padding( padding: const EdgeInsets.only(bottom: 8), @@ -1075,7 +1075,7 @@ class _EquipmentSelectionDialogState extends State { backgroundColor: Colors.orange, foregroundColor: Colors.white, ), - child: const Text('Forcer quand m├¬me'), + child: const Text('Forcer quand même'), ), ], ), @@ -1110,7 +1110,7 @@ class _EquipmentSelectionDialogState extends State { child: _buildMainList(), ), - // Panneau lat├®ral : s├®lection + recommandations + // Panneau latéral : sélection + recommandations Container( width: 320, decoration: BoxDecoration( @@ -1122,20 +1122,23 @@ class _EquipmentSelectionDialogState extends State { child: Column( children: [ Expanded( + flex: 3, child: ValueListenableBuilder( valueListenable: _selectionChangeNotifier, builder: (context, _, __) => _buildSelectionPanel(), ), ), if (_hasRecommendations) - Container( - height: 200, - decoration: BoxDecoration( - border: Border( - top: BorderSide(color: Colors.grey.shade300), + Expanded( + flex: 2, + child: Container( + decoration: BoxDecoration( + border: Border( + top: BorderSide(color: Colors.grey.shade300), + ), ), + child: _buildRecommendationsPanel(), ), - child: _buildRecommendationsPanel(), ), ], ), @@ -1163,7 +1166,7 @@ class _EquipmentSelectionDialogState extends State { const SizedBox(width: 12), const Expanded( child: Text( - 'Ajouter du mat├®riel', + 'Ajouter du matériel', style: TextStyle( color: Colors.white, fontSize: 20, @@ -1193,7 +1196,7 @@ class _EquipmentSelectionDialogState extends State { TextField( controller: _searchController, decoration: InputDecoration( - hintText: 'Rechercher du mat├®riel ou des bo├«tes...', + hintText: 'Rechercher du matériel ou des boîtes...', prefixIcon: const Icon(Icons.search, color: AppColors.rouge), suffixIcon: _searchQuery.isNotEmpty ? IconButton( @@ -1211,14 +1214,14 @@ class _EquipmentSelectionDialogState extends State { ), onChanged: (value) { setState(() => _searchQuery = value.toLowerCase()); - // Recharger depuis le d├®but avec le nouveau filtre + // Recharger depuis le début avec le nouveau filtre _searchDebouncer(_reloadData); }, ), const SizedBox(height: 12), - // Filtres par cat├®gorie (pour les ├®quipements) + // Filtres par catégorie (pour les équipements) SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( @@ -1237,7 +1240,7 @@ class _EquipmentSelectionDialogState extends State { const SizedBox(height: 12), - // Checkbox pour afficher les ├®quipements en conflit + // Checkbox pour afficher les équipements en conflit Row( children: [ Checkbox( @@ -1250,12 +1253,12 @@ class _EquipmentSelectionDialogState extends State { activeColor: AppColors.rouge, ), const Text( - 'Afficher les ├®quipements d├®j├á utilis├®s', + 'Afficher les équipements déjà utilisés', style: TextStyle(fontSize: 14), ), const SizedBox(width: 8), Tooltip( - message: 'Afficher les ├®quipements et bo├«tes qui sont d├®j├á utilis├®s durant ces dates', + message: 'Afficher les équipements et boîtes qui sont déjà utilisés durant ces dates', child: Icon( Icons.info_outline, size: 18, @@ -1267,7 +1270,7 @@ class _EquipmentSelectionDialogState extends State { const SizedBox(height: 12), - // Chip pour switcher entre ├ëquipements et Containers + // Chip pour switcher entre Équipements et Containers Row( children: [ const Text( @@ -1276,7 +1279,7 @@ class _EquipmentSelectionDialogState extends State { ), const SizedBox(width: 8), ChoiceChip( - label: const Text('├ëquipements'), + label: const Text('Équipements'), selected: _displayType == SelectionType.equipment, onSelected: (selected) { if (selected && _displayType != SelectionType.equipment) { @@ -1325,7 +1328,7 @@ class _EquipmentSelectionDialogState extends State { setState(() { _selectedCategory = selected ? category : null; }); - // Recharger depuis le d├®but avec le nouveau filtre + // Recharger depuis le début avec le nouveau filtre _reloadData(); }, selectedColor: AppColors.rouge, @@ -1337,7 +1340,7 @@ class _EquipmentSelectionDialogState extends State { } Widget _buildMainList() { - // Afficher un indicateur de chargement si les donn├®es sont en cours de chargement + // Afficher un indicateur de chargement si les données sont en cours de chargement if (_isLoadingConflicts) { return Center( child: Column( @@ -1346,7 +1349,7 @@ class _EquipmentSelectionDialogState extends State { const CircularProgressIndicator(color: AppColors.rouge), const SizedBox(height: 16), Text( - 'V├®rification de la disponibilit├®...', + 'Vérification de la disponibilité...', style: TextStyle(color: Colors.grey.shade600), ), ], @@ -1354,20 +1357,20 @@ class _EquipmentSelectionDialogState extends State { ); } - // Vue hi├®rarchique unique : Bo├«tes en haut, TOUS les ├®quipements en bas + // Vue hiérarchique unique : Boîtes en haut, TOUS les équipements en bas return _buildHierarchicalList(); } - /// Vue hi├®rarchique unique avec cache pour ├®viter les rebuilds inutiles + /// Vue hiérarchique unique avec cache pour éviter les rebuilds inutiles Widget _buildHierarchicalList() { return ValueListenableBuilder( valueListenable: _selectionChangeNotifier, builder: (context, _, __) { - // Filtrer les donn├®es pagin├®es selon le type affich├® + // Filtrer les données paginées selon le type affiché List itemWidgets = []; if (_displayType == SelectionType.equipment) { - // Filtrer c├┤t├® client pour "Afficher ├®quipements d├®j├á utilis├®s" + // Filtrer côté client pour "Afficher équipements déjà utilisés" final filteredEquipments = _paginatedEquipments.where((eq) { if (!_showConflictingItems && _conflictingEquipmentIds.contains(eq.id)) { return false; @@ -1488,7 +1491,7 @@ class _EquipmentSelectionDialogState extends State { ); } - /// Header de section (version simple, gard├®e pour compatibilit├®) + /// Header de section (version simple, gardée pour compatibilité) Widget _buildSectionHeader(String title, IconData icon, int count) { return Container( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), @@ -1538,7 +1541,7 @@ class _EquipmentSelectionDialogState extends State { final hasConflict = _conflictingEquipmentIds.contains(equipment.id); // CORRECTION ICI ! final conflictDetails = _getConflictDetailsFor(equipment.id); - // Bloquer la s├®lection si en conflit et non forc├® + // Bloquer la sélection si en conflit et non forcé final canSelect = !hasConflict || isSelected; return RepaintBoundary( @@ -1593,7 +1596,7 @@ class _EquipmentSelectionDialogState extends State { const SizedBox(width: 12), - // Ic├┤ne + // Icône equipment.category.getIcon(size: 32, color: equipment.category.color), const SizedBox(width: 16), @@ -1627,7 +1630,7 @@ class _EquipmentSelectionDialogState extends State { const Icon(Icons.warning, size: 14, color: Colors.white), const SizedBox(width: 4), Text( - 'D├®j├á utilis├®', + 'Déjà utilisé', style: const TextStyle( color: Colors.white, fontSize: 11, @@ -1647,7 +1650,7 @@ class _EquipmentSelectionDialogState extends State { fontSize: 14, ), ), - // Affichage des bo├«tes parentes + // Affichage des boîtes parentes if (_getParentContainers(equipment.id).isNotEmpty) Padding( padding: const EdgeInsets.only(top: 4), @@ -1690,7 +1693,7 @@ class _EquipmentSelectionDialogState extends State { ), ), - // S├®lecteur de quantit├® pour consommables (toujours affich├®) + // Sélecteur de quantité pour consommables (toujours affiché) if (isConsumable && availableQty != null) _buildQuantitySelector( equipment.id, @@ -1698,10 +1701,10 @@ class _EquipmentSelectionDialogState extends State { id: equipment.id, name: equipment.id, type: SelectionType.equipment, - quantity: 0, // Quantit├® 0 si non s├®lectionn├® + quantity: 0, // Quantité 0 si non sélectionné ), availableQty, - isSelected: isSelected, // Passer l'├®tat de s├®lection + isSelected: isSelected, // Passer l'état de sélection ), ], ), @@ -1724,7 +1727,7 @@ class _EquipmentSelectionDialogState extends State { Icon(Icons.info_outline, size: 16, color: Colors.orange.shade900), const SizedBox(width: 6), Text( - 'Utilis├® sur ${conflictDetails.length} ├®v├®nement(s) :', + 'Utilisé sur ${conflictDetails.length} événement(s) :', style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, @@ -1735,7 +1738,7 @@ class _EquipmentSelectionDialogState extends State { ), const SizedBox(height: 6), ...conflictDetails.take(2).map((detail) { - final eventName = detail['eventName'] as String? ?? '├ëv├®nement inconnu'; + final eventName = detail['eventName'] as String? ?? 'Événement inconnu'; final viaContainer = detail['viaContainer'] as String?; final viaContainerName = detail['viaContainerName'] as String?; @@ -1791,7 +1794,7 @@ class _EquipmentSelectionDialogState extends State { force: true, ), icon: const Icon(Icons.warning, size: 16), - label: const Text('Forcer quand m├¬me', style: TextStyle(fontSize: 12)), + label: const Text('Forcer quand même', style: TextStyle(fontSize: 12)), style: TextButton.styleFrom( foregroundColor: Colors.orange.shade900, padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), @@ -1810,8 +1813,8 @@ class _EquipmentSelectionDialogState extends State { ); } - /// Widget pour le s├®lecteur de quantit├® - /// Si isSelected = false, le premier clic sur + s├®lectionne l'item avec quantit├® 1 + /// Widget pour le sélecteur de quantité + /// Si isSelected = false, le premier clic sur + sélectionne l'item avec quantité 1 Widget _buildQuantitySelector( String equipmentId, SelectedItem selectedItem, @@ -1854,7 +1857,7 @@ class _EquipmentSelectionDialogState extends State { onPressed: (isSelected && selectedItem.quantity < maxQuantity) || !isSelected ? () { if (!isSelected) { - // Premier clic : s├®lectionner avec quantit├® 1 + // Premier clic : sélectionner avec quantité 1 _toggleSelection( equipmentId, selectedItem.name, @@ -1862,7 +1865,7 @@ class _EquipmentSelectionDialogState extends State { maxQuantity: maxQuantity, ); } else { - // Item d├®j├á s├®lectionn├® : incr├®menter + // Item déjà sélectionné : incrémenter if (mounted) { setState(() { _selectedItems[equipmentId] = selectedItem.copyWith(quantity: selectedItem.quantity + 1); @@ -1888,7 +1891,7 @@ class _EquipmentSelectionDialogState extends State { final hasConflict = conflictInfo != null; final isCompleteConflict = conflictInfo?.status == ContainerConflictStatus.complete; - // Bloquer la s├®lection si tous les enfants sont en conflit (sauf si d├®j├á s├®lectionn├®) + // Bloquer la sélection si tous les enfants sont en conflit (sauf si déjà sélectionné) final canSelect = !isCompleteConflict || isSelected; return RepaintBoundary( @@ -1946,7 +1949,7 @@ class _EquipmentSelectionDialogState extends State { const SizedBox(width: 12), - // Ic├┤ne du conteneur + // Icône du conteneur Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( @@ -1994,7 +1997,7 @@ class _EquipmentSelectionDialogState extends State { ), const SizedBox(width: 4), Text( - isCompleteConflict ? 'Indisponible' : 'Partiellement utilis├®e', + isCompleteConflict ? 'Indisponible' : 'Partiellement utilisée', style: const TextStyle( color: Colors.white, fontSize: 11, @@ -2024,7 +2027,7 @@ class _EquipmentSelectionDialogState extends State { ), const SizedBox(width: 4), Text( - '${container.itemCount} ├®quipement(s)', + '${container.itemCount} équipement(s)', style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w500, @@ -2054,7 +2057,7 @@ class _EquipmentSelectionDialogState extends State { ), ), - // Bouton pour d├®plier/replier + // Bouton pour déplier/replier IconButton( icon: Icon( isExpanded ? Icons.expand_less : Icons.expand_more, @@ -2073,7 +2076,7 @@ class _EquipmentSelectionDialogState extends State { ], ), - // Avertissement pour conteneur compl├¿tement indisponible + // Avertissement pour conteneur complètement indisponible if (isCompleteConflict && !isSelected) Container( margin: const EdgeInsets.only(top: 12), @@ -2089,7 +2092,7 @@ class _EquipmentSelectionDialogState extends State { const SizedBox(width: 8), Expanded( child: Text( - 'Cette bo├«te ne peut pas ├¬tre s├®lectionn├®e car tous ses ├®quipements sont d├®j├á utilis├®s.', + 'Cette boîte ne peut pas être sélectionnée car tous ses équipements sont déjà utilisés.', style: TextStyle( fontSize: 12, color: Colors.red.shade900, @@ -2105,7 +2108,7 @@ class _EquipmentSelectionDialogState extends State { ), ), - // Liste des enfants (si d├®pli├®) + // Liste des enfants (si déplié) if (isExpanded) _buildContainerChildren(container, conflictInfo), ], @@ -2115,9 +2118,9 @@ class _EquipmentSelectionDialogState extends State { ); } - /// Widget pour afficher les ├®quipements enfants d'un conteneur + /// Widget pour afficher les équipements enfants d'un conteneur Widget _buildContainerChildren(ContainerModel container, ContainerConflictInfo? conflictInfo) { - // Utiliser les ├®quipements pagin├®s et le cache + // Utiliser les équipements paginés et le cache final allEquipment = [..._paginatedEquipments, ..._cachedEquipment]; final childEquipments = allEquipment .where((eq) => container.equipmentIds.contains(eq.id)) @@ -2143,7 +2146,7 @@ class _EquipmentSelectionDialogState extends State { Icon(Icons.info_outline, size: 16, color: Colors.grey.shade600), const SizedBox(width: 8), Text( - 'Aucun ├®quipement dans ce conteneur', + 'Aucun équipement dans ce conteneur', style: TextStyle(color: Colors.grey.shade600, fontSize: 13), ), ], @@ -2165,7 +2168,7 @@ class _EquipmentSelectionDialogState extends State { Icon(Icons.list, size: 16, color: Colors.grey.shade700), const SizedBox(width: 6), Text( - 'Contenu de la bo├«te :', + 'Contenu de la boîte :', style: TextStyle( fontSize: 13, color: Colors.grey.shade700, @@ -2191,7 +2194,7 @@ class _EquipmentSelectionDialogState extends State { ), child: Row( children: [ - // Fl├¿che de hi├®rarchie + // Flèche de hiérarchie Icon( Icons.subdirectory_arrow_right, size: 16, @@ -2199,11 +2202,11 @@ class _EquipmentSelectionDialogState extends State { ), const SizedBox(width: 8), - // Ic├┤ne de l'├®quipement + // Icône de l'équipement eq.category.getIcon(size: 20, color: eq.category.color), const SizedBox(width: 12), - // Nom de l'├®quipement + // Nom de l'équipement Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -2232,7 +2235,7 @@ class _EquipmentSelectionDialogState extends State { if (hasConflict) ...[ const SizedBox(width: 8), Tooltip( - message: 'Utilis├® sur ${conflicts.length} ├®v├®nement(s)', + message: 'Utilisé sur ${conflicts.length} événement(s)', child: Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 3), decoration: BoxDecoration( @@ -2267,19 +2270,19 @@ class _EquipmentSelectionDialogState extends State { } Widget _buildSelectionPanel() { - // Compter uniquement les conteneurs et ├®quipements "racine" (pas enfants de conteneurs) + // Compter uniquement les conteneurs et équipements "racine" (pas enfants de conteneurs) final selectedContainers = _selectedItems.entries .where((e) => e.value.type == SelectionType.container) .toList(); - // Collecter tous les IDs d'├®quipements qui sont enfants de conteneurs s├®lectionn├®s + // Collecter tous les IDs d'équipements qui sont enfants de conteneurs sélectionnés final Set equipmentIdsInContainers = {}; for (var containerEntry in selectedContainers) { final childrenIds = _getContainerEquipmentIds(containerEntry.key); equipmentIdsInContainers.addAll(childrenIds); } - // ├ëquipements qui ne sont PAS enfants d'un conteneur s├®lectionn├® + // Équipements qui ne sont PAS enfants d'un conteneur sélectionné final selectedStandaloneEquipment = _selectedItems.entries .where((e) => e.value.type == SelectionType.equipment) .where((e) => !equipmentIdsInContainers.contains(e.key)) @@ -2301,7 +2304,7 @@ class _EquipmentSelectionDialogState extends State { const Icon(Icons.check_circle, color: Colors.white), const SizedBox(width: 8), const Text( - 'S├®lection', + 'Sélection', style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, @@ -2331,7 +2334,7 @@ class _EquipmentSelectionDialogState extends State { child: totalDisplayed == 0 ? const Center( child: Text( - 'Aucune s├®lection', + 'Aucune sélection', style: TextStyle(color: Colors.grey), ), ) @@ -2342,7 +2345,7 @@ class _EquipmentSelectionDialogState extends State { Padding( padding: const EdgeInsets.all(8), child: Text( - 'Bo├«tes ($containerCount)', + 'Boîtes ($containerCount)', style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14, @@ -2355,7 +2358,7 @@ class _EquipmentSelectionDialogState extends State { Padding( padding: const EdgeInsets.all(8), child: Text( - '├ëquipements ($standaloneEquipmentCount)', + 'Équipements ($standaloneEquipmentCount)', style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 14, @@ -2372,14 +2375,14 @@ class _EquipmentSelectionDialogState extends State { } - /// R├®cup├¿re les IDs des ├®quipements d'un conteneur (depuis le cache) + /// Récupère les IDs des équipements d'un conteneur (depuis le cache) List _getContainerEquipmentIds(String containerId) { - // On doit r├®cup├®rer le conteneur depuis le provider de mani├¿re synchrone + // On doit récupérer le conteneur depuis le provider de manière synchrone // Pour cela, on va maintenir un cache local return _containerEquipmentCache[containerId] ?? []; } - // Cache local pour les ├®quipements des conteneurs + // Cache local pour les équipements des conteneurs final Map> _containerEquipmentCache = {}; Widget _buildSelectedContainerTile(String id, SelectedItem item) { @@ -2401,7 +2404,7 @@ class _EquipmentSelectionDialogState extends State { style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold), ), subtitle: Text( - '$childrenCount ├®quipement(s)', + '$childrenCount équipement(s)', style: const TextStyle(fontSize: 11), ), trailing: Row( @@ -2456,7 +2459,7 @@ class _EquipmentSelectionDialogState extends State { style: TextStyle(fontSize: 12, color: Colors.grey.shade700), ), subtitle: item.quantity > 1 - ? Text('Qt├®: ${item.quantity}', style: const TextStyle(fontSize: 10)) + ? Text('Qté: ${item.quantity}', style: const TextStyle(fontSize: 10)) : null, // PAS de bouton de suppression pour les enfants ), @@ -2476,7 +2479,7 @@ class _EquipmentSelectionDialogState extends State { style: const TextStyle(fontSize: 13), ), subtitle: item.quantity > 1 - ? Text('Qt├®: ${item.quantity}', style: const TextStyle(fontSize: 11)) + ? Text('Qté: ${item.quantity}', style: const TextStyle(fontSize: 11)) : null, trailing: IconButton( icon: const Icon(Icons.close, size: 18), @@ -2501,7 +2504,7 @@ class _EquipmentSelectionDialogState extends State { Icon(Icons.lightbulb, color: Colors.white, size: 20), SizedBox(width: 8), Text( - 'Bo├«tes recommand├®es', + 'Boîtes recommandées', style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, @@ -2537,7 +2540,7 @@ class _EquipmentSelectionDialogState extends State { style: const TextStyle(fontSize: 13, fontWeight: FontWeight.bold), ), subtitle: Text( - '${container.itemCount} ├®quipement(s)', + '${container.itemCount} équipement(s)', style: const TextStyle(fontSize: 11), ), trailing: isAlreadySelected @@ -2575,7 +2578,7 @@ class _EquipmentSelectionDialogState extends State { child: Row( children: [ Text( - '${_selectedItems.length} ├®l├®ment(s) s├®lectionn├®(s)', + '${_selectedItems.length} élément(s) sélectionné(s)', style: const TextStyle(fontWeight: FontWeight.w500), ), const Spacer(), @@ -2593,7 +2596,7 @@ class _EquipmentSelectionDialogState extends State { foregroundColor: Colors.white, padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 14), ), - child: const Text('Valider la s├®lection'), + child: const Text('Valider la sélection'), ), ], ),