import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:intl/intl.dart'; import '../../../../shared/design_system/unionflow_design_system.dart'; import '../../../../shared/widgets/core_card.dart'; import '../../../../shared/widgets/info_badge.dart'; import '../../../../shared/widgets/mini_avatar.dart'; import '../../bloc/adhesions_bloc.dart'; import '../../data/models/adhesion_model.dart'; import 'adhesion_detail_page.dart'; import '../widgets/create_adhesion_dialog.dart'; import '../../../authentication/presentation/bloc/auth_bloc.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); _loadTab(0); } @override void dispose() { _tabController.dispose(); super.dispose(); } void _loadTab(int index) { bool isGestionnaire = false; String? membreId; final authState = context.read().state; if (authState is AuthAuthenticated) { isGestionnaire = authState.effectiveRole.level >= 50; membreId = authState.user.id; } if (isGestionnaire) { 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; } } else { // Normal member: always fetch their own records to ensure security if (membreId != null) { context.read().add(LoadAdhesionsByMembre(membreId)); } } } @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: AppColors.error, action: SnackBarAction( label: 'Réessayer', textColor: Colors.white, onPressed: () => _loadTab(_tabController.index), ), ), ); } }, child: Scaffold( backgroundColor: AppColors.background, appBar: UFAppBar( title: 'ADHÉSIONS', backgroundColor: AppColors.surface, foregroundColor: AppColors.textPrimaryLight, actions: [ IconButton( icon: const Icon(Icons.add, size: 20), onPressed: () => _showCreateDialog(), tooltip: 'Nouvelle demande', ), ], bottom: TabBar( controller: _tabController, onTap: _loadTab, isScrollable: true, labelColor: AppColors.primaryGreen, unselectedLabelColor: AppColors.textSecondaryLight, indicatorColor: AppColors.primaryGreen, indicatorSize: TabBarIndicatorSize.label, labelStyle: AppTypography.actionText.copyWith(fontSize: 10, fontWeight: FontWeight.bold), tabs: const [ Tab(child: Text('TOUTES')), Tab(child: Text('ATTENTE')), Tab(child: Text('APPROUVÉES')), Tab(child: Text('PAYÉES')), ], ), ), 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: 40, color: AppColors.textSecondaryLight), const SizedBox(height: 8), Text( 'Aucune demande d\'adhésion', style: AppTypography.bodyTextSmall.copyWith(fontSize: 16, color: AppColors.textSecondaryLight), ), 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) { return CoreCard( margin: const EdgeInsets.only(bottom: 10), onTap: onTap, padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const MiniAvatar(size: 24, fallbackText: '🏢'), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( adhesion.nomOrganisation ?? adhesion.organisationId ?? 'Organisation', style: AppTypography.actionText.copyWith(fontSize: 12), maxLines: 1, overflow: TextOverflow.ellipsis, ), Text( adhesion.numeroReference ?? adhesion.id?.substring(0, 8) ?? '—', style: AppTypography.subtitleSmall.copyWith(fontSize: 9), ), ], ), ), _buildStatutBadge(adhesion.statut), ], ), const SizedBox(height: 12), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('FRAIS D\'ADHÉSION', style: AppTypography.subtitleSmall.copyWith(fontSize: 8, fontWeight: FontWeight.bold)), Text( adhesion.fraisAdhesion != null ? currencyFormat.format(adhesion.fraisAdhesion) : '—', style: AppTypography.headerSmall.copyWith(fontSize: 13, color: AppColors.primaryGreen), ), ], ), if (adhesion.dateDemande != null) Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text('DATE', style: AppTypography.subtitleSmall.copyWith(fontSize: 8, fontWeight: FontWeight.bold)), Text( DateFormat('dd/MM/yyyy').format(adhesion.dateDemande!), style: AppTypography.bodyTextSmall.copyWith(fontSize: 10), ), ], ), ], ), if (adhesion.nomMembreComplet.isNotEmpty) ...[ const SizedBox(height: 8), Text( 'MEMBRE : ${adhesion.nomMembreComplet.toUpperCase()}', style: AppTypography.subtitleSmall.copyWith(fontSize: 8, color: AppColors.textSecondaryLight), ), ], ], ), ); } Widget _buildStatutBadge(String? statut) { Color color; switch (statut) { case 'APPROUVEE': case 'PAYEE': color = AppColors.success; break; case 'REJETEE': case 'ANNULEE': color = AppColors.error; break; case 'EN_ATTENTE': color = AppColors.brandGreenLight; break; case 'EN_PAIEMENT': color = AppColors.warning; break; default: color = AppColors.textSecondaryLight; } return InfoBadge(text: statut ?? 'INCONNU', backgroundColor: color); } }