refactoring

This commit is contained in:
dahoud
2026-03-31 09:14:47 +00:00
parent 9bfffeeebe
commit 5383df6dcb
200 changed files with 11192 additions and 7063 deletions

View File

@@ -20,12 +20,8 @@ class ActiveMemberDashboard extends StatelessWidget {
backgroundColor: UnionFlowColors.background,
appBar: _buildAppBar(),
drawer: DashboardDrawer(
onNavigate: (route) {
Navigator.of(context).pushNamed(route);
},
onLogout: () {
context.read<AuthBloc>().add(const AuthLogoutRequested());
},
onNavigate: (route) => Navigator.of(context).pushNamed(route),
onLogout: () => context.read<AuthBloc>().add(const AuthLogoutRequested()),
),
body: AfricanPatternBackground(
child: BlocBuilder<AuthBloc, AuthState>(
@@ -46,18 +42,25 @@ class ActiveMemberDashboard extends StatelessWidget {
final stats = dashboardData?.stats;
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// En-tête
AnimatedFadeIn(
delay: const Duration(milliseconds: 100),
child: _buildUserHeader(user),
child: UserIdentityCard(
initials: user?.initials ?? 'MA',
name: user?.fullName ?? 'Membre Actif',
subtitle: 'Depuis ${user?.createdAt.year ?? 2024} · Très Actif',
badgeLabel: 'ACTIF',
gradient: UnionFlowColors.warmGradient,
accentColor: UnionFlowColors.gold,
),
),
const SizedBox(height: 24),
const SizedBox(height: 12),
// Balance principale ou Vue Unifiée (Compte Adhérent)
// Balance / Vue Unifiée
AnimatedSlideIn(
delay: const Duration(milliseconds: 200),
child: dashboardData?.monCompte != null
@@ -73,309 +76,147 @@ class ActiveMemberDashboard extends StatelessWidget {
label: 'Mon Solde Total',
amount: _formatAmount(stats?.totalContributionAmount ?? 0),
trend: stats != null && stats.monthlyGrowth != 0
? '${stats.monthlyGrowth > 0 ? '+' : ''}${stats.monthlyGrowth.toStringAsFixed(1)}% ce mois'
: 'Aucune variation',
? '${stats.monthlyGrowth > 0 ? '+' : ''}${stats.monthlyGrowth.toStringAsFixed(1)}%'
: null,
isTrendPositive: (stats?.monthlyGrowth ?? 0) >= 0,
),
),
const SizedBox(height: 12),
const SizedBox(height: 24),
// Bloc KPI unifié (4 stats regroupées)
// KPIs
AnimatedFadeIn(
delay: const Duration(milliseconds: 300),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: UnionFlowColors.border, width: 1),
boxShadow: UnionFlowColors.softShadow,
),
child: Column(
children: [
Row(
children: [
Expanded(
child: UnionStatWidget(
label: 'Cotisations',
value: '${stats?.totalContributions ?? 0}',
icon: Icons.check_circle,
color: (stats?.totalContributions ?? 0) > 0
? UnionFlowColors.success
: UnionFlowColors.textTertiary,
trend: stats != null && stats.totalContributions > 0 && stats.engagementRate > 0
? (stats.engagementRate >= 1.0
? 'Tout payé'
: '${(stats.engagementRate * 100).toStringAsFixed(0)}% payé')
: null,
isTrendUp: (stats?.engagementRate ?? 0) >= 1.0,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionStatWidget(
label: 'Engagement',
value: stats != null && stats.engagementRate > 0
? '${(stats.engagementRate * 100).toStringAsFixed(0)}%'
: stats != null && stats.totalContributions > 0
? ''
: '0%',
icon: Icons.trending_up,
color: UnionFlowColors.gold,
trend: stats != null && stats.engagementRate > 0.9
? 'Excellent'
: stats != null && stats.engagementRate > 0.5
? 'Bon'
: null,
isTrendUp: (stats?.engagementRate ?? 0) > 0.7,
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: UnionStatWidget(
label: 'Contribution Totale',
value: _formatAmount(stats?.contributionsAmountOnly ?? stats?.totalContributionAmount ?? 0),
icon: Icons.savings,
color: UnionFlowColors.amber,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionStatWidget(
label: 'Événements',
value: '${stats?.upcomingEvents ?? 0}',
icon: Icons.event_available,
color: UnionFlowColors.terracotta,
),
),
],
),
],
),
child: GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
mainAxisSpacing: 6,
crossAxisSpacing: 6,
childAspectRatio: 2.0,
children: [
UnionStatWidget(
label: 'Cotisations',
value: '${stats?.totalContributions ?? 0}',
icon: Icons.check_circle,
color: (stats?.totalContributions ?? 0) > 0
? UnionFlowColors.success
: UnionFlowColors.textTertiary,
trend: stats != null && stats.totalContributions > 0 && stats.engagementRate > 0
? (stats.engagementRate >= 1.0
? 'Tout payé'
: '${(stats.engagementRate * 100).toStringAsFixed(0)}% payé')
: null,
isTrendUp: (stats?.engagementRate ?? 0) >= 1.0,
),
UnionStatWidget(
label: 'Engagement',
value: stats != null && stats.engagementRate > 0
? '${(stats.engagementRate * 100).toStringAsFixed(0)}%'
: '0%',
icon: Icons.trending_up,
color: UnionFlowColors.gold,
trend: stats != null && stats.engagementRate > 0.9
? 'Excellent'
: stats != null && stats.engagementRate > 0.5
? 'Bon'
: null,
isTrendUp: (stats?.engagementRate ?? 0) > 0.7,
),
UnionStatWidget(
label: 'Contribution Totale',
value: _formatAmount(stats?.contributionsAmountOnly ?? stats?.totalContributionAmount ?? 0),
icon: Icons.savings,
color: UnionFlowColors.amber,
),
UnionStatWidget(
label: 'Événements',
value: '${stats?.upcomingEvents ?? 0}',
icon: Icons.event_available,
color: UnionFlowColors.terracotta,
),
],
),
),
const SizedBox(height: 24),
const SizedBox(height: 12),
// Activité récente (données backend)
// Activité récente
if (dashboardData != null && dashboardData.hasRecentActivity) ...[
const AnimatedFadeIn(
delay: Duration(milliseconds: 500),
child: Text(
'Activité Récente',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
child: UFSectionHeader('Activité Récente'),
),
const SizedBox(height: 16),
const SizedBox(height: 8),
AnimatedSlideIn(
delay: const Duration(milliseconds: 600),
child: Column(
children: dashboardData.recentActivities.take(3).map((activity) =>
Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: UnionFlowColors.border, width: 1),
),
child: Row(
children: [
CircleAvatar(
radius: 20,
backgroundColor: activity.type == 'contribution'
? UnionFlowColors.success.withOpacity(0.2)
: activity.type == 'event'
? UnionFlowColors.gold.withOpacity(0.2)
: UnionFlowColors.indigo.withOpacity(0.2),
child: Icon(
activity.type == 'contribution'
? Icons.payment
: activity.type == 'event'
? Icons.event
: Icons.person_add,
size: 18,
color: activity.type == 'contribution'
? UnionFlowColors.success
: activity.type == 'event'
? UnionFlowColors.gold
: UnionFlowColors.indigo,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
activity.title,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textPrimary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
Text(
activity.description,
style: const TextStyle(
fontSize: 11,
color: UnionFlowColors.textSecondary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
Text(
activity.timeAgo,
style: const TextStyle(
fontSize: 11,
color: UnionFlowColors.textTertiary,
),
),
],
),
)
DashboardActivityRow(
title: activity.title,
description: activity.description,
timeAgo: activity.timeAgo,
icon: DashboardActivityRow.iconFor(activity.type),
color: DashboardActivityRow.colorFor(activity.type),
),
).toList(),
),
),
const SizedBox(height: 24),
const SizedBox(height: 12),
],
// Bloc Actions rapides unifié (6 boutons regroupés)
// Actions rapides
const AnimatedFadeIn(
delay: Duration(milliseconds: 650),
child: UFSectionHeader('Actions Rapides'),
),
const SizedBox(height: 8),
AnimatedSlideIn(
delay: const Duration(milliseconds: 700),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: UnionFlowColors.border, width: 1),
boxShadow: UnionFlowColors.softShadow,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Actions Rapides',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: UnionActionButton(
label: 'Cotiser',
icon: Icons.payment,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const CotisationsPageWrapper(),
),
);
},
backgroundColor: UnionFlowColors.unionGreen,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Épargner',
icon: Icons.savings_outlined,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const EpargnePage(),
),
);
},
backgroundColor: UnionFlowColors.gold,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Crédit',
icon: Icons.account_balance_wallet,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const EpargnePage(),
),
);
},
backgroundColor: UnionFlowColors.amber,
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: UnionActionButton(
label: 'Événements',
icon: Icons.event,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const EventsPageWrapper(),
),
);
},
backgroundColor: UnionFlowColors.terracotta,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Solidarité',
icon: Icons.favorite_outline,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const DemandesAidePageWrapper(),
),
);
},
backgroundColor: UnionFlowColors.error,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Profil',
icon: Icons.person_outline,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const ProfilePageWrapper(),
),
);
},
backgroundColor: UnionFlowColors.indigo,
),
),
],
),
],
),
child: GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 3,
mainAxisSpacing: 6,
crossAxisSpacing: 6,
childAspectRatio: 2.8,
children: [
UnionActionButton(
label: 'Cotiser',
icon: Icons.payment,
iconColor: UnionFlowColors.unionGreen,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const CotisationsPageWrapper())),
),
UnionActionButton(
label: 'Épargner',
icon: Icons.savings_outlined,
iconColor: UnionFlowColors.gold,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const EpargnePage())),
),
UnionActionButton(
label: 'Crédit',
icon: Icons.account_balance_wallet,
iconColor: UnionFlowColors.amber,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const EpargnePage())),
),
UnionActionButton(
label: 'Événements',
icon: Icons.event,
iconColor: UnionFlowColors.terracotta,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const EventsPageWrapper())),
),
UnionActionButton(
label: 'Solidarité',
icon: Icons.favorite_outline,
iconColor: UnionFlowColors.error,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const DemandesAidePageWrapper())),
),
UnionActionButton(
label: 'Profil',
icon: Icons.person_outline,
iconColor: UnionFlowColors.indigo,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const ProfilePageWrapper())),
),
],
),
),
],
@@ -403,35 +244,14 @@ class ActiveMemberDashboard extends StatelessWidget {
borderRadius: BorderRadius.circular(8),
),
alignment: Alignment.center,
child: const Text(
'U',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w900,
fontSize: 18,
),
),
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: UnionFlowColors.textPrimary,
),
),
Text(
'Membre Actif',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w400,
color: UnionFlowColors.textSecondary,
),
),
Text('UnionFlow', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: UnionFlowColors.textPrimary)),
Text('Membre Actif', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w400, color: UnionFlowColors.textSecondary)),
],
),
],
@@ -440,82 +260,9 @@ class ActiveMemberDashboard extends StatelessWidget {
);
}
Widget _buildUserHeader(dynamic user) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: UnionFlowColors.warmGradient,
borderRadius: BorderRadius.circular(16),
border: const Border(
top: BorderSide(color: UnionFlowColors.gold, width: 3),
),
boxShadow: UnionFlowColors.goldGlowShadow,
),
child: Row(
children: [
CircleAvatar(
radius: 28,
backgroundColor: Colors.white.withOpacity(0.3),
child: Text(
user?.initials ?? 'MA',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: Colors.white,
),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
user?.fullName ?? 'Membre Actif',
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w700,
color: Colors.white,
),
),
const SizedBox(height: 4),
Text(
'Depuis ${user?.createdAt.year ?? 2024} • Très Actif',
style: TextStyle(
fontSize: 12,
color: Colors.white.withOpacity(0.9),
),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'ACTIF',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w800,
color: UnionFlowColors.gold,
letterSpacing: 0.5,
),
),
),
],
),
);
}
String _formatAmount(double amount) {
if (amount >= 1000000) {
return '${(amount / 1000000).toStringAsFixed(1)}M FCFA';
} else if (amount >= 1000) {
return '${(amount / 1000).toStringAsFixed(0)}K FCFA';
}
if (amount >= 1000000) return '${(amount / 1000000).toStringAsFixed(1)}M FCFA';
if (amount >= 1000) return '${(amount / 1000).toStringAsFixed(0)}K FCFA';
return '${amount.toStringAsFixed(0)} FCFA';
}
}

View File

