Initial commit: unionflow-mobile-apps

Application Flutter complète (sans build artifacts).

Signed-off-by: lions dev Team
This commit is contained in:
dahoud
2026-03-15 16:30:08 +00:00
commit d094d6db9c
1790 changed files with 507435 additions and 0 deletions

View File

@@ -0,0 +1,521 @@
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 '../../../../authentication/presentation/bloc/auth_bloc.dart';
import '../../../../contributions/presentation/pages/contributions_page_wrapper.dart';
import '../../../../epargne/presentation/pages/epargne_page.dart';
import '../../../../profile/presentation/pages/profile_page_wrapper.dart';
import '../../../../events/presentation/pages/events_page_wrapper.dart';
import '../../../../solidarity/presentation/pages/demandes_aide_page_wrapper.dart';
import '../../widgets/dashboard_drawer.dart';
/// Dashboard Membre Actif - Design UnionFlow Enrichi
class ActiveMemberDashboard extends StatelessWidget {
const ActiveMemberDashboard({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: UnionFlowColors.background,
appBar: _buildAppBar(),
drawer: DashboardDrawer(
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;
return BlocBuilder<DashboardBloc, DashboardState>(
builder: (context, dashboardState) {
if (dashboardState is DashboardLoading) {
return const Center(
child: CircularProgressIndicator(color: UnionFlowColors.unionGreen),
);
}
final dashboardData = (dashboardState is DashboardLoaded)
? dashboardState.dashboardData
: null;
final stats = dashboardData?.stats;
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// En-tête
AnimatedFadeIn(
delay: const Duration(milliseconds: 100),
child: _buildUserHeader(user),
),
const SizedBox(height: 24),
// Balance principale ou Vue Unifiée (Compte Adhérent)
AnimatedSlideIn(
delay: const Duration(milliseconds: 200),
child: dashboardData?.monCompte != null
? UnionUnifiedAccountCard(
numeroMembre: dashboardData!.monCompte!.numeroMembre,
organisationNom: dashboardData.monCompte!.organisationNom ?? 'UnionFlow',
soldeTotal: _formatAmount(dashboardData.monCompte!.soldeTotalDisponible),
capaciteEmprunt: _formatAmount(dashboardData.monCompte!.capaciteEmprunt),
epargneBloquee: _formatAmount(dashboardData.monCompte!.soldeBloque),
engagementRate: dashboardData.monCompte!.engagementRate,
)
: UnionBalanceCard(
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',
isTrendPositive: (stats?.monthlyGrowth ?? 0) >= 0,
),
),
const SizedBox(height: 24),
// Bloc KPI unifié (4 stats regroupées)
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,
),
),
],
),
],
),
),
),
const SizedBox(height: 24),
// Activité récente (données backend)
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,
),
),
),
const SizedBox(height: 16),
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,
),
),
],
),
)
).toList(),
),
),
const SizedBox(height: 24),
],
// Bloc Actions rapides unifié (6 boutons regroupés)
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,
),
),
],
),
],
),
),
),
],
),
);
},
);
},
),
),
);
}
PreferredSizeWidget _buildAppBar() {
return AppBar(
backgroundColor: UnionFlowColors.surface,
elevation: 0,
title: Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
gradient: UnionFlowColors.primaryGradient,
borderRadius: BorderRadius.circular(8),
),
alignment: Alignment.center,
child: const Text(
'U',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w900,
fontSize: 18,
),
),
),
const SizedBox(width: 12),
const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'UnionFlow',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
Text(
'Membre Actif',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w400,
color: UnionFlowColors.textSecondary,
),
),
],
),
],
),
iconTheme: const IconThemeData(color: UnionFlowColors.textPrimary),
);
}
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';
}
return '${amount.toStringAsFixed(0)} FCFA';
}
}

View File

