// ===== lib/pages/admin_dashboard.dart ===== import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:provider/provider.dart'; import '../main.dart'; import '../widgets/wortis_logo.dart'; import 'login.dart'; // ===== CONFIGURATION API ===== const String baseUrl = 'https://api.live.wortis.cg/tpe'; const String apiBaseUrl = '$baseUrl/api'; const Map apiHeaders = { 'Content-Type': 'application/json', 'Accept': 'application/json', }; // ===== COULEURS GLOBALES ===== class AdminColors { static const Color primary = Color(0xFFE53E3E); static const Color secondary = Color(0xFF006699); static const Color accent = Color(0xFFFF6B35); static const Color background = Color(0xFFF8FAFC); static const Color surface = Colors.white; static const Color success = Color(0xFF38A169); static const Color warning = Color(0xFFF59E0B); static const Color error = Color(0xFFE53E3E); static const Color info = Color(0xFF3182CE); } // ===== ADMIN DASHBOARD ===== class AdminDashboard extends StatefulWidget { const AdminDashboard({super.key}); @override State createState() => _AdminDashboardState(); } class _AdminDashboardState extends State with SingleTickerProviderStateMixin { late AnimationController _animationController; late Animation _fadeAnimation; bool _isLoading = false; int _selectedIndex = 0; // Données du dashboard Map _systemStats = {}; List> _recentActivities = []; List> _agents = []; List> _allRecharges = []; List> _allTransactions = []; Map _rechargeStats = {}; Map _transactionStats = {}; @override void initState() { super.initState(); _setupAnimations(); _loadDashboardData(); } @override void dispose() { _animationController.dispose(); super.dispose(); } void _setupAnimations() { _animationController = AnimationController( duration: const Duration(milliseconds: 1000), vsync: this, ); _fadeAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: _animationController, curve: Curves.easeOut), ); _animationController.forward(); } // ===== MÉTHODES API ===== Future _loadDashboardData() async { setState(() { _isLoading = true; }); try { await Future.wait([ _loadSystemStats(), _loadRecentActivities(), _loadAgents(), _loadAllRecharges(), _loadAllTransactions(), // Ajoutez cette ligne _loadRechargeStats(), // Ajoutez cette ligne _loadTransactionStats(), // Ajoutez cette ligne ]); } catch (e) { print('❌ Erreur chargement dashboard admin: $e'); _showSnackBar('Erreur lors du chargement des données', AdminColors.error); } finally { setState(() { _isLoading = false; }); } } Future _loadSystemStats() async { try { final response = await http.get( Uri.parse('$baseUrl/admin/system/stats'), headers: apiHeaders, ); if (response.statusCode == 200) { final data = jsonDecode(response.body); if (data['success'] == true) { setState(() { _systemStats = data['stats'] ?? {}; }); } } } catch (e) { print('❌ Erreur stats système: $e'); // Données par défaut en cas d'erreur setState(() { _systemStats = { 'active_agents': 247, 'daily_transactions': 1543, 'total_volume': 45200000.0, 'total_commissions': 2100000.0, 'success_rate': 98.5, 'pending_recharges': 12, }; }); } } Future _loadRecentActivities() async { try { final response = await http.get( Uri.parse('$baseUrl/admin/activities/recent'), headers: apiHeaders, ); if (response.statusCode == 200) { final data = jsonDecode(response.body); if (data['success'] == true) { setState(() { _recentActivities = List>.from( data['activities'] ?? [], ); }); } } } catch (e) { print('❌ Erreur activités récentes: $e'); // Données par défaut setState(() { _recentActivities = [ { 'type': 'agent_created', 'title': 'Nouvel agent créé', 'description': 'AGENT1245 - Jean Martin', 'icon': 'person_add', 'color': 'green', 'time': '2min', 'created_at': DateTime.now() .subtract(const Duration(minutes: 2)) .toIso8601String(), }, { 'type': 'recharge_approved', 'title': 'Recharge validée', 'description': 'AGENT0987 - 50,000 F', 'icon': 'check_circle', 'color': 'blue', 'time': '5min', 'created_at': DateTime.now() .subtract(const Duration(minutes: 5)) .toIso8601String(), }, ]; }); } } Future _loadAgents() async { try { final response = await http.get( Uri.parse('$baseUrl/admin/agents'), headers: apiHeaders, ); if (response.statusCode == 200) { final data = jsonDecode(response.body); if (data['success'] == true) { setState(() { _agents = List>.from(data['agents'] ?? []); }); } } } catch (e) { print('❌ Erreur chargement agents: $e'); } } Future _loadAllRecharges() async { try { final response = await http.get( Uri.parse('$baseUrl/recharge/get_recharges'), headers: apiHeaders, ); if (response.statusCode == 200) { final data = jsonDecode(response.body); setState(() { _allRecharges = List>.from( data['all_recharges'] ?? [], ); }); } } catch (e) { print('❌ Erreur chargement recharges: $e'); } } Future _loadAllTransactions() async { try { final response = await http.get( Uri.parse('$baseUrl/admin/transactions'), headers: apiHeaders, ); if (response.statusCode == 200) { final data = jsonDecode(response.body); setState(() { _allTransactions = List>.from( data['transactions'] ?? [], ); }); } } catch (e) { print('❌ Erreur chargement transactions: $e'); } } Future _loadRechargeStats() async { try { final response = await http.get( Uri.parse('$baseUrl/admin/recharges/stats'), headers: apiHeaders, ); if (response.statusCode == 200) { final data = jsonDecode(response.body); setState(() { _rechargeStats = data['stats'] ?? {}; }); } } catch (e) { print('❌ Erreur stats recharges: $e'); } } Future _loadTransactionStats() async { try { final response = await http.get( Uri.parse('$baseUrl/admin/transactions/stats'), headers: apiHeaders, ); if (response.statusCode == 200) { final data = jsonDecode(response.body); setState(() { _transactionStats = data['stats'] ?? {}; }); } } catch (e) { print('❌ Erreur stats transactions: $e'); } } Future _createAgent(Map agentData) async { try { final response = await http.post( Uri.parse('$baseUrl/admin/agents/create'), headers: apiHeaders, body: jsonEncode(agentData), ); if (response.statusCode == 201) { final data = jsonDecode(response.body); if (data['success'] == true) { _showSnackBar('Agent créé avec succès', AdminColors.success); _loadAgents(); } else { _showSnackBar( data['message'] ?? 'Erreur lors de la création', AdminColors.error, ); } } else { _showSnackBar( 'Erreur lors de la création de l\'agent', AdminColors.error, ); } } catch (e) { print('❌ Erreur création agent: $e'); _showSnackBar('Erreur de connexion', AdminColors.error); } } Future _updateAgentStatus(String agentId, String status) async { try { final response = await http.put( Uri.parse('$baseUrl/admin/agents/$agentId/status'), headers: apiHeaders, body: jsonEncode({ 'status': status, 'updated_by': Provider.of(context, listen: false).agentId, }), ); if (response.statusCode == 200) { final data = jsonDecode(response.body); if (data['success'] == true) { _showSnackBar('Statut agent mis à jour', AdminColors.success); _loadAgents(); } else { _showSnackBar( data['message'] ?? 'Erreur mise à jour', AdminColors.error, ); } } } catch (e) { print('❌ Erreur mise à jour statut: $e'); _showSnackBar('Erreur de connexion', AdminColors.error); } } Future _exportData(String type) async { try { final response = await http.get( Uri.parse('$baseUrl/admin/export/$type'), headers: apiHeaders, ); if (response.statusCode == 200) { _showSnackBar('Export $type généré avec succès', AdminColors.success); } else { _showSnackBar('Erreur lors de l\'export', AdminColors.error); } } catch (e) { print('❌ Erreur export: $e'); _showSnackBar('Erreur de connexion', AdminColors.error); } } // ===== MÉTHODES UI ===== void _logout() async { final result = await showDialog( context: context, builder: (BuildContext context) { return AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), title: const Row( children: [ Icon(Icons.logout, color: AdminColors.primary), SizedBox(width: 12), Text('Déconnexion'), ], ), content: const Text('Êtes-vous sûr de vouloir vous déconnecter ?'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), child: const Text('Annuler'), ), ElevatedButton( onPressed: () => Navigator.of(context).pop(true), style: ElevatedButton.styleFrom( backgroundColor: AdminColors.primary, foregroundColor: Colors.white, ), child: const Text('Déconnexion'), ), ], ); }, ); if (result == true) { final authController = Provider.of( context, listen: false, ); await authController.logout(); if (mounted) { Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute(builder: (context) => const LoginPage()), (route) => false, ); } } } void _showSnackBar(String message, Color color) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), backgroundColor: color, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), ); } void _showCreateAgentDialog() { final nameController = TextEditingController(); final phoneController = TextEditingController(); final emailController = TextEditingController(); final typeController = TextEditingController(text: 'agent'); showDialog( context: context, builder: (context) => AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), title: const Row( children: [ Icon(Icons.person_add, color: AdminColors.secondary), SizedBox(width: 8), Text('Créer un agent'), ], ), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: nameController, decoration: const InputDecoration( labelText: 'Nom complet', border: OutlineInputBorder(), prefixIcon: Icon(Icons.person), ), ), const SizedBox(height: 16), TextField( controller: phoneController, keyboardType: TextInputType.phone, decoration: const InputDecoration( labelText: 'Téléphone', border: OutlineInputBorder(), prefixIcon: Icon(Icons.phone), ), ), const SizedBox(height: 16), TextField( controller: emailController, keyboardType: TextInputType.emailAddress, decoration: const InputDecoration( labelText: 'Email', border: OutlineInputBorder(), prefixIcon: Icon(Icons.email), ), ), const SizedBox(height: 16), DropdownButtonFormField( value: typeController.text, decoration: const InputDecoration( labelText: 'Type d\'agent', border: OutlineInputBorder(), prefixIcon: Icon(Icons.category), ), items: const [ DropdownMenuItem( value: 'agent', child: Text('Agent standard'), ), DropdownMenuItem( value: 'rechargeur', child: Text('Rechargeur'), ), DropdownMenuItem( value: 'admin', child: Text('Administrateur'), ), ], onChanged: (value) => typeController.text = value ?? 'agent', ), ], ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Annuler'), ), ElevatedButton( onPressed: () { Navigator.pop(context); _createAgent({ 'nom': nameController.text, 'telephone': phoneController.text, 'email': emailController.text, 'type': typeController.text, }); }, style: ElevatedButton.styleFrom( backgroundColor: AdminColors.secondary, foregroundColor: Colors.white, ), child: const Text('Créer'), ), ], ), ); } void _showAgentDetails(Map agent) { showDialog( context: context, builder: (context) => Dialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), child: SizedBox( width: MediaQuery.of(context).size.width * 0.95, height: MediaQuery.of(context).size.height * 0.85, child: Column( children: [ // Header avec avatar et infos principales Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( gradient: LinearGradient( colors: [ AdminColors.secondary, AdminColors.secondary.withOpacity(0.8), ], ), borderRadius: const BorderRadius.only( topLeft: Radius.circular(20), topRight: Radius.circular(20), ), ), child: Row( children: [ Container( width: 80, height: 80, decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(20), border: Border.all( color: Colors.white.withOpacity(0.3), width: 2, ), ), child: const Icon( Icons.person, size: 40, color: Colors.white, ), ), const SizedBox(width: 20), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( agent['nom'] ?? 'Nom non disponible', style: const TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white, ), ), const SizedBox(height: 4), Text( 'ID: ${agent['agent_id'] ?? 'N/A'}', style: TextStyle( fontSize: 16, color: Colors.white.withOpacity(0.9), ), ), const SizedBox(height: 8), Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), decoration: BoxDecoration( color: Colors.white.withOpacity(0.2), borderRadius: BorderRadius.circular(12), ), child: Text( '${agent['type'] ?? 'agent'}'.toUpperCase(), style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Colors.white, ), ), ), ], ), ), IconButton( onPressed: () => Navigator.pop(context), icon: const Icon( Icons.close, color: Colors.white, size: 28, ), ), ], ), ), // Contenu scrollable Expanded( child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Statistiques rapides Row( children: [ Expanded( child: _buildAgentStatCard( 'Solde', '${(agent['solde'] ?? 0).toStringAsFixed(0)} F', Icons.account_balance_wallet, AdminColors.success, ), ), const SizedBox(width: 12), Expanded( child: _buildAgentStatCard( 'Transactions', '${agent['total_transactions'] ?? 0}', Icons.receipt_long, AdminColors.info, ), ), const SizedBox(width: 12), Expanded( child: _buildAgentStatCard( 'Commissions', '${(agent['total_commissions'] ?? 0).toStringAsFixed(0)} F', Icons.monetization_on, AdminColors.accent, ), ), ], ), const SizedBox(height: 24), // Informations détaillées const Text( 'Informations détaillées', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87, ), ), const SizedBox(height: 16), _buildDetailCard([ _buildDetailRow( 'Nom complet', agent['nom'] ?? 'N/A', ), _buildDetailRow( 'ID Agent', agent['agent_id'] ?? 'N/A', ), _buildDetailRow( 'Téléphone', agent['telephone'] ?? 'N/A', ), _buildDetailRow( 'Email', agent['email'] ?? 'Non renseigné', ), _buildDetailRow('Type', agent['role'] ?? 'agent'), _buildDetailRow( 'Statut', agent['status'] ?? 'active', ), _buildDetailRow( 'Membre depuis', agent['created_at'] != null ? _formatDuration( DateTime.parse(agent['created_at']), ) : 'Non disponible', ), _buildDetailRow( 'Dernière connexion', agent['last_login'] != null ? _formatDate(agent['last_login']) : 'Jamais connecté', ), _buildDetailRow( 'Dernière activité', agent['updated_at'] != null ? _formatDate(agent['updated_at']?['\$date']) : 'Non disponible', ), ]), const SizedBox(height: 24), // Actions Row( children: [ Expanded( child: ElevatedButton.icon( onPressed: () { Navigator.pop(context); _updateAgentStatus( agent['agent_id'], agent['status'] == 'active' ? 'inactive' : 'active', ); }, icon: Icon( agent['status'] == 'active' ? Icons.block : Icons.check_circle, ), label: Text( agent['status'] == 'active' ? 'Désactiver' : 'Activer', ), style: ElevatedButton.styleFrom( backgroundColor: agent['status'] == 'active' ? AdminColors.error : AdminColors.success, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( vertical: 16, ), ), ), ), const SizedBox(width: 12), Expanded( child: ElevatedButton.icon( onPressed: () => _showSnackBar( 'Modification agent - En développement', AdminColors.info, ), icon: const Icon(Icons.edit), label: const Text('Modifier'), style: ElevatedButton.styleFrom( backgroundColor: AdminColors.secondary, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( vertical: 16, ), ), ), ), ], ), ], ), ), ), ], ), ), ), ); } Widget _buildAgentStatCard( String title, String value, IconData icon, Color color, ) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(12), border: Border.all(color: color.withOpacity(0.2)), ), child: Column( children: [ Icon(icon, color: color, size: 24), const SizedBox(height: 8), Text( value, style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: color, ), textAlign: TextAlign.center, ), Text( title, style: const TextStyle(fontSize: 12, color: Colors.grey), textAlign: TextAlign.center, ), ], ), ); } Widget _buildDetailCard(List children) { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[200]!), ), child: Column(children: children), ); } String _formatDuration(DateTime createdAt) { final duration = DateTime.now().difference(createdAt); if (duration.inDays > 365) { return '${(duration.inDays / 365).floor()} an${(duration.inDays / 365).floor() > 1 ? 's' : ''}'; } else if (duration.inDays > 30) { return '${(duration.inDays / 30).floor()} mois'; } else { return '${duration.inDays} jour${duration.inDays > 1 ? 's' : ''}'; } } Widget _buildDetailRow(String label, String value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 100, child: Text( '$label:', style: const TextStyle( fontWeight: FontWeight.w600, color: Colors.grey, ), ), ), Expanded( child: Text( value, style: const TextStyle(fontWeight: FontWeight.w500), ), ), ], ), ); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AdminColors.background, body: FadeTransition( opacity: _fadeAnimation, child: SafeArea( child: RefreshIndicator( onRefresh: _loadDashboardData, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildHeader(), const SizedBox(height: 30), _buildNavigationTabs(), const SizedBox(height: 20), if (_isLoading) const Center(child: CircularProgressIndicator()) else _buildTabContent(), ], ), ), ), ), ), ); } Widget _buildHeader() { return Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [AdminColors.primary, AdminColors.primary.withOpacity(0.8)], ), borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: AdminColors.primary.withOpacity(0.3), blurRadius: 15, offset: const Offset(0, 8), ), ], ), child: Consumer( builder: (context, authController, child) { return Row( children: [ const WortisLogoWidget( size: 60, isWhite: true, withShadow: false, ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon( Icons.admin_panel_settings, color: Colors.white, size: 20, ), const SizedBox(width: 8), Text( 'PANNEAU ADMIN', style: TextStyle( color: Colors.white.withOpacity(0.9), fontSize: 14, fontWeight: FontWeight.w500, ), ), ], ), const SizedBox(height: 4), Text( authController.agentName ?? 'Administrateur', style: const TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), Text( 'ID: ${authController.agentId ?? "ADMIN"}', style: TextStyle( color: Colors.white.withOpacity(0.8), fontSize: 12, ), ), ], ), ), IconButton( onPressed: _logout, icon: const Icon(Icons.logout, color: Colors.white), tooltip: 'Déconnexion', ), ], ); }, ), ); } Widget _buildNavigationTabs() { final tabs = [ {'title': 'Vue d\'ensemble', 'icon': Icons.dashboard}, {'title': 'Agents', 'icon': Icons.people}, {'title': 'Recharges', 'icon': Icons.account_balance_wallet}, { 'title': 'Transactions', 'icon': Icons.receipt_long, }, // Ajoutez cet onglet {'title': 'Rapports', 'icon': Icons.bar_chart}, ]; return Container( height: 60, decoration: BoxDecoration( color: AdminColors.surface, borderRadius: BorderRadius.circular(15), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 2), ), ], ), child: Row( children: tabs.asMap().entries.map((entry) { final index = entry.key; final tab = entry.value; final isSelected = _selectedIndex == index; return Expanded( child: GestureDetector( onTap: () => setState(() => _selectedIndex = index), child: Container( decoration: BoxDecoration( color: isSelected ? AdminColors.primary.withOpacity(0.1) : Colors.transparent, borderRadius: BorderRadius.circular(12), ), margin: const EdgeInsets.all(4), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( tab['icon'] as IconData, color: isSelected ? AdminColors.primary : Colors.grey[600], size: 20, ), const SizedBox(height: 4), Text( tab['title'] as String, style: TextStyle( fontSize: 10, fontWeight: isSelected ? FontWeight.w600 : FontWeight.w400, color: isSelected ? AdminColors.primary : Colors.grey[600], ), textAlign: TextAlign.center, ), ], ), ), ), ); }).toList(), ), ); } Widget _buildTabContent() { switch (_selectedIndex) { case 0: return _buildOverviewTab(); case 1: return _buildAgentsTab(); case 2: return _buildRechargesTab(); case 3: return _buildTransactionsTab(); // Ajoutez cette ligne case 4: // Changez de 3 à 4 return _buildReportsTab(); default: return _buildOverviewTab(); } } Widget _buildOverviewTab() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildStatsOverview(), const SizedBox(height: 30), _buildQuickActions(), const SizedBox(height: 30), _buildRecentActivity(), ], ); } Widget _buildStatsOverview() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Vue d\'ensemble du système', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black87, ), ), const SizedBox(height: 16), Row( children: [ Expanded( child: _buildStatCard( 'Agents actifs', '${_systemStats['active_agents'] ?? 0}', Icons.people, AdminColors.secondary, ), ), const SizedBox(width: 12), Expanded( child: _buildStatCard( 'Transactions/jour', '${_systemStats['daily_transactions'] ?? 0}', Icons.trending_up, AdminColors.success, ), ), ], ), const SizedBox(height: 12), Row( children: [ Expanded( child: _buildStatCard( 'Volume total', '${((_systemStats['total_volume'] ?? 0.0) / 1000000).toStringAsFixed(1)}M F', Icons.monetization_on, AdminColors.warning, ), ), const SizedBox(width: 12), Expanded( child: _buildStatCard( 'Taux de réussite', '${(_systemStats['success_rate'] ?? 0.0).toStringAsFixed(1)}%', Icons.check_circle, AdminColors.primary, ), ), ], ), ], ); } Widget _buildStatCard( String title, String value, IconData icon, Color color, ) { return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AdminColors.surface, borderRadius: BorderRadius.circular(16), border: Border.all(color: color.withOpacity(0.1)), boxShadow: [ BoxShadow( color: color.withOpacity(0.08), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Icon(icon, color: color, size: 24), const Text( '↗ +12%', style: TextStyle( fontSize: 11, color: Colors.green, fontWeight: FontWeight.w600, ), ), ], ), const SizedBox(height: 12), Text( value, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: color, ), ), const SizedBox(height: 4), Text( title, style: TextStyle( fontSize: 12, color: Colors.grey[600], fontWeight: FontWeight.w500, ), ), ], ), ); } Widget _buildQuickActions() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Actions rapides', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black87, ), ), const SizedBox(height: 16), GridView.count( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), crossAxisCount: 2, crossAxisSpacing: 16, mainAxisSpacing: 16, childAspectRatio: 1.3, children: [ _buildActionCard( 'Créer un agent', 'Nouveau compte agent', Icons.person_add, AdminColors.secondary, onTap: _showCreateAgentDialog, ), _buildActionCard( 'Export données', 'Télécharger rapports', Icons.download, AdminColors.info, onTap: () => _showExportDialog(), ), _buildActionCard( 'Statistiques', 'Analyses détaillées', Icons.analytics, AdminColors.success, ), _buildActionCard( 'Configuration', 'Paramètres système', Icons.settings, AdminColors.primary, ), ], ), ], ); } Widget _buildActionCard( String title, String subtitle, IconData icon, Color color, { VoidCallback? onTap, }) { return GestureDetector( onTap: onTap ?? () => _showSnackBar( '$title - Fonction en développement', AdminColors.info, ), child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AdminColors.surface, borderRadius: BorderRadius.circular(16), border: Border.all(color: color.withOpacity(0.2)), boxShadow: [ BoxShadow( color: color.withOpacity(0.1), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, color: color, size: 32), const SizedBox(height: 12), Text( title, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black87, ), ), const SizedBox(height: 4), Text( subtitle, style: TextStyle(fontSize: 11, color: Colors.grey[600]), ), ], ), ), ); } Widget _buildRecentActivity() { return Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: AdminColors.surface, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Activité récente', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87, ), ), const SizedBox(height: 16), if (_recentActivities.isEmpty) const Center( child: Padding( padding: EdgeInsets.all(20), child: Text( 'Aucune activité récente', style: TextStyle(color: Colors.grey), ), ), ) else ..._recentActivities.map( (activity) => _buildActivityItem( activity['title'] ?? '', activity['description'] ?? '', _getIconFromString(activity['icon'] ?? 'info'), _getColorFromString(activity['color'] ?? 'grey'), activity['time'] ?? '', ), ), ], ), ); } Widget _buildActivityItem( String title, String subtitle, IconData icon, Color color, String time, ) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(10), ), child: Icon(icon, color: color, size: 20), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), Text( subtitle, style: TextStyle(fontSize: 12, color: Colors.grey[600]), ), ], ), ), Text(time, style: TextStyle(fontSize: 11, color: Colors.grey[500])), ], ), ); } Widget _buildAgentsTab() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Gestion des agents', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black87, ), ), ElevatedButton.icon( onPressed: _showCreateAgentDialog, icon: const Icon(Icons.add, size: 18), label: const Text('Nouvel agent'), style: ElevatedButton.styleFrom( backgroundColor: AdminColors.secondary, foregroundColor: Colors.white, ), ), ], ), const SizedBox(height: 16), Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AdminColors.surface, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Column( children: [ Row( children: [ Expanded( child: TextField( decoration: InputDecoration( hintText: 'Rechercher un agent...', prefixIcon: const Icon(Icons.search), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 12, ), ), ), ), const SizedBox(width: 12), Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 8, ), decoration: BoxDecoration( color: AdminColors.secondary.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Text( '${_agents.length} agents', style: const TextStyle( fontWeight: FontWeight.w600, color: AdminColors.secondary, ), ), ), ], ), const SizedBox(height: 16), if (_agents.isEmpty) const Center( child: Padding( padding: EdgeInsets.all(40), child: Column( children: [ Icon( Icons.people_outline, size: 64, color: Colors.grey, ), SizedBox(height: 16), Text( 'Aucun agent trouvé', style: TextStyle(fontSize: 16, color: Colors.grey), ), ], ), ), ) else ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: _agents.length, itemBuilder: (context, index) { final agent = _agents[index]; return _buildAgentCard(agent); }, ), ], ), ), ], ); } Widget _buildAgentCard(Map agent) { final status = agent['status'] ?? 'active'; final statusColor = status == 'active' ? AdminColors.success : AdminColors.error; return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[200]!), ), child: Row( children: [ Container( width: 50, height: 50, decoration: BoxDecoration( color: statusColor.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Icon( status == 'active' ? Icons.person : Icons.person_off, color: statusColor, size: 24, ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( agent['nom'] ?? 'Nom non disponible', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black87, ), ), ), Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: statusColor.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Text( status.toUpperCase(), style: TextStyle( fontSize: 10, color: statusColor, fontWeight: FontWeight.w600, ), ), ), ], ), const SizedBox(height: 4), Text( 'ID: ${agent['agent_id'] ?? 'N/A'} • ${agent['role'] ?? 'agent'}', style: TextStyle(fontSize: 12, color: Colors.grey[600]), ), const SizedBox(height: 4), Text( 'Solde: ${(agent['solde'] ?? 0).toStringAsFixed(0)} F', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: AdminColors.secondary, ), ), ], ), ), const SizedBox(width: 12), IconButton( onPressed: () => _showAgentDetails(agent), icon: const Icon(Icons.visibility, color: AdminColors.secondary), tooltip: 'Voir détails', ), ], ), ); } Widget _buildRechargesTab() { // Grouper les recharges par rechargeur final rechargesByRechargeur = >>{}; for (final recharge in _allRecharges) { final rechargeur = recharge['approved_by'] ?? recharge['rejected_by'] ?? 'Non assigné'; if (!rechargesByRechargeur.containsKey(rechargeur)) { rechargesByRechargeur[rechargeur] = []; } rechargesByRechargeur[rechargeur]!.add(recharge); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Supervision des recharges', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black87, ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: AdminColors.secondary.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Text( '${rechargesByRechargeur.length} rechargeurs', style: const TextStyle( fontWeight: FontWeight.w600, color: AdminColors.secondary, ), ), ), ], ), const SizedBox(height: 16), // Statistiques globales recharges _buildRechargeGlobalStats(), const SizedBox(height: 20), // Liste des rechargeurs Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: AdminColors.surface, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Recharges par rechargeur', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87, ), ), const SizedBox(height: 16), if (rechargesByRechargeur.isEmpty) const Center( child: Text( 'Aucune recharge trouvée', style: TextStyle(color: Colors.grey), ), ) else ...rechargesByRechargeur.entries.map( (entry) => _buildRechargeurCard(entry.key, entry.value), ), ], ), ), ], ); } Widget _buildRechargeGlobalStats() { final totalAmount = _allRecharges.fold( 0.0, (sum, r) => sum + (double.tryParse(r['montant']?.toString() ?? '0') ?? 0.0), ); final successfulRecharges = _allRecharges .where((r) => ['approved', 'completed'].contains(r['status'])) .toList(); final successAmount = successfulRecharges.fold( 0.0, (sum, r) => sum + (double.tryParse(r['montant']?.toString() ?? '0') ?? 0.0), ); final successRate = _allRecharges.isNotEmpty ? (successfulRecharges.length / _allRecharges.length * 100) : 0.0; return Row( children: [ Expanded( child: _buildRechargeStatCard( 'Volume total', '${(totalAmount / 1000000).toStringAsFixed(1)}M F', Icons.monetization_on, AdminColors.secondary, ), ), const SizedBox(width: 12), Expanded( child: _buildRechargeStatCard( 'Volume réussi', '${(successAmount / 1000000).toStringAsFixed(1)}M F', Icons.check_circle, AdminColors.success, ), ), const SizedBox(width: 12), Expanded( child: _buildRechargeStatCard( 'Taux de réussite', '${successRate.toStringAsFixed(1)}%', Icons.trending_up, AdminColors.info, ), ), ], ); } Widget _buildRechargeurCard( String rechargeur, List> recharges, ) { final successfulRecharges = recharges .where((r) => ['approved', 'completed'].contains(r['status'])) .toList(); final totalAmount = successfulRecharges.fold( 0.0, (sum, r) => sum + (double.tryParse(r['montant']?.toString() ?? '0') ?? 0.0), ); return Container( margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[200]!), ), child: ExpansionTile( title: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: AdminColors.secondary.withOpacity(0.1), borderRadius: BorderRadius.circular(10), ), child: const Icon( Icons.person, color: AdminColors.secondary, size: 20, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( rechargeur, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.black87, ), ), Text( '${recharges.length} recharges • ${totalAmount.toStringAsFixed(0)} F réussis', style: TextStyle(fontSize: 12, color: Colors.grey[600]), ), ], ), ), ], ), children: recharges .map((recharge) => _buildRechargeListItem(recharge)) .toList(), ), ); } Widget _buildRechargeListItem(Map recharge) { final status = recharge['status'] ?? ''; final statusColor = _getRechargeStatusColor(status); return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Row( children: [ Container( width: 30, height: 30, decoration: BoxDecoration( color: statusColor.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Icon( _getRechargeStatusIcon(status), color: statusColor, size: 16, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Agent ${recharge['agent_id'] ?? 'N/A'}', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, ), ), Text( '${recharge['montant'] ?? 0} F • ${_formatDate(recharge['created_at'])}', style: TextStyle(fontSize: 12, color: Colors.grey[600]), ), ], ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: statusColor.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Text( status.toUpperCase(), style: TextStyle( fontSize: 10, color: statusColor, fontWeight: FontWeight.w600, ), ), ), ], ), ); } Widget _buildTransactionsTab() { // Grouper les transactions par service final transactionsByService = >>{}; for (final transaction in _allTransactions) { final service = transaction['service_name'] ?? 'Service inconnu'; if (!transactionsByService.containsKey(service)) { transactionsByService[service] = []; } transactionsByService[service]!.add(transaction); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Gestion des transactions', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black87, ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: AdminColors.info.withOpacity(0.1), borderRadius: BorderRadius.circular(12), ), child: Text( '${transactionsByService.length} services', style: const TextStyle( fontWeight: FontWeight.w600, color: AdminColors.info, ), ), ), ], ), const SizedBox(height: 16), // Statistiques globales transactions _buildTransactionGlobalStats(), const SizedBox(height: 20), // Liste des services Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: AdminColors.surface, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Transactions par service', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87, ), ), const SizedBox(height: 16), if (transactionsByService.isEmpty) const Center( child: Text( 'Aucune transaction trouvée', style: TextStyle(color: Colors.grey), ), ) else ...transactionsByService.entries.map( (entry) => _buildServiceCard(entry.key, entry.value), ), ], ), ), ], ); } Widget _buildTransactionGlobalStats() { final successfulTransactions = _allTransactions.where((t) => t['status'] == 'completed').toList(); print(successfulTransactions); final totalVolume = successfulTransactions.fold( 0.0, (sum, t) => sum + (double.tryParse(t['montant']?.toString() ?? '0') ?? 0.0), ); final totalCommissions = successfulTransactions.fold( 0.0, (sum, t) => sum + (double.tryParse(t['commission_agent']?.toString() ?? '0') ?? 0.0), ); final successRate = _allTransactions.isNotEmpty ? (successfulTransactions.length / _allTransactions.length * 100) : 0.0; return Row( children: [ Expanded( child: _buildTransactionStatCard( 'Volume total', '${(totalVolume / 1000000).toStringAsFixed(1)}M F', Icons.monetization_on, AdminColors.secondary, ), ), const SizedBox(width: 12), Expanded( child: _buildTransactionStatCard( 'Commissions', '${((totalVolume * 0.66) / 100000).toStringAsFixed(0)}k F', Icons.account_balance, AdminColors.accent, ), ), const SizedBox(width: 12), Expanded( child: _buildTransactionStatCard( 'Taux de réussite', '${successRate.toStringAsFixed(1)}%', Icons.trending_up, AdminColors.success, ), ), ], ); } Widget _buildServiceCard( String service, List> transactions, ) { final successfulTransactions = transactions.where((t) => t['status'] == 'completed').toList(); final totalVolume = successfulTransactions.fold( 0.0, (sum, t) => sum + (double.tryParse(t['montant']?.toString() ?? '0') ?? 0.0), ); Color serviceColor = AdminColors.info; IconData serviceIcon = Icons.widgets; if (service.toLowerCase().contains('internet')) { serviceColor = AdminColors.secondary; serviceIcon = Icons.wifi; } else if (service.toLowerCase().contains('électricité') || service.toLowerCase().contains('eneo')) { serviceColor = AdminColors.warning; serviceIcon = Icons.electrical_services; } else if (service.toLowerCase().contains('eau')) { serviceColor = AdminColors.info; serviceIcon = Icons.water_drop; } return Container( margin: const EdgeInsets.only(bottom: 16), decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[200]!), ), child: ExpansionTile( title: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: serviceColor.withOpacity(0.1), borderRadius: BorderRadius.circular(10), ), child: Icon(serviceIcon, color: serviceColor, size: 20), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( service, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.black87, ), ), Text( '${transactions.length} transactions • ${totalVolume.toStringAsFixed(0)} F', style: TextStyle(fontSize: 12, color: Colors.grey[600]), ), ], ), ), ], ), children: transactions .take(10) .map((transaction) => _buildTransactionListItem(transaction)) .toList(), ), ); } Widget _buildTransactionListItem(Map transaction) { final status = transaction['status'] ?? ''; final statusColor = _getTransactionStatusColor(status); return Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Row( children: [ Container( width: 30, height: 30, decoration: BoxDecoration( color: statusColor.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Icon( _getTransactionStatusIcon(status), color: statusColor, size: 16, ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Agent ${transaction['agent_id'] ?? 'N/A'}', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, ), ), Text( '${transaction['montant'] ?? 0} F • ${_formatDate(transaction['created_at'])}', style: TextStyle(fontSize: 12, color: Colors.grey[600]), ), ], ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: statusColor.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Text( status.toUpperCase(), style: TextStyle( fontSize: 10, color: statusColor, fontWeight: FontWeight.w600, ), ), ), ], ), ); } Widget _buildTransactionStatCard( String title, String value, IconData icon, Color color, ) { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AdminColors.surface, borderRadius: BorderRadius.circular(12), border: Border.all(color: color.withOpacity(0.2)), boxShadow: [ BoxShadow( color: color.withOpacity(0.1), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Column( children: [ Icon(icon, color: color, size: 20), const SizedBox(height: 8), Text( value, style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: color, ), ), Text( title, style: TextStyle(fontSize: 9, color: Colors.grey[600]), textAlign: TextAlign.center, ), ], ), ); } Widget _buildTransactionCard(Map transaction) { final status = transaction['status'] ?? ''; final statusColor = _getTransactionStatusColor(status); return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: statusColor.withOpacity(0.05), borderRadius: BorderRadius.circular(12), border: Border.all(color: statusColor.withOpacity(0.2)), ), child: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: statusColor.withOpacity(0.1), borderRadius: BorderRadius.circular(10), ), child: Icon( _getTransactionStatusIcon(status), color: statusColor, size: 20, ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( 'Agent ${transaction['agent_id'] ?? 'N/A'}', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), ), Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: statusColor.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Text( status.toUpperCase(), style: TextStyle( fontSize: 10, color: statusColor, fontWeight: FontWeight.w600, ), ), ), ], ), const SizedBox(height: 4), Text( 'Service: ${transaction['service_name'] ?? 'N/A'}', style: TextStyle(fontSize: 12, color: Colors.grey[600]), ), Text( 'Montant: ${transaction['montant'] ?? 0} F', style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w500, color: AdminColors.secondary, ), ), if (transaction['created_at'] != null) Text( 'Date: ${_formatDate(transaction['created_at'])}', style: TextStyle(fontSize: 11, color: Colors.grey[600]), ), ], ), ), ], ), ); } Widget _buildRechargeStatCard( String title, String value, IconData icon, Color color, ) { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: AdminColors.surface, borderRadius: BorderRadius.circular(12), border: Border.all(color: color.withOpacity(0.2)), boxShadow: [ BoxShadow( color: color.withOpacity(0.1), blurRadius: 8, offset: const Offset(0, 2), ), ], ), child: Column( children: [ Icon(icon, color: color, size: 20), const SizedBox(height: 8), Text( value, style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: color, ), ), Text( title, style: TextStyle(fontSize: 10, color: Colors.grey[600]), textAlign: TextAlign.center, ), ], ), ); } Widget _buildRechargeCard(Map recharge) { final status = recharge['status'] ?? ''; final statusColor = _getRechargeStatusColor(status); return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: statusColor.withOpacity(0.05), borderRadius: BorderRadius.circular(12), border: Border.all(color: statusColor.withOpacity(0.2)), ), child: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: statusColor.withOpacity(0.1), borderRadius: BorderRadius.circular(10), ), child: Icon( _getRechargeStatusIcon(status), color: statusColor, size: 20, ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text( 'Agent ${recharge['agent_id'] ?? 'N/A'}', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), ), Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: statusColor.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Text( status.toUpperCase(), style: TextStyle( fontSize: 10, color: statusColor, fontWeight: FontWeight.w600, ), ), ), ], ), const SizedBox(height: 4), Text( 'Montant: ${recharge['montant'] ?? 0} F', style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w500, color: AdminColors.secondary, ), ), if (recharge['created_at'] != null) Text( 'Créé: ${_formatDate(recharge['created_at'])}', style: TextStyle(fontSize: 11, color: Colors.grey[600]), ), ], ), ), ], ), ); } Widget _buildReportsTab() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Rapports et analyses', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black87, ), ), const SizedBox(height: 16), Row( children: [ Expanded( child: _buildReportCard( 'Rapport financier', 'Transactions et revenus', Icons.monetization_on, AdminColors.success, onTap: () => _exportData('financial'), ), ), const SizedBox(width: 12), Expanded( child: _buildReportCard( 'Rapport agents', 'Performance des agents', Icons.people, AdminColors.secondary, onTap: () => _exportData('agents'), ), ), ], ), const SizedBox(height: 12), Row( children: [ Expanded( child: _buildReportCard( 'Rapport recharges', 'Historique complet', Icons.account_balance_wallet, AdminColors.warning, onTap: () => _exportData('recharges'), ), ), const SizedBox(width: 12), Expanded( child: _buildReportCard( 'Rapport système', 'Logs et performances', Icons.computer, AdminColors.info, onTap: () => _exportData('system'), ), ), ], ), const SizedBox(height: 20), Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: AdminColors.surface, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.05), blurRadius: 10, offset: const Offset(0, 4), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Statistiques détaillées', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black87, ), ), const SizedBox(height: 16), _buildDetailedStats(), ], ), ), ], ); } Widget _buildReportCard( String title, String subtitle, IconData icon, Color color, { VoidCallback? onTap, }) { return GestureDetector( onTap: onTap, child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: AdminColors.surface, borderRadius: BorderRadius.circular(12), border: Border.all(color: color.withOpacity(0.2)), boxShadow: [ BoxShadow( color: color.withOpacity(0.1), blurRadius: 8, offset: const Offset(0, 4), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Icon(icon, color: color, size: 24), Icon(Icons.download, color: color.withOpacity(0.7), size: 16), ], ), const SizedBox(height: 12), Text( title, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black87, ), ), const SizedBox(height: 4), Text( subtitle, style: TextStyle(fontSize: 11, color: Colors.grey[600]), ), ], ), ), ); } Widget _buildDetailedStats() { return Column( children: [ _buildStatRow( 'Volume total traité', '${((_systemStats['total_volume'] ?? 0.0) / 1000000).toStringAsFixed(2)} M FCFA', ), _buildStatRow( 'Nombre d\'agents', '${_systemStats['active_agents'] ?? 0}', ), _buildStatRow( 'Transactions aujourd\'hui', '${_systemStats['daily_transactions'] ?? 0}', ), _buildStatRow( 'Taux de réussite', '${(_systemStats['success_rate'] ?? 0.0).toStringAsFixed(1)}%', ), _buildStatRow( 'Recharges en attente', '${_systemStats['pending_recharges'] ?? 0}', ), _buildStatRow('Dernière mise à jour', 'Maintenant'), ], ); } Widget _buildStatRow(String label, String value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(label, style: TextStyle(fontSize: 14, color: Colors.grey[700])), Text( value, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), ], ), ); } void _showExportDialog() { showDialog( context: context, builder: (context) => AlertDialog( shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), title: const Row( children: [ Icon(Icons.download, color: AdminColors.info), SizedBox(width: 8), Text('Export de données'), ], ), content: Column( mainAxisSize: MainAxisSize.min, children: [ const Text('Choisissez le type de données à exporter:'), const SizedBox(height: 16), ListTile( leading: const Icon( Icons.monetization_on, color: AdminColors.success, ), title: const Text('Rapport financier'), subtitle: const Text('Transactions et revenus'), onTap: () { Navigator.pop(context); _exportData('financial'); }, ), ListTile( leading: const Icon( Icons.people, color: AdminColors.secondary, ), title: const Text('Liste des agents'), subtitle: const Text('Tous les agents et leurs données'), onTap: () { Navigator.pop(context); _exportData('agents'); }, ), ListTile( leading: const Icon( Icons.account_balance_wallet, color: AdminColors.warning, ), title: const Text('Historique recharges'), subtitle: const Text('Toutes les recharges'), onTap: () { Navigator.pop(context); _exportData('recharges'); }, ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Annuler'), ), ], ), ); } // ===== UTILITAIRES ===== IconData _getIconFromString(String iconName) { switch (iconName) { case 'person_add': return Icons.person_add; case 'check_circle': return Icons.check_circle; case 'description': return Icons.description; default: return Icons.info; } } Color _getColorFromString(String colorName) { switch (colorName) { case 'green': return AdminColors.success; case 'blue': return AdminColors.secondary; case 'orange': return AdminColors.warning; case 'red': return AdminColors.error; default: return Colors.grey; } } Color _getRechargeStatusColor(String status) { switch (status) { case 'pending': return AdminColors.warning; case 'approved': case 'completed': return AdminColors.success; case 'rejected': return AdminColors.error; default: return Colors.grey; } } IconData _getRechargeStatusIcon(String status) { switch (status) { case 'pending': return Icons.pending; case 'approved': case 'completed': return Icons.check_circle; case 'rejected': return Icons.cancel; default: return Icons.help_outline; } } Color _getTransactionStatusColor(String status) { switch (status) { case 'completed': return AdminColors.success; case 'pending': return AdminColors.warning; case 'failed': return AdminColors.error; default: return Colors.grey; } } IconData _getTransactionStatusIcon(String status) { switch (status) { case 'completed': return Icons.check_circle; case 'pending': return Icons.pending; case 'failed': return Icons.error; default: return Icons.help_outline; } } String _formatDate(String? dateString) { if (dateString == null) return 'N/A'; try { final date = DateTime.parse(dateString); final now = DateTime.now(); final difference = now.difference(date); if (difference.inDays > 0) { return '${difference.inDays}j'; } else if (difference.inHours > 0) { return '${difference.inHours}h'; } else if (difference.inMinutes > 0) { return '${difference.inMinutes}min'; } else { return 'Maintenant'; } } catch (e) { return 'N/A'; } } }