@@ -38,150 +38,95 @@ class ConsultantDashboard extends StatelessWidget {
final stats = dashboardData?.stats;
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// En-tête Consultant
AnimatedFadeIn(
delay: const Duration(milliseconds: 100),
child: _buildUserHeader(),
),
const SizedBox(height: 24),
// En-tête Consultant
AnimatedFadeIn(
delay: const Duration(milliseconds: 100),
child: UserIdentityCard(
initials: 'CON',
name: 'Consultant Expert',
subtitle: 'Expertise & Analyses Stratégiques',
badgeLabel: 'EXPERT',
gradient: LinearGradient(
colors: [UnionFlowColors.amber, UnionFlowColors.gold],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
accentColor: UnionFlowColors.amber,
),
),
const SizedBox(height: 12),
// Stats missions (données backend réelles)
// Stats missions
AnimatedSlideIn(
delay: const Duration(milliseconds: 200),
child: Row(
child: GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
mainAxisSpacing: 6,
crossAxisSpacing: 6,
childAspectRatio: 2.0,
children: [
Expanded(
child: UnionStatWidget(
label: 'Événements',
value: '${stats?.totalEvents ?? 0}',
icon: Icons.work_outline,
color: UnionFlowColors.amber,
trend: stats?.upcomingEvents != null ? '${stats!.upcomingEvents} à venir' : null,
isTrendUp: true,
),
UnionStatWidget(
label: 'Événements',
value: '${stats?.totalEvents ?? 0}',
icon: Icons.work_outline,
color: UnionFlowColors.amber,
trend: stats?.upcomingEvents != null ? '${stats!.upcomingEvents} à venir' : null,
isTrendUp: true,
),
const SizedBox(width: 12),
Expanded(
child: UnionStatWidget(
label: 'Organisations',
value: '${stats?.totalOrganizations ?? 0}',
icon: Icons.business_outlined,
color: UnionFlowColors.indigo,
),
UnionStatWidget(
label: 'Organisations',
value: '${stats?.totalOrganizations ?? 0}',
icon: Icons.business_outlined,
color: UnionFlowColors.indigo,
),
UnionStatWidget(
label: 'Demandes',
value: '${stats?.pendingRequests ?? 0}',
icon: Icons.pending_actions,
color: UnionFlowColors.warning,
),
UnionStatWidget(
label: 'Membres',
value: '${stats?.totalMembers ?? 0}',
icon: Icons.people_outline,
color: UnionFlowColors.success,
),
],
),
),
const SizedBox(height: 12),
AnimatedFadeIn(
delay: const Duration(milliseconds: 300),
child: Row(
children: [
Expanded(
child: UnionStatWidget(
label: 'Demandes',
value: '${stats?.pendingRequests ?? 0}',
icon: Icons.pending_actions,
color: UnionFlowColors.warning,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionStatWidget(
label: 'Membres',
value: '${stats?.totalMembers ?? 0}',
icon: Icons.people_outline,
color: UnionFlowColors.success,
),
),
],
),
),
const SizedBox(height: 24),
// Événements à venir (données backend)
// Événements à venir
if (dashboardData != null && dashboardData.hasUpcomingEvents) ...[
AnimatedFadeIn(
delay: const Duration(milliseconds: 400),
child: const Text(
child: UFSectionHeader(
'Prochains Événements',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
trailing: '${dashboardData.upcomingEvents.length} programmés',
),
),
const SizedBox(height: 16),
const SizedBox(height: 8),
AnimatedSlideIn(
delay: const Duration(milliseconds: 500),
child: Column(
children: dashboardData.upcomingEvents.take(3).map((event) =>
Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: UnionFlowColors.border, width: 1),
boxShadow: UnionFlowColors.softShadow,
),
child: Row(
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
gradient: UnionFlowColors.warmGradient,
borderRadius: BorderRadius.circular(10),
),
child: const Icon(
Icons.calendar_today,
color: Colors.white,
size: 22,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
event.title,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textPrimary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
event.formattedDate,
style: const TextStyle(
fontSize: 12,
color: UnionFlowColors.textSecondary,
),
),
],
),
),
],
),
)
DashboardEventRow(
title: event.title,
date: event.formattedDate,
),
).toList(),
),
),
const SizedBox(height: 24),
const SizedBox(height: 12),
],
// Répartition organisations par type (données backend)
// Répartition organisations par type
if (stats != null && stats.organizationTypeDistribution != null && stats.organizationTypeDistribution!.isNotEmpty) ...[
AnimatedFadeIn(
delay: const Duration(milliseconds: 600),
@@ -191,90 +136,64 @@ class ConsultantDashboard extends StatelessWidget {
sections: _buildOrgTypeSections(stats.organizationTypeDistribution!),
),
),
const SizedBox(height: 24),
const SizedBox(height: 12),
],
// Actions consultant
// Mes Outils
AnimatedFadeIn(
delay: const Duration(milliseconds: 700),
child: const Text(
'Mes Outils',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
child: const UFSectionHeader('Mes Outils'),
),
),
),
const SizedBox(height: 16),
const SizedBox(height: 8),
AnimatedSlideIn(
delay: const Duration(milliseconds: 700),
child: Row(
children: [
Expanded(
child: UnionActionButton(
label: 'Audits',
icon: Icons.assessment,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const ReportsPageWrapper())),
backgroundColor: UnionFlowColors.unionGreen,
),
AnimatedSlideIn(
delay: const Duration(milliseconds: 700),
child: GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 3,
mainAxisSpacing: 6,
crossAxisSpacing: 6,
childAspectRatio: 2.8,
children: [
UnionActionButton(
label: 'Audits',
icon: Icons.assessment,
iconColor: UnionFlowColors.unionGreen,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const ReportsPageWrapper())),
),
UnionActionButton(
label: 'Analyses',
icon: Icons.analytics,
iconColor: UnionFlowColors.indigo,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const ReportsPageWrapper())),
),
UnionActionButton(
label: 'Rapports',
icon: Icons.description,
iconColor: UnionFlowColors.gold,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const ReportsPageWrapper())),
),
UnionActionButton(
label: 'Clients',
icon: Icons.people_outline,
iconColor: UnionFlowColors.amber,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const MembersPageWrapper())),
),
UnionActionButton(
label: 'Calendrier',
icon: Icons.calendar_today,
iconColor: UnionFlowColors.terracotta,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const EventsPageWrapper())),
),
UnionActionButton(
label: 'Documents',
icon: Icons.folder_outlined,
iconColor: UnionFlowColors.info,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const HelpSupportPage())),
),
],
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Analyses',
icon: Icons.analytics,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const ReportsPageWrapper())),
backgroundColor: UnionFlowColors.indigo,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Rapports',
icon: Icons.description,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const ReportsPageWrapper())),
backgroundColor: UnionFlowColors.gold,
),
),
],
),
),
const SizedBox(height: 12),
AnimatedSlideIn(
delay: const Duration(milliseconds: 800),
child: Row(
children: [
Expanded(
child: UnionActionButton(
label: 'Clients',
icon: Icons.people_outline,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const MembersPageWrapper())),
backgroundColor: UnionFlowColors.amber,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Calendrier',
icon: Icons.calendar_today,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const EventsPageWrapper())),
backgroundColor: UnionFlowColors.terracotta,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Documents',
icon: Icons.folder_outlined,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const HelpSupportPage())),
backgroundColor: UnionFlowColors.info,
),
),
],
),
),
],
),
@@ -303,35 +222,14 @@ class ConsultantDashboard extends StatelessWidget {
borderRadius: BorderRadius.circular(8),
),
alignment: Alignment.center,
child: const Text(
'C',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w900,
fontSize: 18,
),
),
child: const Text('C', 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: UnionFlowColors.textPrimary,
),
),
Text(
'Consultant',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w400,
color: UnionFlowColors.textSecondary,
),
),
Text('UnionFlow', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: UnionFlowColors.textPrimary)),
Text('Consultant', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w400, color: UnionFlowColors.textSecondary)),
],
),
],
@@ -348,88 +246,8 @@ class ConsultantDashboard extends StatelessWidget {
);
}
Widget _buildUserHeader() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [UnionFlowColors.amber, UnionFlowColors.gold],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
border: const Border(
top: BorderSide(color: UnionFlowColors.amber, width: 3),
),
boxShadow: [
BoxShadow(
color: UnionFlowColors.amber.withOpacity(0.3),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
CircleAvatar(
radius: 28,
backgroundColor: Colors.white.withOpacity(0.3),
child: const Text(
'CON',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: Colors.white,
),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Consultant Expert',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w700,
color: Colors.white,
),
),
const SizedBox(height: 4),
Text(
'Expertise & Analyses Stratégiques',
style: TextStyle(
fontSize: 12,
color: Colors.white.withOpacity(0.9),
),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'EXPERT',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w800,
color: UnionFlowColors.amber,
letterSpacing: 0.5,
),
),
),
],
),
);
}
List<PieChartSectionData> _buildOrgTypeSections(Map<String, int> distribution) {
final colors = [
const colors = [
UnionFlowColors.unionGreen,
UnionFlowColors.gold,
UnionFlowColors.indigo,
@@ -451,5 +269,3 @@ class ConsultantDashboard extends StatelessWidget {
});
}
}

View File