@@ -0,0 +1,455 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fl_chart/fl_chart.dart';
import '../../../../../shared/design_system/unionflow_design_v2.dart';
import '../../bloc/dashboard_bloc.dart';
import '../../widgets/dashboard_drawer.dart';
import '../../../../authentication/presentation/bloc/auth_bloc.dart';
import '../../../../reports/presentation/pages/reports_page_wrapper.dart';
import '../../../../members/presentation/pages/members_page_wrapper.dart';
import '../../../../events/presentation/pages/events_page_wrapper.dart';
import '../../../../help/presentation/pages/help_support_page.dart';
/// Dashboard Consultant - Design UnionFlow Expertise & Analyses
class ConsultantDashboard extends StatelessWidget {
const ConsultantDashboard({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: UnionFlowColors.background,
appBar: _buildAppBar(context),
drawer: DashboardDrawer(
onNavigate: (route) => Navigator.of(context).pushNamed(route),
onLogout: () => context.read<AuthBloc>().add(const AuthLogoutRequested()),
),
body: AfricanPatternBackground(
child: BlocBuilder<DashboardBloc, DashboardState>(
builder: (context, dashboardState) {
if (dashboardState is DashboardLoading) {
return const Center(
child: CircularProgressIndicator(color: UnionFlowColors.amber),
);
}
final dashboardData = (dashboardState is DashboardLoaded)
? dashboardState.dashboardData
: null;
final stats = dashboardData?.stats;
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// En-tête Consultant
AnimatedFadeIn(
delay: const Duration(milliseconds: 100),
child: _buildUserHeader(),
),
const SizedBox(height: 24),
// Stats missions (données backend réelles)
AnimatedSlideIn(
delay: const Duration(milliseconds: 200),
child: Row(
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,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionStatWidget(
label: 'Organisations',
value: '${stats?.totalOrganizations ?? 0}',
icon: Icons.business_outlined,
color: UnionFlowColors.indigo,
),
),
],
),
),
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)
if (dashboardData != null && dashboardData.hasUpcomingEvents) ...[
AnimatedFadeIn(
delay: const Duration(milliseconds: 400),
child: const Text(
'Prochains Événements',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
),
const SizedBox(height: 16),
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,
),
),
],
),
),
],
),
)
).toList(),
),
),
const SizedBox(height: 24),
],
// Répartition organisations par type (données backend)
if (stats != null && stats.organizationTypeDistribution != null && stats.organizationTypeDistribution!.isNotEmpty) ...[
AnimatedFadeIn(
delay: const Duration(milliseconds: 600),
child: UnionPieChart(
title: 'Répartition Organisations',
subtitle: 'Par type',
sections: _buildOrgTypeSections(stats.organizationTypeDistribution!),
),
),
const SizedBox(height: 24),
],
// Actions consultant
AnimatedFadeIn(
delay: const Duration(milliseconds: 700),
child: const Text(
'Mes Outils',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
),
const SizedBox(height: 16),
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,
),
),
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,
),
),
],
),
),
],
),
);
},
),
),
);
}
PreferredSizeWidget _buildAppBar(BuildContext context) {
return AppBar(
backgroundColor: UnionFlowColors.surface,
elevation: 0,
title: Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [UnionFlowColors.amber, UnionFlowColors.gold],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(8),
),
alignment: Alignment.center,
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,
),
),
],
),
],
),
iconTheme: const IconThemeData(color: UnionFlowColors.textPrimary),
actions: [
UnionExportButton(
onExport: (_) => Navigator.of(context).push(
MaterialPageRoute<void>(builder: (_) => const ReportsPageWrapper()),
),
),
const SizedBox(width: 8),
],
);
}
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 = [
UnionFlowColors.unionGreen,
UnionFlowColors.gold,
UnionFlowColors.indigo,
UnionFlowColors.amber,
UnionFlowColors.terracotta,
];
final total = distribution.values.fold(0, (sum, value) => sum + value);
final entries = distribution.entries.toList();
return List.generate(entries.length.clamp(0, 5), (index) {
final entry = entries[index];
final percentage = total > 0 ? (entry.value / total * 100).toStringAsFixed(0) : '0';
return UnionPieChartSection.create(
value: entry.value.toDouble(),
color: colors[index % colors.length],
title: '$percentage%\n${entry.key}',
);
});
}
}

View File

