Files
EM2_ERP/em2rp/lib/views/container_form_page.dart
T
ElPoyo 89ab3673c4 ### Key Changes:
**AI Equipment Proposal (`functions/aiEquipmentProposal.js`):**
- Updated Gemini model to `gemini-3.1-flash-lite-preview` and updated the API key.
- Increased `MAX_TOOL_ITERATIONS` from 12 to 20.
- Added a new tool `list_equipment_by_category` to allow the AI to browse equipment when specific searches fail.
- Enhanced the system prompt with instructions to handle typos via category exploration and authorized more creative equipment suggestions based on event descriptions.
- Improved the user prompt to include more event context (name, location, notes, and options).
- Set `responseMimeType: 'application/json'` in the generation config for better reliability.
- Improved error logging and user-facing error messages for timeouts.

**UI & Pagination (`lib/views/`):**
- **ContainerFormPage**: Replaced `StreamBuilder` with a paginated list using `DataService` for equipment selection. Added a scroll controller to support infinite scrolling and updated UI colors to use the newer `withValues` API.
- **EquipmentSelectionDialog**:
    - Increased pagination limit from 25 to 50 items.
    - Implemented `_checkIfMoreItemsNeeded` logic to automatically fetch more pages if filters (like hiding conflicting items) leave the view too empty.
    - Added a `NotificationListener` to the `ListView` to trigger pagination on scroll.
    - Fixed minor encoding issues in comments.

---

### Proposed Commit Message:

feat: Mise à jour du modèle Gemini et optimisation de la sélection du matériel avec pagination

- Mise à jour du modèle d'IA vers `gemini-3.1-flash-lite-preview` et augmentation de la limite d'itérations des outils à 20.
- Ajout de l'outil `list_equipment_by_category` pour permettre à l'IA d'explorer les alternatives en cas d'échec de recherche textuelle.
- Enrichissement du prompt système et du contexte envoyé à l'IA (nom, lieu, notes et options de l'événement).
- Implémentation de la pagination dans `ContainerFormPage` pour la sélection d'équipements afin d'améliorer les performances.
- Optimisation de `EquipmentSelectionDialog` avec chargement automatique des pages suivantes si les filtres réduisent trop la liste visible.
- Passage à `withValues` pour la gestion des couleurs et amélioration de la gestion des erreurs et du logging.
2026-03-30 12:32:33 +02:00

997 lines
33 KiB
Dart

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