import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:intl/intl.dart'; import '../../core/constants/design_system.dart'; import '../../data/datasources/reservation_remote_data_source.dart'; import '../../data/services/secure_storage.dart'; import '../../domain/entities/reservation.dart'; import '../widgets/animated_widgets.dart'; import '../widgets/custom_snackbar.dart'; import '../widgets/modern_empty_state.dart'; import '../widgets/shimmer_loading.dart'; /// Écran de gestion des réservations avec design moderne. /// /// Permet à l'utilisateur de: /// - Consulter ses réservations (en cours, passées, annulées) /// - Annuler des réservations en cours /// - Voir les détails de chaque réservation class ReservationsScreen extends StatefulWidget { const ReservationsScreen({super.key}); @override State createState() => _ReservationsScreenState(); } class _ReservationsScreenState extends State with SingleTickerProviderStateMixin { final ReservationRemoteDataSource _dataSource = ReservationRemoteDataSource(http.Client()); final SecureStorage _secureStorage = SecureStorage(); List _reservations = []; bool _isLoading = false; String? _errorMessage; late TabController _tabController; @override void initState() { super.initState(); _tabController = TabController(length: 3, vsync: this); _loadReservations(); } @override void dispose() { _tabController.dispose(); super.dispose(); } Future _loadReservations() async { setState(() { _isLoading = true; _errorMessage = null; }); try { final userId = await _secureStorage.getUserId(); if (userId == null || userId.isEmpty) { if (mounted) { setState(() { _isLoading = false; }); } return; } final reservationModels = await _dataSource.getReservationsByUser(userId); if (mounted) { setState(() { _reservations = reservationModels.map((model) => model.toEntity()).toList(); // Trie par date de réservation décroissante _reservations.sort((a, b) => b.reservationDate.compareTo(a.reservationDate)); _isLoading = false; }); } } catch (e) { if (mounted) { setState(() { _errorMessage = e.toString(); _isLoading = false; }); } } } Future _cancelReservation(Reservation reservation) async { final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Annuler la réservation'), content: Text( 'Voulez-vous vraiment annuler votre réservation pour "${reservation.eventTitle}" ?', ), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('Non'), ), ElevatedButton( onPressed: () => Navigator.pop(context, true), style: ElevatedButton.styleFrom( backgroundColor: Colors.red, ), child: const Text('Oui, annuler'), ), ], ), ); if (confirmed != true) return; try { await _dataSource.cancelReservation(reservation.id); if (mounted) { await _loadReservations(); context.showSuccess('Réservation annulée avec succès'); } } catch (e) { if (mounted) { context.showError('Erreur lors de l\'annulation: ${e.toString()}'); } } } List get _activeReservations { return _reservations.where((r) => r.status == ReservationStatus.pending || r.status == ReservationStatus.confirmed ).toList(); } List get _pastReservations { return _reservations.where((r) => r.status == ReservationStatus.completed ).toList(); } List get _cancelledReservations { return _reservations.where((r) => r.status == ReservationStatus.cancelled ).toList(); } @override Widget build(BuildContext context) { final theme = Theme.of(context); return Scaffold( appBar: AppBar( title: const Text('Mes Réservations'), bottom: TabBar( controller: _tabController, tabs: const [ Tab(text: 'En cours', icon: Icon(Icons.event_available)), Tab(text: 'Passées', icon: Icon(Icons.history)), Tab(text: 'Annulées', icon: Icon(Icons.cancel)), ], ), ), body: _isLoading ? const SkeletonList( itemCount: 5, skeletonWidget: ListItemSkeleton(), ) : _errorMessage != null ? _buildErrorState(theme) : TabBarView( controller: _tabController, children: [ _buildReservationList(_activeReservations, 'active'), _buildReservationList(_pastReservations, 'past'), _buildReservationList(_cancelledReservations, 'cancelled'), ], ), ); } Widget _buildErrorState(ThemeData theme) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( Icons.error_outline, size: 64, color: Colors.grey[400], ), const SizedBox(height: DesignSystem.spacingLg), Text( 'Erreur de chargement', style: theme.textTheme.titleLarge, ), const SizedBox(height: DesignSystem.spacingSm), Text( _errorMessage!, style: theme.textTheme.bodyMedium, textAlign: TextAlign.center, ), const SizedBox(height: DesignSystem.spacingXl), ElevatedButton.icon( onPressed: _loadReservations, icon: const Icon(Icons.refresh), label: const Text('Réessayer'), ), ], ), ); } Widget _buildReservationList(List reservations, String type) { if (reservations.isEmpty) { return _buildEmptyState(type); } return RefreshIndicator( onRefresh: _loadReservations, child: ListView.builder( padding: const EdgeInsets.all(DesignSystem.spacingLg), itemCount: reservations.length, itemBuilder: (context, index) { return _buildReservationCard(reservations[index]); }, ), ); } Widget _buildEmptyState(String type) { String title; String description; switch (type) { case 'active': title = 'Aucune réservation en cours'; description = 'Vous n\'avez pas de réservation active pour le moment'; break; case 'past': title = 'Aucune réservation passée'; description = 'Vos réservations terminées apparaîtront ici'; break; case 'cancelled': title = 'Aucune réservation annulée'; description = 'Vos réservations annulées apparaîtront ici'; break; default: title = 'Aucune réservation'; description = ''; } return ModernEmptyState( illustration: EmptyStateIllustration.reservations, title: title, description: description, ); } Widget _buildReservationCard(Reservation reservation) { final theme = Theme.of(context); final canCancel = reservation.status == ReservationStatus.pending || reservation.status == ReservationStatus.confirmed; return FadeInWidget( child: AnimatedCard( margin: const EdgeInsets.only(bottom: DesignSystem.spacingLg), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // En-tête avec titre et statut Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( reservation.eventTitle, style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, ), ), if (reservation.establishmentName != null) ...[ const SizedBox(height: 4), Row( children: [ const Icon(Icons.location_on, size: 14, color: Colors.grey), const SizedBox(width: 4), Expanded( child: Text( reservation.establishmentName!, style: theme.textTheme.bodySmall?.copyWith( color: Colors.grey[600], ), ), ), ], ), ], ], ), ), _buildStatusBadge(reservation.status, theme), ], ), const SizedBox(height: DesignSystem.spacingLg), // Informations de la réservation Row( children: [ const Icon(Icons.event, size: 20), const SizedBox(width: DesignSystem.spacingSm), Expanded( child: Text( DateFormat('EEEE d MMMM yyyy à HH:mm', 'fr_FR') .format(reservation.reservationDate), style: theme.textTheme.bodyMedium, ), ), ], ), const SizedBox(height: DesignSystem.spacingSm), Row( children: [ const Icon(Icons.people, size: 20), const SizedBox(width: DesignSystem.spacingSm), Text( '${reservation.numberOfPeople} personne${reservation.numberOfPeople > 1 ? 's' : ''}', style: theme.textTheme.bodyMedium, ), ], ), // Notes si présentes if (reservation.notes != null && reservation.notes!.isNotEmpty) ...[ const SizedBox(height: DesignSystem.spacingSm), Container( padding: const EdgeInsets.all(DesignSystem.spacingSm), decoration: BoxDecoration( color: theme.brightness == Brightness.dark ? Colors.grey[800] : Colors.grey[100], borderRadius: BorderRadius.circular(DesignSystem.radiusSm), ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Icon(Icons.note, size: 16, color: Colors.grey), const SizedBox(width: DesignSystem.spacingSm), Expanded( child: Text( reservation.notes!, style: theme.textTheme.bodySmall, ), ), ], ), ), ], // Bouton annuler if (canCancel) ...[ const SizedBox(height: DesignSystem.spacingLg), SizedBox( width: double.infinity, child: ElevatedButton.icon( onPressed: () => _cancelReservation(reservation), icon: const Icon(Icons.cancel), label: const Text('Annuler la réservation'), style: ElevatedButton.styleFrom( backgroundColor: Colors.red, foregroundColor: Colors.white, ), ), ), ], ], ), ), ); } Widget _buildStatusBadge(ReservationStatus status, ThemeData theme) { Color color; String label; IconData icon; switch (status) { case ReservationStatus.pending: color = Colors.orange; label = 'En attente'; icon = Icons.schedule; break; case ReservationStatus.confirmed: color = Colors.green; label = 'Confirmée'; icon = Icons.check_circle; break; case ReservationStatus.cancelled: color = Colors.red; label = 'Annulée'; icon = Icons.cancel; break; case ReservationStatus.completed: color = Colors.blue; label = 'Terminée'; icon = Icons.check; break; } return Container( padding: const EdgeInsets.symmetric( horizontal: DesignSystem.spacingSm, vertical: 4, ), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(DesignSystem.radiusSm), border: Border.all(color: color, width: 1), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(icon, size: 14, color: color), const SizedBox(width: 4), Text( label, style: theme.textTheme.bodySmall?.copyWith( color: color, fontWeight: FontWeight.w600, ), ), ], ), ); } }