@@ -0,0 +1,542 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../../shared/design_system/unionflow_design_v2.dart';
import '../../bloc/dashboard_bloc.dart';
import '../../widgets/dashboard_drawer.dart';
import '../../../../authentication/presentation/bloc/auth_bloc.dart';
import '../../../../members/presentation/pages/members_page_wrapper.dart';
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';
/// Dashboard RH Manager - Design UnionFlow Gestion des Ressources Humaines
class HRManagerDashboard extends StatelessWidget {
const HRManagerDashboard({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: UnionFlowColors.background,
appBar: _buildAppBar(context),
drawer: DashboardDrawer(
onNavigate: (route) => Navigator.of(context).pushNamed(route),
onLogout: () => context.read<AuthBloc>().add(const AuthLogoutRequested()),
),
body: AfricanPatternBackground(
child: BlocBuilder<DashboardBloc, DashboardState>(
builder: (context, dashboardState) {
if (dashboardState is DashboardLoading) {
return const Center(
child: CircularProgressIndicator(color: UnionFlowColors.terracotta),
);
}
final dashboardData = (dashboardState is DashboardLoaded)
? dashboardState.dashboardData
: null;
final stats = dashboardData?.stats;
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// En-tête RH
AnimatedFadeIn(
delay: const Duration(milliseconds: 100),
child: _buildUserHeader(),
),
const SizedBox(height: 24),
// Stats RH (données backend réelles)
AnimatedSlideIn(
delay: const Duration(milliseconds: 200),
child: Row(
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,
),
),
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,
),
),
],
),
),
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)
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,
),
),
),
const SizedBox(height: 16),
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,
),
),
],
),
)
).toList(),
),
),
const SizedBox(height: 24),
],
// Répartition membres actifs/inactifs (données backend)
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.textTertiary,
title: '${(((stats.totalMembers - stats.activeMembers) / stats.totalMembers) * 100).toStringAsFixed(0)}%\nInactifs',
),
],
),
),
const SizedBox(height: 24),
// Indicateurs RH
AnimatedFadeIn(
delay: const Duration(milliseconds: 600),
child: const Text(
'Indicateurs Clés',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
),
const SizedBox(height: 16),
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,
),
),
),
const SizedBox(height: 16),
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),
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,
),
),
],
),
),
],
),
);
},
),
),
);
}
PreferredSizeWidget _buildAppBar(BuildContext context) {
return AppBar(
backgroundColor: UnionFlowColors.surface,
elevation: 0,
title: Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [UnionFlowColors.terracotta, UnionFlowColors.terracottaLight],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(8),
),
alignment: Alignment.center,
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,
),
),
],
),
],
),
iconTheme: const IconThemeData(color: UnionFlowColors.textPrimary),
actions: [
UnionExportButton(
onExport: (_) => Navigator.of(context).push(
MaterialPageRoute<void>(builder: (_) => const ReportsPageWrapper()),
),
),
const SizedBox(width: 8),
UnionNotificationBadge(
count: 6,
child: IconButton(
icon: const Icon(Icons.notifications_outlined),
color: UnionFlowColors.textPrimary,
onPressed: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const NotificationsPageWrapper())),
),
),
const SizedBox(width: 8),
],
);
}
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