@@ -38,175 +38,104 @@ class HRManagerDashboard extends StatelessWidget {
final stats = dashboardData?.stats;
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// En-tête RH
AnimatedFadeIn(
delay: const Duration(milliseconds: 100),
child: _buildUserHeader(),
),
const SizedBox(height: 24),
// En-tête RH
AnimatedFadeIn(
delay: const Duration(milliseconds: 100),
child: UserIdentityCard(
initials: 'RH',
name: 'Gestionnaire RH',
subtitle: 'Ressources Humaines & Talents',
badgeLabel: 'RH',
gradient: LinearGradient(
colors: [UnionFlowColors.terracotta, UnionFlowColors.terracottaLight],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
accentColor: UnionFlowColors.terracotta,
),
),
const SizedBox(height: 12),
// Stats RH (données backend réelles)
// Stats RH
AnimatedSlideIn(
delay: const Duration(milliseconds: 200),
child: Row(
child: GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
mainAxisSpacing: 6,
crossAxisSpacing: 6,
childAspectRatio: 2.0,
children: [
Expanded(
child: UnionStatWidget(
label: 'Membres',
value: '${stats?.totalMembers ?? 0}',
icon: Icons.people_outlined,
color: UnionFlowColors.unionGreen,
trend: stats != null && stats.monthlyGrowth > 0
? '+${stats.monthlyGrowth.toStringAsFixed(1)}%'
: null,
isTrendUp: (stats?.monthlyGrowth ?? 0) > 0,
),
UnionStatWidget(
label: 'Membres',
value: '${stats?.totalMembers ?? 0}',
icon: Icons.people_outlined,
color: UnionFlowColors.unionGreen,
trend: stats != null && stats.monthlyGrowth > 0
? '+${stats.monthlyGrowth.toStringAsFixed(1)}%'
: null,
isTrendUp: (stats?.monthlyGrowth ?? 0) > 0,
),
const SizedBox(width: 12),
Expanded(
child: UnionStatWidget(
label: 'Actifs',
value: '${stats?.activeMembers ?? 0}',
icon: Icons.person,
color: UnionFlowColors.success,
trend: stats != null && stats.totalMembers > 0
? '${((stats.activeMembers / stats.totalMembers) * 100).toStringAsFixed(0)}%'
: null,
isTrendUp: true,
),
UnionStatWidget(
label: 'Actifs',
value: '${stats?.activeMembers ?? 0}',
icon: Icons.person,
color: UnionFlowColors.success,
trend: stats != null && stats.totalMembers > 0
? '${((stats.activeMembers / stats.totalMembers) * 100).toStringAsFixed(0)}%'
: null,
isTrendUp: true,
),
UnionStatWidget(
label: 'Demandes',
value: '${stats?.pendingRequests ?? 0}',
icon: Icons.pending_actions,
color: UnionFlowColors.amber,
),
UnionStatWidget(
label: 'Événements',
value: '${stats?.upcomingEvents ?? 0}',
icon: Icons.event,
color: UnionFlowColors.info,
trend: stats?.totalEvents != null ? '${stats!.totalEvents} total' : null,
isTrendUp: true,
),
],
),
),
const SizedBox(height: 12),
AnimatedFadeIn(
delay: const Duration(milliseconds: 300),
child: Row(
children: [
Expanded(
child: UnionStatWidget(
label: 'Demandes',
value: '${stats?.pendingRequests ?? 0}',
icon: Icons.pending_actions,
color: UnionFlowColors.amber,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionStatWidget(
label: 'Événements',
value: '${stats?.upcomingEvents ?? 0}',
icon: Icons.event,
color: UnionFlowColors.info,
trend: stats?.totalEvents != null ? '${stats!.totalEvents} total' : null,
isTrendUp: true,
),
),
],
),
),
const SizedBox(height: 24),
// Activité récente (données backend)
// Activité récente
if (dashboardData != null && dashboardData.hasRecentActivity) ...[
AnimatedFadeIn(
delay: const Duration(milliseconds: 400),
child: const Text(
'Activité RH Récente',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
child: const UFSectionHeader('Activité RH Récente'),
),
const SizedBox(height: 16),
const SizedBox(height: 8),
AnimatedSlideIn(
delay: const Duration(milliseconds: 500),
child: Column(
children: dashboardData.recentActivities.take(4).map((activity) =>
Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: UnionFlowColors.border, width: 1),
),
child: Row(
children: [
CircleAvatar(
radius: 20,
backgroundColor: activity.type == 'member'
? UnionFlowColors.success.withOpacity(0.2)
: activity.type == 'contribution'
? UnionFlowColors.amber.withOpacity(0.2)
: UnionFlowColors.terracotta.withOpacity(0.2),
child: Icon(
activity.type == 'member'
? Icons.person_add
: activity.type == 'contribution'
? Icons.payment
: Icons.event,
size: 18,
color: activity.type == 'member'
? UnionFlowColors.success
: activity.type == 'contribution'
? UnionFlowColors.amber
: UnionFlowColors.terracotta,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
activity.title,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textPrimary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 2),
Text(
activity.userName,
style: const TextStyle(
fontSize: 11,
color: UnionFlowColors.textSecondary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
Text(
activity.timeAgo,
style: const TextStyle(
fontSize: 11,
color: UnionFlowColors.textTertiary,
),
),
],
),
)
DashboardActivityRow(
title: activity.title,
description: activity.description,
timeAgo: activity.timeAgo,
icon: DashboardActivityRow.iconFor(activity.type),
color: DashboardActivityRow.colorFor(activity.type),
),
).toList(),
),
),
const SizedBox(height: 24),
const SizedBox(height: 12),
],
// Répartition membres actifs/inactifs (données backend)
if (stats != null && stats.totalMembers > 0)
// Répartition membres actifs/inactifs
if (stats != null && stats.totalMembers > 0) ...[
AnimatedFadeIn(
delay: const Duration(milliseconds: 500),
child: UnionPieChart(
@@ -226,125 +155,88 @@ class HRManagerDashboard extends StatelessWidget {
],
),
),
const SizedBox(height: 24),
const SizedBox(height: 12),
],
// Indicateurs RH
// Indicateurs Clés
AnimatedFadeIn(
delay: const Duration(milliseconds: 600),
child: const Text(
'Indicateurs Clés',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
child: const UFSectionHeader('Indicateurs Clés'),
),
),
),
const SizedBox(height: 16),
const SizedBox(height: 8),
AnimatedSlideIn(
delay: const Duration(milliseconds: 700),
child: Row(
children: [
Expanded(child: _buildMetric('Turnover', '5%', UnionFlowColors.success)),
const SizedBox(width: 12),
Expanded(child: _buildMetric('Absentéisme', '2%', UnionFlowColors.warning)),
],
),
),
const SizedBox(height: 12),
AnimatedSlideIn(
delay: const Duration(milliseconds: 800),
child: Row(
children: [
Expanded(child: _buildMetric('Satisfaction', '87%', UnionFlowColors.gold)),
const SizedBox(width: 12),
Expanded(child: _buildMetric('Formation', '45h', UnionFlowColors.indigo)),
],
),
),
const SizedBox(height: 24),
// Actions RH
AnimatedFadeIn(
delay: const Duration(milliseconds: 900),
child: const Text(
'Gestion RH',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
AnimatedSlideIn(
delay: const Duration(milliseconds: 700),
child: GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
mainAxisSpacing: 6,
crossAxisSpacing: 6,
childAspectRatio: 2.0,
children: [
UnionStatWidget(label: 'Turnover', value: '5%', icon: Icons.swap_horiz, color: UnionFlowColors.success),
UnionStatWidget(label: 'Absentéisme', value: '2%', icon: Icons.event_busy, color: UnionFlowColors.warning),
UnionStatWidget(label: 'Satisfaction', value: '87%', icon: Icons.sentiment_satisfied, color: UnionFlowColors.gold),
UnionStatWidget(label: 'Formation', value: '45h', icon: Icons.school, color: UnionFlowColors.indigo),
],
),
),
),
),
const SizedBox(height: 16),
const SizedBox(height: 12),
AnimatedSlideIn(
delay: const Duration(milliseconds: 1000),
child: Row(
children: [
Expanded(
child: UnionActionButton(
label: 'Employés',
icon: Icons.people,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const MembersPageWrapper())),
backgroundColor: UnionFlowColors.unionGreen,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Congés',
icon: Icons.event_available,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const EventsPageWrapper())),
backgroundColor: UnionFlowColors.amber,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Paie',
icon: Icons.payments,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const ContributionsPageWrapper())),
backgroundColor: UnionFlowColors.gold,
),
),
],
),
),
const SizedBox(height: 12),
// Actions RH
AnimatedFadeIn(
delay: const Duration(milliseconds: 900),
child: const UFSectionHeader('Gestion RH'),
),
const SizedBox(height: 8),
AnimatedSlideIn(
delay: const Duration(milliseconds: 1100),
child: Row(
children: [
Expanded(
child: UnionActionButton(
label: 'Recrutement',
icon: Icons.person_add,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const MembersPageWrapper())),
backgroundColor: UnionFlowColors.info,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Formation',
icon: Icons.school,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const EventsPageWrapper())),
backgroundColor: UnionFlowColors.indigo,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Rapports',
icon: Icons.analytics,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const ReportsPageWrapper())),
backgroundColor: UnionFlowColors.terracotta,
),
),
AnimatedSlideIn(
delay: const Duration(milliseconds: 1000),
child: GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 3,
mainAxisSpacing: 6,
crossAxisSpacing: 6,
childAspectRatio: 2.8,
children: [
UnionActionButton(
label: 'Employés',
icon: Icons.people,
iconColor: UnionFlowColors.unionGreen,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const MembersPageWrapper())),
),
UnionActionButton(
label: 'Congés',
icon: Icons.event_available,
iconColor: UnionFlowColors.amber,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const EventsPageWrapper())),
),
UnionActionButton(
label: 'Paie',
icon: Icons.payments,
iconColor: UnionFlowColors.gold,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const ContributionsPageWrapper())),
),
UnionActionButton(
label: 'Recrutement',
icon: Icons.person_add,
iconColor: UnionFlowColors.info,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const MembersPageWrapper())),
),
UnionActionButton(
label: 'Formation',
icon: Icons.school,
iconColor: UnionFlowColors.indigo,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const EventsPageWrapper())),
),
UnionActionButton(
label: 'Rapports',
icon: Icons.analytics,
iconColor: UnionFlowColors.terracotta,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const ReportsPageWrapper())),
),
],
),
),
@@ -375,35 +267,14 @@ class HRManagerDashboard extends StatelessWidget {
borderRadius: BorderRadius.circular(8),
),
alignment: Alignment.center,
child: const Text(
'H',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w900,
fontSize: 18,
),
),
child: const Text('H', 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: UnionFlowColors.textPrimary,
),
),
Text(
'RH Manager',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w400,
color: UnionFlowColors.textSecondary,
),
),
Text('UnionFlow', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: UnionFlowColors.textPrimary)),
Text('RH Manager', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w400, color: UnionFlowColors.textSecondary)),
],
),
],
@@ -428,115 +299,4 @@ class HRManagerDashboard extends StatelessWidget {
],
);
}
Widget _buildUserHeader() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [UnionFlowColors.terracotta, UnionFlowColors.terracottaLight],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
border: const Border(
top: BorderSide(color: UnionFlowColors.terracotta, width: 3),
),
boxShadow: [
BoxShadow(
color: UnionFlowColors.terracotta.withOpacity(0.3),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
CircleAvatar(
radius: 28,
backgroundColor: Colors.white.withOpacity(0.3),
child: const Text(
'RH',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: Colors.white,
),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Gestionnaire RH',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w700,
color: Colors.white,
),
),
const SizedBox(height: 4),
Text(
'Ressources Humaines & Talents',
style: TextStyle(
fontSize: 12,
color: Colors.white.withOpacity(0.9),
),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'RH',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w800,
color: UnionFlowColors.terracotta,
letterSpacing: 0.5,
),
),
),
],
),
);
}
Widget _buildMetric(String label, String value, Color color) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(16),
boxShadow: UnionFlowColors.softShadow,
),
child: Column(
children: [
Text(
value,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w800,
color: color,
),
),
const SizedBox(height: 4),
Text(
label,
style: const TextStyle(
fontSize: 12,
color: UnionFlowColors.textSecondary,
),
),
],
),
);
}
}

View File

