feat: WebSocket temps réel + Finance Workflow + corrections

- Task #6: WebSocket /ws/dashboard + Kafka events (5 topics)
  * Backend: KafkaEventProducer, KafkaEventConsumer
  * Mobile: WebSocketService (reconnection, heartbeat, typed events)
  * DashboardBloc: Auto-refresh depuis WebSocket events

- Finance Workflow: approbations + budgets (backend + mobile)
  * Backend: entities, services, resources, migrations Flyway V6
  * Mobile: features finance_workflow complète avec BLoC

- Corrections DI: interfaces IRepository partout
  * IProfileRepository, IOrganizationRepository, IMembreRepository
  * GetIt configuré avec @injectable

- Spec-Kit: constitution + templates mis à jour
  * .specify/memory/constitution.md enrichie
  * Templates agent, plan, spec, tasks, checklist

- Nettoyage: fichiers temporaires supprimés

Signed-off-by: lions dev Team
This commit is contained in:
dahoud
2026-03-15 02:12:17 +00:00
parent bbc409de9d
commit e8ad874015
635 changed files with 58160 additions and 20674 deletions

View File

@@ -6,9 +6,9 @@ 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 '../../../../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 {
@@ -19,6 +19,14 @@ class ActiveMemberDashboard extends StatelessWidget {
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) {
@@ -49,90 +57,115 @@ class ActiveMemberDashboard extends StatelessWidget {
),
const SizedBox(height: 24),
// Balance principale (données backend réelles)
// Balance principale ou Vue Unifiée (Compte Adhérent)
AnimatedSlideIn(
delay: const Duration(milliseconds: 200),
child: 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,
),
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),
// Stats en grille (données backend réelles)
// Bloc KPI unifié (4 stats regroupées)
AnimatedFadeIn(
delay: const Duration(milliseconds: 300),
child: Row(
children: [
Expanded(
child: UnionStatWidget(
label: 'Cotisations',
value: '${stats?.totalContributions ?? 0}',
icon: Icons.check_circle,
color: UnionFlowColors.success,
trend: stats != null && stats.monthlyGrowth > 0
? '+${stats.monthlyGrowth.toStringAsFixed(0)}%'
: null,
isTrendUp: (stats?.monthlyGrowth ?? 0) > 0,
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(width: 12),
Expanded(
child: UnionStatWidget(
label: 'Engagement',
value: stats != null
? '${(stats.engagementRate * 100).toStringAsFixed(0)}%'
: '0%',
icon: Icons.trending_up,
color: UnionFlowColors.gold,
trend: stats != null && stats.engagementRate > 0.7
? '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: 12),
AnimatedFadeIn(
delay: const Duration(milliseconds: 400),
child: 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) ...[
AnimatedFadeIn(
delay: const Duration(milliseconds: 500),
child: const Text(
const AnimatedFadeIn(
delay: Duration(milliseconds: 500),
child: Text(
'Activité Récente',
style: TextStyle(
fontSize: 16,
@@ -221,122 +254,128 @@ class ActiveMemberDashboard extends StatelessWidget {
const SizedBox(height: 24),
],
// Actions rapides
AnimatedFadeIn(
// Bloc Actions rapides unifié (6 boutons regroupés)
AnimatedSlideIn(
delay: const Duration(milliseconds: 700),
child: const Text(
'Actions Rapides',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
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,
),
),
),
const SizedBox(height: 16),
AnimatedSlideIn(
delay: const Duration(milliseconds: 800),
child: Row(
children: [
Expanded(
child: UnionActionButton(
label: 'Cotiser',
icon: Icons.payment,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const CotisationsPageWrapper(),
),
);
},
backgroundColor: UnionFlowColors.unionGreen,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Actions Rapides',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
),
const SizedBox(width: 12),
Expanded(
child: UnionActionButton(
label: 'Épargner',
icon: Icons.savings_outlined,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const EpargnePage(),
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,
),
);
},
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(),
),
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,
),
);
},
backgroundColor: UnionFlowColors.amber,
),
),
],
),
),
const SizedBox(height: 12),
AnimatedSlideIn(
delay: const Duration(milliseconds: 900),
child: Row(
children: [
Expanded(
child: UnionActionButton(
label: 'Événements',
icon: Icons.event,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => const EventsPageWrapper(),
),
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,
),
);
},
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(),
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,
),
);
},
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(),
),
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,
),
);
},
backgroundColor: UnionFlowColors.indigo,
),
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,
),
),
],
),
),
],
],
),
),
),
],
@@ -397,7 +436,7 @@ class ActiveMemberDashboard extends StatelessWidget {
),
],
),
automaticallyImplyLeading: false,
iconTheme: const IconThemeData(color: UnionFlowColors.textPrimary),
);
}