@@ -0,0 +1,675 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import '../../../../../shared/design_system/unionflow_design_v2.dart';
import '../../bloc/dashboard_bloc.dart';
import '../../../../authentication/presentation/bloc/auth_bloc.dart';
import '../../../../contributions/presentation/pages/contributions_page_wrapper.dart';
import '../../../../epargne/presentation/pages/epargne_page.dart';
import '../../../../profile/presentation/pages/profile_page_wrapper.dart';
import '../../../../events/presentation/pages/events_page_wrapper.dart';
import '../../../../solidarity/presentation/pages/demandes_aide_page_wrapper.dart';
import '../../../../adhesions/presentation/pages/adhesions_page_wrapper.dart';
import '../../../../members/presentation/pages/members_page_wrapper.dart';
import '../../../../help/presentation/pages/help_support_page.dart';
import '../../widgets/dashboard_drawer.dart';
/// Dashboard Modérateur - Design UnionFlow pour Gestion Communauté
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(
backgroundColor: UnionFlowColors.background,
appBar: _buildAppBar(),
drawer: DashboardDrawer(
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;
return BlocBuilder<DashboardBloc, DashboardState>(
builder: (context, dashboardState) {
if (dashboardState is DashboardLoading) {
return const Center(
child: CircularProgressIndicator(color: UnionFlowColors.unionGreen),
);
}
final dashboardData = (dashboardState is DashboardLoaded)
? dashboardState.dashboardData
: null;
final stats = dashboardData?.stats;
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// En-tête Modérateur
AnimatedFadeIn(
delay: const Duration(milliseconds: 100),
child: _buildUserHeader(user),
),
const SizedBox(height: 24),
// Balance principale ou Vue Unifiée (Compte Adhérent)
AnimatedSlideIn(
delay: const Duration(milliseconds: 200),
child: dashboardData?.monCompte != null
? UnionUnifiedAccountCard(
numeroMembre: dashboardData!.monCompte!.numeroMembre,
organisationNom: dashboardData.monCompte!.organisationNom ?? 'UnionFlow',
soldeTotal: _formatAmount(dashboardData.monCompte!.soldeTotalDisponible),
capaciteEmprunt: _formatAmount(dashboardData.monCompte!.capaciteEmprunt),
epargneBloquee: _formatAmount(dashboardData.monCompte!.soldeBloque),
engagementRate: dashboardData.monCompte!.engagementRate,
)
: UnionBalanceCard(
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',
isTrendPositive: (stats?.monthlyGrowth ?? 0) >= 0,
),
),
const SizedBox(height: 24),
// Bloc KPI unifié (4 stats regroupées)
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(
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,
),
),
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,
),
),
],
),
),
const SizedBox(height: 12),
AnimatedFadeIn(
delay: const Duration(milliseconds: 300),
child: Row(
children: [
Expanded(
child: UnionStatWidget(
label: 'Événements',
value: '${stats?.upcomingEvents ?? 0}',
icon: Icons.event_outlined,
color: UnionFlowColors.gold,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionStatWidget(
label: 'Membres Total',
value: '${stats?.totalMembers ?? 0}',
icon: Icons.people_outline,
color: UnionFlowColors.unionGreen,
),
),
],
),
),
const SizedBox(height: 24),
// Activité des membres (données backend réelles)
if (stats != null && stats.totalMembers > 0)
AnimatedSlideIn(
delay: const Duration(milliseconds: 400),
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.textTertiary,
title: '${(((stats.totalMembers - stats.activeMembers) / stats.totalMembers) * 100).toStringAsFixed(0)}%\nInactifs',
),
],
),
),
const SizedBox(height: 24),
// Demandes en attente (données backend)
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,
),
),
),
const SizedBox(height: 16),
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,
),
),
],
),
)
).toList(),
),
),
],
const SizedBox(height: 24),
// Actions de modération
AnimatedSlideIn(
delay: const Duration(milliseconds: 700),
child: Row(
children: [
Expanded(
child: UnionActionButton(
label: 'Approuver',
icon: Icons.check_circle,
onTap: () => Navigator.of(context).push(MaterialPageRoute<void>(builder: (_) => const AdhesionsPageWrapper())),
backgroundColor: UnionFlowColors.success,
),
),
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,
),
),
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,
),
),
],
),
),
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,
),
),
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,
),
),
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,
),
),
],
),
),
],
),
);
},
);
},
),
),
);
}
PreferredSizeWidget _buildAppBar() {
return AppBar(
backgroundColor: UnionFlowColors.surface,
elevation: 0,
title: Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
gradient: UnionFlowColors.primaryGradient,
borderRadius: BorderRadius.circular(8),
),
alignment: Alignment.center,
child: const Text(
'U',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w900,
fontSize: 18,
),
),
),
const SizedBox(width: 12),
const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'UnionFlow',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
Text(
'Modérateur',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w400,
color: UnionFlowColors.textSecondary,
),
),
],
),
],
),
iconTheme: const IconThemeData(color: UnionFlowColors.textPrimary),
);
}
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,
),
),
),
],
),
);
}
}

View File

