Cette mise à jour structurelle améliore la classification des équipements en introduisant la notion de sous-catégories et supprime la gestion directe de l'appartenance d'un équipement à une boîte (`parentBoxIds`). L'appartenance est désormais uniquement définie côté conteneur. Une nouvelle catégorie "Régie / Backline" est également ajoutée.
**Changements majeurs :**
- **Suppression de `parentBoxIds` sur `EquipmentModel` :**
- Le champ `parentBoxIds` a été retiré du modèle de données `EquipmentModel` et de toutes les logiques associées (création, mise à jour, copie).
- La responsabilité de lier un équipement à un conteneur est désormais exclusivement gérée par le `ContainerModel` via sa liste `equipmentIds`.
- La logique de synchronisation complexe dans `EquipmentFormPage` qui mettait à jour les conteneurs lors de la modification d'un équipement a été entièrement supprimée, simplifiant considérablement le code.
- Le sélecteur de boîtes parentes (`ParentBoxesSelector`) a été retiré du formulaire d'équipement.
- **Ajout des sous-catégories :**
- Un champ optionnel `subCategory` (String) a été ajouté au `EquipmentModel`.
- Le formulaire de création/modification d'équipement inclut désormais un nouveau champ "Sous-catégorie" avec autocomplétion.
- Ce champ est contextuel : il propose des suggestions basées sur les sous-catégories existantes pour la catégorie principale sélectionnée (ex: "Console", "Micro" pour la catégorie "Son").
- La sous-catégorie est maintenant affichée sur les fiches de détail des équipements et dans les listes de la page de gestion, améliorant la visibilité du classement.
**Nouvelle catégorie d'équipement :**
- Une nouvelle catégorie `backline` ("Régie / Backline") a été ajoutée à `EquipmentCategory` avec une icône (`Icons.piano`) et une couleur associée.
**Refactorisation et nettoyage :**
- Le `EquipmentProvider` et `EquipmentService` ont été mis à jour pour charger et filtrer les sous-catégories.
- De nombreuses instanciations d'un `EquipmentModel` vide (`dummy`) à travers l'application ont été nettoyées pour retirer la référence à `parentBoxIds`.
- **Version de l'application :**
- La version a été incrémentée à `1.0.4`.
309 lines
9.4 KiB
Dart
309 lines
9.4 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
import 'package:em2rp/models/equipment_model.dart';
|
|
import 'package:em2rp/services/data_service.dart';
|
|
import 'package:em2rp/services/api_service.dart';
|
|
|
|
class EquipmentProvider extends ChangeNotifier {
|
|
final DataService _dataService = DataService(FirebaseFunctionsApiService());
|
|
|
|
List<EquipmentModel> _equipment = [];
|
|
List<String> _models = [];
|
|
List<String> _brands = [];
|
|
|
|
EquipmentCategory? _selectedCategory;
|
|
EquipmentStatus? _selectedStatus;
|
|
String? _selectedModel;
|
|
String _searchQuery = '';
|
|
bool _isLoading = false;
|
|
bool _isInitialized = false; // Flag pour savoir si les équipements ont été chargés
|
|
|
|
// Constructeur - Ne charge PAS automatiquement
|
|
// Les équipements seront chargés à la demande (page de gestion ou via getEquipmentsByIds)
|
|
EquipmentProvider();
|
|
|
|
// Getters
|
|
List<EquipmentModel> get equipment => _filteredEquipment;
|
|
List<EquipmentModel> get allEquipment => _equipment; // Tous les équipements sans filtre
|
|
List<String> get models => _models;
|
|
List<String> get brands => _brands;
|
|
EquipmentCategory? get selectedCategory => _selectedCategory;
|
|
EquipmentStatus? get selectedStatus => _selectedStatus;
|
|
String? get selectedModel => _selectedModel;
|
|
String get searchQuery => _searchQuery;
|
|
bool get isLoading => _isLoading;
|
|
bool get isInitialized => _isInitialized;
|
|
|
|
/// S'assure que les équipements sont chargés (charge si nécessaire)
|
|
Future<void> ensureLoaded() async {
|
|
if (_isInitialized || _isLoading) {
|
|
print('[EquipmentProvider] Equipment already loaded or loading, skipping...');
|
|
return;
|
|
}
|
|
print('[EquipmentProvider] Equipment not loaded, loading now...');
|
|
await loadEquipments();
|
|
}
|
|
|
|
/// Charger tous les équipements via l'API (utilisé par la page de gestion)
|
|
Future<void> loadEquipments() async {
|
|
print('[EquipmentProvider] Starting to load equipments...');
|
|
_isLoading = true;
|
|
notifyListeners();
|
|
|
|
try {
|
|
print('[EquipmentProvider] Calling getEquipments API...');
|
|
final equipmentsData = await _dataService.getEquipments();
|
|
print('[EquipmentProvider] Received ${equipmentsData.length} equipments from API');
|
|
|
|
_equipment = equipmentsData.map((data) {
|
|
return EquipmentModel.fromMap(data, data['id'] as String);
|
|
}).toList();
|
|
print('[EquipmentProvider] Mapped ${_equipment.length} equipment models');
|
|
|
|
// Extraire les modèles et marques uniques
|
|
_extractUniqueValues();
|
|
|
|
_isInitialized = true;
|
|
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
print('[EquipmentProvider] Equipment loading complete');
|
|
} catch (e) {
|
|
print('[EquipmentProvider] Error loading equipments: $e');
|
|
_isLoading = false;
|
|
notifyListeners();
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Charge plusieurs équipements par leurs IDs (optimisé pour les détails d'événement)
|
|
Future<List<EquipmentModel>> getEquipmentsByIds(List<String> equipmentIds) async {
|
|
if (equipmentIds.isEmpty) return [];
|
|
|
|
print('[EquipmentProvider] Loading ${equipmentIds.length} equipments by IDs...');
|
|
|
|
try {
|
|
// Vérifier d'abord le cache local
|
|
final cachedEquipments = <EquipmentModel>[];
|
|
final missingIds = <String>[];
|
|
|
|
for (final id in equipmentIds) {
|
|
final cached = _equipment.firstWhere(
|
|
(eq) => eq.id == id,
|
|
orElse: () => EquipmentModel(
|
|
id: '',
|
|
name: '',
|
|
category: EquipmentCategory.other,
|
|
status: EquipmentStatus.available,
|
|
maintenanceIds: [],
|
|
createdAt: DateTime.now(),
|
|
updatedAt: DateTime.now(),
|
|
),
|
|
);
|
|
|
|
if (cached.id.isNotEmpty) {
|
|
cachedEquipments.add(cached);
|
|
} else {
|
|
missingIds.add(id);
|
|
}
|
|
}
|
|
|
|
print('[EquipmentProvider] Found ${cachedEquipments.length} in cache, ${missingIds.length} missing');
|
|
|
|
// Si tous sont en cache, retourner directement
|
|
if (missingIds.isEmpty) {
|
|
return cachedEquipments;
|
|
}
|
|
|
|
// Charger les manquants depuis l'API
|
|
final equipmentsData = await _dataService.getEquipmentsByIds(missingIds);
|
|
|
|
final loadedEquipments = equipmentsData.map((data) {
|
|
return EquipmentModel.fromMap(data, data['id'] as String);
|
|
}).toList();
|
|
|
|
// Ajouter au cache
|
|
for (final eq in loadedEquipments) {
|
|
if (!_equipment.any((e) => e.id == eq.id)) {
|
|
_equipment.add(eq);
|
|
}
|
|
}
|
|
|
|
print('[EquipmentProvider] Loaded ${loadedEquipments.length} equipments from API');
|
|
|
|
// Retourner tous les équipements (cache + chargés)
|
|
return [...cachedEquipments, ...loadedEquipments];
|
|
} catch (e) {
|
|
print('[EquipmentProvider] Error loading equipments by IDs: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Extraire modèles et marques uniques
|
|
void _extractUniqueValues() {
|
|
final modelSet = <String>{};
|
|
final brandSet = <String>{};
|
|
|
|
for (final eq in _equipment) {
|
|
if (eq.model != null && eq.model!.isNotEmpty) {
|
|
modelSet.add(eq.model!);
|
|
}
|
|
if (eq.brand != null && eq.brand!.isNotEmpty) {
|
|
brandSet.add(eq.brand!);
|
|
}
|
|
}
|
|
|
|
_models = modelSet.toList()..sort();
|
|
_brands = brandSet.toList()..sort();
|
|
}
|
|
|
|
/// Obtenir les équipements filtrés
|
|
List<EquipmentModel> get _filteredEquipment {
|
|
var filtered = _equipment;
|
|
|
|
if (_selectedCategory != null) {
|
|
filtered = filtered.where((eq) => eq.category == _selectedCategory).toList();
|
|
}
|
|
|
|
if (_selectedStatus != null) {
|
|
filtered = filtered.where((eq) => eq.status == _selectedStatus).toList();
|
|
}
|
|
|
|
if (_selectedModel != null && _selectedModel!.isNotEmpty) {
|
|
filtered = filtered.where((eq) => eq.model == _selectedModel).toList();
|
|
}
|
|
|
|
if (_searchQuery.isNotEmpty) {
|
|
final query = _searchQuery.toLowerCase();
|
|
filtered = filtered.where((eq) {
|
|
return eq.name.toLowerCase().contains(query) ||
|
|
eq.id.toLowerCase().contains(query) ||
|
|
(eq.model?.toLowerCase().contains(query) ?? false) ||
|
|
(eq.brand?.toLowerCase().contains(query) ?? false);
|
|
}).toList();
|
|
}
|
|
|
|
return filtered;
|
|
}
|
|
|
|
/// Définir le filtre de catégorie
|
|
void setSelectedCategory(EquipmentCategory? category) {
|
|
_selectedCategory = category;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Définir le filtre de statut
|
|
void setSelectedStatus(EquipmentStatus? status) {
|
|
_selectedStatus = status;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Définir le filtre de modèle
|
|
void setSelectedModel(String? model) {
|
|
_selectedModel = model;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Définir la requête de recherche
|
|
void setSearchQuery(String query) {
|
|
_searchQuery = query;
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Réinitialiser tous les filtres
|
|
void clearFilters() {
|
|
_selectedCategory = null;
|
|
_selectedStatus = null;
|
|
_selectedModel = null;
|
|
_searchQuery = '';
|
|
notifyListeners();
|
|
}
|
|
|
|
/// Recharger les équipements
|
|
Future<void> refresh() async {
|
|
await loadEquipments();
|
|
}
|
|
|
|
// === MÉTHODES STREAM (COMPATIBILITÉ) ===
|
|
|
|
/// Stream des équipements (pour compatibilité avec ancien code)
|
|
Stream<List<EquipmentModel>> get equipmentStream async* {
|
|
yield _equipment;
|
|
}
|
|
|
|
/// Supprimer un équipement
|
|
Future<void> deleteEquipment(String equipmentId) async {
|
|
try {
|
|
await _dataService.deleteEquipment(equipmentId);
|
|
await loadEquipments(); // Recharger la liste
|
|
} catch (e) {
|
|
print('Error deleting equipment: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Ajouter un équipement
|
|
Future<void> addEquipment(EquipmentModel equipment) async {
|
|
try {
|
|
await _dataService.createEquipment(equipment.id, equipment.toMap());
|
|
await loadEquipments(); // Recharger la liste
|
|
} catch (e) {
|
|
print('Error adding equipment: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Mettre à jour un équipement
|
|
Future<void> updateEquipment(EquipmentModel equipment) async {
|
|
try {
|
|
await _dataService.updateEquipment(equipment.id, equipment.toMap());
|
|
await loadEquipments(); // Recharger la liste
|
|
} catch (e) {
|
|
print('Error updating equipment: $e');
|
|
rethrow;
|
|
}
|
|
}
|
|
|
|
/// Charger les marques
|
|
Future<void> loadBrands() async {
|
|
// Les marques sont déjà chargées avec loadEquipments
|
|
_extractUniqueValues();
|
|
}
|
|
|
|
/// Charger les modèles
|
|
Future<void> loadModels() async {
|
|
// Les modèles sont déjà chargés avec loadEquipments
|
|
_extractUniqueValues();
|
|
}
|
|
|
|
/// Charger les modèles d'une marque spécifique
|
|
Future<List<String>> loadModelsByBrand(String brand) async {
|
|
// Filtrer les modèles par marque
|
|
final modelsByBrand = _equipment
|
|
.where((eq) => eq.brand == brand && eq.model != null)
|
|
.map((eq) => eq.model!)
|
|
.toSet()
|
|
.toList();
|
|
return modelsByBrand;
|
|
}
|
|
|
|
/// Charger les sous-catégories d'une catégorie spécifique
|
|
Future<List<String>> loadSubCategoriesByCategory(EquipmentCategory category) async {
|
|
// Filtrer les sous-catégories par catégorie
|
|
final subCategoriesByCategory = _equipment
|
|
.where((eq) => eq.category == category && eq.subCategory != null && eq.subCategory!.isNotEmpty)
|
|
.map((eq) => eq.subCategory!)
|
|
.toSet()
|
|
.toList()
|
|
..sort();
|
|
return subCategoriesByCategory;
|
|
}
|
|
|
|
/// Calculer le statut réel d'un équipement (compatibilité)
|
|
Future<EquipmentStatus> calculateRealStatus(EquipmentModel equipment) async {
|
|
// Pour l'instant, retourner le statut stocké
|
|
// TODO: Implémenter le calcul réel si nécessaire
|
|
return equipment.status;
|
|
}
|
|
}
|
|
|