View File

@@ -1,68 +1,670 @@
/// Dashboard Modérateur - Management Hub Focalisé
/// Outils de modération et gestion partielle
library moderator_dashboard;
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/material.dart';
import '../../../../../shared/design_system/unionflow_design_system.dart';
import '../../widgets/dashboard_widgets.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 Management Hub pour Modérateur
/// 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: ColorTokens.surface,
body: CustomScrollView(
slivers: [
// App Bar Modérateur
SliverAppBar(
expandedHeight: 160,
floating: false,
pinned: true,
backgroundColor: const Color(0xFFE17055), // Orange focus
flexibleSpace: FlexibleSpaceBar(
title: const Text(
'Management Hub',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
background: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFFE17055), Color(0xFFD63031)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
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,
),
),
],
),
),
],
),
),
child: const Center(
child: Icon(Icons.manage_accounts, color: Colors.white, size: 60),
),
);
},
);
},
),
),
);
}
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,
),
),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(SpacingTokens.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Métriques modération
_buildModerationMetrics(),
const SizedBox(height: SpacingTokens.xl),
// Actions modération
_buildModerationActions(),
const SizedBox(height: SpacingTokens.xl),
// Tâches en attente
_buildPendingTasks(),
const SizedBox(height: SpacingTokens.xl),
// Activité récente
_buildRecentActivity(),
],
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,
),
),
),
@@ -70,161 +672,4 @@ class ModeratorDashboard extends StatelessWidget {
),
);
}
Widget _buildModerationMetrics() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Métriques de Modération',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
DashboardStatsGrid(
stats: const [
DashboardStat(
icon: Icons.flag,
value: '12',
title: 'Signalements',
color: Color(0xFFE17055),
),
DashboardStat(
icon: Icons.pending_actions,
value: '8',
title: 'En Attente',
color: Color(0xFFD63031),
),
DashboardStat(
icon: Icons.check_circle,
value: '45',
title: 'Résolus',
color: Color(0xFF00B894),
),
DashboardStat(
icon: Icons.people,
value: '156',
title: 'Membres',
color: Color(0xFF0984E3),
),
],
onStatTap: (type) {},
),
],
);
}
Widget _buildModerationActions() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Actions de Modération',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
DashboardQuickActionsGrid(
children: [
DashboardQuickAction(
icon: Icons.gavel,
title: 'Modérer',
color: const Color(0xFFE17055),
onTap: () {},
),
DashboardQuickAction(
icon: Icons.person_remove,
title: 'Suspendre',
color: const Color(0xFFD63031),
onTap: () {},
),
DashboardQuickAction(
icon: Icons.message,
title: 'Communiquer',
color: const Color(0xFF0984E3),
onTap: () {},
),
DashboardQuickAction(
icon: Icons.report,
title: 'Rapport',
color: const Color(0xFF6C5CE7),
onTap: () {},
),
],
),
],
);
}
Widget _buildPendingTasks() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Tâches en Attente',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(RadiusTokens.md),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: const Column(
children: [
ListTile(
leading: CircleAvatar(
backgroundColor: Color(0xFFFFE0E0),
child: Icon(Icons.flag, color: Color(0xFFD63031)),
),
title: Text('Contenu inapproprié signalé'),
subtitle: Text('Commentaire sur événement'),
trailing: Text('Urgent'),
),
Divider(height: 1),
ListTile(
leading: CircleAvatar(
backgroundColor: Color(0xFFFFF3E0),
child: Icon(Icons.person_add, color: Color(0xFFE17055)),
),
title: Text('Demande d\'adhésion'),
subtitle: Text('Marie Dubois'),
trailing: Text('2j'),
),
],
),
),
],
);
}
Widget _buildRecentActivity() {
return const DashboardRecentActivitySection(
children: [
DashboardActivity(
title: 'Signalement traité',
subtitle: 'Contenu supprimé',
icon: Icons.check_circle,
color: Color(0xFF00B894),
time: 'Il y a 1h',
),
DashboardActivity(
title: 'Membre suspendu',
subtitle: 'Violation des règles',
icon: Icons.person_remove,
color: Color(0xFFD63031),
time: 'Il y a 3h',
),
],
);
}
}

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