@@ -17,15 +17,6 @@ import '../../widgets/dashboard_drawer.dart';
class ModeratorDashboard extends StatelessWidget {
const ModeratorDashboard({super.key});
String _formatAmount(double amount) {
if (amount >= 1000000) {
return '${(amount / 1000000).toStringAsFixed(amount % 1000000 == 0 ? 0 : 1)}M FCFA';
} else if (amount >= 1000) {
return '${(amount / 1000).toStringAsFixed(amount % 1000 == 0 ? 0 : 1)}K FCFA';
}
return '${amount.toStringAsFixed(0)} FCFA';
}
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -54,18 +45,25 @@ class ModeratorDashboard extends StatelessWidget {
final stats = dashboardData?.stats;
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// En-tête Modérateur
AnimatedFadeIn(
delay: const Duration(milliseconds: 100),
child: _buildUserHeader(user),
child: UserIdentityCard(
initials: user?.initials ?? 'SM',
name: user?.fullName ?? 'Secrétaire',
subtitle: 'Depuis ${user?.createdAt?.year ?? DateTime.now().year} · Très Actif',
badgeLabel: 'ACTIF',
gradient: UnionFlowColors.warmGradient,
accentColor: UnionFlowColors.gold,
),
),
const SizedBox(height: 24),
const SizedBox(height: 12),
// Balance principale ou Vue Unifiée (Compte Adhérent)
// Balance / Vue Unifiée
AnimatedSlideIn(
delay: const Duration(milliseconds: 200),
child: dashboardData?.monCompte != null
@@ -81,290 +79,180 @@ class ModeratorDashboard extends StatelessWidget {
label: 'Mon Solde Total',
amount: _formatAmount(stats?.totalContributionAmount ?? 0),
trend: stats != null && stats.monthlyGrowth != 0
? '${stats.monthlyGrowth > 0 ? '+' : ''}${stats.monthlyGrowth.toStringAsFixed(1)}% ce mois'
: 'Aucune variation',
? '${stats.monthlyGrowth > 0 ? '+' : ''}${stats.monthlyGrowth.toStringAsFixed(1)}%'
: null,
isTrendPositive: (stats?.monthlyGrowth ?? 0) >= 0,
),
),
const SizedBox(height: 24),
const SizedBox(height: 12),
// Bloc KPI unifié (4 stats regroupées)
// KPIs membre
AnimatedFadeIn(
delay: const Duration(milliseconds: 250),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: UnionFlowColors.border, width: 1),
boxShadow: UnionFlowColors.softShadow,
),
child: Column(
children: [
Row(
children: [
Expanded(
child: UnionStatWidget(
label: 'Cotisations',
value: '${stats?.totalContributions ?? 0}',
icon: Icons.check_circle,
color: (stats?.totalContributions ?? 0) > 0
? UnionFlowColors.success
: UnionFlowColors.textTertiary,
trend: stats != null && stats.totalContributions > 0 && stats.engagementRate > 0
? (stats.engagementRate >= 1.0
? 'Tout payé'
: '${(stats.engagementRate * 100).toStringAsFixed(0)}% payé')
: null,
isTrendUp: (stats?.engagementRate ?? 0) >= 1.0,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionStatWidget(
label: 'Engagement',
value: stats != null && stats.engagementRate > 0
? '${(stats.engagementRate * 100).toStringAsFixed(0)}%'
: stats != null && stats.totalContributions > 0 ? '' : '0%',
icon: Icons.trending_up,
color: UnionFlowColors.gold,
trend: stats != null && stats.engagementRate > 0.9
? 'Excellent'
: stats != null && stats.engagementRate > 0.5 ? 'Bon' : null,
isTrendUp: (stats?.engagementRate ?? 0) > 0.7,
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: UnionStatWidget(
label: 'Contribution Totale',
value: _formatAmount(stats?.contributionsAmountOnly ?? stats?.totalContributionAmount ?? 0),
icon: Icons.savings,
color: UnionFlowColors.amber,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionStatWidget(
label: 'Événements',
value: '${stats?.upcomingEvents ?? 0}',
icon: Icons.event_available,
color: UnionFlowColors.terracotta,
),
),
],
),
],
),
),
),
const SizedBox(height: 24),
// Bloc Actions rapides unifié (6 boutons regroupés)
AnimatedSlideIn(
delay: const Duration(milliseconds: 300),
child: Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(16),
border: Border.all(color: UnionFlowColors.border, width: 1),
boxShadow: UnionFlowColors.softShadow,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Actions Rapides',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: UnionActionButton(
label: 'Cotiser',
icon: Icons.payment,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const CotisationsPageWrapper(),
),
);
},
backgroundColor: UnionFlowColors.unionGreen,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Épargner',
icon: Icons.savings_outlined,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const EpargnePage(),
),
);
},
backgroundColor: UnionFlowColors.gold,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Crédit',
icon: Icons.account_balance_wallet,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const EpargnePage(),
),
);
},
backgroundColor: UnionFlowColors.amber,
),
),
],
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: UnionActionButton(
label: 'Événements',
icon: Icons.event,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const EventsPageWrapper(),
),
);
},
backgroundColor: UnionFlowColors.terracotta,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Solidarité',
icon: Icons.favorite_outline,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const DemandesAidePageWrapper(),
),
);
},
backgroundColor: UnionFlowColors.error,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Profil',
icon: Icons.person_outline,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const ProfilePageWrapper(),
),
);
},
backgroundColor: UnionFlowColors.indigo,
),
),
],
),
],
),
),
),
const SizedBox(height: 32),
// ——— Administration / Modération (tout en bas, après les actions membre) ———
AnimatedFadeIn(
delay: const Duration(milliseconds: 600),
child: const Text(
'Espace Modérateur',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
),
const SizedBox(height: 16),
// Stats de modération (données backend réelles)
AnimatedSlideIn(
delay: const Duration(milliseconds: 600),
child: Row(
child: GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
mainAxisSpacing: 6,
crossAxisSpacing: 6,
childAspectRatio: 2.0,
children: [
Expanded(
child: UnionStatWidget(
label: 'En attente',
value: '${stats?.pendingRequests ?? 0}',
icon: Icons.pending_actions,
color: UnionFlowColors.warning,
trend: stats != null && stats.pendingRequests > 0 ? 'Action requise' : null,
isTrendUp: false,
),
UnionStatWidget(
label: 'Cotisations',
value: '${stats?.totalContributions ?? 0}',
icon: Icons.check_circle,
color: (stats?.totalContributions ?? 0) > 0
? UnionFlowColors.success
: UnionFlowColors.textTertiary,
trend: stats != null && stats.totalContributions > 0 && stats.engagementRate > 0
? (stats.engagementRate >= 1.0
? 'Tout payé'
: '${(stats.engagementRate * 100).toStringAsFixed(0)}% payé')
: null,
isTrendUp: (stats?.engagementRate ?? 0) >= 1.0,
),
const SizedBox(width: 12),
Expanded(
child: UnionStatWidget(
label: 'Membres Actifs',
value: '${stats?.activeMembers ?? 0}',
icon: Icons.check_circle_outline,
color: UnionFlowColors.success,
trend: stats != null && stats.totalMembers > 0
? '${((stats.activeMembers / stats.totalMembers) * 100).toStringAsFixed(0)}%'
: null,
isTrendUp: true,
),
UnionStatWidget(
label: 'Engagement',
value: stats != null && stats.engagementRate > 0
? '${(stats.engagementRate * 100).toStringAsFixed(0)}%'
: '0%',
icon: Icons.trending_up,
color: UnionFlowColors.gold,
trend: stats != null && stats.engagementRate > 0.9
? 'Excellent'
: stats != null && stats.engagementRate > 0.5 ? 'Bon' : null,
isTrendUp: (stats?.engagementRate ?? 0) > 0.7,
),
UnionStatWidget(
label: 'Contribution Totale',
value: _formatAmount(stats?.contributionsAmountOnly ?? stats?.totalContributionAmount ?? 0),
icon: Icons.savings,
color: UnionFlowColors.amber,
),
UnionStatWidget(
label: 'Événements',
value: '${stats?.upcomingEvents ?? 0}',
icon: Icons.event_available,
color: UnionFlowColors.terracotta,
),
],
),
),
const SizedBox(height: 12),
AnimatedFadeIn(
delay: const Duration(milliseconds: 300),
child: Row(
// Actions rapides membre
const AnimatedFadeIn(
delay: Duration(milliseconds: 300),
child: UFSectionHeader('Actions Rapides'),
),
const SizedBox(height: 8),
AnimatedSlideIn(
delay: const Duration(milliseconds: 350),
child: GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 3,
mainAxisSpacing: 6,
crossAxisSpacing: 6,
childAspectRatio: 2.8,
children: [
Expanded(
child: UnionStatWidget(
label: 'Événements',
value: '${stats?.upcomingEvents ?? 0}',
icon: Icons.event_outlined,
color: UnionFlowColors.gold,
),
UnionActionButton(
label: 'Cotiser',
icon: Icons.payment,
iconColor: UnionFlowColors.unionGreen,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const CotisationsPageWrapper())),
),
const SizedBox(width: 12),
Expanded(
child: UnionStatWidget(
label: 'Membres Total',
value: '${stats?.totalMembers ?? 0}',
icon: Icons.people_outline,
color: UnionFlowColors.unionGreen,
),
UnionActionButton(
label: 'Épargner',
icon: Icons.savings_outlined,
iconColor: UnionFlowColors.gold,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const EpargnePage())),
),
UnionActionButton(
label: 'Crédit',
icon: Icons.account_balance_wallet,
iconColor: UnionFlowColors.amber,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const EpargnePage())),
),
UnionActionButton(
label: 'Événements',
icon: Icons.event,
iconColor: UnionFlowColors.terracotta,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const EventsPageWrapper())),
),
UnionActionButton(
label: 'Solidarité',
icon: Icons.favorite_outline,
iconColor: UnionFlowColors.error,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const DemandesAidePageWrapper())),
),
UnionActionButton(
label: 'Profil',
icon: Icons.person_outline,
iconColor: UnionFlowColors.indigo,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const ProfilePageWrapper())),
),
],
),
),
const SizedBox(height: 24),
const SizedBox(height: 16),
// Activité des membres (données backend réelles)
if (stats != null && stats.totalMembers > 0)
// ——— Espace Modérateur ———
const AnimatedFadeIn(
delay: Duration(milliseconds: 600),
child: UFSectionHeader('Espace Modérateur'),
),
const SizedBox(height: 8),
// Stats de modération
AnimatedSlideIn(
delay: const Duration(milliseconds: 600),
child: GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
mainAxisSpacing: 6,
crossAxisSpacing: 6,
childAspectRatio: 2.0,
children: [
UnionStatWidget(
label: 'En attente',
value: '${stats?.pendingRequests ?? 0}',
icon: Icons.pending_actions,
color: UnionFlowColors.warning,
trend: stats != null && stats.pendingRequests > 0 ? 'Action requise' : null,
isTrendUp: false,
),
UnionStatWidget(
label: 'Membres Actifs',
value: '${stats?.activeMembers ?? 0}',
icon: Icons.check_circle_outline,
color: UnionFlowColors.success,
trend: stats != null && stats.totalMembers > 0
? '${((stats.activeMembers / stats.totalMembers) * 100).toStringAsFixed(0)}%'
: null,
isTrendUp: true,
),
UnionStatWidget(
label: 'Événements',
value: '${stats?.upcomingEvents ?? 0}',
icon: Icons.event_outlined,
color: UnionFlowColors.gold,
),
UnionStatWidget(
label: 'Membres Total',
value: '${stats?.totalMembers ?? 0}',
icon: Icons.people_outline,
color: UnionFlowColors.unionGreen,
),
],
),
),
const SizedBox(height: 12),
// Activité des membres
if (stats != null && stats.totalMembers > 0) ...[
AnimatedSlideIn(
delay: const Duration(milliseconds: 400),
child: UnionPieChart(
@@ -384,157 +272,79 @@ class ModeratorDashboard extends StatelessWidget {
],
),
),
const SizedBox(height: 24),
const SizedBox(height: 12),
],
// Demandes en attente (données backend)
// Activité récente à modérer
if (dashboardData != null && dashboardData.hasRecentActivity) ...[
AnimatedFadeIn(
delay: const Duration(milliseconds: 500),
child: const Text(
'Activité Récente à Modérer',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
child: const UFSectionHeader('Activité Récente à Modérer'),
),
const SizedBox(height: 16),
const SizedBox(height: 8),
AnimatedSlideIn(
delay: const Duration(milliseconds: 600),
child: Column(
children: dashboardData.recentActivities.take(4).map((activity) =>
Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: UnionFlowColors.border, width: 1),
boxShadow: UnionFlowColors.softShadow,
),
child: Row(
children: [
CircleAvatar(
radius: 22,
backgroundColor: UnionFlowColors.indigo.withOpacity(0.2),
child: Icon(
activity.type == 'member' ? Icons.person_add :
activity.type == 'event' ? Icons.event :
Icons.info_outline,
size: 20,
color: UnionFlowColors.indigo,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
activity.title,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textPrimary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
activity.description,
style: const TextStyle(
fontSize: 11,
color: UnionFlowColors.textSecondary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
Text(
activity.timeAgo,
style: const TextStyle(
fontSize: 11,
color: UnionFlowColors.textTertiary,
),
),
],
),
)
DashboardActivityRow(
title: activity.title,
description: activity.description,
timeAgo: activity.timeAgo,
icon: DashboardActivityRow.iconFor(activity.type),
color: DashboardActivityRow.colorFor(activity.type),
),
).toList(),
),
),
const SizedBox(height: 12),
],
const SizedBox(height: 24),
// Actions de modération
AnimatedSlideIn(
delay: const Duration(milliseconds: 700),
child: Row(
child: GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 3,
mainAxisSpacing: 6,
crossAxisSpacing: 6,
childAspectRatio: 2.8,
children: [
Expanded(
child: UnionActionButton(
label: 'Approuver',
icon: Icons.check_circle,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const AdhesionsPageWrapper())),
backgroundColor: UnionFlowColors.success,
),
UnionActionButton(
label: 'Approuver',
icon: Icons.check_circle,
iconColor: UnionFlowColors.success,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const AdhesionsPageWrapper())),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Vérifier',
icon: Icons.visibility,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const AdhesionsPageWrapper())),
backgroundColor: UnionFlowColors.info,
),
UnionActionButton(
label: 'Vérifier',
icon: Icons.visibility,
iconColor: UnionFlowColors.info,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const AdhesionsPageWrapper())),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Signaler',
icon: Icons.flag,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const HelpSupportPage())),
backgroundColor: UnionFlowColors.error,
),
UnionActionButton(
label: 'Signaler',
icon: Icons.flag,
iconColor: UnionFlowColors.error,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const HelpSupportPage())),
),
],
),
),
const SizedBox(height: 12),
AnimatedSlideIn(
delay: const Duration(milliseconds: 800),
child: Row(
children: [
Expanded(
child: UnionActionButton(
label: 'Membres',
icon: Icons.people,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const MembersPageWrapper())),
backgroundColor: UnionFlowColors.unionGreen,
),
UnionActionButton(
label: 'Membres',
icon: Icons.people,
iconColor: UnionFlowColors.unionGreen,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const MembersPageWrapper())),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Contenus',
icon: Icons.article,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const EventsPageWrapper())),
backgroundColor: UnionFlowColors.gold,
),
UnionActionButton(
label: 'Contenus',
icon: Icons.article,
iconColor: UnionFlowColors.gold,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const EventsPageWrapper())),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Historique',
icon: Icons.history,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const ContributionsPageWrapper())),
backgroundColor: UnionFlowColors.indigo,
),
UnionActionButton(
label: 'Historique',
icon: Icons.history,
iconColor: UnionFlowColors.indigo,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const ContributionsPageWrapper())),
),
],
),
@@ -564,35 +374,14 @@ class ModeratorDashboard extends StatelessWidget {
borderRadius: BorderRadius.circular(8),
),
alignment: Alignment.center,
child: const Text(
'U',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w900,
fontSize: 18,
),
),
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: UnionFlowColors.textPrimary,
),
),
Text(
'Modérateur',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w400,
color: UnionFlowColors.textSecondary,
),
),
Text('UnionFlow', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: UnionFlowColors.textPrimary)),
Text('Modérateur', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w400, color: UnionFlowColors.textSecondary)),
],
),
],
@@ -601,74 +390,12 @@ class ModeratorDashboard extends StatelessWidget {
);
}
Widget _buildUserHeader(dynamic user) {
final year = user?.createdAt?.year ?? DateTime.now().year;
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: UnionFlowColors.warmGradient,
borderRadius: BorderRadius.circular(16),
border: const Border(
top: BorderSide(color: UnionFlowColors.gold, width: 3),
),
boxShadow: UnionFlowColors.goldGlowShadow,
),
child: Row(
children: [
CircleAvatar(
radius: 28,
backgroundColor: Colors.white.withOpacity(0.3),
child: Text(
user?.initials ?? 'SM',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: Colors.white,
),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
user?.fullName ?? 'Secrétaire',
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w700,
color: Colors.white,
),
),
const SizedBox(height: 4),
Text(
'Depuis $year • Très Actif',
style: TextStyle(
fontSize: 12,
color: Colors.white.withOpacity(0.9),
),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'ACTIF',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w800,
color: UnionFlowColors.gold,
letterSpacing: 0.5,
),
),
),
],
),
);
String _formatAmount(double amount) {
if (amount >= 1000000) {
return '${(amount / 1000000).toStringAsFixed(amount % 1000000 == 0 ? 0 : 1)}M FCFA';
} else if (amount >= 1000) {
return '${(amount / 1000).toStringAsFixed(amount % 1000 == 0 ? 0 : 1)}K FCFA';
}
return '${amount.toStringAsFixed(0)} FCFA';
}
}

View File

