761 lines
24 KiB
Dart
761 lines
24 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import 'package:fl_chart/fl_chart.dart';
|
|
import '../../../../shared/design_system/unionflow_design_v2.dart';
|
|
import '../../../../shared/design_system/unionflow_design_system.dart';
|
|
import '../../../contributions/presentation/pages/contributions_page_wrapper.dart';
|
|
import '../../../epargne/presentation/pages/epargne_page.dart';
|
|
import '../../../events/presentation/pages/events_page_wrapper.dart';
|
|
import '../bloc/dashboard_bloc.dart';
|
|
import '../../domain/entities/dashboard_entity.dart';
|
|
|
|
/// Page dashboard connectée au backend - Design UnionFlow Animé
|
|
class ConnectedDashboardPage extends StatefulWidget {
|
|
final String organizationId;
|
|
final String userId;
|
|
|
|
const ConnectedDashboardPage({
|
|
super.key,
|
|
required this.organizationId,
|
|
required this.userId,
|
|
});
|
|
|
|
@override
|
|
State<ConnectedDashboardPage> createState() => _ConnectedDashboardPageState();
|
|
}
|
|
|
|
class _ConnectedDashboardPageState extends State<ConnectedDashboardPage> with SingleTickerProviderStateMixin {
|
|
late TabController _tabController;
|
|
PeriodFilter _selectedPeriod = PeriodFilter.month;
|
|
int _unreadNotifications = 5;
|
|
bool _isExporting = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_tabController = TabController(length: 3, vsync: this);
|
|
context.read<DashboardBloc>().add(LoadDashboardData(
|
|
organizationId: widget.organizationId,
|
|
userId: widget.userId,
|
|
));
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_tabController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: AppColors.lightBackground,
|
|
appBar: _buildAppBar(),
|
|
body: AfricanPatternBackground(
|
|
child: BlocBuilder<DashboardBloc, DashboardState>(
|
|
builder: (context, state) {
|
|
if (state is DashboardLoading) {
|
|
return const Center(
|
|
child: CircularProgressIndicator(color: AppColors.primaryGreen),
|
|
);
|
|
}
|
|
|
|
if (state is DashboardMemberNotRegistered) {
|
|
return _buildMemberNotRegisteredState();
|
|
}
|
|
|
|
if (state is DashboardError) {
|
|
return _buildErrorState(state.message);
|
|
}
|
|
|
|
if (state is DashboardLoaded) {
|
|
return _buildDashboardContent(state);
|
|
}
|
|
|
|
return const SizedBox.shrink();
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
PreferredSizeWidget _buildAppBar() {
|
|
return AppBar(
|
|
backgroundColor: AppColors.lightSurface,
|
|
elevation: 0,
|
|
title: Row(
|
|
children: [
|
|
Hero(
|
|
tag: 'unionflow_logo',
|
|
child: Container(
|
|
width: 32,
|
|
height: 32,
|
|
decoration: BoxDecoration(
|
|
gradient: UnionFlowColors.primaryGradient,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
alignment: Alignment.center,
|
|
child: const Text(
|
|
'U',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.w900,
|
|
fontSize: 18,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
const Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'UnionFlow',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w700,
|
|
color: AppColors.textPrimaryLight,
|
|
),
|
|
),
|
|
Text(
|
|
'Dashboard',
|
|
style: TextStyle(
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w400,
|
|
color: AppColors.textSecondaryLight,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
automaticallyImplyLeading: false,
|
|
actions: [
|
|
UnionExportButton(
|
|
isLoading: _isExporting,
|
|
onExport: (exportType) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => ExportConfirmDialog(
|
|
exportType: exportType,
|
|
onConfirm: () => _handleExport(exportType),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
const SizedBox(width: 8),
|
|
UnionNotificationBadge(
|
|
count: _unreadNotifications,
|
|
child: IconButton(
|
|
icon: const Icon(Icons.notifications_outlined),
|
|
color: AppColors.textPrimaryLight,
|
|
onPressed: () {
|
|
setState(() => _unreadNotifications = 0);
|
|
UnionNotificationToast.show(
|
|
context,
|
|
title: 'Notifications',
|
|
message: 'Aucune nouvelle notification',
|
|
icon: Icons.notifications_active,
|
|
color: UnionFlowColors.info,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
],
|
|
bottom: TabBar(
|
|
controller: _tabController,
|
|
labelColor: AppColors.primaryGreen,
|
|
unselectedLabelColor: AppColors.textSecondaryLight,
|
|
indicatorColor: AppColors.primaryGreen,
|
|
labelStyle: const TextStyle(fontSize: 13, fontWeight: FontWeight.w700),
|
|
tabs: const [
|
|
Tab(text: 'Vue d\'ensemble'),
|
|
Tab(text: 'Analytique'),
|
|
Tab(text: 'Activités'),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDashboardContent(DashboardLoaded state) {
|
|
final data = state.dashboardData;
|
|
|
|
return RefreshIndicator(
|
|
onRefresh: () async {
|
|
context.read<DashboardBloc>().add(LoadDashboardData(
|
|
organizationId: widget.organizationId,
|
|
userId: widget.userId,
|
|
));
|
|
},
|
|
color: AppColors.primaryGreen,
|
|
child: TabBarView(
|
|
controller: _tabController,
|
|
children: [
|
|
_buildOverviewTab(data),
|
|
_buildAnalyticsTab(data),
|
|
_buildActivitiesTab(data),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
UnionTransactionTile _activityToTile(RecentActivityEntity a) {
|
|
final amount = a.metadata != null && a.metadata!['amount'] != null
|
|
? '${a.metadata!['amount']} FCFA'
|
|
: (a.title.isNotEmpty ? a.title : '-');
|
|
return UnionTransactionTile(
|
|
name: a.userName,
|
|
amount: amount,
|
|
status: a.type.isNotEmpty ? a.type : 'Confirmé',
|
|
date: a.timeAgo,
|
|
);
|
|
}
|
|
|
|
Widget _buildOverviewTab(DashboardEntity data) {
|
|
final stats = data.stats;
|
|
return SingleChildScrollView(
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
|
padding: const EdgeInsets.all(12),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Balance principale - Animée
|
|
AnimatedSlideIn(
|
|
delay: const Duration(milliseconds: 100),
|
|
child: UnionBalanceCard(
|
|
label: 'Caisse Totale',
|
|
amount: _formatAmount(stats.totalContributionAmount),
|
|
trend: stats.monthlyGrowth > 0 ? '+${(stats.monthlyGrowth * 100).toStringAsFixed(0)}% ce mois' : 'Stable',
|
|
isTrendPositive: true,
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
// Stats en grille - Animées avec délai
|
|
AnimatedSlideIn(
|
|
delay: const Duration(milliseconds: 200),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: UnionStatWidget(
|
|
label: 'Membres',
|
|
value: stats.totalMembers.toString(),
|
|
icon: Icons.people_outline,
|
|
color: AppColors.primaryGreen,
|
|
trend: '+8%',
|
|
isTrendUp: true,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: UnionStatWidget(
|
|
label: 'Actifs',
|
|
value: stats.activeMembers.toString(),
|
|
icon: Icons.check_circle_outline,
|
|
color: UnionFlowColors.success,
|
|
trend: '+5%',
|
|
isTrendUp: true,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
AnimatedSlideIn(
|
|
delay: const Duration(milliseconds: 300),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: UnionStatWidget(
|
|
label: 'Événements',
|
|
value: stats.totalEvents.toString(),
|
|
icon: Icons.event_outlined,
|
|
color: UnionFlowColors.gold,
|
|
trend: '+3',
|
|
isTrendUp: true,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: UnionStatWidget(
|
|
label: 'À venir',
|
|
value: stats.upcomingEvents.toString(),
|
|
icon: Icons.calendar_today,
|
|
color: UnionFlowColors.amber,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
|
|
// Progression - Animée
|
|
AnimatedFadeIn(
|
|
delay: const Duration(milliseconds: 400),
|
|
child: UnionProgressCard(
|
|
title: 'Progression des Cotisations',
|
|
progress: 0.7,
|
|
subtitle: '70% des membres ont cotisé ce mois',
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
|
|
// Actions rapides - Animées
|
|
AnimatedSlideIn(
|
|
delay: const Duration(milliseconds: 500),
|
|
begin: const Offset(0, 0.2),
|
|
child: UnionActionGrid(
|
|
actions: [
|
|
UnionActionButton(
|
|
icon: Icons.payment,
|
|
label: 'Cotiser',
|
|
onTap: () {
|
|
Navigator.of(context).push(
|
|
MaterialPageRoute<void>(builder: (_) => const ContributionsPageWrapper()),
|
|
);
|
|
},
|
|
backgroundColor: UnionFlowColors.unionGreenPale,
|
|
iconColor: AppColors.primaryGreen,
|
|
),
|
|
UnionActionButton(
|
|
icon: Icons.send,
|
|
label: 'Envoyer',
|
|
onTap: () {
|
|
Navigator.of(context).push(
|
|
MaterialPageRoute<void>(builder: (_) => const ContributionsPageWrapper()),
|
|
);
|
|
},
|
|
backgroundColor: UnionFlowColors.goldPale,
|
|
iconColor: UnionFlowColors.gold,
|
|
),
|
|
UnionActionButton(
|
|
icon: Icons.download,
|
|
label: 'Retirer',
|
|
onTap: () {
|
|
Navigator.of(context).push(
|
|
MaterialPageRoute<void>(builder: (_) => const EpargnePage()),
|
|
);
|
|
},
|
|
backgroundColor: UnionFlowColors.terracottaPale,
|
|
iconColor: UnionFlowColors.terracotta,
|
|
),
|
|
UnionActionButton(
|
|
icon: Icons.add_circle_outline,
|
|
label: 'Créer',
|
|
onTap: () {
|
|
Navigator.of(context).push(
|
|
MaterialPageRoute<void>(builder: (_) => const EventsPageWrapper()),
|
|
);
|
|
},
|
|
backgroundColor: UnionFlowColors.infoPale,
|
|
iconColor: UnionFlowColors.info,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
|
|
// Activité récente - Animée
|
|
AnimatedFadeIn(
|
|
delay: const Duration(milliseconds: 600),
|
|
child: UnionTransactionCard(
|
|
title: 'Activité Récente',
|
|
onSeeAll: () {
|
|
Navigator.of(context).push(
|
|
MaterialPageRoute<void>(builder: (_) => const ContributionsPageWrapper()),
|
|
);
|
|
},
|
|
transactions: data.recentActivities.take(6).map((a) => _activityToTile(a)).toList(),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildAnalyticsTab(DashboardEntity data) {
|
|
final stats = data.stats;
|
|
final entrees = stats.totalContributionAmount;
|
|
final sorties = stats.pendingRequests * 1000.0;
|
|
final benefice = entrees - sorties;
|
|
final taux = (stats.engagementRate * 100).toStringAsFixed(0);
|
|
return SingleChildScrollView(
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
|
padding: const EdgeInsets.all(12),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Filtre de période - Animé
|
|
AnimatedFadeIn(
|
|
delay: const Duration(milliseconds: 50),
|
|
child: UnionPeriodFilter(
|
|
selectedPeriod: _selectedPeriod,
|
|
onPeriodChanged: (period) {
|
|
setState(() => _selectedPeriod = period);
|
|
UnionNotificationToast.show(
|
|
context,
|
|
title: 'Période mise à jour',
|
|
message: 'Affichage pour ${period.label.toLowerCase()}',
|
|
icon: Icons.calendar_today,
|
|
color: AppColors.primaryGreen,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
// Line Chart - Animé (évolution basée sur total cotisations + croissance)
|
|
AnimatedSlideIn(
|
|
delay: const Duration(milliseconds: 100),
|
|
child: UnionLineChart(
|
|
title: 'Évolution de la Caisse',
|
|
subtitle: 'Derniers 12 mois',
|
|
spots: _buildEvolutionSpots(stats.totalContributionAmount, stats.monthlyGrowth),
|
|
),
|
|
),
|
|
const SizedBox(height: 24),
|
|
|
|
// Pie Chart - Animé
|
|
AnimatedFadeIn(
|
|
delay: const Duration(milliseconds: 300),
|
|
child: UnionPieChart(
|
|
title: 'Répartition des Cotisations',
|
|
subtitle: 'Par catégorie',
|
|
sections: [
|
|
UnionPieChartSection.create(
|
|
value: 40,
|
|
color: AppColors.primaryGreen,
|
|
title: '40%\nCotisations',
|
|
),
|
|
UnionPieChartSection.create(
|
|
value: 30,
|
|
color: UnionFlowColors.gold,
|
|
title: '30%\nÉpargne',
|
|
),
|
|
UnionPieChartSection.create(
|
|
value: 20,
|
|
color: UnionFlowColors.terracotta,
|
|
title: '20%\nSolidarité',
|
|
),
|
|
UnionPieChartSection.create(
|
|
value: 10,
|
|
color: UnionFlowColors.amber,
|
|
title: '10%\nAutres',
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
|
|
// Titre
|
|
AnimatedFadeIn(
|
|
delay: const Duration(milliseconds: 400),
|
|
child: const Text(
|
|
'Métriques Financières',
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w700,
|
|
color: AppColors.textPrimaryLight,
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
|
|
// Métriques - Animées (données backend)
|
|
AnimatedSlideIn(
|
|
delay: const Duration(milliseconds: 500),
|
|
begin: const Offset(0, 0.2),
|
|
child: Column(
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildFinanceMetric(
|
|
'Entrées',
|
|
_formatFcfa(entrees),
|
|
Icons.arrow_downward,
|
|
UnionFlowColors.success,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: _buildFinanceMetric(
|
|
'Sorties',
|
|
_formatFcfa(sorties),
|
|
Icons.arrow_upward,
|
|
UnionFlowColors.error,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
const SizedBox(height: 12),
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: _buildFinanceMetric(
|
|
'Bénéfice',
|
|
_formatFcfa(benefice),
|
|
Icons.trending_up,
|
|
UnionFlowColors.gold,
|
|
),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: _buildFinanceMetric(
|
|
'Taux',
|
|
'$taux%',
|
|
Icons.percent,
|
|
UnionFlowColors.info,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildActivitiesTab(DashboardEntity data) {
|
|
final tiles = data.recentActivities.map((a) => _activityToTile(a)).toList();
|
|
return SingleChildScrollView(
|
|
physics: const AlwaysScrollableScrollPhysics(),
|
|
padding: const EdgeInsets.all(12),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
AnimatedSlideIn(
|
|
delay: const Duration(milliseconds: 100),
|
|
child: UnionTransactionCard(
|
|
title: 'Toutes les Activités',
|
|
onSeeAll: () {
|
|
Navigator.of(context).push(
|
|
MaterialPageRoute<void>(builder: (_) => const ContributionsPageWrapper()),
|
|
);
|
|
},
|
|
transactions: tiles,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> _handleExport(ExportType exportType) async {
|
|
setState(() => _isExporting = true);
|
|
|
|
// Simulation de l'export (dans un vrai cas, appel API ici)
|
|
await Future.delayed(const Duration(seconds: 2));
|
|
|
|
setState(() => _isExporting = false);
|
|
|
|
if (mounted) {
|
|
UnionNotificationToast.show(
|
|
context,
|
|
title: 'Export réussi',
|
|
message: 'Le rapport ${exportType.label} a été généré avec succès',
|
|
icon: Icons.check_circle,
|
|
color: UnionFlowColors.success,
|
|
);
|
|
}
|
|
}
|
|
|
|
String _formatFcfa(double value) {
|
|
if (value >= 1000000) return '${(value / 1000000).toStringAsFixed(1)}M FCFA';
|
|
if (value >= 1000) return '${(value / 1000).toStringAsFixed(0)}K FCFA';
|
|
return '${value.toStringAsFixed(0)} FCFA';
|
|
}
|
|
|
|
List<FlSpot> _buildEvolutionSpots(double totalAmount, double monthlyGrowth) {
|
|
final spots = <FlSpot>[];
|
|
var v = totalAmount * 0.5;
|
|
for (var i = 0; i < 12; i++) {
|
|
spots.add(FlSpot(i.toDouble(), v));
|
|
v = v * (1 + (monthlyGrowth > 0 ? monthlyGrowth : 0.02));
|
|
}
|
|
if (spots.isNotEmpty) spots[spots.length - 1] = FlSpot(11, totalAmount);
|
|
return spots;
|
|
}
|
|
|
|
Widget _buildFinanceMetric(String label, String value, IconData icon, Color color) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(10),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.lightSurface,
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(7),
|
|
decoration: BoxDecoration(
|
|
color: color.withOpacity(0.1),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Icon(icon, size: 16, color: color),
|
|
),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
label,
|
|
style: const TextStyle(
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.textSecondaryLight,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
value,
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w700,
|
|
color: color,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildMemberNotRegisteredState() {
|
|
return Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(24),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
gradient: UnionFlowColors.primaryGradient,
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: const Icon(
|
|
Icons.person_add_alt_1_outlined,
|
|
size: 36,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
const SizedBox(height: 14),
|
|
const Text(
|
|
'Bienvenue dans UnionFlow',
|
|
style: TextStyle(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w800,
|
|
color: AppColors.textPrimaryLight,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 8),
|
|
const Text(
|
|
'Votre compte est en cours de configuration par un administrateur. '
|
|
'Votre tableau de bord sera disponible dès que votre profil membre aura été activé.',
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: AppColors.textSecondaryLight,
|
|
height: 1.5,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: AppColors.primaryGreen.withOpacity(0.08),
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(color: AppColors.primaryGreen.withOpacity(0.3)),
|
|
),
|
|
child: const Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(Icons.info_outline, size: 18, color: AppColors.primaryGreen),
|
|
SizedBox(width: 10),
|
|
Flexible(
|
|
child: Text(
|
|
'Contactez votre administrateur si ce message persiste.',
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
color: AppColors.primaryGreen,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildErrorState(String message) {
|
|
return Center(
|
|
child: AnimatedFadeIn(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Container(
|
|
padding: const EdgeInsets.all(16),
|
|
decoration: BoxDecoration(
|
|
color: UnionFlowColors.errorPale,
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: const Icon(
|
|
Icons.error_outline,
|
|
size: 40,
|
|
color: UnionFlowColors.error,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
const Text(
|
|
'Erreur de chargement',
|
|
style: TextStyle(
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w700,
|
|
color: AppColors.textPrimaryLight,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
message,
|
|
style: const TextStyle(
|
|
fontSize: 13,
|
|
color: AppColors.textSecondaryLight,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 24),
|
|
UFPrimaryButton(
|
|
onPressed: () {
|
|
context.read<DashboardBloc>().add(LoadDashboardData(
|
|
organizationId: widget.organizationId,
|
|
userId: widget.userId,
|
|
));
|
|
},
|
|
label: 'RÉESSAYER',
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
String _formatAmount(num amount) {
|
|
return '${amount.toStringAsFixed(0).replaceAllMapped(
|
|
RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'),
|
|
(Match m) => '${m[1]},',
|
|
)} FCFA';
|
|
}
|
|
}
|