feat: merge branche IA (beta) - Intégration assistant IA logisticien Gemini
This commit is contained in:
@@ -7,6 +7,8 @@ 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;
|
||||
@@ -650,25 +652,86 @@ class _EquipmentSelectorDialog extends StatefulWidget {
|
||||
|
||||
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;
|
||||
late final Future<void> _loadingFuture;
|
||||
|
||||
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);
|
||||
_loadingFuture = widget.equipmentProvider.loadEquipments();
|
||||
_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(
|
||||
@@ -718,6 +781,7 @@ class _EquipmentSelectorDialogState extends State<_EquipmentSelectorDialog> {
|
||||
setState(() {
|
||||
_searchQuery = '';
|
||||
});
|
||||
_reloadData();
|
||||
},
|
||||
)
|
||||
: null,
|
||||
@@ -726,6 +790,7 @@ class _EquipmentSelectorDialogState extends State<_EquipmentSelectorDialog> {
|
||||
setState(() {
|
||||
_searchQuery = value;
|
||||
});
|
||||
_reloadData();
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
@@ -743,6 +808,7 @@ class _EquipmentSelectorDialogState extends State<_EquipmentSelectorDialog> {
|
||||
setState(() {
|
||||
_filterCategory = null;
|
||||
});
|
||||
_reloadData();
|
||||
},
|
||||
selectedColor: AppColors.rouge,
|
||||
labelStyle: TextStyle(
|
||||
@@ -761,6 +827,7 @@ class _EquipmentSelectorDialogState extends State<_EquipmentSelectorDialog> {
|
||||
setState(() {
|
||||
_filterCategory = selected ? category : null;
|
||||
});
|
||||
_reloadData();
|
||||
},
|
||||
selectedColor: AppColors.rouge,
|
||||
labelStyle: TextStyle(
|
||||
@@ -780,7 +847,7 @@ class _EquipmentSelectorDialogState extends State<_EquipmentSelectorDialog> {
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.rouge.withOpacity(0.1),
|
||||
color: AppColors.rouge.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
@@ -798,90 +865,62 @@ class _EquipmentSelectorDialogState extends State<_EquipmentSelectorDialog> {
|
||||
|
||||
// Liste des équipements
|
||||
Expanded(
|
||||
child: FutureBuilder<void>(
|
||||
future: _loadingFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (snapshot.hasError) {
|
||||
return Center(child: Text('Erreur: ${snapshot.error}'));
|
||||
}
|
||||
|
||||
var equipment = List<EquipmentModel>.from(
|
||||
widget.equipmentProvider.allEquipment,
|
||||
);
|
||||
|
||||
// Filtrer par catégorie
|
||||
if (_filterCategory != null) {
|
||||
equipment = equipment
|
||||
.where((e) => e.category == _filterCategory)
|
||||
.toList();
|
||||
}
|
||||
|
||||
// Filtrer par recherche
|
||||
if (_searchQuery.isNotEmpty) {
|
||||
final query = _searchQuery.toLowerCase();
|
||||
equipment = equipment.where((e) {
|
||||
return e.id.toLowerCase().contains(query) ||
|
||||
(e.brand?.toLowerCase().contains(query) ?? false) ||
|
||||
(e.model?.toLowerCase().contains(query) ?? false);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
if (equipment.isEmpty) {
|
||||
return const Center(
|
||||
child: Text('Aucun équipement trouvé'),
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
itemCount: equipment.length,
|
||||
itemBuilder: (context, index) {
|
||||
final item = equipment[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,
|
||||
),
|
||||
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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
secondary: Icon(
|
||||
_getCategoryIcon(item.category),
|
||||
color: AppColors.rouge,
|
||||
),
|
||||
activeColor: AppColors.rouge,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user