@@ -10,6 +10,7 @@ import '../../../../events/presentation/pages/events_page_wrapper.dart';
import '../../../../contributions/presentation/pages/contributions_page_wrapper.dart';
import '../../../../reports/presentation/pages/reports_page_wrapper.dart';
import '../../../../notifications/presentation/pages/notifications_page_wrapper.dart';
import '../../../../adhesions/presentation/pages/adhesions_page_wrapper.dart';
import '../../widgets/dashboard_drawer.dart';
import '../../../data/datasources/dashboard_remote_datasource.dart';
import '../../../data/services/dashboard_export_service.dart';
@@ -25,12 +26,8 @@ class OrgAdminDashboard extends StatelessWidget {
backgroundColor: UnionFlowColors.background,
appBar: _buildAppBar(context),
drawer: DashboardDrawer(
onNavigate: (route) {
Navigator.of(context).pushNamed(route);
},
onLogout: () {
context.read<AuthBloc>().add(const AuthLogoutRequested());
},
onNavigate: (route) => Navigator.of(context).pushNamed(route),
onLogout: () => context.read<AuthBloc>().add(const AuthLogoutRequested()),
),
body: AfricanPatternBackground(
child: BlocBuilder<AuthBloc, AuthState>(
@@ -54,277 +51,98 @@ class OrgAdminDashboard extends StatelessWidget {
final stats = dashboardData?.stats;
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// En-tête Admin
AnimatedFadeIn(
delay: const Duration(milliseconds: 100),
child: _buildUserHeader(user, orgContext),
child: UserIdentityCard(
initials: user?.initials ?? 'AD',
name: user?.fullName ?? 'Administrateur',
subtitle: orgContext?.organizationName ?? 'Organisation',
badgeLabel: 'ADMIN',
gradient: UnionFlowColors.goldGradient,
accentColor: UnionFlowColors.gold,
),
),
const SizedBox(height: 24),
const SizedBox(height: 12),
// Balance organisation (données backend réelles)
// Balance organisation
AnimatedSlideIn(
delay: const Duration(milliseconds: 200),
child: UnionBalanceCard(
label: 'Caisse de l\'Organisation',
amount: _formatAmount(stats?.totalContributionAmount ?? 0),
trend: stats != null && stats.monthlyGrowth != 0
? '${stats.monthlyGrowth > 0 ? '+' : ''}${stats.monthlyGrowth.toStringAsFixed(1)}% ce mois'
: 'Aucune variation',
? '${stats.monthlyGrowth > 0 ? '+' : ''}${stats.monthlyGrowth.toStringAsFixed(1)}%'
: null,
isTrendPositive: (stats?.monthlyGrowth ?? 0) >= 0,
),
),
const SizedBox(height: 24),
const SizedBox(height: 12),
// Stats organisation (données backend réelles)
// Stats organisation — cellules très plates
AnimatedFadeIn(
delay: const Duration(milliseconds: 300),
child: Row(
child: GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
mainAxisSpacing: 6,
crossAxisSpacing: 6,
childAspectRatio: 3.0,
children: [
Expanded(
child: UnionStatWidget(
label: 'Membres',
value: '${stats?.totalMembers ?? 0}',
icon: Icons.people_outlined,
color: UnionFlowColors.unionGreen,
trend: stats != null && stats.monthlyGrowth > 0
? '+${stats.monthlyGrowth.toStringAsFixed(1)}%'
: null,
isTrendUp: (stats?.monthlyGrowth ?? 0) > 0,
),
UnionStatWidget(
compact: true,
label: 'Membres',
value: '${stats?.totalMembers ?? 0}',
icon: Icons.people_outlined,
color: UnionFlowColors.unionGreen,
trend: stats != null && stats.monthlyGrowth > 0
? '+${stats.monthlyGrowth.toStringAsFixed(1)}%'
: null,
isTrendUp: (stats?.monthlyGrowth ?? 0) > 0,
),
const SizedBox(width: 12),
Expanded(
child: UnionStatWidget(
label: 'Actifs',
value: '${stats?.activeMembers ?? 0}',
icon: Icons.check_circle_outline,
color: UnionFlowColors.success,
trend: stats != null && stats.totalMembers > 0
? '${((stats.activeMembers / stats.totalMembers) * 100).toStringAsFixed(0)}%'
: null,
isTrendUp: true,
),
UnionStatWidget(
compact: true,
label: 'Actifs',
value: '${stats?.activeMembers ?? 0}',
icon: Icons.check_circle_outline,
color: UnionFlowColors.success,
trend: stats != null && stats.totalMembers > 0
? '${((stats.activeMembers / stats.totalMembers) * 100).toStringAsFixed(0)}%'
: null,
isTrendUp: true,
),
UnionStatWidget(
compact: true,
label: 'Événements',
value: '${stats?.upcomingEvents ?? 0}',
icon: Icons.event_outlined,
color: UnionFlowColors.gold,
trend: stats?.totalEvents != null ? '${stats!.totalEvents} total' : null,
isTrendUp: true,
),
UnionStatWidget(
compact: true,
label: 'Cotisations',
value: '${stats?.totalContributions ?? 0}',
icon: Icons.trending_up,
color: UnionFlowColors.amber,
trend: _formatAmount(stats?.totalContributionAmount ?? 0),
isTrendUp: true,
),
],
),
),
const SizedBox(height: 12),
AnimatedFadeIn(
delay: const Duration(milliseconds: 400),
child: Row(
children: [
Expanded(
child: UnionStatWidget(
label: 'Événements',
value: '${stats?.upcomingEvents ?? 0}',
icon: Icons.event_outlined,
color: UnionFlowColors.gold,
trend: stats?.totalEvents != null ? '${stats!.totalEvents} total' : null,
isTrendUp: true,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionStatWidget(
label: 'Cotisations',
value: '${stats?.totalContributions ?? 0}',
icon: Icons.trending_up,
color: UnionFlowColors.amber,
trend: _formatAmount(stats?.totalContributionAmount ?? 0),
isTrendUp: true,
),
),
],
),
),
const SizedBox(height: 24),
// Événements à venir (données backend)
if (dashboardData != null && dashboardData.hasUpcomingEvents) ...[
AnimatedFadeIn(
delay: const Duration(milliseconds: 500),
child: const Text(
'Événements à Venir',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
),
const SizedBox(height: 16),
// Répartition actifs / inactifs
if (stats != null && stats.totalMembers > 0) ...[
AnimatedSlideIn(
delay: const Duration(milliseconds: 600),
child: Column(
children: dashboardData.upcomingEvents.take(3).map((event) =>
Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: UnionFlowColors.border, width: 1),
boxShadow: UnionFlowColors.softShadow,
),
child: Row(
children: [
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
gradient: UnionFlowColors.warmGradient,
borderRadius: BorderRadius.circular(10),
),
child: const Icon(
Icons.event,
color: Colors.white,
size: 26,
),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
event.title,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textPrimary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
event.formattedDate,
style: const TextStyle(
fontSize: 12,
color: UnionFlowColors.textSecondary,
),
),
],
),
),
if (event.hasParticipantInfo)
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: UnionFlowColors.gold.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Text(
'${event.currentParticipants}/${event.maxParticipants}',
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w700,
color: UnionFlowColors.gold,
),
),
),
],
),
)
).toList(),
),
),
const SizedBox(height: 24),
],
// Activité récente (données backend)
if (dashboardData != null && dashboardData.hasRecentActivity) ...[
AnimatedFadeIn(
delay: const Duration(milliseconds: 520),
child: const Text(
'Activité Récente',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
),
const SizedBox(height: 16),
AnimatedSlideIn(
delay: const Duration(milliseconds: 580),
child: Column(
children: dashboardData.recentActivities.take(4).map((activity) =>
Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: UnionFlowColors.border, width: 1),
boxShadow: UnionFlowColors.softShadow,
),
child: Row(
children: [
CircleAvatar(
radius: 22,
backgroundColor: UnionFlowColors.gold.withOpacity(0.2),
child: Icon(
activity.type == 'member' ? Icons.person_add :
activity.type == 'event' ? Icons.event :
Icons.info_outline,
size: 20,
color: UnionFlowColors.gold,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
activity.title,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textPrimary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
activity.description,
style: const TextStyle(
fontSize: 11,
color: UnionFlowColors.textSecondary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
Text(
activity.timeAgo,
style: const TextStyle(
fontSize: 11,
color: UnionFlowColors.textTertiary,
),
),
],
),
),
).toList(),
),
),
const SizedBox(height: 24),
],
// Répartition actifs / inactifs (données backend)
if (stats != null && stats.totalMembers > 0)
AnimatedSlideIn(
delay: const Duration(milliseconds: 700),
delay: const Duration(milliseconds: 400),
child: UnionPieChart(
title: 'Activité des Membres',
subtitle: '${stats.totalMembers} membres',
@@ -346,100 +164,112 @@ class OrgAdminDashboard extends StatelessWidget {
],
),
),
const SizedBox(height: 24),
const SizedBox(height: 12),
],
// Gestion
AnimatedFadeIn(
delay: const Duration(milliseconds: 800),
child: const Text(
'Gestion de l\'Organisation',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
// Événements à venir
if (dashboardData != null && dashboardData.hasUpcomingEvents) ...[
AnimatedFadeIn(
delay: const Duration(milliseconds: 500),
child: UFSectionHeader(
'Événements à Venir',
trailing: '${dashboardData.upcomingEvents.length} programmés',
),
),
const SizedBox(height: 8),
AnimatedSlideIn(
delay: const Duration(milliseconds: 600),
child: Column(
children: dashboardData.upcomingEvents.take(3).map((event) =>
DashboardEventRow(
title: event.title,
date: event.formattedDate,
participants: event.hasParticipantInfo
? '${event.currentParticipants}/${event.maxParticipants}'
: null,
),
).toList(),
),
),
const SizedBox(height: 12),
],
// Activité récente
if (dashboardData != null && dashboardData.hasRecentActivity) ...[
AnimatedFadeIn(
delay: const Duration(milliseconds: 520),
child: const UFSectionHeader('Activité Récente'),
),
const SizedBox(height: 8),
AnimatedSlideIn(
delay: const Duration(milliseconds: 580),
child: Column(
children: dashboardData.recentActivities.take(4).map((activity) =>
DashboardActivityRow(
title: activity.title,
description: activity.description,
timeAgo: activity.timeAgo,
icon: DashboardActivityRow.iconFor(activity.type),
color: DashboardActivityRow.colorFor(activity.type),
),
).toList(),
),
),
const SizedBox(height: 12),
],
// Gestion de l'Organisation
AnimatedFadeIn(
delay: const Duration(milliseconds: 800),
child: const UFSectionHeader('Gestion de l\'Organisation'),
),
const SizedBox(height: 16),
const SizedBox(height: 8),
AnimatedSlideIn(
delay: const Duration(milliseconds: 900),
child: Row(
child: GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 3,
mainAxisSpacing: 6,
crossAxisSpacing: 6,
childAspectRatio: 2.8,
children: [
Expanded(
child: UnionActionButton(
label: 'Membres',
icon: Icons.people,
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => const MembersPageWrapper(),
),
),
backgroundColor: UnionFlowColors.unionGreen,
),
UnionActionButton(
label: 'Membres',
icon: Icons.people,
iconColor: UnionFlowColors.unionGreen,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const MembersPageWrapper())),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Finance',
icon: Icons.account_balance,
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => const CotisationsPageWrapper(),
),
),
backgroundColor: UnionFlowColors.gold,
),
UnionActionButton(
label: 'Finance',
icon: Icons.account_balance,
iconColor: UnionFlowColors.gold,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const CotisationsPageWrapper())),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Événements',
icon: Icons.event,
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => const EventsPageWrapper(),
),
),
backgroundColor: UnionFlowColors.terracotta,
),
UnionActionButton(
label: 'Événements',
icon: Icons.event,
iconColor: UnionFlowColors.terracotta,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const EventsPageWrapper())),
),
],
),
),
const SizedBox(height: 12),
// Paramètres Système réservé au super admin (pas affiché pour org admin)
AnimatedSlideIn(
delay: const Duration(milliseconds: 1000),
child: Row(
children: [
Expanded(
child: UnionActionButton(
label: 'Rapports',
icon: Icons.description,
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => const ReportsPageWrapper(),
),
),
backgroundColor: UnionFlowColors.info,
),
UnionActionButton(
label: 'Rapports',
icon: Icons.description,
iconColor: UnionFlowColors.info,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const ReportsPageWrapper())),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Historique',
icon: Icons.history,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const ReportsPageWrapper(),
),
);
},
backgroundColor: UnionFlowColors.amber,
),
UnionActionButton(
label: 'Adhésions',
icon: Icons.person_add,
iconColor: UnionFlowColors.indigo,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const AdhesionsPageWrapper())),
),
UnionActionButton(
label: 'Historique',
icon: Icons.history,
iconColor: UnionFlowColors.amber,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const ReportsPageWrapper())),
),
],
),
@@ -471,42 +301,22 @@ class OrgAdminDashboard extends StatelessWidget {
alignment: Alignment.center,
child: const Text(
'A',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w900,
fontSize: 18,
),
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: UnionFlowColors.textPrimary,
),
),
Text(
'Admin Organisation',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w400,
color: UnionFlowColors.textSecondary,
),
),
Text('UnionFlow', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: UnionFlowColors.textPrimary)),
Text('Admin Organisation', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w400, color: UnionFlowColors.textSecondary)),
],
),
],
),
iconTheme: const IconThemeData(color: UnionFlowColors.textPrimary),
actions: [
UnionExportButton(
onExport: (_) => _handleExport(context),
),
UnionExportButton(onExport: (_) => _handleExport(context)),
const SizedBox(width: 8),
UnionNotificationBadge(
count: 0,
@@ -514,9 +324,7 @@ class OrgAdminDashboard extends StatelessWidget {
icon: const Icon(Icons.notifications_outlined),
color: UnionFlowColors.textPrimary,
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => const NotificationsPageWrapper(),
),
MaterialPageRoute(builder: (_) => const NotificationsPageWrapper()),
),
),
),
@@ -525,86 +333,9 @@ class OrgAdminDashboard extends StatelessWidget {
);
}
Widget _buildUserHeader(dynamic user, dynamic orgContext) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: UnionFlowColors.goldGradient,
borderRadius: BorderRadius.circular(16),
border: const Border(
top: BorderSide(color: UnionFlowColors.gold, width: 3),
),
boxShadow: UnionFlowColors.goldGlowShadow,
),
child: Column(
children: [
Row(
children: [
CircleAvatar(
radius: 28,
backgroundColor: Colors.white.withOpacity(0.3),
child: Text(
user?.initials ?? 'AD',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: Colors.white,
),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
user?.fullName ?? 'Administrateur',
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w700,
color: Colors.white,
),
),
const SizedBox(height: 4),
Text(
orgContext?.organizationName ?? 'Organisation',
style: TextStyle(
fontSize: 12,
color: Colors.white.withOpacity(0.9),
),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'ADMIN',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w800,
color: UnionFlowColors.gold,
letterSpacing: 0.5,
),
),
),
],
),
],
),
);
}
String _formatAmount(double amount) {
if (amount >= 1000000) {
return '${(amount / 1000000).toStringAsFixed(1)}M FCFA';
} else if (amount >= 1000) {
return '${(amount / 1000).toStringAsFixed(0)}K FCFA';
}
if (amount >= 1000000) return '${(amount / 1000000).toStringAsFixed(1)}M FCFA';
if (amount >= 1000) return '${(amount / 1000).toStringAsFixed(0)}K FCFA';
return '${amount.toStringAsFixed(0)} FCFA';
}
@@ -629,9 +360,6 @@ class OrgAdminDashboard extends StatelessWidget {
return;
}
final orgCtx = user.organizationContexts.first;
final orgId = orgCtx.organizationId;
final orgName = orgCtx.organizationName;
final userId = user.id;
showDialog<void>(
context: context,
barrierDismissible: false,
@@ -639,10 +367,10 @@ class OrgAdminDashboard extends StatelessWidget {
);
try {
final dataSource = sl<DashboardRemoteDataSource>();
final data = await dataSource.getDashboardData(orgId, userId);
final data = await dataSource.getDashboardData(orgCtx.organizationId, user.id);
final path = await DashboardExportService().exportDashboardReport(
dashboardData: data,
organizationName: orgName,
organizationName: orgCtx.organizationName,
reportTitle: 'Rapport dashboard - ${DateTime.now().day}/${DateTime.now().month}/${DateTime.now().year}',
);
if (context.mounted) {

View File

@@ -19,12 +19,8 @@ class SimpleMemberDashboard extends StatelessWidget {
backgroundColor: UnionFlowColors.background,
appBar: _buildAppBar(),
drawer: DashboardDrawer(
onNavigate: (route) {
Navigator.of(context).pushNamed(route);
},
onLogout: () {
context.read<AuthBloc>().add(const AuthLogoutRequested());
},
onNavigate: (route) => Navigator.of(context).pushNamed(route),
onLogout: () => context.read<AuthBloc>().add(const AuthLogoutRequested()),
),
body: AfricanPatternBackground(
child: BlocBuilder<AuthBloc, AuthState>(
@@ -45,16 +41,24 @@ class SimpleMemberDashboard extends StatelessWidget {
final stats = dashboardData?.stats;
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// En-tête avec badge de rôle
AnimatedFadeIn(
delay: const Duration(milliseconds: 100),
child: _buildUserHeader(user),
child: UserIdentityCard(
initials: user?.initials ?? 'M',
name: user?.fullName ?? 'Membre',
subtitle: 'Membre depuis ${user?.createdAt.year ?? 2024}',
badgeLabel: 'MEMBRE',
gradient: UnionFlowColors.subtleGradient,
accentColor: UnionFlowColors.unionGreen,
lightBackground: true,
),
),
const SizedBox(height: 24),
const SizedBox(height: 12),
// Solde personnel
AnimatedSlideIn(
@@ -63,232 +67,114 @@ class SimpleMemberDashboard extends StatelessWidget {
label: 'Mon Solde',
amount: _formatAmount(stats?.totalContributionAmount ?? 0),
trend: stats != null && stats.monthlyGrowth != 0
? '${stats.monthlyGrowth > 0 ? '+' : ''}${stats.monthlyGrowth.toStringAsFixed(1)}% ce mois'
: 'Aucune variation',
? '${stats.monthlyGrowth > 0 ? '+' : ''}${stats.monthlyGrowth.toStringAsFixed(1)}%'
: null,
isTrendPositive: (stats?.monthlyGrowth ?? 0) >= 0,
),
),
const SizedBox(height: 24),
const SizedBox(height: 12),
// Ma situation
// Ma Situation
AnimatedFadeIn(
delay: const Duration(milliseconds: 300),
child: const Text(
'Ma Situation',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
child: const UFSectionHeader('Ma Situation'),
),
const SizedBox(height: 16),
const SizedBox(height: 8),
AnimatedSlideIn(
delay: const Duration(milliseconds: 400),
child: Row(
child: GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
mainAxisSpacing: 6,
crossAxisSpacing: 6,
childAspectRatio: 2.0,
children: [
Expanded(
child: UnionStatWidget(
label: 'Cotisations',
value: stats != null && stats.totalContributions > 0 ? 'À jour' : 'En retard',
icon: Icons.check_circle_outline,
color: stats != null && stats.totalContributions > 0
? UnionFlowColors.success
: UnionFlowColors.warning,
),
UnionStatWidget(
label: 'Cotisations',
value: stats != null && stats.totalContributions > 0 ? 'À jour' : 'En retard',
icon: Icons.check_circle_outline,
color: stats != null && stats.totalContributions > 0
? UnionFlowColors.success
: UnionFlowColors.warning,
),
const SizedBox(width: 12),
Expanded(
child: UnionStatWidget(
label: 'Événements',
value: '${stats?.upcomingEvents ?? 0}',
icon: Icons.event_outlined,
color: UnionFlowColors.gold,
),
),
],
),
),
const SizedBox(height: 24),
// Actions rapides
AnimatedFadeIn(
delay: const Duration(milliseconds: 500),
child: const Text(
'Actions Rapides',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
),
const SizedBox(height: 16),
AnimatedSlideIn(
delay: const Duration(milliseconds: 600),
child: Row(
children: [
Expanded(
child: UnionActionButton(
label: 'Cotiser',
icon: Icons.payment,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const CotisationsPageWrapper(),
),
);
},
backgroundColor: UnionFlowColors.unionGreen,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Épargner',
icon: Icons.savings_outlined,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const EpargnePage(),
),
);
},
backgroundColor: UnionFlowColors.gold,
),
UnionStatWidget(
label: 'Événements',
value: '${stats?.upcomingEvents ?? 0}',
icon: Icons.event_outlined,
color: UnionFlowColors.gold,
),
],
),
),
const SizedBox(height: 12),
// Actions rapides
AnimatedFadeIn(
delay: const Duration(milliseconds: 500),
child: const UFSectionHeader('Actions Rapides'),
),
const SizedBox(height: 8),
AnimatedSlideIn(
delay: const Duration(milliseconds: 700),
child: Row(
delay: const Duration(milliseconds: 600),
child: GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 2,
mainAxisSpacing: 6,
crossAxisSpacing: 6,
childAspectRatio: 2.8,
children: [
Expanded(
child: UnionActionButton(
label: 'Mes Infos',
icon: Icons.person_outline,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const ProfilePageWrapper(),
),
);
},
backgroundColor: UnionFlowColors.indigo,
),
UnionActionButton(
label: 'Cotiser',
icon: Icons.payment,
iconColor: UnionFlowColors.unionGreen,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const CotisationsPageWrapper())),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Support',
icon: Icons.help_outline,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const HelpSupportPage(),
),
);
},
backgroundColor: UnionFlowColors.terracotta,
),
UnionActionButton(
label: 'Épargner',
icon: Icons.savings_outlined,
iconColor: UnionFlowColors.gold,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const EpargnePage())),
),
UnionActionButton(
label: 'Mes Infos',
icon: Icons.person_outline,
iconColor: UnionFlowColors.indigo,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const ProfilePageWrapper())),
),
UnionActionButton(
label: 'Support',
icon: Icons.help_outline,
iconColor: UnionFlowColors.terracotta,
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const HelpSupportPage())),
),
],
),
),
const SizedBox(height: 24),
// Événements à venir (données backend)
// Événements à venir
if (dashboardData != null && dashboardData.hasUpcomingEvents) ...[
const SizedBox(height: 12),
AnimatedFadeIn(
delay: const Duration(milliseconds: 800),
child: const Text(
child: UFSectionHeader(
'Événements à Venir',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
trailing: '${dashboardData.upcomingEvents.length} programmés',
),
),
const SizedBox(height: 16),
const SizedBox(height: 8),
AnimatedSlideIn(
delay: const Duration(milliseconds: 900),
child: Column(
children: dashboardData.upcomingEvents.take(2).map((event) =>
Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: UnionFlowColors.border, width: 1),
boxShadow: UnionFlowColors.softShadow,
),
child: Row(
children: [
Container(
width: 48,
height: 48,
decoration: BoxDecoration(
color: UnionFlowColors.gold.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(
Icons.event,
color: UnionFlowColors.gold,
size: 24,
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
event.title,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textPrimary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const SizedBox(height: 4),
Text(
event.formattedDate,
style: const TextStyle(
fontSize: 12,
color: UnionFlowColors.textSecondary,
),
),
],
),
),
if (event.daysUntilEventInt <= 7)
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: UnionFlowColors.warning.withOpacity(0.1),
borderRadius: BorderRadius.circular(6),
),
child: Text(
'${event.daysUntilEventInt}j',
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w700,
color: UnionFlowColors.warning,
),
),
),
],
),
)
DashboardEventRow(
title: event.title,
date: event.formattedDate,
daysUntil: event.daysUntilEventInt <= 7 ? '${event.daysUntilEventInt}j' : null,
),
).toList(),
),
),
@@ -318,35 +204,14 @@ class SimpleMemberDashboard extends StatelessWidget {
borderRadius: BorderRadius.circular(8),
),
alignment: Alignment.center,
child: const Text(
'U',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w900,
fontSize: 18,
),
),
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: UnionFlowColors.textPrimary,
),
),
Text(
'Membre Simple',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w400,
color: UnionFlowColors.textSecondary,
),
),
Text('UnionFlow', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w700, color: UnionFlowColors.textPrimary)),
Text('Membre Simple', style: TextStyle(fontSize: 11, fontWeight: FontWeight.w400, color: UnionFlowColors.textSecondary)),
],
),
],
@@ -355,82 +220,9 @@ class SimpleMemberDashboard extends StatelessWidget {
);
}
Widget _buildUserHeader(dynamic user) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
gradient: UnionFlowColors.subtleGradient,
borderRadius: BorderRadius.circular(16),
border: const Border(
top: BorderSide(color: UnionFlowColors.unionGreen, width: 3),
),
boxShadow: UnionFlowColors.softShadow,
),
child: Row(
children: [
CircleAvatar(
radius: 28,
backgroundColor: UnionFlowColors.unionGreen.withOpacity(0.2),
child: Text(
user?.initials ?? 'M',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: UnionFlowColors.unionGreen,
),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
user?.fullName ?? 'Membre',
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
const SizedBox(height: 4),
Text(
'Membre depuis ${user?.createdAt.year ?? 2024}',
style: const TextStyle(
fontSize: 12,
color: UnionFlowColors.textSecondary,
),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: UnionFlowColors.unionGreen,
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'MEMBRE',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w800,
color: Colors.white,
letterSpacing: 0.5,
),
),
),
],
),
);
}
String _formatAmount(double amount) {
if (amount >= 1000000) {
return '${(amount / 1000000).toStringAsFixed(1)}M FCFA';
} else if (amount >= 1000) {
return '${(amount / 1000).toStringAsFixed(0)}K FCFA';
}
if (amount >= 1000000) return '${(amount / 1000000).toStringAsFixed(1)}M FCFA';
if (amount >= 1000) return '${(amount / 1000).toStringAsFixed(0)}K FCFA';
return '${amount.toStringAsFixed(0)} FCFA';
}
}