@@ -0,0 +1,661 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/material.dart';
import '../../../../../shared/design_system/unionflow_design_v2.dart';
import '../../../../../core/di/injection_container.dart';
import '../../bloc/dashboard_bloc.dart';
import '../../../../authentication/presentation/bloc/auth_bloc.dart';
import '../../../../members/presentation/pages/members_page_wrapper.dart';
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 '../../widgets/dashboard_drawer.dart';
import '../../../data/datasources/dashboard_remote_datasource.dart';
import '../../../data/services/dashboard_export_service.dart';
import 'package:share_plus/share_plus.dart';
/// Dashboard Admin Organisation - Design UnionFlow Gestion Complète
class OrgAdminDashboard extends StatelessWidget {
const OrgAdminDashboard({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: UnionFlowColors.background,
appBar: _buildAppBar(context),
drawer: DashboardDrawer(
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;
final orgContext = user?.organizationContexts.isNotEmpty == true
? user!.organizationContexts.first
: null;
return BlocBuilder<DashboardBloc, DashboardState>(
builder: (context, dashboardState) {
if (dashboardState is DashboardLoading) {
return const Center(
child: CircularProgressIndicator(color: UnionFlowColors.gold),
);
}
final dashboardData = (dashboardState is DashboardLoaded)
? dashboardState.dashboardData
: null;
final stats = dashboardData?.stats;
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// En-tête Admin
AnimatedFadeIn(
delay: const Duration(milliseconds: 100),
child: _buildUserHeader(user, orgContext),
),
const SizedBox(height: 24),
// Balance organisation (données backend réelles)
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',
isTrendPositive: (stats?.monthlyGrowth ?? 0) >= 0,
),
),
const SizedBox(height: 24),
// Stats organisation (données backend réelles)
AnimatedFadeIn(
delay: const Duration(milliseconds: 300),
child: Row(
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,
),
),
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,
),
),
],
),
),
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),
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),
child: UnionPieChart(
title: 'Activité des Membres',
subtitle: '${stats.totalMembers} membres',
sections: [
UnionPieChartSection.create(
value: stats.activeMembers.toDouble(),
color: UnionFlowColors.unionGreen,
title: stats.totalMembers > 0
? '${((stats.activeMembers / stats.totalMembers) * 100).toStringAsFixed(0)}%\nActifs'
: '0%\nActifs',
),
UnionPieChartSection.create(
value: (stats.totalMembers - stats.activeMembers).toDouble(),
color: UnionFlowColors.textTertiary,
title: stats.totalMembers > 0
? '${(((stats.totalMembers - stats.activeMembers) / stats.totalMembers) * 100).toStringAsFixed(0)}%\nInactifs'
: '0%\nInactifs',
),
],
),
),
const SizedBox(height: 24),
// Gestion
AnimatedFadeIn(
delay: const Duration(milliseconds: 800),
child: const Text(
'Gestion de l\'Organisation',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
),
const SizedBox(height: 16),
AnimatedSlideIn(
delay: const Duration(milliseconds: 900),
child: Row(
children: [
Expanded(
child: UnionActionButton(
label: 'Membres',
icon: Icons.people,
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => const MembersPageWrapper(),
),
),
backgroundColor: UnionFlowColors.unionGreen,
),
),
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,
),
),
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,
),
),
],
),
),
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,
),
),
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,
),
),
],
),
),
],
),
);
},
);
},
),
),
);
}
PreferredSizeWidget _buildAppBar(BuildContext context) {
return AppBar(
backgroundColor: UnionFlowColors.surface,
elevation: 0,
title: Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
gradient: UnionFlowColors.goldGradient,
borderRadius: BorderRadius.circular(8),
),
alignment: Alignment.center,
child: const Text(
'A',
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,
),
),
],
),
],
),
iconTheme: const IconThemeData(color: UnionFlowColors.textPrimary),
actions: [
UnionExportButton(
onExport: (_) => _handleExport(context),
),
const SizedBox(width: 8),
UnionNotificationBadge(
count: 0,
child: IconButton(
icon: const Icon(Icons.notifications_outlined),
color: UnionFlowColors.textPrimary,
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => const NotificationsPageWrapper(),
),
),
),
),
const SizedBox(width: 8),
],
);
}
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';
}
return '${amount.toStringAsFixed(0)} FCFA';
}
static Future<void> _handleExport(BuildContext context) async {
if (kIsWeb) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Export PDF disponible sur l\'app mobile.')),
);
}
return;
}
final authState = context.read<AuthBloc>().state;
if (authState is! AuthAuthenticated) return;
final user = authState.user;
if (user.organizationContexts.isEmpty) {
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Aucune organisation associée.')),
);
}
return;
}
final orgCtx = user.organizationContexts.first;
final orgId = orgCtx.organizationId;
final orgName = orgCtx.organizationName;
final userId = user.id;
showDialog<void>(
context: context,
barrierDismissible: false,
builder: (_) => const Center(child: CircularProgressIndicator()),
);
try {
final dataSource = sl<DashboardRemoteDataSource>();
final data = await dataSource.getDashboardData(orgId, userId);
final path = await DashboardExportService().exportDashboardReport(
dashboardData: data,
organizationName: orgName,
reportTitle: 'Rapport dashboard - ${DateTime.now().day}/${DateTime.now().month}/${DateTime.now().year}',
);
if (context.mounted) {
Navigator.of(context).pop();
await Share.shareXFiles([XFile(path)], text: 'Rapport UnionFlow');
}
} catch (e) {
if (context.mounted) {
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Export impossible: $e')),
);
}
}
}
}

View File

