import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:em2rp/models/equipment_model.dart'; import 'package:em2rp/models/maintenance_model.dart'; import 'package:em2rp/providers/equipment_provider.dart'; import 'package:em2rp/providers/local_user_provider.dart'; import 'package:em2rp/services/equipment_service.dart'; import 'package:em2rp/services/qr_code_service.dart'; import 'package:em2rp/utils/colors.dart'; import 'package:em2rp/utils/permission_gate.dart'; import 'package:em2rp/views/widgets/nav/custom_app_bar.dart'; import 'package:em2rp/views/equipment_form_page.dart'; import 'package:em2rp/views/widgets/equipment/equipment_parent_containers.dart'; import 'package:intl/intl.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:printing/printing.dart'; class EquipmentDetailPage extends StatefulWidget { final EquipmentModel equipment; const EquipmentDetailPage({super.key, required this.equipment}); @override State createState() => _EquipmentDetailPageState(); } class _EquipmentDetailPageState extends State { final EquipmentService _equipmentService = EquipmentService(); List _maintenances = []; bool _isLoadingMaintenances = true; @override void initState() { super.initState(); _loadMaintenances(); } Future _loadMaintenances() async { try { final maintenances = await _equipmentService.getMaintenancesForEquipment(widget.equipment.id); setState(() { _maintenances = maintenances; _isLoadingMaintenances = false; }); } catch (e) { setState(() { _isLoadingMaintenances = false; }); } } @override Widget build(BuildContext context) { final isMobile = MediaQuery.of(context).size.width < 800; final userProvider = Provider.of(context); final hasManagePermission = userProvider.hasPermission('manage_equipment'); return Scaffold( appBar: CustomAppBar( title: widget.equipment.id, actions: [ IconButton( icon: const Icon(Icons.qr_code), tooltip: 'Générer QR Code', onPressed: _showQRCode, ), if (hasManagePermission) IconButton( icon: const Icon(Icons.edit), tooltip: 'Modifier', onPressed: _editEquipment, ), if (hasManagePermission) IconButton( icon: const Icon(Icons.delete, color: Colors.red), tooltip: 'Supprimer', onPressed: _deleteEquipment, ), ], ), body: SingleChildScrollView( padding: EdgeInsets.all(isMobile ? 16 : 24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildHeader(), const SizedBox(height: 24), _buildMainInfoSection(), const SizedBox(height: 24), if (hasManagePermission) ...[ _buildPriceSection(), const SizedBox(height: 24), ], if (widget.equipment.category == EquipmentCategory.consumable || widget.equipment.category == EquipmentCategory.cable) ...[ _buildQuantitySection(), const SizedBox(height: 24), ], if (widget.equipment.parentBoxIds.isNotEmpty) ...[ EquipmentParentContainers( parentBoxIds: widget.equipment.parentBoxIds, ), const SizedBox(height: 24), ], _buildDatesSection(), const SizedBox(height: 24), if (widget.equipment.notes != null && widget.equipment.notes!.isNotEmpty) ...[ _buildNotesSection(), const SizedBox(height: 24), ], _buildMaintenanceHistorySection(hasManagePermission), const SizedBox(height: 24), _buildAssociatedEventsSection(), ], ), ), ); } Widget _buildHeader() { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( gradient: LinearGradient( colors: [AppColors.rouge, AppColors.rouge.withValues(alpha: 0.8)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: AppColors.rouge.withValues(alpha: 0.3), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ CircleAvatar( backgroundColor: Colors.white, radius: 30, child: Icon( _getCategoryIcon(widget.equipment.category), color: AppColors.rouge, size: 32, ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.equipment.id, style: const TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white, ), ), const SizedBox(height: 4), Text( '${widget.equipment.brand ?? ''} ${widget.equipment.model ?? ''}'.trim().isNotEmpty ? '${widget.equipment.brand ?? ''} ${widget.equipment.model ?? ''}'.trim() : 'Marque/Modèle non défini', style: const TextStyle( fontSize: 16, color: Colors.white70, ), ), ], ), ), if (widget.equipment.category != EquipmentCategory.consumable && widget.equipment.category != EquipmentCategory.cable) _buildStatusBadge(), ], ), ], ), ); } Widget _buildStatusBadge() { final statusInfo = _getStatusInfo(widget.equipment.status); return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 8, height: 8, decoration: BoxDecoration( color: statusInfo.$2, shape: BoxShape.circle, ), ), const SizedBox(width: 8), Text( statusInfo.$1, style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: statusInfo.$2, ), ), ], ), ); } Widget _buildMainInfoSection() { return Card( elevation: 2, child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.info_outline, color: AppColors.rouge), const SizedBox(width: 8), Text( 'Informations principales', style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, ), ), ], ), const Divider(height: 24), _buildInfoRow('Catégorie', _getCategoryName(widget.equipment.category)), if (widget.equipment.brand != null && widget.equipment.brand!.isNotEmpty) _buildInfoRow('Marque', widget.equipment.brand!), if (widget.equipment.model != null && widget.equipment.model!.isNotEmpty) _buildInfoRow('Modèle', widget.equipment.model!), if (widget.equipment.category != EquipmentCategory.consumable && widget.equipment.category != EquipmentCategory.cable) _buildInfoRow('Statut', _getStatusInfo(widget.equipment.status).$1), ], ), ), ); } Widget _buildPriceSection() { final hasPrices = widget.equipment.purchasePrice != null || widget.equipment.rentalPrice != null; if (!hasPrices) return const SizedBox.shrink(); return Card( elevation: 2, child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.euro, color: AppColors.rouge), const SizedBox(width: 8), Text( 'Prix', style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, ), ), ], ), const Divider(height: 24), if (widget.equipment.purchasePrice != null) _buildInfoRow( 'Prix d\'achat', '${widget.equipment.purchasePrice!.toStringAsFixed(2)} €', ), if (widget.equipment.rentalPrice != null) _buildInfoRow( 'Prix de location', '${widget.equipment.rentalPrice!.toStringAsFixed(2)} €/jour', ), ], ), ), ); } Widget _buildQuantitySection() { final availableQty = widget.equipment.availableQuantity ?? 0; final totalQty = widget.equipment.totalQuantity ?? 0; final criticalThreshold = widget.equipment.criticalThreshold ?? 0; final isCritical = criticalThreshold > 0 && availableQty <= criticalThreshold; return Card( elevation: 2, color: isCritical ? Colors.red.shade50 : null, child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( isCritical ? Icons.warning : Icons.inventory, color: isCritical ? Colors.red : AppColors.rouge, ), const SizedBox(width: 8), Text( 'Quantités', style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, color: isCritical ? Colors.red : null, ), ), if (isCritical) ...[ const SizedBox(width: 12), Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), decoration: BoxDecoration( color: Colors.red, borderRadius: BorderRadius.circular(12), ), child: const Text( 'STOCK CRITIQUE', style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 12, ), ), ), ], ], ), const Divider(height: 24), _buildInfoRow( 'Quantité disponible', availableQty.toString(), valueColor: isCritical ? Colors.red : null, valueWeight: isCritical ? FontWeight.bold : null, ), _buildInfoRow('Quantité totale', totalQty.toString()), if (criticalThreshold > 0) _buildInfoRow('Seuil critique', criticalThreshold.toString()), ], ), ), ); } Widget _buildDatesSection() { return Card( elevation: 2, child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.calendar_today, color: AppColors.rouge), const SizedBox(width: 8), Text( 'Dates', style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, ), ), ], ), const Divider(height: 24), if (widget.equipment.purchaseDate != null) _buildInfoRow( 'Date d\'achat', DateFormat('dd/MM/yyyy').format(widget.equipment.purchaseDate!), ), if (widget.equipment.lastMaintenanceDate != null) _buildInfoRow( 'Dernière maintenance', DateFormat('dd/MM/yyyy').format(widget.equipment.lastMaintenanceDate!), ), if (widget.equipment.nextMaintenanceDate != null) _buildInfoRow( 'Prochaine maintenance', DateFormat('dd/MM/yyyy').format(widget.equipment.nextMaintenanceDate!), valueColor: widget.equipment.nextMaintenanceDate!.isBefore(DateTime.now()) ? Colors.red : null, ), _buildInfoRow( 'Créé le', DateFormat('dd/MM/yyyy à HH:mm').format(widget.equipment.createdAt), ), _buildInfoRow( 'Modifié le', DateFormat('dd/MM/yyyy à HH:mm').format(widget.equipment.updatedAt), ), ], ), ), ); } Widget _buildNotesSection() { return Card( elevation: 2, child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.notes, color: AppColors.rouge), const SizedBox(width: 8), Text( 'Notes', style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, ), ), ], ), const Divider(height: 24), Text( widget.equipment.notes!, style: const TextStyle(fontSize: 14), ), ], ), ), ); } Widget _buildMaintenanceHistorySection(bool hasManagePermission) { return Card( elevation: 2, child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.build, color: AppColors.rouge), const SizedBox(width: 8), Expanded( child: Text( 'Historique des maintenances', style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, ), ), ), ], ), const Divider(height: 24), if (_isLoadingMaintenances) const Center(child: CircularProgressIndicator()) else if (_maintenances.isEmpty) const Padding( padding: EdgeInsets.all(16.0), child: Center( child: Text( 'Aucune maintenance enregistrée', style: TextStyle(color: Colors.grey), ), ), ) else ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: _maintenances.length, separatorBuilder: (context, index) => const Divider(), itemBuilder: (context, index) { final maintenance = _maintenances[index]; return _buildMaintenanceItem(maintenance, hasManagePermission); }, ), ], ), ), ); } Widget _buildMaintenanceItem(MaintenanceModel maintenance, bool showCost) { final isCompleted = maintenance.completedDate != null; final typeInfo = _getMaintenanceTypeInfo(maintenance.type); return ListTile( contentPadding: const EdgeInsets.symmetric(vertical: 8), leading: CircleAvatar( backgroundColor: isCompleted ? Colors.green.withValues(alpha: 0.2) : Colors.orange.withValues(alpha: 0.2), child: Icon( isCompleted ? Icons.check_circle : Icons.schedule, color: isCompleted ? Colors.green : Colors.orange, ), ), title: Text( maintenance.name, style: const TextStyle(fontWeight: FontWeight.bold), ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 4), Row( children: [ Icon(typeInfo.$2, size: 16, color: Colors.grey[600]), const SizedBox(width: 4), Text(typeInfo.$1, style: TextStyle(color: Colors.grey[600], fontSize: 12)), ], ), const SizedBox(height: 4), Text( isCompleted ? 'Effectuée le ${DateFormat('dd/MM/yyyy').format(maintenance.completedDate!)}' : 'Planifiée le ${DateFormat('dd/MM/yyyy').format(maintenance.scheduledDate)}', style: TextStyle(fontSize: 12, color: Colors.grey[700]), ), if (showCost && maintenance.cost != null) ...[ const SizedBox(height: 4), Text( 'Coût: ${maintenance.cost!.toStringAsFixed(2)} €', style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500), ), ], ], ), ); } Widget _buildAssociatedEventsSection() { return Card( elevation: 2, child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.event, color: AppColors.rouge), const SizedBox(width: 8), Text( 'Événements associés', style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, ), ), ], ), const Divider(height: 24), const Padding( padding: EdgeInsets.all(16.0), child: Center( child: Text( 'Fonctionnalité à implémenter', style: TextStyle(color: Colors.grey, fontStyle: FontStyle.italic), ), ), ), ], ), ), ); } Widget _buildInfoRow( String label, String value, { Color? valueColor, FontWeight? valueWeight, }) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 180, child: Text( label, style: TextStyle( fontWeight: FontWeight.w500, color: Colors.grey[700], ), ), ), Expanded( child: Text( value, style: TextStyle( color: valueColor, fontWeight: valueWeight ?? FontWeight.w600, ), ), ), ], ), ); } void _showQRCode() { showDialog( context: context, builder: (context) => Dialog( child: Container( padding: const EdgeInsets.all(24), constraints: const BoxConstraints(maxWidth: 500), child: Column( mainAxisSize: MainAxisSize.min, children: [ Row( children: [ const Icon(Icons.qr_code, color: AppColors.rouge, size: 32), const SizedBox(width: 12), Expanded( child: Text( 'QR Code - ${widget.equipment.id}', style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), ), IconButton( icon: const Icon(Icons.close), onPressed: () => Navigator.pop(context), ), ], ), const SizedBox(height: 24), Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.grey[300]!), ), child: QrImageView( data: widget.equipment.id, version: QrVersions.auto, size: 300, backgroundColor: Colors.white, ), ), const SizedBox(height: 16), Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(8), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( widget.equipment.id, style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), ), const SizedBox(height: 4), Text( '${widget.equipment.brand ?? ''} ${widget.equipment.model ?? ''}'.trim(), style: TextStyle(color: Colors.grey[700]), ), ], ), ), const SizedBox(height: 24), Row( children: [ Expanded( child: OutlinedButton.icon( onPressed: () => _exportQRCode(), icon: const Icon(Icons.download), label: const Text('Télécharger PNG'), style: OutlinedButton.styleFrom( minimumSize: const Size(0, 48), ), ), ), const SizedBox(width: 12), Expanded( child: ElevatedButton.icon( onPressed: () { Navigator.pop(context); }, style: ElevatedButton.styleFrom( backgroundColor: AppColors.rouge, minimumSize: const Size(0, 48), ), icon: const Icon(Icons.close, color: Colors.white), label: const Text( 'Fermer', style: TextStyle(color: Colors.white), ), ), ), ], ), ], ), ), ), ); } Future _exportQRCode() async { try { final qrImage = await QRCodeService.generateQRCode( widget.equipment.id, size: 1024, useCache: false, ); await Printing.sharePdf( bytes: qrImage, filename: 'QRCode_${widget.equipment.id}.png', ); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('QR Code exporté avec succès'), backgroundColor: Colors.green, ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Erreur lors de l\'export: $e')), ); } } } void _editEquipment() { Navigator.push( context, MaterialPageRoute( builder: (context) => EquipmentFormPage(equipment: widget.equipment), ), ).then((_) { Navigator.pop(context); }); } void _deleteEquipment() { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Confirmer la suppression'), content: Text( 'Voulez-vous vraiment supprimer "${widget.equipment.id}" ?\n\nCette action est irréversible.', ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Annuler'), ), TextButton( onPressed: () async { Navigator.pop(context); try { await context .read() .deleteEquipment(widget.equipment.id); if (mounted) { Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Équipement supprimé avec succès'), backgroundColor: Colors.green, ), ); } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Erreur: $e')), ); } } }, style: TextButton.styleFrom(foregroundColor: Colors.red), child: const Text('Supprimer'), ), ], ), ); } IconData _getCategoryIcon(EquipmentCategory category) { switch (category) { case EquipmentCategory.lighting: return Icons.light_mode; case EquipmentCategory.sound: return Icons.volume_up; 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_2; case EquipmentCategory.cable: return Icons.cable; case EquipmentCategory.other: return Icons.more_horiz; } } String _getCategoryName(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.other: return 'Autre'; } } (String, Color) _getStatusInfo(EquipmentStatus status) { switch (status) { case EquipmentStatus.available: return ('Disponible', Colors.green); case EquipmentStatus.inUse: return ('En prestation', Colors.blue); case EquipmentStatus.rented: return ('Loué', Colors.orange); case EquipmentStatus.lost: return ('Perdu', Colors.red); case EquipmentStatus.outOfService: return ('HS', Colors.red[900]!); case EquipmentStatus.maintenance: return ('Maintenance', Colors.amber); } } (String, IconData) _getMaintenanceTypeInfo(MaintenanceType type) { switch (type) { case MaintenanceType.preventive: return ('Préventive', Icons.schedule); case MaintenanceType.corrective: return ('Corrective', Icons.build); case MaintenanceType.inspection: return ('Inspection', Icons.search); } } }