/// Page liste des demandes d'adhésion library adhesions_page; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:intl/intl.dart'; import '../../bloc/adhesions_bloc.dart'; import '../../data/models/adhesion_model.dart'; import 'adhesion_detail_page.dart'; import '../widgets/create_adhesion_dialog.dart'; class AdhesionsPage extends StatefulWidget { const AdhesionsPage({super.key}); @override State createState() => _AdhesionsPageState(); } class _AdhesionsPageState extends State with SingleTickerProviderStateMixin { late TabController _tabController; final _currencyFormat = NumberFormat.currency(locale: 'fr_FR', symbol: 'FCFA'); @override void initState() { super.initState(); _tabController = TabController(length: 4, vsync: this); context.read().add(const LoadAdhesions()); } @override void dispose() { _tabController.dispose(); super.dispose(); } void _loadTab(int index) { switch (index) { case 0: context.read().add(const LoadAdhesions()); break; case 1: context.read().add(const LoadAdhesionsEnAttente()); break; case 2: context.read().add(const LoadAdhesionsByStatut('APPROUVEE')); break; case 3: context.read().add(const LoadAdhesionsByStatut('PAYEE')); break; } } @override Widget build(BuildContext context) { return BlocListener( listener: (context, state) { if (state.status == AdhesionsStatus.error && state.message != null) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(state.message!), backgroundColor: Colors.red, action: SnackBarAction( label: 'Réessayer', textColor: Colors.white, onPressed: () => _loadTab(_tabController.index), ), ), ); } }, child: Scaffold( appBar: AppBar( title: const Text('Demandes d\'adhésion'), bottom: TabBar( controller: _tabController, onTap: _loadTab, tabs: const [ Tab(text: 'Toutes', icon: Icon(Icons.list)), Tab(text: 'En attente', icon: Icon(Icons.schedule)), Tab(text: 'Approuvées', icon: Icon(Icons.check_circle_outline)), Tab(text: 'Payées', icon: Icon(Icons.payment)), ], ), actions: [ IconButton( icon: const Icon(Icons.add), onPressed: () => _showCreateDialog(), tooltip: 'Nouvelle demande', ), ], ), body: TabBarView( controller: _tabController, children: [ _buildList(null), _buildList('EN_ATTENTE'), _buildList('APPROUVEE'), // tab 2 charge déjà par statut _buildList('PAYEE'), // tab 3 charge déjà par statut ], ), ), ); } Widget _buildList(String? statutFilter) { return BlocBuilder( buildWhen: (prev, curr) => prev.status != curr.status || prev.adhesions != curr.adhesions, builder: (context, state) { if (state.status == AdhesionsStatus.loading && state.adhesions.isEmpty) { return const Center(child: CircularProgressIndicator()); } var list = state.adhesions; if (statutFilter != null) { list = list.where((a) => a.statut == statutFilter).toList(); } if (list.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.assignment_outlined, size: 64, color: Colors.grey[400]), const SizedBox(height: 16), Text( 'Aucune demande d\'adhésion', style: TextStyle(fontSize: 16, color: Colors.grey[600]), ), const SizedBox(height: 8), TextButton.icon( onPressed: () => _showCreateDialog(), icon: const Icon(Icons.add), label: const Text('Créer une demande'), ), ], ), ); } return RefreshIndicator( onRefresh: () async => _loadTab(_tabController.index), child: ListView.builder( padding: const EdgeInsets.all(8), itemCount: list.length, itemBuilder: (context, index) { final a = list[index]; return _AdhesionCard( adhesion: a, currencyFormat: _currencyFormat, onTap: () => _openDetail(a), ); }, ), ); }, ); } void _openDetail(AdhesionModel a) { if (a.id == null) return; Navigator.of(context).push( MaterialPageRoute( builder: (context) => BlocProvider.value( value: context.read(), child: AdhesionDetailPage(adhesionId: a.id!), ), ), ).then((_) => _loadTab(_tabController.index)); } void _showCreateDialog() { showDialog( context: context, builder: (context) => CreateAdhesionDialog( onCreated: () { Navigator.of(context).pop(); _loadTab(_tabController.index); }, ), ); } } class _AdhesionCard extends StatelessWidget { final AdhesionModel adhesion; final NumberFormat currencyFormat; final VoidCallback onTap; const _AdhesionCard({ required this.adhesion, required this.currencyFormat, required this.onTap, }); @override Widget build(BuildContext context) { final theme = Theme.of(context); return Card( margin: const EdgeInsets.only(bottom: 8), child: InkWell( onTap: onTap, borderRadius: BorderRadius.circular(8), child: Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( adhesion.numeroReference ?? adhesion.id ?? '—', style: theme.textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w600, ), ), ), _StatutChip(statut: adhesion.statut), ], ), const SizedBox(height: 4), Text( adhesion.nomOrganisation ?? adhesion.organisationId ?? 'Organisation', style: theme.textTheme.bodyMedium, ), if (adhesion.nomMembreComplet.isNotEmpty) Text( adhesion.nomMembreComplet, style: theme.textTheme.bodySmall?.copyWith(color: Colors.grey[600]), ), const SizedBox(height: 4), Row( children: [ Text( adhesion.fraisAdhesion != null ? currencyFormat.format(adhesion.fraisAdhesion) : '—', style: theme.textTheme.titleSmall?.copyWith( color: theme.colorScheme.primary, ), ), if (adhesion.dateDemande != null) ...[ const Spacer(), Text( DateFormat('dd/MM/yyyy').format(adhesion.dateDemande!), style: theme.textTheme.bodySmall, ), ], ], ), ], ), ), ), ); } } class _StatutChip extends StatelessWidget { final String? statut; const _StatutChip({this.statut}); @override Widget build(BuildContext context) { Color color; switch (statut) { case 'EN_ATTENTE': color = Colors.orange; break; case 'APPROUVEE': case 'PAYEE': color = Colors.green; break; case 'REJETEE': color = Colors.red; break; case 'ANNULEE': color = Colors.grey; break; case 'EN_PAIEMENT': color = Colors.blue; break; default: color = Colors.grey; } return Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: color.withOpacity(0.2), borderRadius: BorderRadius.circular(12), ), child: Text( statut ?? '—', style: TextStyle(fontSize: 12, color: color, fontWeight: FontWeight.w500), ), ); } }