@@ -6,6 +6,8 @@ library role_dashboards;
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

@@ -1,360 +1,436 @@
/// Dashboard Membre Simple - Personal Space Minimaliste
/// Interface simplifiée pour accès basique
library simple_member_dashboard;
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter/material.dart';
import '../../../../../shared/design_system/unionflow_design_system.dart';
import '../../widgets/dashboard_widgets.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 Personal Space pour Membre Simple
/// Dashboard Membre Simple - Design UnionFlow
class SimpleMemberDashboard extends StatelessWidget {
const SimpleMemberDashboard({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: ColorTokens.surface,
body: CustomScrollView(
slivers: [
// App Bar Membre Simple
SliverAppBar(
expandedHeight: 140,
floating: false,
pinned: true,
backgroundColor: const Color(0xFF00CEC9), // Teal simple
flexibleSpace: FlexibleSpaceBar(
title: const Text(
'Mon Espace',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
background: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF00CEC9), Color(0xFF00B3B3)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
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(),
),
),
],
],
),
),
child: const Center(
child: Icon(Icons.person, color: Colors.white, size: 50),
),
),
),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(SpacingTokens.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Profil personnel
_buildPersonalProfile(),
const SizedBox(height: SpacingTokens.xl),
// Mes informations
_buildMyInfo(),
const SizedBox(height: SpacingTokens.xl),
// Actions simples
_buildSimpleActions(),
const SizedBox(height: SpacingTokens.xl),
// Événements publics
_buildPublicEvents(),
const SizedBox(height: SpacingTokens.xl),
// Mon historique
_buildMyHistory(),
],
),
),
),
],
);
},
);
},
),
),
);
}
Widget _buildPersonalProfile() {
return Container(
padding: const EdgeInsets.all(SpacingTokens.lg),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(RadiusTokens.lg),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
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: [
const CircleAvatar(
radius: 35,
backgroundColor: Color(0xFF00CEC9),
child: Icon(Icons.person, color: Colors.white, size: 35),
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: SpacingTokens.md),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Pierre Dupont',
style: TypographyTokens.headlineMedium.copyWith(
fontWeight: FontWeight.bold,
user?.fullName ?? 'Membre',
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
const SizedBox(height: 4),
Text(
'Membre depuis 6 mois',
style: TypographyTokens.bodyMedium.copyWith(
color: ColorTokens.textSecondary,
),
),
const SizedBox(height: SpacingTokens.sm),
Container(
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.sm,
vertical: SpacingTokens.xs,
),
decoration: BoxDecoration(
color: const Color(0xFF00CEC9).withOpacity(0.1),
borderRadius: BorderRadius.circular(RadiusTokens.sm),
),
child: Text(
'Membre Simple',
style: TypographyTokens.bodySmall.copyWith(
color: const Color(0xFF00CEC9),
fontWeight: FontWeight.w600,
),
'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,
),
),
),
],
),
);
}
Widget _buildMyInfo() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Mes Informations',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
const DashboardStatsGrid(
stats: [
DashboardStat(
icon: Icons.payment,
value: 'À jour',
title: 'Cotisations',
color: Color(0xFF00B894),
),
DashboardStat(
icon: Icons.event,
value: '2',
title: 'Événements',
color: Color(0xFF00CEC9),
),
DashboardStat(
icon: Icons.account_circle,
value: '100%',
title: 'Profil',
color: Color(0xFF0984E3),
),
DashboardStat(
icon: Icons.notifications,
value: '3',
title: 'Notifications',
color: Color(0xFFE17055),
),
],
),
],
);
}
Widget _buildSimpleActions() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Actions Disponibles',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
DashboardQuickActionsGrid(
children: [
DashboardQuickAction(
icon: Icons.edit,
title: 'Modifier Profil',
color: const Color(0xFF00CEC9),
onTap: () {},
),
DashboardQuickAction(
icon: Icons.payment,
title: 'Mes Cotisations',
color: const Color(0xFF0984E3),
onTap: () {},
),
DashboardQuickAction(
icon: Icons.event,
title: 'Événements',
color: const Color(0xFF00B894),
onTap: () {},
),
DashboardQuickAction(
icon: Icons.help,
title: 'Aide',
color: const Color(0xFFE17055),
onTap: () {},
),
],
),
],
);
}
Widget _buildPublicEvents() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Événements Disponibles',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(RadiusTokens.md),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
ListTile(
leading: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: const Color(0xFF00B894).withOpacity(0.1),
borderRadius: BorderRadius.circular(25),
),
child: const Icon(
Icons.event,
color: Color(0xFF00B894),
),
),
title: const Text('Assemblée Générale'),
subtitle: const Text('15 décembre • 19h00'),
trailing: Container(
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.sm,
vertical: SpacingTokens.xs,
),
decoration: BoxDecoration(
color: const Color(0xFF00B894).withOpacity(0.1),
borderRadius: BorderRadius.circular(RadiusTokens.sm),
),
child: const Text(
'Public',
style: TextStyle(
color: Color(0xFF00B894),
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
),
),
const Divider(height: 1),
ListTile(
leading: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: const Color(0xFF00CEC9).withOpacity(0.1),
borderRadius: BorderRadius.circular(25),
),
child: const Icon(
Icons.celebration,
color: Color(0xFF00CEC9),
),
),
title: const Text('Soirée de Noël'),
subtitle: const Text('22 décembre • 20h00'),
trailing: Container(
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.sm,
vertical: SpacingTokens.xs,
),
decoration: BoxDecoration(
color: const Color(0xFF00CEC9).withOpacity(0.1),
borderRadius: BorderRadius.circular(RadiusTokens.sm),
),
child: const Text(
'Public',
style: TextStyle(
color: Color(0xFF00CEC9),
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
),
),
],
),
),
],
);
}
Widget _buildMyHistory() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Mon Historique',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
const DashboardRecentActivitySection(
children: [
DashboardActivity(
title: 'Cotisation payée',
subtitle: 'Décembre 2024',
icon: Icons.payment,
color: Color(0xFF00B894),
time: 'Il y a 1j',
),
DashboardActivity(
title: 'Profil mis à jour',
subtitle: 'Informations personnelles',
icon: Icons.edit,
color: Color(0xFF00CEC9),
time: 'Il y a 1 sem',
),
DashboardActivity(
title: 'Inscription événement',
subtitle: 'Assemblée Générale',
icon: Icons.event,
color: Color(0xFF0984E3),
time: 'Il y a 2 sem',
),
],
),
],
);
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