View File

@@ -2,6 +2,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/material.dart';
import '../../../../../shared/design_system/unionflow_design_v2.dart';
import '../../bloc/dashboard_bloc.dart';
import '../../../domain/entities/dashboard_entity.dart';
import '../../../../authentication/presentation/bloc/auth_bloc.dart';
import '../../../../organizations/presentation/pages/organizations_page_wrapper.dart';
import '../../../../admin/presentation/pages/user_management_page.dart';
@@ -10,7 +11,7 @@ import '../../../../backup/presentation/pages/backup_page.dart';
import '../../../../help/presentation/pages/help_support_page.dart';
import '../../widgets/dashboard_drawer.dart';
/// Dashboard Super Admin - Design UnionFlow Contrôle Système
/// Dashboard Super Admin — design fintech compact
class SuperAdminDashboard extends StatelessWidget {
const SuperAdminDashboard({super.key});
@@ -18,448 +19,271 @@ class SuperAdminDashboard extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: UnionFlowColors.background,
appBar: _buildAppBar(),
appBar: _buildAppBar(context),
drawer: DashboardDrawer(
onNavigate: (route) {
Navigator.of(context).pushNamed(route);
},
onLogout: () {
context.read<AuthBloc>().add(const AuthLogoutRequested());
},
onNavigate: (route) => Navigator.of(context).pushNamed(route),
onLogout: () => context.read<AuthBloc>().add(const AuthLogoutRequested()),
),
body: AfricanPatternBackground(
child: BlocBuilder<AuthBloc, AuthState>(
builder: (context, authState) {
final user = (authState is AuthAuthenticated) ? authState.user : null;
body: BlocBuilder<AuthBloc, AuthState>(
builder: (context, authState) {
final user = (authState is AuthAuthenticated) ? authState.user : null;
return BlocBuilder<DashboardBloc, DashboardState>(
builder: (context, dashboardState) {
if (dashboardState is DashboardLoading) {
return const Center(
child: CircularProgressIndicator(
color: UnionFlowColors.error,
strokeWidth: 2,
),
);
}
final dashboardData = (dashboardState is DashboardLoaded)
? dashboardState.dashboardData
: null;
final stats = dashboardData?.stats;
return BlocBuilder<DashboardBloc, DashboardState>(
builder: (context, dashboardState) {
if (dashboardState is DashboardLoading) {
return const Center(
child: CircularProgressIndicator(color: UnionFlowColors.error),
);
}
final dashboardData = (dashboardState is DashboardLoaded)
? dashboardState.dashboardData
: null;
final stats = dashboardData?.stats;
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
return RefreshIndicator(
color: UnionFlowColors.error,
strokeWidth: 2,
onRefresh: () async {
context.read<DashboardBloc>().add(LoadDashboardData(
organizationId: dashboardData?.organizationId ?? '',
userId: user?.id ?? '',
useGlobalDashboard: true,
));
await Future.delayed(const Duration(milliseconds: 600));
},
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.fromLTRB(16, 12, 16, 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// En-tête Root
AnimatedFadeIn(
delay: const Duration(milliseconds: 100),
child: _buildUserHeader(user),
),
const SizedBox(height: 24),
// Balance globale
AnimatedSlideIn(
delay: const Duration(milliseconds: 200),
child: UnionBalanceCard(
label: 'Caisse Globale Système',
amount: _formatAmount(stats?.totalContributionAmount ?? 0),
trend: stats != null && stats.monthlyGrowth != 0
? '${stats.monthlyGrowth > 0 ? '+' : ''}${stats.monthlyGrowth.toStringAsFixed(1)}% ce mois'
: 'Aucune variation',
isTrendPositive: (stats?.monthlyGrowth ?? 0) >= 0,
),
),
const SizedBox(height: 24),
// Stats système
AnimatedFadeIn(
delay: const Duration(milliseconds: 300),
child: Row(
children: [
Expanded(
child: UnionStatWidget(
label: 'Organisations',
value: stats != null ? '${stats.totalOrganizations ?? 0}' : '0',
icon: Icons.business_outlined,
color: UnionFlowColors.unionGreen,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionStatWidget(
label: 'Utilisateurs',
value: '${stats?.totalMembers ?? 0}',
icon: Icons.groups_outlined,
color: UnionFlowColors.gold,
trend: stats != null && stats.monthlyGrowth > 0
? '+${stats.monthlyGrowth.toStringAsFixed(0)}%'
: null,
isTrendUp: (stats?.monthlyGrowth ?? 0) > 0,
),
),
],
// ── Carte identité ───────────────────────────────
UserIdentityCard(
initials: user?.initials ?? 'SA',
name: user?.fullName ?? 'Super Administrateur',
subtitle: 'Accès global système',
badgeLabel: 'ROOT',
gradient: const LinearGradient(
colors: [Color(0xFFB91C1C), Color(0xFF7F1D1D)],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
accentColor: UnionFlowColors.error,
showTopBorder: false,
),
const SizedBox(height: 12),
AnimatedFadeIn(
delay: const Duration(milliseconds: 400),
child: Row(
children: [
Expanded(
child: UnionStatWidget(
label: 'Projets',
value: '${stats?.completedProjects ?? 0}',
icon: Icons.account_balance_outlined,
color: UnionFlowColors.info,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionStatWidget(
label: 'Engagement',
value: stats != null
? '${(stats.engagementRate * 100).toStringAsFixed(0)}%'
: '0%',
icon: Icons.trending_up,
color: (stats?.engagementRate ?? 0) >= 0.7
? UnionFlowColors.success
: UnionFlowColors.warning,
),
),
],
),
),
const SizedBox(height: 24),
// Répartition Membres (données réelles)
if (stats != null && stats.totalMembers > 0)
AnimatedFadeIn(
delay: const Duration(milliseconds: 500),
child: UnionPieChart(
title: 'Activité des Membres',
subtitle: '${stats.totalMembers} membres au total',
sections: [
UnionPieChartSection.create(
value: stats.activeMembers.toDouble(),
color: UnionFlowColors.success,
title: '${((stats.activeMembers / stats.totalMembers) * 100).toStringAsFixed(0)}%\nActifs',
),
UnionPieChartSection.create(
value: (stats.totalMembers - stats.activeMembers).toDouble(),
color: UnionFlowColors.warning,
title: '${(((stats.totalMembers - stats.activeMembers) / stats.totalMembers) * 100).toStringAsFixed(0)}%\nInactifs',
),
],
),
),
if (stats != null && stats.totalMembers > 0)
const SizedBox(height: 24),
// Événements à venir (données réelles)
if (dashboardData != null && dashboardData.hasUpcomingEvents)
AnimatedFadeIn(
delay: const Duration(milliseconds: 600),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Événements à venir',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
const SizedBox(height: 12),
...dashboardData.upcomingEvents.take(3).map((event) => Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(12),
border: Border(
left: BorderSide(color: UnionFlowColors.unionGreen, width: 3),
),
),
child: Row(
children: [
Icon(Icons.event, color: UnionFlowColors.unionGreen, size: 20),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
event.title,
style: const TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textPrimary,
),
),
const SizedBox(height: 2),
Text(
event.daysUntilEvent,
style: const TextStyle(
fontSize: 11,
color: UnionFlowColors.textSecondary,
),
),
],
),
),
Text(
'${event.currentParticipants}/${event.maxParticipants}',
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textSecondary,
),
),
],
),
),
)),
],
),
),
if (dashboardData != null && dashboardData.hasUpcomingEvents)
const SizedBox(height: 24),
// Activités récentes (données réelles)
if (dashboardData != null && dashboardData.hasRecentActivity)
AnimatedFadeIn(
delay: const Duration(milliseconds: 650),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Activités Récentes',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
const SizedBox(height: 12),
...dashboardData.recentActivities.take(5).map((activity) => Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
CircleAvatar(
radius: 16,
backgroundColor: UnionFlowColors.unionGreen.withOpacity(0.2),
child: Text(
activity.userName[0].toUpperCase(),
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w700,
color: UnionFlowColors.unionGreen,
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
activity.title,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textPrimary,
),
),
Text(
activity.description,
style: const TextStyle(
fontSize: 11,
color: UnionFlowColors.textSecondary,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
Text(
activity.timeAgo,
style: const TextStyle(
fontSize: 10,
color: UnionFlowColors.textTertiary,
),
),
],
),
),
)),
],
),
),
if (dashboardData != null && dashboardData.hasRecentActivity)
const SizedBox(height: 24),
const SizedBox(height: 24),
// Panel Root
AnimatedFadeIn(
delay: const Duration(milliseconds: 700),
child: const Text(
'Panel Root',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
),
const SizedBox(height: 16),
AnimatedSlideIn(
delay: const Duration(milliseconds: 800),
child: Row(
children: [
Expanded(
child: UnionActionButton(
label: 'Organisations',
icon: Icons.business,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const OrganizationsPageWrapper(),
),
);
},
backgroundColor: UnionFlowColors.unionGreen,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Utilisateurs',
icon: Icons.people,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const UserManagementPage(),
),
);
},
backgroundColor: UnionFlowColors.gold,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Système',
icon: Icons.settings,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SystemSettingsPage(),
),
);
},
backgroundColor: UnionFlowColors.indigo,
),
),
],
),
// ── Balance principale ───────────────────────────
UnionBalanceCard(
label: 'Caisse Globale Système',
amount: _formatAmount(stats?.totalContributionAmount ?? 0),
trend: stats != null && stats.totalContributionAmount > 0
? '${stats.monthlyGrowth >= 0 ? '+' : ''}${stats.monthlyGrowth.toStringAsFixed(1)}%'
: null,
isTrendPositive: (stats?.monthlyGrowth ?? 0) >= 0,
),
const SizedBox(height: 12),
AnimatedSlideIn(
delay: const Duration(milliseconds: 900),
child: Row(
children: [
Expanded(
child: UnionActionButton(
label: 'Backup',
icon: Icons.backup,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const BackupPage(),
),
);
},
backgroundColor: UnionFlowColors.warning,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Sécurité',
icon: Icons.security,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SystemSettingsPage(),
),
);
},
backgroundColor: UnionFlowColors.error,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Support',
icon: Icons.help_outline,
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const HelpSupportPage(),
),
);
},
backgroundColor: UnionFlowColors.info,
),
),
],
// ── Grille 3×2 KPIs ─────────────────────────────
GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 3,
mainAxisSpacing: 6,
crossAxisSpacing: 6,
childAspectRatio: 2.0,
children: [
UnionStatWidget(
label: 'Organisations',
value: '${stats?.totalOrganizations ?? 0}',
icon: Icons.business_rounded,
color: UnionFlowColors.unionGreen,
),
UnionStatWidget(
label: 'Utilisateurs',
value: '${stats?.totalMembers ?? 0}',
icon: Icons.groups_rounded,
color: UnionFlowColors.gold,
trend: stats != null && stats.monthlyGrowth > 0
? '+${stats.monthlyGrowth.toStringAsFixed(0)}%'
: null,
),
UnionStatWidget(
label: 'Événements',
value: '${stats?.totalEvents ?? 0}',
icon: Icons.event_rounded,
color: UnionFlowColors.info,
trend: stats != null && stats.upcomingEvents > 0
? '${stats.upcomingEvents} à venir'
: null,
),
UnionStatWidget(
label: 'Cotisations',
value: '${stats?.totalContributions ?? 0}',
icon: Icons.payments_rounded,
color: UnionFlowColors.amber,
),
UnionStatWidget(
label: 'Demandes',
value: '${stats?.pendingRequests ?? 0}',
icon: Icons.pending_actions_rounded,
color: (stats?.pendingRequests ?? 0) > 0
? UnionFlowColors.warning
: UnionFlowColors.success,
),
UnionStatWidget(
label: 'Engagement',
value: stats != null
? '${(stats.engagementRate * 100).toStringAsFixed(0)}%'
: '',
icon: Icons.trending_up_rounded,
color: (stats?.engagementRate ?? 0) >= 0.7
? UnionFlowColors.success
: UnionFlowColors.warning,
),
],
),
const SizedBox(height: 12),
// ── Activité membres (pie compact) ───────────────
if (stats != null && stats.totalMembers > 0) ...[
_buildMemberActivityRow(stats),
const SizedBox(height: 12),
],
// ── Répartition types d'org ──────────────────────
if (stats?.organizationTypeDistribution != null &&
stats!.organizationTypeDistribution!.isNotEmpty) ...[
_buildOrgTypeRow(stats),
const SizedBox(height: 12),
],
// ── Événements à venir ───────────────────────────
if (dashboardData != null && dashboardData.hasUpcomingEvents) ...[
UFSectionHeader(
'Événements à venir',
trailing: '${dashboardData.upcomingEvents.length} programmés',
),
const SizedBox(height: 6),
...dashboardData.upcomingEvents.take(3).map(
(e) => DashboardEventRow(
title: e.title,
date: e.formattedDate,
daysUntil: e.daysUntilEvent,
participants:
'${e.currentParticipants}/${e.maxParticipants}',
),
),
const SizedBox(height: 12),
],
// ── Activités récentes ───────────────────────────
if (dashboardData != null && dashboardData.hasRecentActivity) ...[
const UFSectionHeader('Activités récentes',
trailing: 'Dernières actions'),
const SizedBox(height: 6),
...dashboardData.recentActivities.take(5).map(
(a) => DashboardActivityRow(
title: a.title,
description: a.description,
timeAgo: a.timeAgo,
icon: DashboardActivityRow.iconFor(a.type),
color: DashboardActivityRow.colorFor(a.type),
),
),
const SizedBox(height: 12),
],
// ── Actions rapides ──────────────────────────────
const UFSectionHeader('Actions'),
const SizedBox(height: 8),
GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 3,
mainAxisSpacing: 6,
crossAxisSpacing: 6,
childAspectRatio: 2.8,
children: [
UnionActionButton(
label: 'Organisations',
icon: Icons.business_rounded,
iconColor: UnionFlowColors.unionGreen,
onTap: () => Navigator.push(context,
MaterialPageRoute(builder: (_) => const OrganizationsPageWrapper())),
),
UnionActionButton(
label: 'Utilisateurs',
icon: Icons.people_rounded,
iconColor: UnionFlowColors.gold,
onTap: () => Navigator.push(context,
MaterialPageRoute(builder: (_) => const UserManagementPage())),
),
UnionActionButton(
label: 'Système',
icon: Icons.settings_rounded,
iconColor: UnionFlowColors.indigo,
onTap: () => Navigator.push(context,
MaterialPageRoute(builder: (_) => const SystemSettingsPage())),
),
UnionActionButton(
label: 'Backup',
icon: Icons.backup_rounded,
iconColor: UnionFlowColors.warning,
onTap: () => Navigator.push(context,
MaterialPageRoute(builder: (_) => const BackupPage())),
),
UnionActionButton(
label: 'Sécurité',
icon: Icons.security_rounded,
iconColor: UnionFlowColors.error,
onTap: () => Navigator.push(context,
MaterialPageRoute(builder: (_) => const SystemSettingsPage())),
),
UnionActionButton(
label: 'Support',
icon: Icons.help_outline_rounded,
iconColor: UnionFlowColors.info,
onTap: () => Navigator.push(context,
MaterialPageRoute(builder: (_) => const HelpSupportPage())),
),
],
),
],
),
);
},
);
},
),
),
);
},
);
},
),
);
}
PreferredSizeWidget _buildAppBar() {
// ─── AppBar ────────────────────────────────────────────────────────────────
PreferredSizeWidget _buildAppBar(BuildContext context) {
return AppBar(
backgroundColor: UnionFlowColors.surface,
elevation: 0,
scrolledUnderElevation: 1,
shadowColor: UnionFlowColors.border,
leading: Builder(
builder: (ctx) => IconButton(
icon: const Icon(Icons.menu, size: 22, color: UnionFlowColors.textPrimary),
onPressed: () => Scaffold.of(ctx).openDrawer(),
),
),
title: Row(
children: [
Container(
width: 32,
height: 32,
width: 28,
height: 28,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [UnionFlowColors.error, Colors.red.shade900],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(8),
color: UnionFlowColors.error,
borderRadius: BorderRadius.circular(6),
),
alignment: Alignment.center,
child: const Text(
@@ -467,27 +291,26 @@ class SuperAdminDashboard extends StatelessWidget {
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w900,
fontSize: 18,
fontSize: 15,
),
),
),
const SizedBox(width: 12),
const SizedBox(width: 8),
const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'UnionFlow',
style: TextStyle(
fontSize: 16,
fontSize: 14,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
Text(
'Super Admin (Root)',
'Super Admin',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w400,
fontSize: 10,
color: UnionFlowColors.textSecondary,
),
),
@@ -495,102 +318,173 @@ class SuperAdminDashboard extends StatelessWidget {
),
],
),
iconTheme: const IconThemeData(color: UnionFlowColors.textPrimary),
actions: [
UnionExportButton(
onExport: (exportType) {},
),
const SizedBox(width: 8),
UnionExportButton(onExport: (_) {}),
const SizedBox(width: 6),
],
);
}
Widget _buildUserHeader(dynamic user) {
// ─── Activité membres ─────────────────────────────────────────────────────
Widget _buildMemberActivityRow(DashboardStatsEntity stats) {
final total = stats.totalMembers;
final active = stats.activeMembers;
final inactive = total - active;
final pct = total > 0 ? active / total : 0.0;
return Container(
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.fromLTRB(14, 12, 14, 12),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [UnionFlowColors.error, Colors.red.shade900],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(16),
border: const Border(
top: BorderSide(color: UnionFlowColors.error, width: 3),
),
boxShadow: [
BoxShadow(
color: UnionFlowColors.error.withOpacity(0.4),
blurRadius: 16,
offset: const Offset(0, 4),
),
],
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: UnionFlowColors.border),
),
child: Row(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircleAvatar(
radius: 28,
backgroundColor: Colors.white.withOpacity(0.3),
child: Text(
user?.initials ?? 'SA',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: Colors.white,
Row(
children: [
const Text(
'Activité membres',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
const Spacer(),
Text(
'$total membres',
style: const TextStyle(fontSize: 10, color: UnionFlowColors.textTertiary),
),
],
),
const SizedBox(height: 10),
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: LinearProgressIndicator(
value: pct,
minHeight: 6,
backgroundColor: UnionFlowColors.border,
valueColor: const AlwaysStoppedAnimation<Color>(UnionFlowColors.success),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
user?.fullName ?? 'Super Administrateur',
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w700,
color: Colors.white,
),
),
const SizedBox(height: 4),
Text(
'Global System Root Access',
style: TextStyle(
fontSize: 12,
color: Colors.white.withOpacity(0.9),
),
),
],
),
),
Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: const Text(
'ROOT',
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w800,
color: UnionFlowColors.error,
letterSpacing: 0.5,
const SizedBox(height: 8),
Row(
children: [
_dot(UnionFlowColors.success),
const SizedBox(width: 4),
const Text('Actifs', style: TextStyle(fontSize: 10, color: UnionFlowColors.textSecondary)),
const SizedBox(width: 4),
Text(
'$active (${(pct * 100).toStringAsFixed(0)}%)',
style: const TextStyle(fontSize: 10, fontWeight: FontWeight.w700, color: UnionFlowColors.textPrimary),
),
),
const Spacer(),
_dot(UnionFlowColors.border),
const SizedBox(width: 4),
const Text('Inactifs', style: TextStyle(fontSize: 10, color: UnionFlowColors.textSecondary)),
const SizedBox(width: 4),
Text(
'$inactive (${total > 0 ? ((inactive / total) * 100).toStringAsFixed(0) : 0}%)',
style: const TextStyle(fontSize: 10, fontWeight: FontWeight.w700, color: UnionFlowColors.textPrimary),
),
],
),
],
),
);
}
// ─── Répartition types d'org ──────────────────────────────────────────────
Widget _buildOrgTypeRow(DashboardStatsEntity stats) {
final dist = stats.organizationTypeDistribution!;
final total = dist.values.fold(0, (s, v) => s + v);
const colors = [
UnionFlowColors.unionGreen,
UnionFlowColors.gold,
UnionFlowColors.info,
UnionFlowColors.warning,
UnionFlowColors.error,
];
final entries = dist.entries.toList();
return Container(
padding: const EdgeInsets.fromLTRB(14, 12, 14, 12),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: UnionFlowColors.border),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Text(
'Types d\'organisation',
style: TextStyle(fontSize: 12, fontWeight: FontWeight.w700, color: UnionFlowColors.textPrimary),
),
const Spacer(),
Text('$total org.', style: const TextStyle(fontSize: 10, color: UnionFlowColors.textTertiary)),
],
),
const SizedBox(height: 10),
ClipRRect(
borderRadius: BorderRadius.circular(4),
child: Row(
children: entries.asMap().entries.map((e) {
final frac = total > 0 ? e.value.value / total : 0.0;
return Flexible(
flex: (frac * 100).round(),
child: Container(height: 6, color: colors[e.key % colors.length]),
);
}).toList(),
),
),
const SizedBox(height: 8),
...entries.asMap().entries.map((e) {
final color = colors[e.key % colors.length];
final pct = total > 0 ? (e.value.value / total * 100).toStringAsFixed(0) : '0';
return Padding(
padding: const EdgeInsets.only(bottom: 4),
child: Row(
children: [
_dot(color),
const SizedBox(width: 6),
Expanded(
child: Text(
e.value.key,
style: const TextStyle(fontSize: 10, color: UnionFlowColors.textSecondary),
overflow: TextOverflow.ellipsis,
),
),
Text(
'$pct% · ${e.value.value}',
style: const TextStyle(fontSize: 10, fontWeight: FontWeight.w700, color: UnionFlowColors.textPrimary),
),
],
),
);
}),
],
),
);
}
Widget _dot(Color color) => Container(
width: 8,
height: 8,
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
);
// ─── Helpers ──────────────────────────────────────────────────────────────
String _formatAmount(double amount) {
if (amount >= 1000000) {
return '${(amount / 1000000).toStringAsFixed(1)}M FCFA';
} else if (amount >= 1000) {
return '${(amount / 1000).toStringAsFixed(0)}K FCFA';
}
if (amount >= 1000000) return '${(amount / 1000000).toStringAsFixed(1)}M FCFA';
if (amount >= 1000) return '${(amount / 1000).toStringAsFixed(0)}K FCFA';
return '${amount.toStringAsFixed(0)} FCFA';
}
}