@@ -0,0 +1,102 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../../core/di/injection.dart';
import '../../../../../shared/design_system/unionflow_design_v2.dart';
import '../../bloc/dashboard_bloc.dart';
import '../../../../organizations/data/models/organization_model.dart';
import '../../../../organizations/data/services/organization_service.dart';
import 'org_admin_dashboard.dart';
/// Charge l'organisation du membre connecté (GET /api/organisations/mes) puis
/// affiche le dashboard admin avec les données backend pour cette organisation.
class OrgAdminDashboardLoader extends StatelessWidget {
const OrgAdminDashboardLoader({
super.key,
required this.userId,
});
final String userId;
@override
Widget build(BuildContext context) {
return FutureBuilder<List<OrganizationModel>>(
future: getIt<OrganizationService>().getMesOrganisations(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Scaffold(
backgroundColor: UnionFlowColors.background,
body: const Center(
child: CircularProgressIndicator(color: UnionFlowColors.gold),
),
);
}
if (snapshot.hasError) {
return Scaffold(
backgroundColor: UnionFlowColors.background,
body: Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, size: 48, color: UnionFlowColors.error),
const SizedBox(height: 16),
Text(
'Impossible de charger votre organisation',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: UnionFlowColors.textPrimary,
),
),
const SizedBox(height: 8),
Text(
'${snapshot.error}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 13,
color: UnionFlowColors.textSecondary,
),
),
],
),
),
),
);
}
final orgs = snapshot.data ?? [];
final orgsWithId = orgs.where((o) => o.id != null && o.id!.isNotEmpty).toList();
if (orgsWithId.isEmpty) {
return Scaffold(
backgroundColor: UnionFlowColors.background,
body: Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Text(
orgs.isEmpty
? 'Aucune organisation associée à votre compte.'
: 'Aucune organisation valide (id manquant).',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
color: UnionFlowColors.textSecondary,
),
),
),
),
);
}
final firstOrgId = orgsWithId.first.id!;
return BlocProvider<DashboardBloc>(
create: (context) => getIt<DashboardBloc>()
..add(LoadDashboardData(
organizationId: firstOrgId,
userId: userId,
)),
child: const OrgAdminDashboard(),
);
},
);
}
}

View File

@@ -0,0 +1,13 @@
/// Export de tous les dashboards spécifiques par rôle
/// Facilite l'importation des dashboards dans l'application
library role_dashboards;
// Dashboards spécifiques par rôle
export 'super_admin_dashboard.dart';
export 'org_admin_dashboard.dart';
export 'moderator_dashboard.dart';
export 'consultant_dashboard.dart';
export 'hr_manager_dashboard.dart';
export 'active_member_dashboard.dart';
export 'simple_member_dashboard.dart';
export 'visitor_dashboard.dart';

View File

@@ -0,0 +1,436 @@
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 '../../../../authentication/presentation/bloc/auth_bloc.dart';
import '../../../../contributions/presentation/pages/contributions_page_wrapper.dart';
import '../../../../epargne/presentation/pages/epargne_page.dart';
import '../../../../profile/presentation/pages/profile_page_wrapper.dart';
import '../../../../help/presentation/pages/help_support_page.dart';
import '../../widgets/dashboard_drawer.dart';
/// Dashboard Membre Simple - Design UnionFlow
class SimpleMemberDashboard extends StatelessWidget {
const SimpleMemberDashboard({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: UnionFlowColors.background,
appBar: _buildAppBar(),
drawer: DashboardDrawer(
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;
return BlocBuilder<DashboardBloc, DashboardState>(
builder: (context, dashboardState) {
if (dashboardState is DashboardLoading) {
return const Center(
child: CircularProgressIndicator(color: UnionFlowColors.unionGreen),
);
}
final dashboardData = (dashboardState is DashboardLoaded)
? dashboardState.dashboardData
: null;
final stats = dashboardData?.stats;
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// En-tête avec badge de rôle
AnimatedFadeIn(
delay: const Duration(milliseconds: 100),
child: _buildUserHeader(user),
),
const SizedBox(height: 24),
// Solde personnel
AnimatedSlideIn(
delay: const Duration(milliseconds: 200),
child: UnionBalanceCard(
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',
isTrendPositive: (stats?.monthlyGrowth ?? 0) >= 0,
),
),
const SizedBox(height: 24),
// Ma situation
AnimatedFadeIn(
delay: const Duration(milliseconds: 300),
child: const Text(
'Ma Situation',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
),
const SizedBox(height: 16),
AnimatedSlideIn(
delay: const Duration(milliseconds: 400),
child: Row(
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,
),
),
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,
),
),
],
),
),
const SizedBox(height: 12),
AnimatedSlideIn(
delay: const Duration(milliseconds: 700),
child: Row(
children: [
Expanded(
child: UnionActionButton(
label: 'Mes Infos',
icon: Icons.person_outline,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const ProfilePageWrapper(),
),
);
},
backgroundColor: UnionFlowColors.indigo,
),
),
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,
),
),
],
),
),
const SizedBox(height: 24),
// Événements à venir (données backend)
if (dashboardData != null && dashboardData.hasUpcomingEvents) ...[
AnimatedFadeIn(
delay: const Duration(milliseconds: 800),
child: const Text(
'Événements à Venir',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
),
const SizedBox(height: 16),
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,
),
),
),
],
),
)
).toList(),
),
),
],
],
),
);
},
);
},
),
),
);
}
PreferredSizeWidget _buildAppBar() {
return AppBar(
backgroundColor: UnionFlowColors.surface,
elevation: 0,
title: Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
gradient: UnionFlowColors.primaryGradient,
borderRadius: BorderRadius.circular(8),
),
alignment: Alignment.center,
child: const Text(
'U',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w900,
fontSize: 18,
),
),
),
const SizedBox(width: 12),
const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'UnionFlow',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
Text(
'Membre Simple',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w400,
color: UnionFlowColors.textSecondary,
),
),
],
),
],
),
iconTheme: const IconThemeData(color: UnionFlowColors.textPrimary),
);
}
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';
}
return '${amount.toStringAsFixed(0)} FCFA';
}
}