@@ -1,156 +1,364 @@
/// Dashboard Visiteur - Landing Experience Accueillante
/// Interface publique pour découvrir l'organisation
library visitor_dashboard;
import 'package:flutter/material.dart';
import '../../../../../shared/design_system/tokens/color_tokens.dart';
import '../../../../../shared/design_system/tokens/radius_tokens.dart';
import '../../../../../shared/design_system/tokens/spacing_tokens.dart';
import '../../../../../shared/design_system/tokens/typography_tokens.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 Landing Experience pour Visiteur
/// Dashboard Visiteur - Design UnionFlow Version Publique
class VisitorDashboard extends StatelessWidget {
const VisitorDashboard({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: ColorTokens.surface,
body: CustomScrollView(
slivers: [
// App Bar Visiteur
SliverAppBar(
expandedHeight: 200,
floating: false,
pinned: true,
backgroundColor: const Color(0xFF6C5CE7), // Indigo accueillant
flexibleSpace: FlexibleSpaceBar(
title: const Text(
'Découvrir UnionFlow',
style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
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(),
),
background: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF6C5CE7), Color(0xFF5A4FCF)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
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,
),
),
child: Stack(
),
const SizedBox(height: 16),
AnimatedFadeIn(
delay: const Duration(milliseconds: 300),
child: Row(
children: [
// Motif d'accueil
Positioned.fill(
child: CustomPaint(
painter: _WelcomePatternPainter(),
Expanded(
child: UnionStatWidget(
label: 'Organisations',
value: '500+',
icon: Icons.business_outlined,
color: UnionFlowColors.unionGreen,
),
),
const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.waving_hand, color: Colors.white, size: 60),
SizedBox(height: 8),
Text(
'Bienvenue !',
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
],
const SizedBox(width: 12),
Expanded(
child: UnionStatWidget(
label: 'Utilisateurs',
value: '10K+',
icon: Icons.people_outlined,
color: UnionFlowColors.gold,
),
),
],
),
),
),
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(SpacingTokens.md),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Message d'accueil
_buildWelcomeMessage(),
const SizedBox(height: SpacingTokens.xl),
// À propos de l'organisation
_buildAboutOrganization(),
const SizedBox(height: SpacingTokens.xl),
// Événements publics
_buildPublicEvents(),
const SizedBox(height: SpacingTokens.xl),
// Comment rejoindre
_buildHowToJoin(),
const SizedBox(height: SpacingTokens.xl),
// Contact
_buildContactInfo(),
],
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),
),
),
),
],
),
),
),
],
),
],
),
),
);
}
Widget _buildWelcomeMessage() {
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(SpacingTokens.lg),
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF6C5CE7), Color(0xFF5A4FCF)],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
gradient: UnionFlowColors.subtleGradient,
borderRadius: BorderRadius.circular(20),
border: const Border(
top: BorderSide(color: UnionFlowColors.unionGreen, width: 3),
),
borderRadius: BorderRadius.circular(RadiusTokens.lg),
boxShadow: UnionFlowColors.mediumShadow,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
const Icon(Icons.info_outline, color: Colors.white, size: 30),
const SizedBox(width: SpacingTokens.sm),
Expanded(
child: Text(
'Découvrez notre communauté',
style: TypographyTokens.headlineMedium.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
),
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: SpacingTokens.md),
const SizedBox(height: 16),
Text(
'Bienvenue sur UnionFlow ! Explorez notre organisation, découvrez nos événements publics et apprenez comment nous rejoindre.',
style: TypographyTokens.bodyLarge.copyWith(
color: Colors.white.withOpacity(0.9),
),
),
const SizedBox(height: SpacingTokens.md),
ElevatedButton(
onPressed: () => _onJoinNow(),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: const Color(0xFF6C5CE7),
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.lg,
vertical: SpacingTokens.md,
),
),
child: const Text(
'Nous Rejoindre',
style: TextStyle(fontWeight: FontWeight.bold),
'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),
),
),
],
@@ -158,397 +366,53 @@ class VisitorDashboard extends StatelessWidget {
);
}
Widget _buildAboutOrganization() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'À Propos de Nous',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
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),
),
const SizedBox(height: SpacingTokens.md),
Container(
padding: const EdgeInsets.all(SpacingTokens.lg),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(RadiusTokens.md),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
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),
),
child: Column(
children: [
Row(
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
color: const Color(0xFF6C5CE7).withOpacity(0.1),
borderRadius: BorderRadius.circular(30),
),
child: const Icon(
Icons.business,
color: Color(0xFF6C5CE7),
size: 30,
),
),
const SizedBox(width: SpacingTokens.md),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Association des Développeurs',
style: TypographyTokens.headlineSmall.copyWith(
fontWeight: FontWeight.bold,
),
),
Text(
'Communauté tech passionnée',
style: TypographyTokens.bodyMedium.copyWith(
color: ColorTokens.textSecondary,
),
),
],
),
),
],
),
const SizedBox(height: SpacingTokens.md),
const Text(
'Nous sommes une association dynamique qui rassemble les passionnés de technologie. Notre mission est de favoriser l\'apprentissage, le partage de connaissances et l\'entraide dans le domaine du développement.',
style: TypographyTokens.bodyMedium,
),
const SizedBox(height: SpacingTokens.md),
// Statistiques publiques
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildPublicStat('156', 'Membres'),
_buildPublicStat('24', 'Événements/an'),
_buildPublicStat('5', 'Ans d\'existence'),
],
),
],
),
),
],
);
}
Widget _buildPublicStat(String value, String label) {
return Column(
children: [
Text(
value,
style: TypographyTokens.headlineMedium.copyWith(
color: const Color(0xFF6C5CE7),
fontWeight: FontWeight.bold,
),
),
Text(
label,
style: TypographyTokens.bodySmall.copyWith(
color: ColorTokens.textSecondary,
),
),
],
);
}
Widget _buildPublicEvents() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Événements Publics',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(RadiusTokens.md),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
ListTile(
leading: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: const Color(0xFF00B894).withOpacity(0.1),
borderRadius: BorderRadius.circular(25),
),
child: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('15', style: TextStyle(fontWeight: FontWeight.bold)),
Text('DÉC', style: TextStyle(fontSize: 10)),
],
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w700,
color: UnionFlowColors.textPrimary,
),
),
title: const Text('Assemblée Générale Publique'),
subtitle: const Text('Salle communale • 19h00 • Gratuit'),
trailing: Container(
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.sm,
vertical: SpacingTokens.xs,
),
decoration: BoxDecoration(
color: const Color(0xFF00B894).withOpacity(0.1),
borderRadius: BorderRadius.circular(RadiusTokens.sm),
),
child: const Text(
'OUVERT',
style: TextStyle(
color: Color(0xFF00B894),
fontSize: 10,
fontWeight: FontWeight.bold,
),
const SizedBox(height: 4),
Text(
description,
style: const TextStyle(
fontSize: 12,
color: UnionFlowColors.textSecondary,
),
),
),
const Divider(height: 1),
ListTile(
leading: Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: const Color(0xFF6C5CE7).withOpacity(0.1),
borderRadius: BorderRadius.circular(25),
),
child: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('20', style: TextStyle(fontWeight: FontWeight.bold)),
Text('DÉC', style: TextStyle(fontSize: 10)),
],
),
),
title: const Text('Conférence Tech Trends 2025'),
subtitle: const Text('Amphithéâtre Université • 14h00 • Gratuit'),
trailing: Container(
padding: const EdgeInsets.symmetric(
horizontal: SpacingTokens.sm,
vertical: SpacingTokens.xs,
),
decoration: BoxDecoration(
color: const Color(0xFF6C5CE7).withOpacity(0.1),
borderRadius: BorderRadius.circular(RadiusTokens.sm),
),
child: const Text(
'OUVERT',
style: TextStyle(
color: Color(0xFF6C5CE7),
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
),
],
);
}
Widget _buildHowToJoin() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Comment Nous Rejoindre',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
Container(
padding: const EdgeInsets.all(SpacingTokens.lg),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(RadiusTokens.md),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: Column(
children: [
_buildJoinStep('1', 'Créer un compte', 'Inscription gratuite en 2 minutes'),
const SizedBox(height: SpacingTokens.md),
_buildJoinStep('2', 'Compléter le profil', 'Partagez vos centres d\'intérêt'),
const SizedBox(height: SpacingTokens.md),
_buildJoinStep('3', 'Validation', 'Approbation par nos modérateurs'),
const SizedBox(height: SpacingTokens.lg),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () => _onStartRegistration(),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF6C5CE7),
padding: const EdgeInsets.symmetric(vertical: SpacingTokens.md),
),
child: const Text(
'Commencer l\'inscription',
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
),
],
),
),
],
);
}
Widget _buildJoinStep(String number, String title, String description) {
return Row(
children: [
Container(
width: 30,
height: 30,
decoration: BoxDecoration(
color: const Color(0xFF6C5CE7),
borderRadius: BorderRadius.circular(15),
),
child: Center(
child: Text(
number,
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
],
),
),
),
const SizedBox(width: SpacingTokens.md),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TypographyTokens.bodyMedium.copyWith(
fontWeight: FontWeight.w600,
),
),
Text(
description,
style: TypographyTokens.bodySmall.copyWith(
color: ColorTokens.textSecondary,
),
),
],
),
),
],
],
),
);
}
Widget _buildContactInfo() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Nous Contacter',
style: TypographyTokens.headlineMedium.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: SpacingTokens.md),
Container(
padding: const EdgeInsets.all(SpacingTokens.lg),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(RadiusTokens.md),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
offset: const Offset(0, 2),
),
],
),
child: const Column(
children: [
ListTile(
leading: Icon(Icons.email, color: Color(0xFF6C5CE7)),
title: Text('Email'),
subtitle: Text('contact@association-dev.fr'),
contentPadding: EdgeInsets.zero,
),
ListTile(
leading: Icon(Icons.phone, color: Color(0xFF6C5CE7)),
title: Text('Téléphone'),
subtitle: Text('+33 1 23 45 67 89'),
contentPadding: EdgeInsets.zero,
),
ListTile(
leading: Icon(Icons.location_on, color: Color(0xFF6C5CE7)),
title: Text('Adresse'),
subtitle: Text('123 Rue de la Tech, 75001 Paris'),
contentPadding: EdgeInsets.zero,
),
],
),
),
],
);
}
// === CALLBACKS ===
void _onJoinNow() {
// Navigation vers l'inscription
}
void _onStartRegistration() {
// Démarrer le processus d'inscription
}
}
/// Painter pour le motif d'accueil
class _WelcomePatternPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.white.withOpacity(0.1)
..strokeWidth = 1
..style = PaintingStyle.stroke;
// Dessiner des cercles concentriques
for (int i = 1; i <= 5; i++) {
canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
i * size.width / 10,
paint,
);
}
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}