import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:em2rp/utils/colors.dart'; import 'package:em2rp/utils/permission_gate.dart'; import 'package:em2rp/views/widgets/nav/main_drawer.dart'; import 'package:em2rp/views/widgets/nav/custom_app_bar.dart'; import 'package:em2rp/providers/container_provider.dart'; import 'package:em2rp/models/container_model.dart'; import 'package:em2rp/models/equipment_model.dart'; import 'package:em2rp/services/container_pdf_generator_service.dart'; import 'package:em2rp/views/widgets/common/qr_code_dialog.dart'; import 'package:em2rp/mixins/selection_mode_mixin.dart'; import 'package:printing/printing.dart'; import 'package:pdf/pdf.dart'; class ContainerManagementPage extends StatefulWidget { const ContainerManagementPage({super.key}); @override State createState() => _ContainerManagementPageState(); } class _ContainerManagementPageState extends State with SelectionModeMixin { final TextEditingController _searchController = TextEditingController(); ContainerType? _selectedType; EquipmentStatus? _selectedStatus; @override void dispose() { _searchController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final isMobile = MediaQuery.of(context).size.width < 800; return PermissionGate( requiredPermissions: const ['view_equipment'], fallback: Scaffold( appBar: const CustomAppBar(title: 'Accès refusé'), drawer: const MainDrawer(currentPage: '/container_management'), body: const Center( child: Padding( padding: EdgeInsets.all(24.0), child: Text( 'Vous n\'avez pas les permissions nécessaires pour accéder à la gestion des containers.', textAlign: TextAlign.center, style: TextStyle(fontSize: 16), ), ), ), ), child: Scaffold( appBar: isSelectionMode ? AppBar( backgroundColor: AppColors.rouge, leading: IconButton( icon: const Icon(Icons.close, color: Colors.white), onPressed: toggleSelectionMode, ), title: Text( '$selectedCount sélectionné(s)', style: const TextStyle(color: Colors.white), ), actions: [ if (hasSelection) ...[ IconButton( icon: const Icon(Icons.qr_code, color: Colors.white), tooltip: 'Générer QR Codes', onPressed: _generateQRCodesForSelected, ), IconButton( icon: const Icon(Icons.delete, color: Colors.white), tooltip: 'Supprimer', onPressed: _deleteSelectedContainers, ), ], ], ) : const CustomAppBar(title: 'Gestion des Containers'), drawer: const MainDrawer(currentPage: '/container_management'), floatingActionButton: !isSelectionMode ? FloatingActionButton.extended( onPressed: () => _navigateToForm(context), backgroundColor: AppColors.rouge, icon: const Icon(Icons.add, color: Colors.white), label: const Text( 'Nouveau Container', style: TextStyle(color: Colors.white), ), ) : null, body: isMobile ? _buildMobileLayout() : _buildDesktopLayout(), ), ); } Widget _buildMobileLayout() { return Column( children: [ _buildSearchBar(), _buildMobileFilters(), Expanded(child: _buildContainerList()), ], ); } Widget _buildDesktopLayout() { return Row( children: [ SizedBox( width: 250, child: _buildSidebar(), ), const VerticalDivider(width: 1, thickness: 1), Expanded( child: Column( children: [ _buildSearchBar(), Expanded(child: _buildContainerList()), ], ), ), ], ); } Widget _buildSearchBar() { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.1), spreadRadius: 1, blurRadius: 3, offset: const Offset(0, 1), ), ], ), child: Row( children: [ Expanded( child: TextField( controller: _searchController, decoration: InputDecoration( hintText: 'Rechercher un container...', prefixIcon: const Icon(Icons.search, color: AppColors.rouge), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: Colors.grey.shade300), ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), ), onChanged: (value) { context.read().setSearchQuery(value); }, ), ), const SizedBox(width: 12), if (!isSelectionMode) IconButton( icon: const Icon(Icons.checklist, color: AppColors.rouge), tooltip: 'Mode sélection', onPressed: toggleSelectionMode, ), ], ), ); } Widget _buildMobileFilters() { return Container( padding: const EdgeInsets.symmetric(vertical: 8), color: Colors.grey.shade50, child: SingleChildScrollView( scrollDirection: Axis.horizontal, padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ _buildTypeChip(null, 'Tous'), const SizedBox(width: 8), ...ContainerType.values.map((type) { return Padding( padding: const EdgeInsets.only(right: 8), child: _buildTypeChip(type, type.label), ); }), ], ), ), ); } Widget _buildTypeChip(ContainerType? type, String label) { final isSelected = _selectedType == type; final color = isSelected ? Colors.white : AppColors.noir; return ChoiceChip( label: Row( mainAxisSize: MainAxisSize.min, children: [ if (type != null) ...[ type.getIcon(size: 16, color: color), const SizedBox(width: 8), ], Text(label), ], ), selected: isSelected, onSelected: (selected) { setState(() { _selectedType = selected ? type : null; context.read().setSelectedType(_selectedType); }); }, selectedColor: AppColors.rouge, labelStyle: TextStyle( color: color, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, ), ); } Widget _buildSidebar() { return Container( color: Colors.grey.shade50, child: ListView( padding: const EdgeInsets.all(16), children: [ Text( 'Filtres', style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, color: AppColors.noir, ), ), const SizedBox(height: 16), // Filtre par type Text( 'Type de container', style: Theme.of(context).textTheme.titleSmall?.copyWith( fontWeight: FontWeight.bold, color: AppColors.noir, ), ), const SizedBox(height: 8), _buildFilterOption(null, 'Tous les types'), ...ContainerType.values.map((type) { return _buildFilterOption(type, type.label); }), const Divider(height: 32), // Filtre par statut Text( 'Statut', style: Theme.of(context).textTheme.titleSmall?.copyWith( fontWeight: FontWeight.bold, color: AppColors.noir, ), ), const SizedBox(height: 8), _buildStatusFilter(null, 'Tous les statuts'), _buildStatusFilter(EquipmentStatus.available, 'Disponible'), _buildStatusFilter(EquipmentStatus.inUse, 'En prestation'), _buildStatusFilter(EquipmentStatus.maintenance, 'En maintenance'), _buildStatusFilter(EquipmentStatus.outOfService, 'Hors service'), ], ), ); } Widget _buildFilterOption(ContainerType? type, String label) { final isSelected = _selectedType == type; return RadioListTile( title: Text(label), value: type, groupValue: _selectedType, activeColor: AppColors.rouge, dense: true, contentPadding: EdgeInsets.zero, onChanged: (value) { setState(() { _selectedType = value; context.read().setSelectedType(_selectedType); }); }, ); } Widget _buildStatusFilter(EquipmentStatus? status, String label) { final isSelected = _selectedStatus == status; return RadioListTile( title: Text(label), value: status, groupValue: _selectedStatus, activeColor: AppColors.rouge, dense: true, contentPadding: EdgeInsets.zero, onChanged: (value) { setState(() { _selectedStatus = value; context.read().setSelectedStatus(_selectedStatus); }); }, ); } Widget _buildContainerList() { return Consumer( builder: (context, provider, child) { return StreamBuilder>( stream: provider.containersStream, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } if (snapshot.hasError) { return Center( child: Text('Erreur: ${snapshot.error}'), ); } final containers = snapshot.data ?? []; if (containers.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.inventory_2_outlined, size: 80, color: Colors.grey.shade400, ), const SizedBox(height: 16), Text( 'Aucun container trouvé', style: TextStyle( fontSize: 18, color: Colors.grey.shade600, ), ), ], ), ); } return ListView.builder( padding: const EdgeInsets.all(16), itemCount: containers.length, itemBuilder: (context, index) { final container = containers[index]; return _buildContainerCard(container); }, ); }, ); }, ); } Widget _buildContainerCard(ContainerModel container) { final isSelected = isItemSelected(container.id); return Card( margin: const EdgeInsets.only(bottom: 12), elevation: 2, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: isSelected ? const BorderSide(color: AppColors.rouge, width: 2) : BorderSide.none, ), child: InkWell( onTap: () { if (isSelectionMode) { toggleItemSelection(container.id); } else { _viewContainerDetails(container); } }, onLongPress: () { if (!isSelectionMode) { toggleSelectionMode(); toggleItemSelection(container.id); } }, borderRadius: BorderRadius.circular(8), child: Padding( padding: const EdgeInsets.all(16), child: Row( children: [ if (isSelectionMode) Padding( padding: const EdgeInsets.only(right: 16), child: Checkbox( value: isSelected, onChanged: (value) { toggleItemSelection(container.id); }, activeColor: AppColors.rouge, ), ), // Icône du type de container container.type.getIcon( size: 40, color: AppColors.rouge, ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( container.id, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), ), const SizedBox(height: 4), Text( container.name, style: TextStyle( fontSize: 14, color: Colors.grey.shade700, ), ), const SizedBox(height: 4), Row( children: [ _buildInfoChip( container.type.label, Icons.category, ), const SizedBox(width: 8), _buildInfoChip( '${container.itemCount} items', Icons.inventory, ), ], ), ], ), ), const SizedBox(width: 16), // Badge de statut _buildStatusBadge(container.status), if (!isSelectionMode) ...[ const SizedBox(width: 8), PopupMenuButton( icon: const Icon(Icons.more_vert), onSelected: (value) => _handleMenuAction(value, container), itemBuilder: (context) => [ const PopupMenuItem( value: 'view', child: Row( children: [ Icon(Icons.visibility, size: 20), SizedBox(width: 8), Text('Voir détails'), ], ), ), const PopupMenuItem( value: 'edit', child: Row( children: [ Icon(Icons.edit, size: 20), SizedBox(width: 8), Text('Modifier'), ], ), ), const PopupMenuItem( value: 'qr', child: Row( children: [ Icon(Icons.qr_code, size: 20), SizedBox(width: 8), Text('QR Code'), ], ), ), const PopupMenuItem( value: 'delete', child: Row( children: [ Icon(Icons.delete, color: Colors.red, size: 20), SizedBox(width: 8), Text('Supprimer', style: TextStyle(color: Colors.red)), ], ), ), ], ), ], ], ), ), ), ); } Widget _buildInfoChip(String label, IconData icon) { return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 14, color: Colors.grey.shade700), const SizedBox(width: 4), Text( label, style: TextStyle( fontSize: 12, color: Colors.grey.shade700, ), ), ], ), ); } Widget _buildStatusBadge(EquipmentStatus status) { return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: status.color.withOpacity(0.1), borderRadius: BorderRadius.circular(16), border: Border.all(color: status.color), ), child: Text( status.label, style: TextStyle( color: status.color, fontWeight: FontWeight.bold, fontSize: 12, ), ), ); } void _handleMenuAction(String action, ContainerModel container) { switch (action) { case 'view': _viewContainerDetails(container); break; case 'edit': _editContainer(container); break; case 'qr': showDialog( context: context, builder: (context) => QRCodeDialog.forContainer(container), ); break; case 'delete': _deleteContainer(container); break; } } void _navigateToForm(BuildContext context) async { final result = await Navigator.pushNamed(context, '/container_form'); if (result == true) { // Rafraîchir la liste } } void _viewContainerDetails(ContainerModel container) async { await Navigator.pushNamed( context, '/container_detail', arguments: container, ); } void _editContainer(ContainerModel container) async { await Navigator.pushNamed( context, '/container_form', arguments: container, ); } Future _generateQRCodesForSelected() async { if (!hasSelection) return; // Récupérer les containers sélectionnés final containerProvider = context.read(); final List selectedContainers = []; final Map> containerEquipmentMap = {}; for (final id in selectedIds) { final container = await containerProvider.getContainerById(id); if (container != null) { selectedContainers.add(container); // Charger les équipements pour ce container final equipment = await containerProvider.getContainerEquipment(id); containerEquipmentMap[id] = equipment; } } if (selectedContainers.isEmpty) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Aucun container trouvé')), ); } return; } // Afficher le dialogue de sélection de format final format = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Format des étiquettes'), content: Column( mainAxisSize: MainAxisSize.min, children: [ ListTile( leading: const Icon(Icons.qr_code_2), title: const Text('Petits QR codes'), subtitle: const Text('2×2 cm - QR code + ID (20 par page)'), onTap: () => Navigator.pop(context, ContainerQRLabelFormat.small), ), ListTile( leading: const Icon(Icons.qr_code), title: const Text('QR codes moyens'), subtitle: const Text('4×4 cm - QR code + ID (6 par page)'), onTap: () => Navigator.pop(context, ContainerQRLabelFormat.medium), ), ListTile( leading: const Icon(Icons.label), title: const Text('Grandes étiquettes'), subtitle: const Text('QR code + ID + Type + Contenu (6 par page)'), onTap: () => Navigator.pop(context, ContainerQRLabelFormat.large), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Annuler'), ), ], ), ); if (format == null || !mounted) return; // Générer et afficher le PDF try { final pdfBytes = await ContainerPDFGeneratorService.generateQRCodesPDF( containerList: selectedContainers, containerEquipmentMap: containerEquipmentMap, format: format, ); if (mounted) { await Printing.layoutPdf( onLayout: (PdfPageFormat format) async => pdfBytes, ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Erreur lors de la génération: $e')), ); } } } Future _deleteContainer(ContainerModel container) async { final confirm = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Confirmer la suppression'), content: Text( 'Êtes-vous sûr de vouloir supprimer le container "${container.name}" ?\n\n' 'Cette action est irréversible.', ), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('Annuler'), ), ElevatedButton( onPressed: () => Navigator.pop(context, true), style: ElevatedButton.styleFrom(backgroundColor: Colors.red), child: const Text('Supprimer'), ), ], ), ); if (confirm == true && mounted) { try { await context.read().deleteContainer(container.id); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Container supprimé avec succès')), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Erreur lors de la suppression: $e')), ); } } } } Future _deleteSelectedContainers() async { final confirm = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Confirmer la suppression'), content: Text( 'Êtes-vous sûr de vouloir supprimer $selectedCount container(s) ?\n\n' 'Cette action est irréversible.', ), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('Annuler'), ), ElevatedButton( onPressed: () => Navigator.pop(context, true), style: ElevatedButton.styleFrom(backgroundColor: Colors.red), child: const Text('Supprimer'), ), ], ), ); if (confirm == true && mounted) { try { final provider = context.read(); for (final id in selectedIds) { await provider.deleteContainer(id); } disableSelectionMode(); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Containers supprimés avec succès')), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Erreur lors de la suppression: $e')), ); } } } } }