View File

@@ -0,0 +1,596 @@
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 '../../../../authentication/presentation/bloc/auth_bloc.dart';
import '../../../../organizations/presentation/pages/organizations_page_wrapper.dart';
import '../../../../admin/presentation/pages/user_management_page.dart';
import '../../../../settings/presentation/pages/system_settings_page.dart';
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
class SuperAdminDashboard extends StatelessWidget {
const SuperAdminDashboard({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: UnionFlowColors.background,
appBar: _buildAppBar(),
drawer: DashboardDrawer(
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;
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),
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,
),
),
],
),
),
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,
),
),
],
),
),
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,
),
),
],
),
),
],
),
);
},
);
},
),
),
);
}
PreferredSizeWidget _buildAppBar() {
return AppBar(
backgroundColor: UnionFlowColors.surface,
elevation: 0,
title: Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [UnionFlowColors.error, Colors.red.shade900],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(8),
),
alignment: Alignment.center,
child: const Text(
'R',
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(
'Super Admin (Root)',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w400,
color: UnionFlowColors.textSecondary,
),
),
],
),
],
),
iconTheme: const IconThemeData(color: UnionFlowColors.textPrimary),
actions: [
UnionExportButton(
onExport: (exportType) {},
),
const SizedBox(width: 8),
],
);
}
Widget _buildUserHeader(dynamic user) {
return Container(
padding: const EdgeInsets.all(16),
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),
),
],
),
child: Row(
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,
),
),
),
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,
),
),
),
],
),
);
}
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';
}
return '${amount.toStringAsFixed(0)} FCFA';
}
}

View File