View File

@@ -23,7 +23,7 @@ class VisitorDashboard extends StatelessWidget {
),
body: AfricanPatternBackground(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -32,7 +32,7 @@ class VisitorDashboard extends StatelessWidget {
delay: const Duration(milliseconds: 100),
child: _buildWelcomeCard(),
),
const SizedBox(height: 24),
const SizedBox(height: 12),
// Fonctionnalités UnionFlow
AnimatedSlideIn(
@@ -40,13 +40,13 @@ class VisitorDashboard extends StatelessWidget {
child: const Text(
'Découvrez UnionFlow',
style: TextStyle(
fontSize: 18,
fontSize: 13,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
),
const SizedBox(height: 16),
const SizedBox(height: 8),
AnimatedFadeIn(
delay: const Duration(milliseconds: 300),
@@ -98,7 +98,7 @@ class VisitorDashboard extends StatelessWidget {
],
),
),
const SizedBox(height: 24),
const SizedBox(height: 12),
// Avantages
AnimatedSlideIn(
@@ -106,13 +106,13 @@ class VisitorDashboard extends StatelessWidget {
child: const Text(
'Nos Avantages',
style: TextStyle(
fontSize: 16,
fontSize: 13,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
),
const SizedBox(height: 16),
const SizedBox(height: 8),
AnimatedFadeIn(
delay: const Duration(milliseconds: 600),
@@ -156,45 +156,44 @@ class VisitorDashboard extends StatelessWidget {
UnionFlowColors.gold,
),
),
const SizedBox(height: 32),
const SizedBox(height: 16),
// Call to Action
AnimatedSlideIn(
delay: const Duration(milliseconds: 1000),
child: Container(
padding: const EdgeInsets.all(24),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
gradient: UnionFlowColors.primaryGradient,
borderRadius: BorderRadius.circular(20),
boxShadow: UnionFlowColors.greenGlowShadow,
borderRadius: BorderRadius.circular(12),
),
child: Column(
children: [
const Icon(
Icons.rocket_launch,
size: 48,
size: 24,
color: Colors.white,
),
const SizedBox(height: 16),
const SizedBox(height: 8),
const Text(
'Prêt à Commencer ?',
style: TextStyle(
fontSize: 20,
fontSize: 15,
fontWeight: FontWeight.w800,
color: Colors.white,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
const SizedBox(height: 4),
Text(
'Rejoignez des milliers d\'organisations qui nous font confiance',
style: TextStyle(
fontSize: 13,
fontSize: 11,
color: Colors.white.withOpacity(0.9),
),
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
const SizedBox(height: 10),
SizedBox(
width: double.infinity,
child: ElevatedButton(
@@ -204,22 +203,22 @@ class VisitorDashboard extends StatelessWidget {
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: UnionFlowColors.unionGreen,
padding: const EdgeInsets.symmetric(vertical: 16),
padding: const EdgeInsets.symmetric(vertical: 10),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(8),
),
elevation: 0,
),
child: const Text(
'Créer un Compte',
style: TextStyle(
fontSize: 15,
fontSize: 13,
fontWeight: FontWeight.w800,
),
),
),
),
const SizedBox(height: 12),
const SizedBox(height: 6),
TextButton(
onPressed: () {
Navigator.of(context).pushNamed('/login');
@@ -227,7 +226,7 @@ class VisitorDashboard extends StatelessWidget {
child: Text(
'Déjà membre ? Se connecter',
style: TextStyle(
fontSize: 13,
fontSize: 11,
fontWeight: FontWeight.w600,
color: Colors.white.withOpacity(0.9),
),
@@ -300,10 +299,10 @@ class VisitorDashboard extends StatelessWidget {
Widget _buildWelcomeCard() {
return Container(
padding: const EdgeInsets.all(24),
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
gradient: UnionFlowColors.subtleGradient,
borderRadius: BorderRadius.circular(20),
borderRadius: BorderRadius.circular(10),
border: const Border(
top: BorderSide(color: UnionFlowColors.unionGreen, width: 3),
),
@@ -315,15 +314,15 @@ class VisitorDashboard extends StatelessWidget {
Row(
children: [
Container(
padding: const EdgeInsets.all(12),
padding: const EdgeInsets.all(7),
decoration: BoxDecoration(
gradient: UnionFlowColors.primaryGradient,
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(
Icons.waving_hand,
color: Colors.white,
size: 28,
size: 16,
),
),
const SizedBox(width: 16),
@@ -334,7 +333,7 @@ class VisitorDashboard extends StatelessWidget {
Text(
'Bienvenue sur UnionFlow',
style: TextStyle(
fontSize: 18,
fontSize: 14,
fontWeight: FontWeight.w800,
color: UnionFlowColors.textPrimary,
),
@@ -343,7 +342,7 @@ class VisitorDashboard extends StatelessWidget {
Text(
'Votre plateforme de gestion mutualiste et associative',
style: TextStyle(
fontSize: 13,
fontSize: 11,
color: UnionFlowColors.textSecondary,
),
),
@@ -352,11 +351,11 @@ class VisitorDashboard extends StatelessWidget {
),
],
),
const SizedBox(height: 16),
const SizedBox(height: 8),
Text(
'Gérez vos mutuelles, tontines, coopératives et associations en toute simplicité. UnionFlow est la solution complète pour la solidarité africaine.',
style: TextStyle(
fontSize: 14,
fontSize: 12,
height: 1.5,
color: UnionFlowColors.textPrimary.withOpacity(0.8),
),
@@ -368,24 +367,23 @@ class VisitorDashboard extends StatelessWidget {
Widget _buildFeature(String title, String description, IconData icon, Color color) {
return Container(
padding: const EdgeInsets.all(16),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(16),
borderRadius: BorderRadius.circular(8),
border: Border(
left: BorderSide(color: color, width: 4),
left: BorderSide(color: color, width: 3),
),
boxShadow: UnionFlowColors.softShadow,
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
padding: const EdgeInsets.all(7),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, color: color, size: 24),
child: Icon(icon, color: color, size: 15),
),
const SizedBox(width: 16),
Expanded(