@@ -0,0 +1,418 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../../shared/design_system/unionflow_design_v2.dart';
import '../../../../authentication/presentation/bloc/auth_bloc.dart';
import '../../widgets/dashboard_drawer.dart';
/// Dashboard Visiteur - Design UnionFlow Version Publique
class VisitorDashboard extends StatelessWidget {
const VisitorDashboard({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: UnionFlowColors.background,
appBar: _buildAppBar(),
drawer: DashboardDrawer(
onNavigate: (route) {
Navigator.of(context).pushNamed(route);
},
onLogout: () {
context.read<AuthBloc>().add(const AuthLogoutRequested());
},
),
body: AfricanPatternBackground(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Message de bienvenue
AnimatedFadeIn(
delay: const Duration(milliseconds: 100),
child: _buildWelcomeCard(),
),
const SizedBox(height: 24),
// Fonctionnalités UnionFlow
AnimatedSlideIn(
delay: const Duration(milliseconds: 200),
child: const Text(
'Découvrez UnionFlow',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
),
const SizedBox(height: 16),
AnimatedFadeIn(
delay: const Duration(milliseconds: 300),
child: Row(
children: [
Expanded(
child: UnionStatWidget(
label: 'Organisations',
value: '500+',
icon: Icons.business_outlined,
color: UnionFlowColors.unionGreen,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionStatWidget(
label: 'Utilisateurs',
value: '10K+',
icon: Icons.people_outlined,
color: UnionFlowColors.gold,
),
),
],
),
),
const SizedBox(height: 12),
AnimatedFadeIn(
delay: const Duration(milliseconds: 400),
child: Row(
children: [
Expanded(
child: UnionStatWidget(
label: 'Transactions',
value: '1M+',
icon: Icons.payment_outlined,
color: UnionFlowColors.indigo,
),
),
const SizedBox(width: 12),
Expanded(
child: UnionStatWidget(
label: 'Confiance',
value: '99%',
icon: Icons.verified_outlined,
color: UnionFlowColors.success,
),
),
],
),
),
const SizedBox(height: 24),
// Avantages
AnimatedSlideIn(
delay: const Duration(milliseconds: 500),
child: const Text(
'Nos Avantages',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
),
const SizedBox(height: 16),
AnimatedFadeIn(
delay: const Duration(milliseconds: 600),
child: _buildFeature(
'Gestion Simplifiée',
'Gérez vos cotisations, épargnes et crédits en un seul endroit',
Icons.dashboard_customize,
UnionFlowColors.unionGreen,
),
),
const SizedBox(height: 12),
AnimatedFadeIn(
delay: const Duration(milliseconds: 700),
child: _buildFeature(
'Sécurité Optimale',
'Vos données sont protégées avec un chiffrement de niveau bancaire',
Icons.security,
UnionFlowColors.indigo,
),
),
const SizedBox(height: 12),
AnimatedFadeIn(
delay: const Duration(milliseconds: 800),
child: _buildFeature(
'Solidarité Africaine',
'Entraide, tontines, mutuelles et coopératives à votre portée',
Icons.favorite_outline,
UnionFlowColors.terracotta,
),
),
const SizedBox(height: 12),
AnimatedFadeIn(
delay: const Duration(milliseconds: 900),
child: _buildFeature(
'Rapports Détaillés',
'Suivi en temps réel avec exports PDF, Excel et CSV',
Icons.analytics_outlined,
UnionFlowColors.gold,
),
),
const SizedBox(height: 32),
// Call to Action
AnimatedSlideIn(
delay: const Duration(milliseconds: 1000),
child: Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
gradient: UnionFlowColors.primaryGradient,
borderRadius: BorderRadius.circular(20),
boxShadow: UnionFlowColors.greenGlowShadow,
),
child: Column(
children: [
const Icon(
Icons.rocket_launch,
size: 48,
color: Colors.white,
),
const SizedBox(height: 16),
const Text(
'Prêt à Commencer ?',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w800,
color: Colors.white,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'Rejoignez des milliers d\'organisations qui nous font confiance',
style: TextStyle(
fontSize: 13,
color: Colors.white.withOpacity(0.9),
),
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
Navigator.of(context).pushNamed('/login');
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: UnionFlowColors.unionGreen,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
elevation: 0,
),
child: const Text(
'Créer un Compte',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w800,
),
),
),
),
const SizedBox(height: 12),
TextButton(
onPressed: () {
Navigator.of(context).pushNamed('/login');
},
child: Text(
'Déjà membre ? Se connecter',
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: Colors.white.withOpacity(0.9),
),
),
),
],
),
),
),
],
),
),
),
);
}
PreferredSizeWidget _buildAppBar() {
return AppBar(
backgroundColor: UnionFlowColors.surface,
elevation: 0,
title: Row(
children: [
Hero(
tag: 'unionflow_logo',
child: Container(
width: 32,
height: 32,
decoration: BoxDecoration(
gradient: UnionFlowColors.primaryGradient,
borderRadius: BorderRadius.circular(8),
),
alignment: Alignment.center,
child: const Text(
'U',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.w900,
fontSize: 18,
),
),
),
),
const SizedBox(width: 12),
const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'UnionFlow',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
Text(
'Découverte',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w400,
color: UnionFlowColors.textSecondary,
),
),
],
),
],
),
iconTheme: const IconThemeData(color: UnionFlowColors.textPrimary),
);
}
Widget _buildWelcomeCard() {
return Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
gradient: UnionFlowColors.subtleGradient,
borderRadius: BorderRadius.circular(20),
border: const Border(
top: BorderSide(color: UnionFlowColors.unionGreen, width: 3),
),
boxShadow: UnionFlowColors.mediumShadow,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
gradient: UnionFlowColors.primaryGradient,
borderRadius: BorderRadius.circular(12),
),
child: const Icon(
Icons.waving_hand,
color: Colors.white,
size: 28,
),
),
const SizedBox(width: 16),
const Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Bienvenue sur UnionFlow',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w800,
color: UnionFlowColors.textPrimary,
),
),
SizedBox(height: 4),
Text(
'Votre plateforme de gestion mutualiste et associative',
style: TextStyle(
fontSize: 13,
color: UnionFlowColors.textSecondary,
),
),
],
),
),
],
),
const SizedBox(height: 16),
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,
height: 1.5,
color: UnionFlowColors.textPrimary.withOpacity(0.8),
),
),
],
),
);
}
Widget _buildFeature(String title, String description, IconData icon, Color color) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: UnionFlowColors.surface,
borderRadius: BorderRadius.circular(16),
border: Border(
left: BorderSide(color: color, width: 4),
),
boxShadow: UnionFlowColors.softShadow,
),
child: Row(
children: [
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12),
),
child: Icon(icon, color: color, size: 24),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
const SizedBox(height: 4),
Text(
description,
style: const TextStyle(
fontSize: 12,
color: UnionFlowColors.textSecondary,
),
),
],
),
),
],
),
);
}
}